API Docs for: 3.8.0
Show:

File: recordset/js/recordset-base.js

  1. /**
  2. The Recordset utility provides a standard way for dealing with
  3. a collection of similar objects.
  4. @module recordset
  5. @main recordset
  6. @submodule recordset-base
  7. **/


  8. var ArrayList = Y.ArrayList,
  9. Lang = Y.Lang,

  10. /**
  11. The Recordset utility provides a standard way for dealing with
  12. a collection of similar objects.

  13. Provides the base Recordset implementation, which can be extended to add
  14. additional functionality, such as custom indexing. sorting, and filtering.

  15. @class Recordset
  16. @extends Base
  17. @uses ArrayList
  18. @param config {Object} Configuration object with initial attribute values
  19. @constructor
  20. **/
  21. Recordset = Y.Base.create('recordset', Y.Base, [], {


  22.     /**
  23.      * Publish default functions for events. Create the initial hash table.
  24.      *
  25.      * @method initializer
  26.      * @protected
  27.      */
  28.     initializer: function() {
  29.         // The reason the conditional is needed is because of two scenarios:
  30.         // 1. Instantiating new Y.Recordset() will not go into the setter of "records", and so it is necessary to create this._items in the initializer.
  31.         // 2. Instantiating new Y.Recordset({records: [{...}]}) will call the setter of "records" and create this._items. In this case, we don't want that to be overwritten by [].
  32.         if (!this._items) {
  33.             this._items = [];
  34.         }

  35.         //set up event listener to fire events when recordset is modified in anyway
  36.         this.publish({
  37.             /**
  38.              * <p>At least one record is being added. Additional properties of
  39.              * the event are:</p>
  40.              * <dl>
  41.              *     <dt>added</dt>
  42.              *         <dd>Array of new records to be added</dd>
  43.              *     <dt>index</dt>
  44.              *         <dd>The insertion index in the Recordset's internal
  45.              *         array</dd>
  46.              * </dl>
  47.              *
  48.              * <p>Preventing this event will cause the new records NOT to be
  49.              * added to the Recordset's internal collection.</p>
  50.              *
  51.              * @event add
  52.              * @preventable _defAddFn
  53.              */
  54.             add: { defaultFn: this._defAddFn },

  55.             /**
  56.              * <p>At least one record is being removed. Additional properties of
  57.              * the event are:</p>
  58.              * <dl>
  59.              *     <dt>removed</dt>
  60.              *         <dd>Array of records to be removed</dd>
  61.              *     <dt>range</dt>
  62.              *         <dd>Number of records to be removed</dd>
  63.              *     <dt>index</dt>
  64.              *         <dd>The starting index in the Recordset's internal
  65.              *         array from which to remove records</dd>
  66.              * </dl>
  67.              *
  68.              * <p>Preventing this event will cause the records NOT to be
  69.              * removed from the Recordset's internal collection.</p>
  70.              *
  71.              * @event remove
  72.              * @preventable _defRemoveFn
  73.              */
  74.             remove: { defaultFn: this._defRemoveFn },

  75.             /**
  76.              * The Recordset is being flushed of all records.
  77.              *
  78.              * @event empty
  79.              * @preventable _defEmptyFn
  80.              */
  81.             empty: { defaultFn: this._defEmptyFn },

  82.             /**
  83.              * <p>At least one record is being updated. Additional properties of
  84.              * the event are:</p>
  85.              * <dl>
  86.              *     <dt>updated</dt>
  87.              *         <dd>Array of records with updated values</dd>
  88.              *     <dt>overwritten</dt>
  89.              *         <dd>Array of current records that will be replaced</dd>
  90.              *     <dt>index</dt>
  91.              *         <dd>The starting index in the Recordset's internal
  92.              *         array from which to update will apply</dd>
  93.              * </dl>
  94.              *
  95.              * <p>Preventing this event will cause the records NOT to be
  96.              * updated in the Recordset's internal collection.</p>
  97.              *
  98.              * @event update
  99.              * @preventable _defUpdateFn
  100.              */
  101.             update: { defaultFn: this._defUpdateFn }
  102.         });

  103.         this._buildHashTable(this.get('key'));

  104.         this.after([
  105.             'recordsChange',
  106.             'add',
  107.             'remove',
  108.             'update',
  109.             'empty'], this._updateHash);
  110.     },

  111.     /**
  112.      * Returns the record with particular ID or index
  113.      *
  114.      * @method getRecord
  115.      * @param i {String, Number} The ID of the record if a string, or the index if a number.
  116.      * @return {Record} A Y.Record instance
  117.      */
  118.     getRecord: function(i) {

  119.         if (Lang.isString(i)) {
  120.             return this.get('table')[i];
  121.         }
  122.         else if (Lang.isNumber(i)) {
  123.             return this._items[i];
  124.         }
  125.         return null;
  126.     },


  127.     /**
  128.      * Returns the record at a particular index
  129.      *
  130.      * @method getRecordByIndex
  131.      * @param i {Number} Index at which the required record resides
  132.      * @return {Record} A Y.Record instance
  133.      */
  134.     getRecordByIndex: function(i) {
  135.         return this._items[i];
  136.     },

  137.     /**
  138.      * Returns a range of records beginning at particular index
  139.      *
  140.      * @method getRecordsByIndex
  141.      * @param index {Number} Index at which the required record resides
  142.      * @param range {Number} (Optional) Number of records to retrieve. The default is 1
  143.      * @return {Array} An array of Y.Record instances
  144.      */
  145.     getRecordsByIndex: function(index, range) {
  146.         var i = 0,
  147.         returnedRecords = [];
  148.         //Range cannot take on negative values
  149.         range = (Lang.isNumber(range) && (range > 0)) ? range: 1;

  150.         for (; i < range; i++) {
  151.             returnedRecords.push(this._items[index + i]);
  152.         }
  153.         return returnedRecords;
  154.     },

  155.     /**
  156.      * Returns the length of the recordset
  157.      *
  158.      * @method getLength
  159.      * @return {Number} Number of records in the recordset
  160.      */
  161.     getLength: function() {
  162.         return this.size();
  163.     },

  164.     /**
  165.     Gets an array of values for a data _key_ in the set's records.  If no _key_
  166.     is supplied, the returned array will contain the full data object for each
  167.     record.

  168.     @method getValuesByKey
  169.     @param {String} [key] Data property to get from all records
  170.     @return {Array} An array of values for the given _key_ if supplied.
  171.         Otherwise, an array of each record's data hash.
  172.     **/
  173.     getValuesByKey: function(key) {
  174.         var i = 0,
  175.         len = this._items.length,
  176.         retVals = [];
  177.         for (; i < len; i++) {
  178.             retVals.push(this._items[i].getValue(key));
  179.         }
  180.         return retVals;
  181.     },


  182.     /**
  183.      * Adds one or more Records to the RecordSet at the given index. If index is null, then adds the Records to the end of the RecordSet.
  184.      *
  185.      * @method add
  186.      * @param {Record|Object|Array} oData A Y.Record instance, An object literal of data or an array of object literals
  187.      * @param [index] {Number} [index] Index at which to add the record(s)
  188.      * @return {Recordset} The updated recordset instance
  189.      */
  190.     add: function(oData, index) {

  191.         var newRecords = [],
  192.         idx,
  193.         i = 0;

  194.         idx = (Lang.isNumber(index) && (index > -1)) ? index: this._items.length;

  195.         //Passing in array of object literals for oData
  196.         if (Lang.isArray(oData)) {
  197.             for (; i < oData.length; i++) {
  198.                 newRecords[i] = this._changeToRecord(oData[i]);
  199.             }
  200.         } else if (Lang.isObject(oData)) {
  201.             newRecords[0] = this._changeToRecord(oData);
  202.         }

  203.         this.fire('add', {
  204.             added: newRecords,
  205.             index: idx
  206.         });
  207.         return this;
  208.     },

  209.     /**
  210.     Removes one or more Records to the RecordSet at the given index. If index
  211.     is null, then removes a single Record from the end of the RecordSet.
  212.    
  213.     @method remove
  214.     @param {Number} [index] Index at which to remove the record(s) from
  215.     @param {Number} [range] Number of records to remove (including the one
  216.         at the index)
  217.     @return {Recordset} The updated recordset instance
  218.     **/
  219.     remove: function(index, range) {
  220.         var remRecords = [];

  221.         //Default is to only remove the last record - the length is always 1 greater than the last index
  222.         index = (index > -1) ? index: (this._items.length - 1);
  223.         range = (range > 0) ? range: 1;

  224.         remRecords = this._items.slice(index, (index + range));
  225.         this.fire('remove', {
  226.             removed: remRecords,
  227.             range: range,
  228.             index: index
  229.         });
  230.         //this._recordRemoved(remRecords, index);
  231.         //return ({data: remRecords, index:index});
  232.         return this;
  233.     },

  234.     /**
  235.      * Empties the recordset
  236.      *
  237.      * @method empty
  238.      * @return {Recordset} The updated recordset instance
  239.      */
  240.     empty: function() {
  241.         this.fire('empty', {});
  242.         return this;
  243.     },

  244.     /**
  245.     Updates the recordset with the new records passed in. Overwrites existing
  246.     records when updating the index with the new records.
  247.    
  248.     @method update
  249.     @param {Record|Object|Array} data A Y.Record instance, An object literal of
  250.         data or an array of object literals
  251.     @param {Number} [index] The index to start updating from.
  252.     @return {Recordset} The updated recordset instance
  253.     **/
  254.     update: function(data, index) {
  255.         var rec,
  256.             arr,
  257.             i = 0;

  258.         // Whatever is passed in, we are changing it to an array so that it can
  259.         // be easily iterated in the _defUpdateFn method
  260.         arr = (!(Lang.isArray(data))) ? [data] : data;
  261.         rec = this._items.slice(index, index + arr.length);

  262.         for (; i < arr.length; i++) {
  263.             arr[i] = this._changeToRecord(arr[i]);
  264.         }

  265.         this.fire('update', {
  266.             updated: arr,
  267.             overwritten: rec,
  268.             index: index
  269.         });

  270.         return this;
  271.     },

  272.     /**
  273.      * Default behavior for the "add" event. Adds Record instances starting from
  274.      * the index specified in `e.index`.
  275.      *
  276.      * @method _defAddFn
  277.      * @param {EventFacade} e The add event
  278.      * @private
  279.      */
  280.     _defAddFn: function(e) {
  281.         this._items.splice.apply(this._items, [e.index, 0].concat(e.added));
  282.     },

  283.     /**
  284.      * Default behavior for the "remove" event. Removes Records from the
  285.      * internal array starting from `e.index`.  By default, it will remove one
  286.      * Record. But if `e.range` is set, it will remove that many Records.
  287.      *
  288.      * @method _defRemoveFn
  289.      * @param {EventFacade} e The remove event
  290.      * @private
  291.      */
  292.     _defRemoveFn: function(e) {
  293.         this._items.splice(e.index, e.range || 1);
  294.     },

  295.     /**
  296.      * Default behavior for the "update" event. Sets Record instances for each
  297.      * item in `e.updated` at indexes starting from `e.index`.
  298.      *
  299.      * @method _defUpdateFn
  300.      * @param {EventFacade} e The update event
  301.      * @private
  302.      */
  303.     _defUpdateFn: function(e) {
  304.         for (var i = 0; i < e.updated.length; i++) {
  305.             this._items[e.index + i] = this._changeToRecord(e.updated[i]);
  306.         }
  307.     },

  308.     /**
  309.      * Default behavior for the "empty" event. Clears the internal array of
  310.      * Records.
  311.      *
  312.      * @method _defEmptyFn
  313.      * @param {EventFacade} e The empty event
  314.      * @private
  315.      */
  316.     _defEmptyFn: function(e) {
  317.         this._items = [];
  318.         Y.log('empty fired');
  319.     },

  320.     /**
  321.     Updates the internal hash table.

  322.     @method _defUpdateHash
  323.     @param {EventFacade} e Event triggering the hash table update
  324.     @private
  325.     **/
  326.     _updateHash: function (e) {
  327.         var handler = "_hash",
  328.             type = e.type.replace(/.*:/,''),
  329.             newHash;

  330.         // _hashAdd, _hashRemove, _hashEmpty, etc
  331.         // Not a switch or else if setup to allow for external expansion.
  332.         handler += type.charAt(0).toUpperCase() + type.slice(1);

  333.         newHash = this[handler] &&
  334.                     this[handler](this.get('table'), this.get('key'), e);

  335.         if (newHash) {
  336.             this.set('table', newHash);
  337.         }
  338.     },

  339.     /**
  340.     Regenerates the hash table from the current internal array of Records.

  341.     @method _hashRecordsChange
  342.     @param {Object} hash The hash map before replacement
  343.     @param {String} key The key by which to add items to the hash
  344.     @param {Object} e The event or object containing the items to be added.
  345.                       Items are expected to be stored in an array assigned to
  346.                       the `added` property.
  347.     @return {Object} The updated hash map
  348.     @private
  349.     **/
  350.     _hashRecordsChange: function (hash, key, e) {
  351.         return this._buildHashTable(key);
  352.     },

  353.     /**
  354.     Builds a hash table from the current internal array of Records.

  355.     @method _buildHashTable
  356.     @param {String} key The Record key to hash the items by
  357.     @return {Object} A new hash map of Records keyed by each Records' key
  358.     @private
  359.     **/
  360.     _buildHashTable: function (key) {
  361.         return this._hashAdd({}, key, { added: this._items });
  362.     },

  363.     /**
  364.     Adds items to the hash table.  Items are the values, and the keys are the
  365.     values of the item's attribute named in the `key` parameter.

  366.     @method _hashAdd
  367.     @param {Object} hash The hash map before adding items
  368.     @param {String} key The key by which to add the items to the hash
  369.     @param {Object} e The event or object containing the items to be added.
  370.                       Items are expected to be stored in an array assigned to
  371.                       the `added` property.
  372.     @return {Object} The updated hash map
  373.     @private
  374.     **/
  375.     _hashAdd: function(hash, key, e) {
  376.         var items = e.added,
  377.             i, len;

  378.         for (i = 0, len = e.added.length; i < len; ++i) {
  379.             hash[items[i].get(key)] = items[i];
  380.         }

  381.         return hash;
  382.     },

  383.     /**
  384.     Removes items from the hash table.

  385.     @method _hashRemove
  386.     @param {Object} hash The hash map before removing items
  387.     @param {String} key The key by which to remove the items from the hash
  388.     @param {Object} e The event or object containing the items to be removed.
  389.                       Items are expected to be stored in an array assigned to
  390.                       the `removed` property.
  391.     @return {Object} The updated hash map
  392.     @private
  393.     **/
  394.     _hashRemove: function(hash, key, e) {
  395.         for (var i = e.removed.length - 1; i >= 0; --i) {
  396.             delete hash[e.removed[i].get(key)];
  397.         }

  398.         return hash;
  399.     },

  400.     /**
  401.     Updates items in the hash table.

  402.     @method _hashUpdate
  403.     @param {Object} hash The hash map before updating items
  404.     @param {String} key The key by which to update the items to the hash
  405.     @param {Object} e The event or object containing the items to be updated.
  406.                       Items are expected to be stored in an array assigned to
  407.                       the `updated` property. Optionally, items can be
  408.                       identified for being overwritten by including them in an
  409.                       array assigned to the `overwritten` property.
  410.     @return {Object} The updated hash map
  411.     @private
  412.     **/
  413.     _hashUpdate: function (hash, key, e) {
  414.         if (e.overwritten && e.overwritten.length) {
  415.             hash = this._hashRemove(hash, key, { removed: e.overwritten });
  416.         }

  417.         return this._hashAdd(hash, key, { added: e.updated });
  418.     },

  419.     /**
  420.     Clears the hash table.

  421.     @method _hashEmpty
  422.     @param {Object} hash The hash map before adding items
  423.     @param {String} key The key by which to remove the items from the hash
  424.     @param {Object} e The event or object containing the items to be removed.
  425.                       Items are expected to be stored in an array assigned to
  426.                       the `removed` property.
  427.     @return {Object} An empty hash
  428.     @private
  429.     **/
  430.     _hashEmpty: function() {
  431.         return {};
  432.     },

  433.     /**
  434.      * Sets up the hashtable with all the records currently in the recordset
  435.      *
  436.      * @method _initHashTable
  437.      * @private
  438.      */
  439.     _initHashTable: function() {
  440.         return this._hashAdd({}, this.get('key'), { added: this._items || [] });
  441.     },

  442.     /**
  443.      * Helper method - it takes an object bag and converts it to a Y.Record
  444.      *
  445.      * @method _changeToRecord
  446.      * @param obj {Object|Record} Any objet literal or Y.Record instance
  447.      * @return {Record} A Record instance.
  448.      * @private
  449.      */
  450.     _changeToRecord: function(obj) {
  451.         return (obj instanceof Y.Record) ? obj : new Y.Record({ data: obj });
  452.     },

  453.     /**
  454.     Ensures the value being set is an array of Record instances. If array items
  455.     are raw object data, they are turned into Records.

  456.     @method _setRecords
  457.     @param {Record[]|Object[]} items The Records or data Objects to store as
  458.                                      Records.
  459.     @return {Record[]}
  460.     **/
  461.     _setRecords: function (items) {
  462.         if (!Y.Lang.isArray(items)) {
  463.             return Y.Attribute.INVALID_VALUE;
  464.         }

  465.         var records = [],
  466.             i, len;

  467.         // FIXME: This should use the flyweight pattern if possible
  468.         for (i = 0, len = items.length; i < len; ++i) {
  469.             records[i] = this._changeToRecord(items[i]);
  470.         }

  471.         return (this._items = records);
  472.     }
  473. }, {
  474.     ATTRS: {

  475.         /**
  476.         * An array of Records that the Recordset is storing.  Passing an array
  477.         * of raw record data is also accepted.  The data for each item will be
  478.         * wrapped in a Record instance.
  479.         *
  480.         * @attribute records
  481.         * @type {Record[]}
  482.         */
  483.         records: {
  484.             // TODO: necessary? valueFn?
  485.             lazyAdd: false,
  486.             getter: function() {
  487.                 // give them a copy, not the internal object
  488.                 return Y.Array(this._items);
  489.             },
  490.             setter: "_setRecords"
  491.         },

  492.         /**
  493.         A hash table where the ID of the record is the key, and the record
  494.         instance is the value.
  495.        
  496.         @attribute table
  497.         @type object
  498.         **/
  499.         table: {
  500.             valueFn: '_initHashTable'
  501.         },

  502.         /**
  503.         The ID to use as the key in the hash table.
  504.        
  505.         @attribute key
  506.         @type string
  507.         **/
  508.         key: {
  509.             value: 'id',
  510.             readOnly: true
  511.         }

  512.     }
  513. });
  514. Y.augment(Recordset, ArrayList);
  515. Y.Recordset = Recordset;


  516.