"use strict"; /** @fileOverview An object and array collector @module ink/collector */ var probe = require( "ink-probe" ); var sys = require( "lodash" ); var dcl = require( "dcl" ); /** * A collector * @constructor */ var CollectorBase = dcl( Destroyable, { declaredClass : "CollectorBase", constructor : function ( obj ) { var that = this; if ( obj && !sys.isObject( obj ) ) { throw new TypeError( "Collectors require an initial object or array passed to the constructor" ); } /** * The collection that being managed * @type {object|array} */ this.heap = obj || {}; // mixin the probe probe.mixTo( this, this.heap ); /** * Get the size of the collection * @name length * @type {number} * @memberOf module:documents/collector~CollectorBase# */ Object.defineProperty( this, "length", { get : function () { return sys.size( that.heap ); } } ); /** * Creates an array of shuffled array values, using a version of the Fisher-Yates shuffle. * See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. * @function * @memberOf module:documents/collector~CollectorBase# * @returns {array} */ this.shuffle = sys.bind( sys.shuffle, this, this.heap ); }, /** * Adds an item to the collection * @param {*} key The key to use for the item being added. * @param {*} item The item to add to the collection. The item is not iterated so that you could add bundled items to the collection */ add : function ( key, item ) { this.heap[key] = item; }, /** * Iterate over each item in the collection, or a subset that matches a query. This supports two signatures: * `.each(query, function)` and `.each(function)`. If you pass in a query, only the items that match the query * are iterated over. * @param {object=} query A query to evaluate * @param {function(val, key)} iterator Function to execute against each item in the collection * @param {object=} thisobj The value of `this` */ each : function ( query, iterator, thisobj ) { if ( sys.isPlainObject( query ) ) { thisobj = thisobj || this; sys.each( this.find( query ), iterator, thisobj ); } else { thisobj = iterator || this; sys.each( this.heap, query, thisobj ); } }, /** * Returns the collection as an array. If it is already an array, it just returns that. * @return {array} */ toArray : function () { return sys.toArray( this.heap ); }, /** * Supports conversion to a JSON string or for passing over the wire * @return {object} * @returns {Object|array} */ toJSON : function () { return this.heap; }, /** * Maps the contents to an array by iterating over it and transforming it. You supply the iterator. Supports two signatures: * `.map(query, function)` and `.map(function)`. If you pass in a query, only the items that match the query * are iterated over. * @param {object=} query A query to evaluate * @param {function(val, key)} iterator Function to execute against each item in the collection * @param {object=} thisobj The value of `this` */ map : function ( query, iterator, thisobj ) { if ( sys.isPlainObject( query ) ) { thisobj = thisobj || this; return sys.map( this.find( query ), iterator, thisobj ); } else { thisobj = iterator || this; return sys.map( this.heap, query, thisobj ); } }, /** * Reduces a collection to a value which is the accumulated result of running each element in the collection through the * callback, where each successive callback execution consumes the return value of the previous execution. If accumulator * is not passed, the first element of the collection will be used as the initial accumulator value. * are iterated over. * @param {object=} query A query to evaluate * @param {function(result, val, key)} iterator The function that will be executed in each item in the collection * @param {*=} accumulator Initial value of the accumulator. * @param {object=} thisobj The value of `this` * @return {*} */ reduce : function ( query, iterator, accumulator, thisobj ) { if ( sys.isPlainObject( query ) ) { thisobj = thisobj || this; return sys.reduce( this.find( query ), iterator, accumulator, thisobj ); } else { thisobj = accumulator || this; return sys.reduce( this.heap, query, iterator, thisobj ); } }, /** * Creates an object composed of keys returned from running each element * of the collection through the given callback. The corresponding value of each key * is the number of times the key was returned by the callback. * @param {object=} query A query to evaluate. If you pass in a query, only the items that match the query * are iterated over. * @param {function(value, key, collection)} iterator * @param {object=} thisobj The value of `this` * @return {object} */ countBy : function ( query, iterator, thisobj ) { if ( sys.isPlainObject( query ) ) { thisobj = thisobj || this; return sys.countBy( this.find( query ), iterator, thisobj ); } else { thisobj = iterator || this; return sys.countBy( this.heap, query, thisobj ); } }, /** * Creates an object composed of keys returned from running each element of the collection through the callback. * The corresponding value of each key is an array of elements passed to callback that returned the key. * The callback is invoked with three arguments: (value, index|key, collection). * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query * are iterated over. * @param {function(value, key, collection)} iterator * @param {object=} thisobj The value of `this` * @return {object} */ groupBy : function ( query, iterator, thisobj ) { if ( sys.isPlainObject( query ) ) { thisobj = thisobj || this; return sys.groupBy( this.find( query ), iterator, thisobj ); } else { thisobj = iterator || this; return sys.groupBy( this.heap, query, thisobj ); } }, /** * Reduce the collection to a single value. Supports two signatures: * `.pluck(query, function)` and `.pluck(function)` * @param {object=} query The query to evaluate. If you pass in a query, only the items that match the query * are iterated over. * @param {string} property The property that will be 'plucked' from the contents of the collection * @return {*} */ pluck : function ( query, property ) { if ( arguments.length === 2 ) { return sys.map( this.find( query ), function ( record ) { return probe.get( record, property ); } ); } else { return sys.map( this.heap, function ( record ) { return probe.get( record, query ); } ); } }, /** * Returns a sorted copy of the collection. * @param {object=} query The query to evaluate. If you pass in a query, only the items that match the query * are iterated over. * @param {function(value, key)} iterator * @param {object=} thisobj The value of `this` * @return {array} */ sortBy : function ( query, iterator, thisobj ) { if ( sys.isPlainObject( query ) ) { thisobj = thisobj || this; return sys.sortBy( this.find( query ), iterator, thisobj ); } else { thisobj = iterator || this; return sys.sortBy( this.heap, query, thisobj ); } }, /** * Retrieves the maximum value of an array. If callback is passed, * it will be executed for each value in the array to generate the criterion by which the value is ranked. * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query * are iterated over. * @param {function(value, key, collection)} iterator * @param {object=} thisobj The value of `this` * @return {number} */ max : function ( query, iterator, thisobj ) { if ( sys.isPlainObject( query ) ) { thisobj = thisobj || this; return sys.max( this.find( query ), iterator, thisobj ); } else { thisobj = iterator || this; return sys.max( this.heap, query, thisobj ); } }, /** * Retrieves the minimum value of an array. If callback is passed, * it will be executed for each value in the array to generate the criterion by which the value is ranked. * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query * are iterated over. * @param {function(value, key, collection)} iterator * @param {object=} thisobj The value of `this` * @return {number} */ min : function ( query, iterator, thisobj ) { if ( sys.isPlainObject( query ) ) { thisobj = thisobj || this; return sys.min( this.find( query ), iterator, thisobj ); } else { thisobj = iterator || this; return sys.min( this.heap, query, thisobj ); } }, /** * Destructor called when the object is destroyed. */ destroy : function () { this.heap = null; } } ); /** * An object based collector * @extends module:documents/collector~CollectorBase * @constructor */ var OCollector = dcl( CollectorBase, { /** * Get a record by key * @param {*} key The key of the record to get * @return {*} */ key : function ( key ) { return this.heap[key]; } } ); //noinspection JSCommentMatchesSignature /** An array based collector @extends module:documents/collector~CollectorBase @constructor */ var ACollector = dcl( CollectorBase, { constructor : function ( obj ) { if ( obj && !sys.isArray( obj ) ) { throw new TypeError( "Collectors require an array passed to the constructor" ); } this.heap = obj || []; /** * Creates an array of array elements not present in the other arrays using strict equality for comparisons, i.e. ===. * @returns {array} */ this.difference = sys.bind( sys.difference, this, this.heap ); /** * This method gets all but the first values of array * @param {number=} n The numer of items to return * @returns {*} */ this.tail = sys.bind( sys.tail, this, this.heap ); /** * Gets the first n values of the array * @param {number=} n The numer of items to return * @returns {*} */ this.head = sys.bind( sys.head, this, this.heap ); }, /** * Adds to the top of the collection * @param {*} item The item to add to the collection. Only one item at a time can be added */ add : function ( item ) { this.heap.unshift( item ); }, /** * Add to the bottom of the list * @param {*} item The item to add to the collection. Only one item at a time can be added */ append : function ( item ) { this.heap.push( item ); }, /** * Add an item to the top of the list. This is identical to `add`, but is provided for stack semantics * @param {*} item The item to add to the collection. Only one item at a time can be added */ push : function ( item ) { this.add( item ); }, /** * Modifies the collection with all falsey values of array removed. The values false, null, 0, "", undefined and NaN are all falsey. */ compact : function () { this.heap = sys.compact( this.heap ); }, /** * Creates an array of elements from the specified indexes, or keys, of the collection. Indexes may be specified as * individual arguments or as arrays of indexes * @param {indexes} args The indexes to use */ at : function () { var arr = sys.toArray( arguments ); arr.unshift( this.heap ); return sys.at.apply( this, arr ); }, /** * Flattens a nested array (the nesting can be to any depth). If isShallow is truthy, array will only be flattened a single level. * If callback is passed, each element of array is passed through a callback before flattening. * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query * are iterated over. * @param {function(value, key, collection)} iterator, * @param {object=} thisobj The value of `this` * @return {number} */ flatten : function ( query, iterator, thisobj ) { if ( sys.isPlainObject( query ) ) { thisobj = thisobj || this; return sys.flatten( this.find( query ), iterator, thisobj ); } else { thisobj = iterator || this; return sys.flatten( this.heap, query, thisobj ); } }, /** * Gets an items by its index * @param {number} key The index to get * @return {*} */ index : function ( index ) { return this.heap[ index ]; } } ); /** Collect an object @param {array|object} obj What to collect @return {ACollector|OCollector} */ exports.collect = function ( obj ) { if ( sys.isArray( obj ) ) { return new ACollector( obj ); } else { return new OCollector( obj ); } }; exports.array = function ( obj ) { return new ACollector( obj ); }; exports.object = function ( obj ) { return new OCollector( obj ); }; /** Returns true if all items match the query. Aliases as `all` @function @param {object} qu The query to execute @returns {boolean} @name every @memberOf module:documents/collector~CollectorBase# */ /** Returns true if any of the items match the query. Aliases as `any` @function @param {object} qu The query to execute @returns {boolean} @memberOf module:documents/collector~CollectorBase# @name some */ /** Returns the set of unique records that match a query @param {object} qu The query to execute. @return {array} @memberOf module:documents/collector~CollectorBase# @name unique @method **/ /** Returns true if all items match the query. Aliases as `every` @function @param {object} qu The query to execute @returns {boolean} @name all @memberOf module:documents/collector~CollectorBase# */ /** Returns true if any of the items match the query. Aliases as `all` @function @param {object} qu The query to execute @returns {boolean} @memberOf module:documents/collector~CollectorBase# @name any */ /** Remove all items in the object/array that match the query @param {object} qu The query to execute. See {@link module:ink/probe.queryOperators} for the operators you can use. @return {object|array} The array or object as appropriate without the records. @memberOf module:documents/collector~CollectorBase# @name remove @method **/ /** Returns the first record that matches the query and returns its key or index depending on whether `obj` is an object or array respectively. Aliased as `seekKey`. @param {object} qu The query to execute. @returns {object} @memberOf module:documents/collector~CollectorBase# @name findOneKey @method */ /** Returns the first record that matches the query. Aliased as `seek`. @param {object} qu The query to execute. @returns {object} @memberOf module:documents/collector~CollectorBase# @name findOne @method */ /** 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 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 @param {object} qu The query to execute. @returns {array} @memberOf module:documents/collector~CollectorBase# @name findKeys @method */ /** Find all records that match a query @param {object} qu The query to execute. @returns {array} The results @memberOf module:documents/collector~CollectorBase# @name find @method **/ /** Updates all records in obj that match the query. See {@link module:ink/probe.updateOperators} for the operators that are supported. @param {object} qu The query which will be used to identify the records to updated @param {object} setDocument The update operator. See {@link module:ink/probe.updateOperators} @memberOf module:documents/collector~CollectorBase# @name update @method */