index.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. /**
  2. * Copyright (c) 2014-present, Facebook, Inc.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. /**
  8. * DEPRECATED
  9. *
  10. * The Cursor API is deprecated and will be removed in a future major release.
  11. *
  12. * It is strongly suggested that you use the excellent `immutable-cursor` module
  13. * which has an extremely similar API but is much higher quality.
  14. *
  15. * https://github.com/redbadger/immutable-cursor
  16. */
  17. typeof console === 'object' && console.warn && console.warn(
  18. 'The Cursor API is deprecated and will be removed in a future major release.\n' +
  19. '\n' +
  20. 'It is strongly suggested that you use the excellent `immutable-cursor` module\n' +
  21. 'which has an extremely similar API but is much higher quality.\n' +
  22. '\n' +
  23. 'https://github.com/redbadger/immutable-cursor\n' +
  24. );
  25. /**
  26. * Cursor is expected to be required in a node or other CommonJS context:
  27. *
  28. * var Cursor = require('immutable/contrib/cursor');
  29. *
  30. * If you wish to use it in the browser, please check out Browserify or WebPack!
  31. */
  32. var Immutable = require('../../');
  33. var Iterable = Immutable.Iterable;
  34. var Iterator = Iterable.Iterator;
  35. var Seq = Immutable.Seq;
  36. var Map = Immutable.Map;
  37. var Record = Immutable.Record;
  38. function cursorFrom(rootData, keyPath, onChange) {
  39. if (arguments.length === 1) {
  40. keyPath = [];
  41. } else if (typeof keyPath === 'function') {
  42. onChange = keyPath;
  43. keyPath = [];
  44. } else {
  45. keyPath = valToKeyPath(keyPath);
  46. }
  47. return makeCursor(rootData, keyPath, onChange);
  48. }
  49. var KeyedCursorPrototype = Object.create(Seq.Keyed.prototype);
  50. var IndexedCursorPrototype = Object.create(Seq.Indexed.prototype);
  51. function KeyedCursor(rootData, keyPath, onChange, size) {
  52. this.size = size;
  53. this._rootData = rootData;
  54. this._keyPath = keyPath;
  55. this._onChange = onChange;
  56. }
  57. KeyedCursorPrototype.constructor = KeyedCursor;
  58. function IndexedCursor(rootData, keyPath, onChange, size) {
  59. this.size = size;
  60. this._rootData = rootData;
  61. this._keyPath = keyPath;
  62. this._onChange = onChange;
  63. }
  64. IndexedCursorPrototype.constructor = IndexedCursor;
  65. KeyedCursorPrototype.toString = function() {
  66. return this.__toString('Cursor {', '}');
  67. }
  68. IndexedCursorPrototype.toString = function() {
  69. return this.__toString('Cursor [', ']');
  70. }
  71. KeyedCursorPrototype.deref =
  72. KeyedCursorPrototype.valueOf =
  73. IndexedCursorPrototype.deref =
  74. IndexedCursorPrototype.valueOf = function(notSetValue) {
  75. return this._rootData.getIn(this._keyPath, notSetValue);
  76. }
  77. KeyedCursorPrototype.get =
  78. IndexedCursorPrototype.get = function(key, notSetValue) {
  79. return this.getIn([key], notSetValue);
  80. }
  81. KeyedCursorPrototype.getIn =
  82. IndexedCursorPrototype.getIn = function(keyPath, notSetValue) {
  83. keyPath = listToKeyPath(keyPath);
  84. if (keyPath.length === 0) {
  85. return this;
  86. }
  87. var value = this._rootData.getIn(newKeyPath(this._keyPath, keyPath), NOT_SET);
  88. return value === NOT_SET ? notSetValue : wrappedValue(this, keyPath, value);
  89. }
  90. IndexedCursorPrototype.set =
  91. KeyedCursorPrototype.set = function(key, value) {
  92. if(arguments.length === 1) {
  93. return updateCursor(this, function() { return key; }, []);
  94. } else {
  95. return updateCursor(this, function (m) { return m.set(key, value); }, [key]);
  96. }
  97. }
  98. IndexedCursorPrototype.push = function(/* values */) {
  99. var args = arguments;
  100. return updateCursor(this, function (m) {
  101. return m.push.apply(m, args);
  102. });
  103. }
  104. IndexedCursorPrototype.pop = function() {
  105. return updateCursor(this, function (m) {
  106. return m.pop();
  107. });
  108. }
  109. IndexedCursorPrototype.unshift = function(/* values */) {
  110. var args = arguments;
  111. return updateCursor(this, function (m) {
  112. return m.unshift.apply(m, args);
  113. });
  114. }
  115. IndexedCursorPrototype.shift = function() {
  116. return updateCursor(this, function (m) {
  117. return m.shift();
  118. });
  119. }
  120. IndexedCursorPrototype.setIn =
  121. KeyedCursorPrototype.setIn = Map.prototype.setIn;
  122. KeyedCursorPrototype.remove =
  123. KeyedCursorPrototype['delete'] =
  124. IndexedCursorPrototype.remove =
  125. IndexedCursorPrototype['delete'] = function(key) {
  126. return updateCursor(this, function (m) { return m.remove(key); }, [key]);
  127. }
  128. IndexedCursorPrototype.removeIn =
  129. IndexedCursorPrototype.deleteIn =
  130. KeyedCursorPrototype.removeIn =
  131. KeyedCursorPrototype.deleteIn = Map.prototype.deleteIn;
  132. KeyedCursorPrototype.clear =
  133. IndexedCursorPrototype.clear = function() {
  134. return updateCursor(this, function (m) { return m.clear(); });
  135. }
  136. IndexedCursorPrototype.update =
  137. KeyedCursorPrototype.update = function(keyOrFn, notSetValue, updater) {
  138. return arguments.length === 1 ?
  139. updateCursor(this, keyOrFn) :
  140. this.updateIn([keyOrFn], notSetValue, updater);
  141. }
  142. IndexedCursorPrototype.updateIn =
  143. KeyedCursorPrototype.updateIn = function(keyPath, notSetValue, updater) {
  144. return updateCursor(this, function (m) {
  145. return m.updateIn(keyPath, notSetValue, updater);
  146. }, keyPath);
  147. }
  148. IndexedCursorPrototype.merge =
  149. KeyedCursorPrototype.merge = function(/*...iters*/) {
  150. var args = arguments;
  151. return updateCursor(this, function (m) {
  152. return m.merge.apply(m, args);
  153. });
  154. }
  155. IndexedCursorPrototype.mergeWith =
  156. KeyedCursorPrototype.mergeWith = function(merger/*, ...iters*/) {
  157. var args = arguments;
  158. return updateCursor(this, function (m) {
  159. return m.mergeWith.apply(m, args);
  160. });
  161. }
  162. IndexedCursorPrototype.mergeIn =
  163. KeyedCursorPrototype.mergeIn = Map.prototype.mergeIn;
  164. IndexedCursorPrototype.mergeDeep =
  165. KeyedCursorPrototype.mergeDeep = function(/*...iters*/) {
  166. var args = arguments;
  167. return updateCursor(this, function (m) {
  168. return m.mergeDeep.apply(m, args);
  169. });
  170. }
  171. IndexedCursorPrototype.mergeDeepWith =
  172. KeyedCursorPrototype.mergeDeepWith = function(merger/*, ...iters*/) {
  173. var args = arguments;
  174. return updateCursor(this, function (m) {
  175. return m.mergeDeepWith.apply(m, args);
  176. });
  177. }
  178. IndexedCursorPrototype.mergeDeepIn =
  179. KeyedCursorPrototype.mergeDeepIn = Map.prototype.mergeDeepIn;
  180. KeyedCursorPrototype.withMutations =
  181. IndexedCursorPrototype.withMutations = function(fn) {
  182. return updateCursor(this, function (m) {
  183. return (m || Map()).withMutations(fn);
  184. });
  185. }
  186. KeyedCursorPrototype.cursor =
  187. IndexedCursorPrototype.cursor = function(subKeyPath) {
  188. subKeyPath = valToKeyPath(subKeyPath);
  189. return subKeyPath.length === 0 ? this : subCursor(this, subKeyPath);
  190. }
  191. /**
  192. * All iterables need to implement __iterate
  193. */
  194. KeyedCursorPrototype.__iterate =
  195. IndexedCursorPrototype.__iterate = function(fn, reverse) {
  196. var cursor = this;
  197. var deref = cursor.deref();
  198. return deref && deref.__iterate ? deref.__iterate(
  199. function (v, k) { return fn(wrappedValue(cursor, [k], v), k, cursor); },
  200. reverse
  201. ) : 0;
  202. }
  203. /**
  204. * All iterables need to implement __iterator
  205. */
  206. KeyedCursorPrototype.__iterator =
  207. IndexedCursorPrototype.__iterator = function(type, reverse) {
  208. var deref = this.deref();
  209. var cursor = this;
  210. var iterator = deref && deref.__iterator &&
  211. deref.__iterator(Iterator.ENTRIES, reverse);
  212. return new Iterator(function () {
  213. if (!iterator) {
  214. return { value: undefined, done: true };
  215. }
  216. var step = iterator.next();
  217. if (step.done) {
  218. return step;
  219. }
  220. var entry = step.value;
  221. var k = entry[0];
  222. var v = wrappedValue(cursor, [k], entry[1]);
  223. return {
  224. value: type === Iterator.KEYS ? k : type === Iterator.VALUES ? v : [k, v],
  225. done: false
  226. };
  227. });
  228. }
  229. KeyedCursor.prototype = KeyedCursorPrototype;
  230. IndexedCursor.prototype = IndexedCursorPrototype;
  231. var NOT_SET = {}; // Sentinel value
  232. function makeCursor(rootData, keyPath, onChange, value) {
  233. if (arguments.length < 4) {
  234. value = rootData.getIn(keyPath);
  235. }
  236. var size = value && value.size;
  237. var CursorClass = Iterable.isIndexed(value) ? IndexedCursor : KeyedCursor;
  238. var cursor = new CursorClass(rootData, keyPath, onChange, size);
  239. if (value instanceof Record) {
  240. defineRecordProperties(cursor, value);
  241. }
  242. return cursor;
  243. }
  244. function defineRecordProperties(cursor, value) {
  245. try {
  246. value._keys.forEach(setProp.bind(undefined, cursor));
  247. } catch (error) {
  248. // Object.defineProperty failed. Probably IE8.
  249. }
  250. }
  251. function setProp(prototype, name) {
  252. Object.defineProperty(prototype, name, {
  253. get: function() {
  254. return this.get(name);
  255. },
  256. set: function(value) {
  257. if (!this.__ownerID) {
  258. throw new Error('Cannot set on an immutable record.');
  259. }
  260. }
  261. });
  262. }
  263. function wrappedValue(cursor, keyPath, value) {
  264. return Iterable.isIterable(value) ? subCursor(cursor, keyPath, value) : value;
  265. }
  266. function subCursor(cursor, keyPath, value) {
  267. if (arguments.length < 3) {
  268. return makeCursor( // call without value
  269. cursor._rootData,
  270. newKeyPath(cursor._keyPath, keyPath),
  271. cursor._onChange
  272. );
  273. }
  274. return makeCursor(
  275. cursor._rootData,
  276. newKeyPath(cursor._keyPath, keyPath),
  277. cursor._onChange,
  278. value
  279. );
  280. }
  281. function updateCursor(cursor, changeFn, changeKeyPath) {
  282. var deepChange = arguments.length > 2;
  283. var newRootData = cursor._rootData.updateIn(
  284. cursor._keyPath,
  285. deepChange ? Map() : undefined,
  286. changeFn
  287. );
  288. var keyPath = cursor._keyPath || [];
  289. var result = cursor._onChange && cursor._onChange.call(
  290. undefined,
  291. newRootData,
  292. cursor._rootData,
  293. deepChange ? newKeyPath(keyPath, changeKeyPath) : keyPath
  294. );
  295. if (result !== undefined) {
  296. newRootData = result;
  297. }
  298. return makeCursor(newRootData, cursor._keyPath, cursor._onChange);
  299. }
  300. function newKeyPath(head, tail) {
  301. return head.concat(listToKeyPath(tail));
  302. }
  303. function listToKeyPath(list) {
  304. return Array.isArray(list) ? list : Immutable.Iterable(list).toArray();
  305. }
  306. function valToKeyPath(val) {
  307. return Array.isArray(val) ? val :
  308. Iterable.isIterable(val) ? val.toArray() :
  309. [val];
  310. }
  311. exports.from = cursorFrom;