Skip to content

Commit 84101b9

Browse files
committed
Add deep operations and merge methods to Cursor
1 parent 0b75521 commit 84101b9

File tree

5 files changed

+226
-17
lines changed

5 files changed

+226
-17
lines changed

contrib/cursor/__tests__/Cursor.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,32 @@ describe('Cursor', () => {
256256
var c = Cursor.from(data).values();
257257
var c1 = c.next().value.get('val');
258258
expect(c1).toBe(1);
259-
})
259+
});
260+
261+
it('can update deeply', () => {
262+
var onChange = jest.genMockFunction();
263+
var data = Immutable.fromJS({a:{b:{c:1}}});
264+
var c = Cursor.from(data, ['a'], onChange);
265+
var c1 = c.updateIn(['b', 'c'], x => x * 10);
266+
expect(c1.getIn(['b', 'c'])).toBe(10);
267+
expect(onChange).lastCalledWith(
268+
Immutable.fromJS({a:{b:{c:10}}}),
269+
data,
270+
['a', 'b', 'c']
271+
);
272+
});
273+
274+
it('can set deeply', () => {
275+
var onChange = jest.genMockFunction();
276+
var data = Immutable.fromJS({a:{b:{c:1}}});
277+
var c = Cursor.from(data, ['a'], onChange);
278+
var c1 = c.setIn(['b', 'c'], 10);
279+
expect(c1.getIn(['b', 'c'])).toBe(10);
280+
expect(onChange).lastCalledWith(
281+
Immutable.fromJS({a:{b:{c:10}}}),
282+
data,
283+
['a', 'b', 'c']
284+
);
285+
});
260286

261287
});

contrib/cursor/index.d.ts

