collector.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. "use strict";
  2. /**
  3. @fileOverview An object and array collector
  4. @module ink/collector
  5. */
  6. var probe = require( "ink-probe" );
  7. var sys = require( "lodash" );
  8. var dcl = require( "dcl" );
  9. /**
  10. * A collector
  11. * @constructor
  12. */
  13. var CollectorBase = dcl( Destroyable, {
  14. declaredClass : "CollectorBase",
  15. constructor : function ( obj ) {
  16. var that = this;
  17. if ( obj && !sys.isObject( obj ) ) {
  18. throw new TypeError( "Collectors require an initial object or array passed to the constructor" );
  19. }
  20. /**
  21. * The collection that being managed
  22. * @type {object|array}
  23. */
  24. this.heap = obj || {};
  25. // mixin the probe
  26. probe.mixTo( this, this.heap );
  27. /**
  28. * Get the size of the collection
  29. * @name length
  30. * @type {number}
  31. * @memberOf module:documents/collector~CollectorBase#
  32. */
  33. Object.defineProperty( this, "length", {
  34. get : function () {
  35. return sys.size( that.heap );
  36. }
  37. }
  38. );
  39. /**
  40. * Creates an array of shuffled array values, using a version of the Fisher-Yates shuffle.
  41. * See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
  42. * @function
  43. * @memberOf module:documents/collector~CollectorBase#
  44. * @returns {array}
  45. */
  46. this.shuffle = sys.bind( sys.shuffle, this, this.heap );
  47. },
  48. /**
  49. * Adds an item to the collection
  50. * @param {*} key The key to use for the item being added.
  51. * @param {*} item The item to add to the collection. The item is not iterated so that you could add bundled items to the collection
  52. */
  53. add : function ( key, item ) {
  54. this.heap[key] = item;
  55. },
  56. /**
  57. * Iterate over each item in the collection, or a subset that matches a query. This supports two signatures:
  58. * `.each(query, function)` and `.each(function)`. If you pass in a query, only the items that match the query
  59. * are iterated over.
  60. * @param {object=} query A query to evaluate
  61. * @param {function(val, key)} iterator Function to execute against each item in the collection
  62. * @param {object=} thisobj The value of `this`
  63. */
  64. each : function ( query, iterator, thisobj ) {
  65. if ( sys.isPlainObject( query ) ) {
  66. thisobj = thisobj || this;
  67. sys.each( this.find( query ), iterator, thisobj );
  68. } else {
  69. thisobj = iterator || this;
  70. sys.each( this.heap, query, thisobj );
  71. }
  72. },
  73. /**
  74. * Returns the collection as an array. If it is already an array, it just returns that.
  75. * @return {array}
  76. */
  77. toArray : function () {
  78. return sys.toArray( this.heap );
  79. },
  80. /**
  81. * Supports conversion to a JSON string or for passing over the wire
  82. * @return {object}
  83. * @returns {Object|array}
  84. */
  85. toJSON : function () {
  86. return this.heap;
  87. },
  88. /**
  89. * Maps the contents to an array by iterating over it and transforming it. You supply the iterator. Supports two signatures:
  90. * `.map(query, function)` and `.map(function)`. If you pass in a query, only the items that match the query
  91. * are iterated over.
  92. * @param {object=} query A query to evaluate
  93. * @param {function(val, key)} iterator Function to execute against each item in the collection
  94. * @param {object=} thisobj The value of `this`
  95. */
  96. map : function ( query, iterator, thisobj ) {
  97. if ( sys.isPlainObject( query ) ) {
  98. thisobj = thisobj || this;
  99. return sys.map( this.find( query ), iterator, thisobj );
  100. } else {
  101. thisobj = iterator || this;
  102. return sys.map( this.heap, query, thisobj );
  103. }
  104. },
  105. /**
  106. * Reduces a collection to a value which is the accumulated result of running each element in the collection through the
  107. * callback, where each successive callback execution consumes the return value of the previous execution. If accumulator
  108. * is not passed, the first element of the collection will be used as the initial accumulator value.
  109. * are iterated over.
  110. * @param {object=} query A query to evaluate
  111. * @param {function(result, val, key)} iterator The function that will be executed in each item in the collection
  112. * @param {*=} accumulator Initial value of the accumulator.
  113. * @param {object=} thisobj The value of `this`
  114. * @return {*}
  115. */
  116. reduce : function ( query, iterator, accumulator, thisobj ) {
  117. if ( sys.isPlainObject( query ) ) {
  118. thisobj = thisobj || this;
  119. return sys.reduce( this.find( query ), iterator, accumulator, thisobj );
  120. } else {
  121. thisobj = accumulator || this;
  122. return sys.reduce( this.heap, query, iterator, thisobj );
  123. }
  124. },
  125. /**
  126. * Creates an object composed of keys returned from running each element
  127. * of the collection through the given callback. The corresponding value of each key
  128. * is the number of times the key was returned by the callback.
  129. * @param {object=} query A query to evaluate. If you pass in a query, only the items that match the query
  130. * are iterated over.
  131. * @param {function(value, key, collection)} iterator
  132. * @param {object=} thisobj The value of `this`
  133. * @return {object}
  134. */
  135. countBy : function ( query, iterator, thisobj ) {
  136. if ( sys.isPlainObject( query ) ) {
  137. thisobj = thisobj || this;
  138. return sys.countBy( this.find( query ), iterator, thisobj );
  139. } else {
  140. thisobj = iterator || this;
  141. return sys.countBy( this.heap, query, thisobj );
  142. }
  143. },
  144. /**
  145. * Creates an object composed of keys returned from running each element of the collection through the callback.
  146. * The corresponding value of each key is an array of elements passed to callback that returned the key.
  147. * The callback is invoked with three arguments: (value, index|key, collection).
  148. * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query
  149. * are iterated over.
  150. * @param {function(value, key, collection)} iterator
  151. * @param {object=} thisobj The value of `this`
  152. * @return {object}
  153. */
  154. groupBy : function ( query, iterator, thisobj ) {
  155. if ( sys.isPlainObject( query ) ) {
  156. thisobj = thisobj || this;
  157. return sys.groupBy( this.find( query ), iterator, thisobj );
  158. } else {
  159. thisobj = iterator || this;
  160. return sys.groupBy( this.heap, query, thisobj );
  161. }
  162. },
  163. /**
  164. * Reduce the collection to a single value. Supports two signatures:
  165. * `.pluck(query, function)` and `.pluck(function)`
  166. * @param {object=} query The query to evaluate. If you pass in a query, only the items that match the query
  167. * are iterated over.
  168. * @param {string} property The property that will be 'plucked' from the contents of the collection
  169. * @return {*}
  170. */
  171. pluck : function ( query, property ) {
  172. if ( arguments.length === 2 ) {
  173. return sys.map( this.find( query ), function ( record ) {
  174. return probe.get( record, property );
  175. } );
  176. } else {
  177. return sys.map( this.heap, function ( record ) {
  178. return probe.get( record, query );
  179. } );
  180. }
  181. },
  182. /**
  183. * Returns a sorted copy of the collection.
  184. * @param {object=} query The query to evaluate. If you pass in a query, only the items that match the query
  185. * are iterated over.
  186. * @param {function(value, key)} iterator
  187. * @param {object=} thisobj The value of `this`
  188. * @return {array}
  189. */
  190. sortBy : function ( query, iterator, thisobj ) {
  191. if ( sys.isPlainObject( query ) ) {
  192. thisobj = thisobj || this;
  193. return sys.sortBy( this.find( query ), iterator, thisobj );
  194. } else {
  195. thisobj = iterator || this;
  196. return sys.sortBy( this.heap, query, thisobj );
  197. }
  198. },
  199. /**
  200. * Retrieves the maximum value of an array. If callback is passed,
  201. * it will be executed for each value in the array to generate the criterion by which the value is ranked.
  202. * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query
  203. * are iterated over.
  204. * @param {function(value, key, collection)} iterator
  205. * @param {object=} thisobj The value of `this`
  206. * @return {number}
  207. */
  208. max : function ( query, iterator, thisobj ) {
  209. if ( sys.isPlainObject( query ) ) {
  210. thisobj = thisobj || this;
  211. return sys.max( this.find( query ), iterator, thisobj );
  212. } else {
  213. thisobj = iterator || this;
  214. return sys.max( this.heap, query, thisobj );
  215. }
  216. },
  217. /**
  218. * Retrieves the minimum value of an array. If callback is passed,
  219. * it will be executed for each value in the array to generate the criterion by which the value is ranked.
  220. * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query
  221. * are iterated over.
  222. * @param {function(value, key, collection)} iterator
  223. * @param {object=} thisobj The value of `this`
  224. * @return {number}
  225. */
  226. min : function ( query, iterator, thisobj ) {
  227. if ( sys.isPlainObject( query ) ) {
  228. thisobj = thisobj || this;
  229. return sys.min( this.find( query ), iterator, thisobj );
  230. } else {
  231. thisobj = iterator || this;
  232. return sys.min( this.heap, query, thisobj );
  233. }
  234. },
  235. /**
  236. * Destructor called when the object is destroyed.
  237. */
  238. destroy : function () {
  239. this.heap = null;
  240. }
  241. } );
  242. /**
  243. * An object based collector
  244. * @extends module:documents/collector~CollectorBase
  245. * @constructor
  246. */
  247. var OCollector = dcl( CollectorBase, {
  248. /**
  249. * Get a record by key
  250. * @param {*} key The key of the record to get
  251. * @return {*}
  252. */
  253. key : function ( key ) {
  254. return this.heap[key];
  255. }
  256. } );
  257. //noinspection JSCommentMatchesSignature
  258. /**
  259. An array based collector
  260. @extends module:documents/collector~CollectorBase
  261. @constructor
  262. */
  263. var ACollector = dcl( CollectorBase, {
  264. constructor : function ( obj ) {
  265. if ( obj && !sys.isArray( obj ) ) {
  266. throw new TypeError( "Collectors require an array passed to the constructor" );
  267. }
  268. this.heap = obj || [];
  269. /**
  270. * Creates an array of array elements not present in the other arrays using strict equality for comparisons, i.e. ===.
  271. * @returns {array}
  272. */
  273. this.difference = sys.bind( sys.difference, this, this.heap );
  274. /**
  275. * This method gets all but the first values of array
  276. * @param {number=} n The numer of items to return
  277. * @returns {*}
  278. */
  279. this.tail = sys.bind( sys.tail, this, this.heap );
  280. /**
  281. * Gets the first n values of the array
  282. * @param {number=} n The numer of items to return
  283. * @returns {*}
  284. */
  285. this.head = sys.bind( sys.head, this, this.heap );
  286. },
  287. /**
  288. * Adds to the top of the collection
  289. * @param {*} item The item to add to the collection. Only one item at a time can be added
  290. */
  291. add : function ( item ) {
  292. this.heap.unshift( item );
  293. },
  294. /**
  295. * Add to the bottom of the list
  296. * @param {*} item The item to add to the collection. Only one item at a time can be added
  297. */
  298. append : function ( item ) {
  299. this.heap.push( item );
  300. },
  301. /**
  302. * Add an item to the top of the list. This is identical to `add`, but is provided for stack semantics
  303. * @param {*} item The item to add to the collection. Only one item at a time can be added
  304. */
  305. push : function ( item ) {
  306. this.add( item );
  307. },
  308. /**
  309. * Modifies the collection with all falsey values of array removed. The values false, null, 0, "", undefined and NaN are all falsey.
  310. */
  311. compact : function () {
  312. this.heap = sys.compact( this.heap );
  313. },
  314. /**
  315. * Creates an array of elements from the specified indexes, or keys, of the collection. Indexes may be specified as
  316. * individual arguments or as arrays of indexes
  317. * @param {indexes} args The indexes to use
  318. */
  319. at : function () {
  320. var arr = sys.toArray( arguments );
  321. arr.unshift( this.heap );
  322. return sys.at.apply( this, arr );
  323. },
  324. /**
  325. * Flattens a nested array (the nesting can be to any depth). If isShallow is truthy, array will only be flattened a single level.
  326. * If callback is passed, each element of array is passed through a callback before flattening.
  327. * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query
  328. * are iterated over.
  329. * @param {function(value, key, collection)} iterator,
  330. * @param {object=} thisobj The value of `this`
  331. * @return {number}
  332. */
  333. flatten : function ( query, iterator, thisobj ) {
  334. if ( sys.isPlainObject( query ) ) {
  335. thisobj = thisobj || this;
  336. return sys.flatten( this.find( query ), iterator, thisobj );
  337. } else {
  338. thisobj = iterator || this;
  339. return sys.flatten( this.heap, query, thisobj );
  340. }
  341. },
  342. /**
  343. * Gets an items by its index
  344. * @param {number} key The index to get
  345. * @return {*}
  346. */
  347. index : function ( index ) {
  348. return this.heap[ index ];
  349. }
  350. }
  351. );
  352. /**
  353. Collect an object
  354. @param {array|object} obj What to collect
  355. @return {ACollector|OCollector}
  356. */
  357. exports.collect = function ( obj ) {
  358. if ( sys.isArray( obj ) ) {
  359. return new ACollector( obj );
  360. } else {
  361. return new OCollector( obj );
  362. }
  363. };
  364. exports.array = function ( obj ) {
  365. return new ACollector( obj );
  366. };
  367. exports.object = function ( obj ) {
  368. return new OCollector( obj );
  369. };
  370. /**
  371. Returns true if all items match the query. Aliases as `all`
  372. @function
  373. @param {object} qu The query to execute
  374. @returns {boolean}
  375. @name every
  376. @memberOf module:documents/collector~CollectorBase#
  377. */
  378. /**
  379. Returns true if any of the items match the query. Aliases as `any`
  380. @function
  381. @param {object} qu The query to execute
  382. @returns {boolean}
  383. @memberOf module:documents/collector~CollectorBase#
  384. @name some
  385. */
  386. /**
  387. Returns the set of unique records that match a query
  388. @param {object} qu The query to execute.
  389. @return {array}
  390. @memberOf module:documents/collector~CollectorBase#
  391. @name unique
  392. @method
  393. **/
  394. /**
  395. Returns true if all items match the query. Aliases as `every`
  396. @function
  397. @param {object} qu The query to execute
  398. @returns {boolean}
  399. @name all
  400. @memberOf module:documents/collector~CollectorBase#
  401. */
  402. /**
  403. Returns true if any of the items match the query. Aliases as `all`
  404. @function
  405. @param {object} qu The query to execute
  406. @returns {boolean}
  407. @memberOf module:documents/collector~CollectorBase#
  408. @name any
  409. */
  410. /**
  411. Remove all items in the object/array that match the query
  412. @param {object} qu The query to execute. See {@link module:ink/probe.queryOperators} for the operators you can use.
  413. @return {object|array} The array or object as appropriate without the records.
  414. @memberOf module:documents/collector~CollectorBase#
  415. @name remove
  416. @method
  417. **/
  418. /**
  419. Returns the first record that matches the query and returns its key or index depending on whether `obj` is an object or array respectively.
  420. Aliased as `seekKey`.
  421. @param {object} qu The query to execute.
  422. @returns {object}
  423. @memberOf module:documents/collector~CollectorBase#
  424. @name findOneKey
  425. @method
  426. */
  427. /**
  428. Returns the first record that matches the query. Aliased as `seek`.
  429. @param {object} qu The query to execute.
  430. @returns {object}
  431. @memberOf module:documents/collector~CollectorBase#
  432. @name findOne
  433. @method
  434. */
  435. /**
  436. Find all records that match a query and returns the keys for those items. This is similar to {@link module:ink/probe.find} but instead of returning
  437. records, returns the keys. If `obj` is an object it will return the hash key. If 'obj' is an array, it will return the index
  438. @param {object} qu The query to execute.
  439. @returns {array}
  440. @memberOf module:documents/collector~CollectorBase#
  441. @name findKeys
  442. @method
  443. */
  444. /**
  445. Find all records that match a query
  446. @param {object} qu The query to execute.
  447. @returns {array} The results
  448. @memberOf module:documents/collector~CollectorBase#
  449. @name find
  450. @method
  451. **/
  452. /**
  453. Updates all records in obj that match the query. See {@link module:ink/probe.updateOperators} for the operators that are supported.
  454. @param {object} qu The query which will be used to identify the records to updated
  455. @param {object} setDocument The update operator. See {@link module:ink/probe.updateOperators}
  456. @memberOf module:documents/collector~CollectorBase#
  457. @name update
  458. @method
  459. */