/** * Copyright (c) 2014-2015, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ /** * Cursor is expected to be required in a node or other CommonJS context: * * var Cursor = require('immutable/contrib/cursor'); * * If you wish to use it in the browser, please check out Browserify or WebPack! */ var Immutable = require('../../'); var Iterable = Immutable.Iterable; var Iterator = Iterable.Iterator; var Seq = Immutable.Seq; var Map = Immutable.Map; var Record = Immutable.Record; function cursorFrom(rootData, keyPath, onChange) { if (arguments.length === 1) { keyPath = []; } else if (typeof keyPath === 'function') { onChange = keyPath; keyPath = []; } else { keyPath = valToKeyPath(keyPath); } return makeCursor(rootData, keyPath, onChange); } var KeyedCursorPrototype = Object.create(Seq.Keyed.prototype); var IndexedCursorPrototype = Object.create(Seq.Indexed.prototype); function KeyedCursor(rootData, keyPath, onChange, size) { this.size = size; this._rootData = rootData; this._keyPath = keyPath; this._onChange = onChange; } KeyedCursorPrototype.constructor = KeyedCursor; function IndexedCursor(rootData, keyPath, onChange, size) { this.size = size; this._rootData = rootData; this._keyPath = keyPath; this._onChange = onChange; } IndexedCursorPrototype.constructor = IndexedCursor; KeyedCursorPrototype.toString = function() { return this.__toString('Cursor {', '}'); } IndexedCursorPrototype.toString = function() { return this.__toString('Cursor [', ']'); } KeyedCursorPrototype.deref = KeyedCursorPrototype.valueOf = IndexedCursorPrototype.deref = IndexedCursorPrototype.valueOf = function(notSetValue) { return this._rootData.getIn(this._keyPath, notSetValue); } KeyedCursorPrototype.get = IndexedCursorPrototype.get = function(key, notSetValue) { return this.getIn([key], notSetValue); } KeyedCursorPrototype.getIn = IndexedCursorPrototype.getIn = function(keyPath, notSetValue) { keyPath = listToKeyPath(keyPath); if (keyPath.length === 0) { return this; } var value = this._rootData.getIn(newKeyPath(this._keyPath, keyPath), NOT_SET); return value === NOT_SET ? notSetValue : wrappedValue(this, keyPath, value); } IndexedCursorPrototype.set = KeyedCursorPrototype.set = function(key, value) { if(arguments.length === 1) { return updateCursor(this, function() { return key; }, []); } else { return updateCursor(this, function (m) { return m.set(key, value); }, [key]); } } IndexedCursorPrototype.push = function(/* values */) { var args = arguments; return updateCursor(this, function (m) { return m.push.apply(m, args); }); } IndexedCursorPrototype.pop = function() { return updateCursor(this, function (m) { return m.pop(); }); } IndexedCursorPrototype.unshift = function(/* values */) { var args = arguments; return updateCursor(this, function (m) { return m.unshift.apply(m, args); }); } IndexedCursorPrototype.shift = function() { return updateCursor(this, function (m) { return m.shift(); }); } IndexedCursorPrototype.setIn = KeyedCursorPrototype.setIn = Map.prototype.setIn; KeyedCursorPrototype.remove = KeyedCursorPrototype['delete'] = IndexedCursorPrototype.remove = IndexedCursorPrototype['delete'] = function(key) { return updateCursor(this, function (m) { return m.remove(key); }, [key]); } IndexedCursorPrototype.removeIn = IndexedCursorPrototype.deleteIn = KeyedCursorPrototype.removeIn = KeyedCursorPrototype.deleteIn = Map.prototype.deleteIn; KeyedCursorPrototype.clear = IndexedCursorPrototype.clear = function() { return updateCursor(this, function (m) { return m.clear(); }); } IndexedCursorPrototype.update = KeyedCursorPrototype.update = function(keyOrFn, notSetValue, updater) { return arguments.length === 1 ? updateCursor(this, keyOrFn) : this.updateIn([keyOrFn], notSetValue, updater); } IndexedCursorPrototype.updateIn = KeyedCursorPrototype.updateIn = function(keyPath, notSetValue, updater) { return updateCursor(this, function (m) { return m.updateIn(keyPath, notSetValue, updater); }, keyPath); } IndexedCursorPrototype.merge = KeyedCursorPrototype.merge = function(/*...iters*/) { var args = arguments; return updateCursor(this, function (m) { return m.merge.apply(m, args); }); } IndexedCursorPrototype.mergeWith = KeyedCursorPrototype.mergeWith = function(merger/*, ...iters*/) { var args = arguments; return updateCursor(this, function (m) { return m.mergeWith.apply(m, args); }); } IndexedCursorPrototype.mergeIn = KeyedCursorPrototype.mergeIn = Map.prototype.mergeIn; IndexedCursorPrototype.mergeDeep = KeyedCursorPrototype.mergeDeep = function(/*...iters*/) { var args = arguments; return updateCursor(this, function (m) { return m.mergeDeep.apply(m, args); }); } IndexedCursorPrototype.mergeDeepWith = KeyedCursorPrototype.mergeDeepWith = function(merger/*, ...iters*/) { var args = arguments; return updateCursor(this, function (m) { return m.mergeDeepWith.apply(m, args); }); } IndexedCursorPrototype.mergeDeepIn = KeyedCursorPrototype.mergeDeepIn = Map.prototype.mergeDeepIn; KeyedCursorPrototype.withMutations = IndexedCursorPrototype.withMutations = function(fn) { return updateCursor(this, function (m) { return (m || Map()).withMutations(fn); }); } KeyedCursorPrototype.cursor = IndexedCursorPrototype.cursor = function(subKeyPath) { subKeyPath = valToKeyPath(subKeyPath); return subKeyPath.length === 0 ? this : subCursor(this, subKeyPath); } /** * All iterables need to implement __iterate */ KeyedCursorPrototype.__iterate = IndexedCursorPrototype.__iterate = function(fn, reverse) { var cursor = this; var deref = cursor.deref(); return deref && deref.__iterate ? deref.__iterate( function (v, k) { return fn(wrappedValue(cursor, [k], v), k, cursor); }, reverse ) : 0; } /** * All iterables need to implement __iterator */ KeyedCursorPrototype.__iterator = IndexedCursorPrototype.__iterator = function(type, reverse) { var deref = this.deref(); var cursor = this; var iterator = deref && deref.__iterator && deref.__iterator(Iterator.ENTRIES, reverse); return new Iterator(function () { if (!iterator) { return { value: undefined, done: true }; } var step = iterator.next(); if (step.done) { return step; } var entry = step.value; var k = entry[0]; var v = wrappedValue(cursor, [k], entry[1]); return { value: type === Iterator.KEYS ? k : type === Iterator.VALUES ? v : [k, v], done: false }; }); } KeyedCursor.prototype = KeyedCursorPrototype; IndexedCursor.prototype = IndexedCursorPrototype; var NOT_SET = {}; // Sentinel value function makeCursor(rootData, keyPath, onChange, value) { if (arguments.length < 4) { value = rootData.getIn(keyPath); } var size = value && value.size; var CursorClass = Iterable.isIndexed(value) ? IndexedCursor : KeyedCursor; var cursor = new CursorClass(rootData, keyPath, onChange, size); if (value instanceof Record) { defineRecordProperties(cursor, value); } return cursor; } function defineRecordProperties(cursor, value) { try { value._keys.forEach(setProp.bind(undefined, cursor)); } catch (error) { // Object.defineProperty failed. Probably IE8. } } function setProp(prototype, name) { Object.defineProperty(prototype, name, { get: function() { return this.get(name); }, set: function(value) { if (!this.__ownerID) { throw new Error('Cannot set on an immutable record.'); } } }); } function wrappedValue(cursor, keyPath, value) { return Iterable.isIterable(value) ? subCursor(cursor, keyPath, value) : value; } function subCursor(cursor, keyPath, value) { if (arguments.length < 3) { return makeCursor( // call without value cursor._rootData, newKeyPath(cursor._keyPath, keyPath), cursor._onChange ); } return makeCursor( cursor._rootData, newKeyPath(cursor._keyPath, keyPath), cursor._onChange, value ); } function updateCursor(cursor, changeFn, changeKeyPath) { var deepChange = arguments.length > 2; var newRootData = cursor._rootData.updateIn( cursor._keyPath, deepChange ? Map() : undefined, changeFn ); var keyPath = cursor._keyPath || []; var result = cursor._onChange && cursor._onChange.call( undefined, newRootData, cursor._rootData, deepChange ? newKeyPath(keyPath, changeKeyPath) : keyPath ); if (result !== undefined) { newRootData = result; } return makeCursor(newRootData, cursor._keyPath, cursor._onChange); } function newKeyPath(head, tail) { return head.concat(listToKeyPath(tail)); } function listToKeyPath(list) { return Array.isArray(list) ? list : Immutable.Iterable(list).toArray(); } function valToKeyPath(val) { return Array.isArray(val) ? val : Iterable.isIterable(val) ? val.toArray() : [val]; } exports.from = cursorFrom;