+134-2
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,10 @@ declare module 'immutable/contrib/cursor' {
9999
* point in the new data.
100100
*
101101
* Note: `delete` cannot be safely used in IE8
102-
* @alias delete
102+
* @alias remove
103103
*/
104-
remove(key: any): Cursor;
105104
delete(key: any): Cursor;
105+
remove(key: any): Cursor;
106106

107107
/**
108108
* Clears the value at this cursor, returning a new cursor to the same
@@ -119,6 +119,138 @@ declare module 'immutable/contrib/cursor' {
119119
update(key: any, updater: (value: any) => any): Cursor;
120120
update(key: any, notSetValue: any, updater: (value: any) => any): Cursor;
121121

122+
/**
123+
* @see `Map#merge`
124+
*/
125+
merge(...iterables: Immutable.Iterable<any, any>[]): Cursor;
126+
merge(...iterables: {[key: string]: any}[]): Cursor;
127+
128+
/**
129+
* @see `Map#mergeWith`
130+
*/
131+
mergeWith(
132+
merger: (previous?: any, next?: any) => any,
133+
...iterables: Immutable.Iterable<any, any>[]
134+
): Cursor;
135+
mergeWith(
136+
merger: (previous?: any, next?: any) => any,
137+
...iterables: {[key: string]: any}[]
138+
): Cursor;
139+
140+
/**
141+
* @see `Map#mergeDeep`
142+
*/
143+
mergeDeep(...iterables: Immutable.Iterable<any, any>[]): Cursor;
144+
mergeDeep(...iterables: {[key: string]: any}[]): Cursor;
145+
146+
/**
147+
* @see `Map#mergeDeepWith`
148+
*/
149+
mergeDeepWith(
150+
merger: (previous?: any, next?: any) => any,
151+
...iterables: Immutable.Iterable<any, any>[]
152+
): Cursor;
153+
mergeDeepWith(
154+
merger: (previous?: any, next?: any) => any,
155+
...iterables: {[key: string]: any}[]
156+
): Cursor;
157+
158+
// Deep persistent changes
159+
160+
/**
161+
* Returns a new Cursor having set `value` at this `keyPath`. If any keys in
162+
* `keyPath` do not exist, a new immutable Map will be created at that key.
163+
*/
164+
setIn(keyPath: Array<any>, value: any): Cursor;
165+
setIn(keyPath: Immutable.Iterable<any, any>, value: any): Cursor;
166+
167+
/**
168+
* Returns a new Cursor having removed the value at this `keyPath`.
169+
*
170+
* @alias removeIn
171+
*/
172+
deleteIn(keyPath: Array<any>): Cursor;
173+
deleteIn(keyPath: Immutable.Iterable<any, any>): Cursor;
174+
removeIn(keyPath: Array<any>): Cursor;
175+
removeIn(keyPath: Immutable.Iterable<any, any>): Cursor;
176+
177+
/**
178+
* Returns a new Cursor having applied the `updater` to the value found at
179+
* the keyPath.
180+
*
181+
* If any keys in `keyPath` do not exist, new Immutable `Map`s will
182+
* be created at those keys. If the `keyPath` does not already contain a
183+
* value, the `updater` function will be called with `notSetValue`, if
184+
* provided, otherwise `undefined`.
185+
*
186+
* If the `updater` function returns the same value it was called with, then
187+
* no change will occur. This is still true if `notSetValue` is provided.
188+
*/
189+
updateIn(
190+
keyPath: Array<any>,
191+
updater: (value: any) => any
192+
): Cursor;
193+
updateIn(
194+
keyPath: Array<any>,
195+
notSetValue: any,
196+
updater: (value: any) => any
197+
): Cursor;
198+
updateIn(
199+
keyPath: Immutable.Iterable<any, any>,
200+
updater: (value: any) => any
201+
): Cursor;
202+
updateIn(
203+
keyPath: Immutable.Iterable<any, any>,
204+
notSetValue: any,
205+
updater: (value: any) => any
206+
): Cursor;
207+
208+
/**
209+
* A combination of `updateIn` and `merge`, returning a new Cursor, but
210+
* performing the merge at a point arrived at by following the keyPath.
211+
* In other words, these two lines are equivalent:
212+
*
213+
* x.updateIn(['a', 'b', 'c'], abc => abc.merge(y));
214+
* x.mergeIn(['a', 'b', 'c'], y);
215+
*
216+
*/
217+
mergeIn(
218+
keyPath: Immutable.Iterable<any, any>,
219+
...iterables: Immutable.Iterable<any, any>[]
220+
): Cursor;
221+
mergeIn(
222+
keyPath: Array<any>,
223+
...iterables: Immutable.Iterable<any, any>[]
224+
): Cursor;
225+
mergeIn(
226+
keyPath: Array<any>,
227+
...iterables: {[key: string]: any}[]
228+
): Cursor;
229+
230+
/**
231+
* A combination of `updateIn` and `mergeDeep`, returning a new Cursor, but
232+
* performing the deep merge at a point arrived at by following the keyPath.
233+
* In other words, these two lines are equivalent:
234+
*
235+
* x.updateIn(['a', 'b', 'c'], abc => abc.mergeDeep(y));
236+
* x.mergeDeepIn(['a', 'b', 'c'], y);
237+
*
238+
*/
239+
mergeDeepIn(
240+
keyPath: Immutable.Iterable<any, any>,
241+
...iterables: Immutable.Iterable<any, any>[]
242+
): Cursor;
243+
mergeDeepIn(
244+
keyPath: Array<any>,
245+
...iterables: Immutable.Iterable<any, any>[]
246+
): Cursor;
247+
mergeDeepIn(
248+
keyPath: Array<any>,
249+
...iterables: {[key: string]: any}[]
250+
): Cursor;
251+
252+
// Transient changes
253+
122254
/**
123255
* Every time you call one of the above functions, a new immutable value is
124256
* created and the callback is triggered. If you need to apply a series of

contrib/cursor/index.js

+60-6
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ IndexedCursorPrototype.getIn = function(key, notSetValue) {
8181
if (key.length === 0) {
8282
return this;
8383
}
84-
var value = this._rootData.getIn(this._keyPath.concat(key), NOT_SET);
84+
var value = this._rootData.getIn(newKeyPath(this._keyPath, key), NOT_SET);
8585
return value === NOT_SET ? notSetValue : wrappedValue(this, key, value);
8686
}
8787

@@ -90,13 +90,21 @@ KeyedCursorPrototype.set = function(key, value) {
9090
return updateCursor(this, function (m) { return m.set(key, value); }, key);
9191
}
9292

93+
IndexedCursorPrototype.setIn =
94+
KeyedCursorPrototype.setIn = Map.prototype.setIn;
95+
9396
KeyedCursorPrototype.remove =
9497
KeyedCursorPrototype['delete'] =
9598
IndexedCursorPrototype.remove =
9699
IndexedCursorPrototype['delete'] = function(key) {
97100
return updateCursor(this, function (m) { return m.remove(key); }, key);
98101
}
99102

103+
IndexedCursorPrototype.removeIn =
104+
IndexedCursorPrototype.deleteIn =
105+
KeyedCursorPrototype.removeIn =
106+
KeyedCursorPrototype.deleteIn = Map.prototype.deleteIn;
107+
100108
KeyedCursorPrototype.clear =
101109
IndexedCursorPrototype.clear = function() {
102110
return updateCursor(this, function (m) { return m.clear(); });
@@ -106,11 +114,54 @@ IndexedCursorPrototype.update =
106114
KeyedCursorPrototype.update = function(keyOrFn, notSetValue, updater) {
107115
return arguments.length === 1 ?
108116
updateCursor(this, keyOrFn) :
109-
updateCursor(this, function (map) {
110-
return map.update(keyOrFn, notSetValue, updater);
111-
}, keyOrFn);
117+
this.updateIn([keyOrFn], notSetValue, updater);
118+
}
119+
120+
IndexedCursorPrototype.updateIn =
121+
KeyedCursorPrototype.updateIn = function(keyPath, notSetValue, updater) {
122+
return updateCursor(this, function (m) {
123+
return m.updateIn(keyPath, notSetValue, updater);
124+
}, keyPath);
125+
}
126+
127+
IndexedCursorPrototype.merge =
128+
KeyedCursorPrototype.merge = function(/*...iters*/) {
129+
var args = arguments;
130+
return updateCursor(this, function (m) {
131+
return m.merge.apply(m, args);
132+
});
133+
}
134+
135+
IndexedCursorPrototype.mergeWith =
136+
KeyedCursorPrototype.mergeWith = function(merger/*, ...iters*/) {
137+
var args = arguments;
138+
return updateCursor(this, function (m) {
139+
return m.mergeWith.apply(m, args);
140+
});
141+
}
142+
143+
IndexedCursorPrototype.mergeIn =
144+
KeyedCursorPrototype.mergeIn = Map.prototype.mergeIn;
145+
146+
IndexedCursorPrototype.mergeDeep =
147+
KeyedCursorPrototype.mergeDeep = function(/*...iters*/) {
148+
var args = arguments;
149+
return updateCursor(this, function (m) {
150+
return m.mergeDeep.apply(m, args);
151+
});
112152
}
113153

154+
IndexedCursorPrototype.mergeDeepWith =
155+
KeyedCursorPrototype.mergeDeepWith = function(merger/*, ...iters*/) {
156+
var args = arguments;
157+
return updateCursor(this, function (m) {
158+
return m.mergeDeepWith.apply(m, args);
159+
});
160+
}
161+
162+
IndexedCursorPrototype.mergeDeepIn =
163+
KeyedCursorPrototype.mergeDeepIn = Map.prototype.mergeDeepIn;
164+
114165
KeyedCursorPrototype.withMutations =
115166
IndexedCursorPrototype.withMutations = function(fn) {
116167
return updateCursor(this, function (m) {
@@ -186,7 +237,7 @@ function wrappedValue(cursor, key, value) {
186237
function subCursor(cursor, key, value) {
187238
return makeCursor(
188239
cursor._rootData,
189-
cursor._keyPath.concat(key),
240+
newKeyPath(cursor._keyPath, key),
190241
cursor._onChange,
191242
value
192243
);
@@ -203,13 +254,16 @@ function updateCursor(cursor, changeFn, changeKey) {
203254
undefined,
204255
newRootData,
205256
cursor._rootData,
206-
changeKey ? keyPath.concat(changeKey) : keyPath
257+
newKeyPath(keyPath, changeKey)
207258
);
208259
if (result !== undefined) {
209260
newRootData = result;
210261
}
211262
return makeCursor(newRootData, cursor._keyPath, cursor._onChange);
212263
}
213264

265+
function newKeyPath(head, tail) {
266+
return tail ? Seq(head).concat(tail).toArray() : head;
267+
}
214268

215269
exports.from = cursorFrom;

dist/immutable.d.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,7 @@ declare module 'immutable' {
277277

278278
/**
279279
* Returns a new List having removed the value at this `keyPath`. If any
280-
* keys in `keyPath` do not exist, a new immutable Map will be created at
281-
* that key.
280+
* keys in `keyPath` do not exist, no change will occur.
282281
*
283282
* @alias removeIn
284283
*/
@@ -530,8 +529,7 @@ declare module 'immutable' {
530529

531530
/**
532531
* Returns a new Map having removed the value at this `keyPath`. If any keys
533-
* in `keyPath` do not exist, a new immutable Map will be created at
534-
* that key.
532+
* in `keyPath` do not exist, no change will occur.
535533
*
536534
* @alias removeIn
537535
*/
@@ -1975,6 +1973,7 @@ declare module 'immutable' {
19751973
context?: any,
19761974
notSetValue?: V
19771975
): /*[K, V]*/Array<any>;
1976+
19781977
/**
19791978
* Returns the maximum value in this collection. If any values are
19801979
* comparatively equivalent, the first one found will be returned.

type-definitions/Immutable.d.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,7 @@ declare module 'immutable' {
277277

278278
/**
279279
* Returns a new List having removed the value at this `keyPath`. If any
280-
* keys in `keyPath` do not exist, a new immutable Map will be created at
281-
* that key.
280+
* keys in `keyPath` do not exist, no change will occur.
282281
*
283282
* @alias removeIn
284283
*/
@@ -530,8 +529,7 @@ declare module 'immutable' {
530529

531530
/**
532531
* Returns a new Map having removed the value at this `keyPath`. If any keys
533-
* in `keyPath` do not exist, a new immutable Map will be created at
534-
* that key.
532+
* in `keyPath` do not exist, no change will occur.
535533
*
536534
* @alias removeIn
537535
*/

0 commit comments

Comments
 (0)