]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/recordset/recordset.js
Release 6.5.0
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / recordset / recordset.js
1 /*
2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
5 version: 3.3.0
6 build: 3167
7 */
8 YUI.add('recordset-base', function(Y) {
9
10 /**
11  * Provides a wrapper around a standard javascript object. Can be inserted into a Recordset instance.
12  *
13  * @class Record
14  */
15 var Record = Y.Base.create('record', Y.Base, [], {
16     _setId: function() {
17         return Y.guid();
18     },
19
20     initializer: function() {
21     },
22
23     destructor: function() {
24     },
25
26     /**
27      * Retrieve a particular (or all) values from the object
28          *
29      * @param field {string} (optional) The key to retrieve the value from. If not supplied, the entire object is returned.
30      * @method getValue
31      * @public
32      */
33     getValue: function(field) {
34         if (field === undefined) {
35             return this.get("data");
36         }
37         else {
38             return this.get("data")[field];
39         }
40         return null;
41     }
42 },
43 {
44     ATTRS: {
45
46         /**
47             * @description Unique ID of the record instance
48             * @attribute id
49             * @type string
50             */
51         id: {
52             valueFn: "_setId"
53         },
54
55         /**
56             * @description The object stored within the record instance
57             * @attribute data
58             * @type object
59             */
60         data: {
61             value: null
62         }
63     }
64 });
65
66 Y.Record = Record;
67
68 /**
69  * The Recordset utility provides a standard way for dealing with
70  * a collection of similar objects.
71  * @module recordset
72  * @submodule recordset-base
73  */
74
75
76 var ArrayList = Y.ArrayList,
77 Lang = Y.Lang,
78
79 /**
80          * The Recordset utility provides a standard way for dealing with
81          * a collection of similar objects.
82          *
83          * Provides the base Recordset implementation, which can be extended to add
84          * additional functionality, such as custom indexing. sorting, and filtering.
85          *
86          * @class Recordset
87          * @extends Base
88          * @augments ArrayList
89          * @param config {Object} Configuration object literal with initial attribute values
90          * @constructor
91          */
92
93 Recordset = Y.Base.create('recordset', Y.Base, [], {
94
95
96     /**
97      * @description Publish default functions for events. Create the initial hash table.
98      *
99      * @method initializer
100          * @protected
101      */
102     initializer: function() {
103
104         /*
105                 If this._items does not exist, then create it and set it to an empty array.
106                 The reason the conditional is needed is because of two scenarios:
107                 Instantiating new Y.Recordset() will not go into the setter of "records", and so
108                 it is necessary to create this._items in the initializer.
109                 
110                 Instantiating new Y.Recordset({records: [{...}]}) will call the setter of "records" and create
111                 this._items. In this case, we don't want that to be overwritten by [].
112                 */
113
114         if (!this._items) {
115             this._items = [];
116         }
117
118         //set up event listener to fire events when recordset is modified in anyway
119         this.publish('add', {
120             defaultFn: this._defAddFn
121         });
122         this.publish('remove', {
123             defaultFn: this._defRemoveFn
124         });
125         this.publish('empty', {
126             defaultFn: this._defEmptyFn
127         });
128         this.publish('update', {
129             defaultFn: this._defUpdateFn
130         });
131
132         this._recordsetChanged();
133         //Fires recordset changed event when any updates are made to the recordset
134         this._syncHashTable();
135         //Fires appropriate hashTable methods on "add", "remove", "update" and "empty" events
136     },
137
138     destructor: function() {
139         },
140
141     /**
142      * @description Helper method called upon by add() - it is used to create a new record(s) in the recordset
143      *
144      * @method _defAddFn
145      * @return {Y.Record} A Record instance.
146      * @private
147      */
148     _defAddFn: function(e) {
149         var len = this._items.length,
150         recs = e.added,
151         index = e.index,
152         i = 0;
153         //index = (Lang.isNumber(index) && (index > -1)) ? index : len;
154         for (; i < recs.length; i++) {
155             //if records are to be added one at a time, push them in one at a time
156             if (index === len) {
157                 this._items.push(recs[i]);
158             }
159             else {
160                 this._items.splice(index, 0, recs[i]);
161                 index++;
162             }
163         }
164
165
166     },
167
168     /**
169      * @description Helper method called upon by remove() - it is used to remove record(s) from the recordset
170      *
171      * @method _defRemoveFn
172      * @private
173      */
174     _defRemoveFn: function(e) {
175         
176         //remove from beginning
177         if (e.index === 0) {
178             this._items.shift();
179         }
180         
181         //remove from end
182         else if (e.index === this._items.length - 1) {
183             this._items.pop();
184         }
185         
186         //remove from middle
187         else {
188             this._items.splice(e.index, e.range);
189         }
190     },
191
192     /**
193      * Helper method called upon by empty() - it is used to empty the recordset
194      *
195      * @method _defEmptyFn
196      * @private
197      */
198     _defEmptyFn: function(e) {
199         this._items = [];
200     },
201
202     /**
203      * @description Helper method called upon by update() - it is used to update the recordset
204      *
205      * @method _defUpdateFn
206      * @private
207      */
208     _defUpdateFn: function(e) {
209
210         for (var i = 0; i < e.updated.length; i++) {
211             this._items[e.index + i] = this._changeToRecord(e.updated[i]);
212         }
213     },
214
215
216     //---------------------------------------------
217     // Hash Table Methods
218     //---------------------------------------------
219
220     /**
221      * @description Method called whenever "recordset:add" event is fired. It adds the new record(s) to the hashtable.
222      *
223      * @method _defAddHash
224      * @private
225      */
226     _defAddHash: function(e) {
227         var obj = this.get('table'),
228         key = this.get('key'),
229         i = 0;
230         for (; i < e.added.length; i++) {
231             obj[e.added[i].get(key)] = e.added[i];
232         }
233         this.set('table', obj);
234     },
235
236     /**
237      * @description Method called whenever "recordset:remove" event is fired. It removes the record(s) from the recordset.
238      *
239      * @method _defRemoveHash
240      * @private
241      */
242     _defRemoveHash: function(e) {
243         var obj = this.get('table'),
244         key = this.get('key'),
245         i = 0;
246         for (; i < e.removed.length; i++) {
247             delete obj[e.removed[i].get(key)];
248         }
249         this.set('table', obj);
250     },
251
252
253     /**
254      * @description Method called whenever "recordset:update" event is fired. It updates the record(s) by adding the new ones and removing the overwritten ones.
255      *
256      * @method _defUpdateHash
257      * @private
258      */
259     _defUpdateHash: function(e) {
260         var obj = this.get('table'),
261         key = this.get('key'),
262         i = 0;
263
264         //deletes the object key that held on to an overwritten record and
265         //creates an object key to hold on to the updated record
266         for (; i < e.updated.length; i++) {
267             if (e.overwritten[i]) {
268                 delete obj[e.overwritten[i].get(key)];
269             }
270             obj[e.updated[i].get(key)] = e.updated[i];
271         }
272         this.set('table', obj);
273     },
274
275     /**
276      * @description Method called whenever "recordset:empty" event is fired. It empties the hash table.
277      *
278      * @method _defEmptyHash
279      * @private
280      */
281     _defEmptyHash: function() {
282         this.set('table', {});
283     },
284
285     /**
286      * @description Sets up the hashtable with all the records currently in the recordset
287      *
288      * @method _setHashTable
289      * @private
290      */
291     _setHashTable: function() {
292         var obj = {},
293         key = this.get('key'),
294         i = 0;
295
296         //If it is not an empty recordset - go through and set up the hash table.
297         if (this._items && this._items.length > 0) {
298             var len = this._items.length;
299             for (; i < len; i++) {
300                 obj[this._items[i].get(key)] = this._items[i];
301             }
302         }
303         return obj;
304     },
305
306
307     /**
308      * @description Helper method - it takes an object bag and converts it to a Y.Record
309      *
310      * @method _changeToRecord
311      * @param obj {Object || Y.Record} Any objet literal or Y.Record instance
312      * @return {Y.Record} A Record instance.
313      * @private
314      */
315     _changeToRecord: function(obj) {
316         var oRec;
317         if (obj instanceof Y.Record) {
318             oRec = obj;
319         }
320         else {
321             oRec = new Y.Record({
322                 data: obj
323             });
324         }
325
326         return oRec;
327     },
328
329     //---------------------------------------------
330     // Events
331     //---------------------------------------------
332     /**
333      * @description Event that is fired whenever the recordset is changed. Note that multiple simultaneous changes still fires this event once. (ie: Adding multiple records via an array will only fire this event once at the completion of all the additions)
334      *
335      * @method _recordSetUpdated
336      * @private
337      */
338     _recordsetChanged: function() {
339
340         this.on(['update', 'add', 'remove', 'empty'],
341         function() {
342             this.fire('change', {});
343         });
344     },
345
346
347     /**
348      * @description Syncs up the private hash methods with their appropriate triggering events.
349      *
350      * @method _syncHashTable
351      * @private
352      */
353     _syncHashTable: function() {
354
355         this.after('add',
356         function(e) {
357             this._defAddHash(e);
358         });
359         this.after('remove',
360         function(e) {
361             this._defRemoveHash(e);
362         });
363         this.after('update',
364         function(e) {
365             this._defUpdateHash(e);
366         });
367         this.after('update',
368         function(e) {
369             this._defEmptyHash();
370         });
371
372     },
373
374     //---------------------------------------------
375     // Public Methods
376     //---------------------------------------------
377     /**
378      * @description Returns the record with particular ID or index
379      *
380      * @method getRecord
381      * @param i {String, Number} The ID of the record if a string, or the index if a number.
382      * @return {Y.Record} An Y.Record instance
383      * @public
384      */
385     getRecord: function(i) {
386
387         if (Lang.isString(i)) {
388             return this.get('table')[i];
389         }
390         else if (Lang.isNumber(i)) {
391             return this._items[i];
392         }
393         return null;
394     },
395
396
397     /**
398      * @description Returns the record at a particular index
399      *
400      * @method getRecordByIndex
401      * @param i {Number} Index at which the required record resides
402      * @return {Y.Record} An Y.Record instance
403      * @public
404      */
405     getRecordByIndex: function(i) {
406         return this._items[i];
407     },
408
409     /**
410      * @description Returns a range of records beginning at particular index
411      *
412      * @method getRecordsByIndex
413      * @param index {Number} Index at which the required record resides
414          * @param range {Number} (Optional) Number of records to retrieve. The default is 1
415      * @return {Array} An array of Y.Record instances
416      * @public
417      */
418     getRecordsByIndex: function(index, range) {
419         var i = 0,
420         returnedRecords = [];
421         //Range cannot take on negative values
422         range = (Lang.isNumber(range) && (range > 0)) ? range: 1;
423
424         for (; i < range; i++) {
425             returnedRecords.push(this._items[index + i]);
426         }
427         return returnedRecords;
428     },
429
430     /**
431      * @description Returns the length of the recordset
432      *
433      * @method getLength
434      * @return {Number} Number of records in the recordset
435      * @public
436      */
437     getLength: function() {
438         return this.size();
439     },
440
441     /**
442      * @description Returns an array of values for a specified key in the recordset
443      *
444      * @method getValuesByKey
445      * @param index {Number} (optional) Index at which the required record resides
446      * @return {Array} An array of values for the given key
447      * @public
448      */
449     getValuesByKey: function(key) {
450         var i = 0,
451         len = this._items.length,
452         retVals = [];
453         for (; i < len; i++) {
454             retVals.push(this._items[i].getValue(key));
455         }
456         return retVals;
457     },
458
459
460     /**
461      * @description 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.
462      *
463      * @method add
464      * @param oData {Y.Record, Object Literal, Array} A Y.Record instance, An object literal of data or an array of object literals
465      * @param index {Number} (optional) Index at which to add the record(s)
466      * @return {Y.Recordset} The updated recordset instance
467      * @public
468      */
469     add: function(oData, index) {
470
471         var newRecords = [],
472         idx,
473         i = 0;
474
475         idx = (Lang.isNumber(index) && (index > -1)) ? index: this._items.length;
476
477
478
479         //Passing in array of object literals for oData
480         if (Lang.isArray(oData)) {
481             for (; i < oData.length; i++) {
482                 newRecords[i] = this._changeToRecord(oData[i]);
483             }
484
485         }
486         else if (Lang.isObject(oData)) {
487             newRecords[0] = this._changeToRecord(oData);
488         }
489
490         this.fire('add', {
491             added: newRecords,
492             index: idx
493         });
494         return this;
495     },
496
497     /**
498      * @description Removes one or more Records to the RecordSet at the given index. If index is null, then removes a single Record from the end of the RecordSet.
499      *
500      * @method remove
501      * @param index {Number} (optional) Index at which to remove the record(s) from
502      * @param range {Number} (optional) Number of records to remove (including the one at the index)
503      * @return {Y.Recordset} The updated recordset instance
504      * @public
505      */
506     remove: function(index, range) {
507         var remRecords = [];
508
509         //Default is to only remove the last record - the length is always 1 greater than the last index
510         index = (index > -1) ? index: (this._items.length - 1);
511         range = (range > 0) ? range: 1;
512
513         remRecords = this._items.slice(index, (index + range));
514         this.fire('remove', {
515             removed: remRecords,
516             range: range,
517             index: index
518         });
519         //this._recordRemoved(remRecords, index);
520         //return ({data: remRecords, index:index});
521         return this;
522     },
523
524     /**
525      * @description Empties the recordset
526      *
527      * @method empty
528          * @return {Y.Recordset} The updated recordset instance
529      * @public
530      */
531     empty: function() {
532         this.fire('empty', {});
533         return this;
534     },
535
536     /**
537      * @description Updates the recordset with the new records passed in. Overwrites existing records when updating the index with the new records.
538      *
539      * @method update
540      * @param data {Y.Record, Object Literal, Array} A Y.Record instance, An object literal of data or an array of object literals
541      * @param index {Number} (optional) The index to start updating from. 
542      * @return {Y.Recordset} The updated recordset instance
543      * @public
544      */
545     update: function(data, index) {
546         var rec,
547         arr,
548         i = 0;
549
550         //Whatever is passed in, we are changing it to an array so that it can be easily iterated in the _defUpdateFn method
551         arr = (!(Lang.isArray(data))) ? [data] : data;
552         rec = this._items.slice(index, index + arr.length);
553
554         for (; i < arr.length; i++) {
555             arr[i] = this._changeToRecord(arr[i]);
556         }
557
558         this.fire('update', {
559             updated: arr,
560             overwritten: rec,
561             index: index
562         });
563
564         return this;
565     }
566
567
568 },
569 {
570     ATTRS: {
571
572         /**
573             * @description An array of records that the recordset is storing
574             *
575             * @attribute records
576             * @public
577             * @type array
578             */
579         records: {
580             validator: Lang.isArray,
581             getter: function() {
582                 // give them a copy, not the internal object
583                 return new Y.Array(this._items);
584             },
585             setter: function(allData) {
586                 //For allData passed in here, see if each instance is a Record.
587                 //If its not, change it to a record.
588                 //Then push it into the array.
589                 var records = [];
590                 function initRecord(oneData) {
591                     var o;
592
593                     if (oneData instanceof Y.Record) {
594                         records.push(oneData);
595                     }
596                     else {
597                         o = new Y.Record({
598                             data: oneData
599                         });
600                         records.push(o);
601                     }
602                 }
603
604                 //This conditional statement handles creating empty recordsets
605                 if (allData) {
606                     Y.Array.each(allData, initRecord);
607                     this._items = Y.Array(records);
608                 }
609             },
610
611             //value: [],
612             //initialization of the attribute must be done before the first call to get('records') is made.
613             //if lazyAdd were set to true, then instantiating using new Y.Recordset({records:[..]}) would
614             //not call the setter.
615             //see http://developer.yahoo.com/yui/3/api/Attribute.html#method_addAttr for details on this
616             lazyAdd: false
617         },
618
619         /**
620     * @description A hash table where the ID of the record is the key, and the record instance is the value.
621     *
622     * @attribute table
623     * @public
624     * @type object
625         */
626         table: {
627             //Initially, create the hash table with all records currently in the recordset
628             valueFn: '_setHashTable'
629         },
630
631         /**
632     * @description The ID to use as the key in the hash table.
633     *
634     * @attribute key
635     * @public
636     * @type string
637         */
638         key: {
639             value: 'id',
640             //set to readonly true. If you want custom hash tables, you should use the recordset-indexer plugin.
641             readOnly: true
642         }
643
644     }
645 });
646 Y.augment(Recordset, ArrayList);
647 Y.Recordset = Recordset;
648
649
650
651
652 }, '3.3.0' ,{requires:['base','arraylist']});
653
654 YUI.add('recordset-sort', function(Y) {
655
656 /**
657  * Adds default and custom sorting functionality to the Recordset utility
658  * @module recordset
659  * @submodule recordset-sort
660  */
661
662 var Compare = Y.ArraySort.compare,
663 isValue = Y.Lang.isValue;
664
665 /**
666  * Plugin that adds default and custom sorting functionality to the Recordset utility
667  * @class RecordsetSort
668  */
669
670 function RecordsetSort(field, desc, sorter) {
671     RecordsetSort.superclass.constructor.apply(this, arguments);
672 }
673
674 Y.mix(RecordsetSort, {
675     NS: "sort",
676
677     NAME: "recordsetSort",
678
679     ATTRS: {
680
681         /**
682             * @description The last properties used to sort. Consists of an object literal with the keys "field", "desc", and "sorter"
683             *
684             * @attribute lastSortProperties
685             * @public
686             * @type object
687             */
688         lastSortProperties: {
689             value: {
690                 field: undefined,
691                 desc: true,
692                 sorter: undefined
693             },
694             validator: function(v) {
695                 return (isValue(v.field) && isValue(v.desc) && isValue(v.sorter));
696             }
697         },
698
699         /**
700             * @description Default sort function to use if none is specified by the user.
701                 * Takes two records, the key to sort by, and whether sorting direction is descending or not (boolean).
702                 * If two records have the same value for a given key, the ID is used as the tie-breaker.
703             *
704             * @attribute defaultSorter
705             * @public
706             * @type function
707             */
708         defaultSorter: {
709             value: function(recA, recB, field, desc) {
710                 var sorted = Compare(recA.getValue(field), recB.getValue(field), desc);
711                 if (sorted === 0) {
712                     return Compare(recA.get("id"), recB.get("id"), desc);
713                 }
714                 else {
715                     return sorted;
716                 }
717             }
718         },
719
720         /**
721             * @description A boolean telling if the recordset is in a sorted state.
722             *
723             * @attribute defaultSorter
724             * @public
725             * @type function
726             */
727         isSorted: {
728             value: false
729         }
730     }
731 });
732
733 Y.extend(RecordsetSort, Y.Plugin.Base, {
734
735     /**
736      * @description Sets up the default function to use when the "sort" event is fired.
737      *
738      * @method initializer
739      * @protected
740      */
741     initializer: function(config) {
742
743         var self = this,
744         host = this.get('host');
745
746
747         this.publish("sort", {
748             defaultFn: Y.bind("_defSortFn", this)
749         });
750
751         //Toggle the isSorted ATTR based on events.
752         //Remove events dont affect isSorted, as they are just popped/sliced out
753         this.on("sort",
754         function() {
755             self.set('isSorted', true);
756         });
757
758         this.onHostEvent('add',
759         function() {
760             self.set('isSorted', false);
761         },
762         host);
763         this.onHostEvent('update',
764         function() {
765             self.set('isSorted', false);
766         },
767         host);
768
769     },
770
771     destructor: function(config) {
772         },
773
774     /**
775      * @description Method that all sort calls go through. 
776          * Sets up the lastSortProperties object with the details of the sort, and passes in parameters 
777          * to the "defaultSorter" or a custom specified sort function.
778      *
779      * @method _defSortFn
780      * @private
781      */
782     _defSortFn: function(e) {
783         //have to work directly with _items here - changing the recordset.
784         this.get("host")._items.sort(function(a, b) {
785             return (e.sorter)(a, b, e.field, e.desc);
786         });
787         
788         this.set('lastSortProperties', e);
789     },
790
791     /**
792      * @description Sorts the recordset.
793          *
794          * @method sort
795      * @param field {string} A key to sort by.
796      * @param desc {boolean} True if you want sort order to be descending, false if you want sort order to be ascending
797      * @public
798      */
799     sort: function(field, desc, sorter) {
800         this.fire("sort", {
801             field: field,
802             desc: desc,
803             sorter: sorter || this.get("defaultSorter")
804         });
805     },
806
807     /**
808      * @description Resorts the recordset based on the last-used sort parameters (stored in 'lastSortProperties' ATTR)
809          *
810      * @method resort
811      * @public
812      */
813     resort: function() {
814         var p = this.get('lastSortProperties');
815         this.fire("sort", {
816             field: p.field,
817             desc: p.desc,
818             sorter: p.sorter || this.get("defaultSorter")
819         });
820     },
821
822     /**
823      * @description Reverses the recordset calling the standard array.reverse() method.
824          *
825      * @method reverse
826      * @public
827      */
828     reverse: function() {
829         this.get('host')._items.reverse();
830     },
831
832     /**
833      * @description Sorts the recordset based on the last-used sort parameters, but flips the order. (ie: Descending becomes ascending, and vice versa).
834          *
835      * @method flip
836      * @public
837      */
838     flip: function() {
839         var p = this.get('lastSortProperties');
840
841         //If a predefined field is not provided by which to sort by, throw an error
842         if (isValue(p.field)) {
843             this.fire("sort", {
844                 field: p.field,
845                 desc: !p.desc,
846                 sorter: p.sorter || this.get("defaultSorter")
847             });
848         }
849         else {
850         }
851     }
852 });
853
854 Y.namespace("Plugin").RecordsetSort = RecordsetSort;
855
856
857
858
859 }, '3.3.0' ,{requires:['arraysort','recordset-base','plugin']});
860
861 YUI.add('recordset-filter', function(Y) {
862
863 /**
864  * Plugin that provides the ability to filter through a recordset.
865  * Uses the filter methods available on Y.Array (see arrayextras submodule) to filter the recordset.
866  * @module recordset
867  * @submodule recordset-filter
868  */
869
870
871 var YArray = Y.Array,
872 Lang = Y.Lang;
873
874
875 /**
876  * Plugin that provides the ability to filter through a recordset.
877  * Uses the filter methods available on Y.Array (see arrayextras submodule) to filter the recordset. 
878  * @class RecordsetFilter
879  */
880 function RecordsetFilter(config) {
881     RecordsetFilter.superclass.constructor.apply(this, arguments);
882 }
883
884 Y.mix(RecordsetFilter, {
885     NS: "filter",
886
887     NAME: "recordsetFilter",
888
889     ATTRS: {
890     }
891
892 });
893
894
895 Y.extend(RecordsetFilter, Y.Plugin.Base, {
896
897
898     initializer: function(config) {
899     },
900
901     destructor: function(config) {
902     },
903
904     /**
905      * @description Filter through the recordset with a custom filter function, or a key-value pair.
906          *
907          * @method filter
908      * @param f {Function, String} A custom filter function or a string representing the key to filter by.
909      * @param v {any} (optional) If a string is passed into f, this represents the value that key should take in order to be accepted by the filter. Do not pass in anything if 'f' is a custom function
910          * @return recordset {Y.Recordset} A new filtered recordset instance
911      * @public
912      */
913     filter: function(f, v) {
914         var recs = this.get('host').get('records'),
915         oRecs = [],
916         func = f;
917
918         //If a key-value pair is passed in, generate a custom function
919         if (Lang.isString(f) && Lang.isValue(v)) {
920
921             func = function(item) {
922                 if (item.getValue(f) === v) {
923                     return true;
924                 }
925                 else {
926                     return false;
927                 }
928             };
929         }
930
931         oRecs = YArray.filter(recs, func);
932
933
934         //TODO: PARENT CHILD RELATIONSHIP
935         return new Y.Recordset({
936             records: oRecs
937         });
938         //return new host.constructor({records:arr});
939     },
940
941     /**
942         * @description The inverse of filter. Executes the supplied function on each item. Returns a new Recordset containing the items that the supplied function returned *false* for.
943         * @method reject
944         * @param {Function} f is the function to execute on each item.
945         * @return {Y.Recordset} A new Recordset instance containing the items on which the supplied function returned false.
946         */
947     reject: function(f) {
948         return new Y.Recordset({
949             records: YArray.reject(this.get('host').get('records'), f)
950         });
951     },
952
953     /**
954         * @description Iterates over the Recordset, returning a new Recordset of all the elements that match the supplied regular expression
955         * @method grep
956         * @param {pattern} pattern The regular expression to test against
957         * each record.
958         * @return {Y.Recordset} A Recordset instance containing all the items in the collection that produce a match against the supplied regular expression. If no items match, an empty Recordset instance is returned.
959         */
960     grep: function(pattern) {
961         return new Y.Recordset({
962             records: YArray.grep(this.get('host').get('records'), pattern)
963         });
964     }
965
966     //TODO: Add more pass-through methods to arrayextras
967 });
968
969 Y.namespace("Plugin").RecordsetFilter = RecordsetFilter;
970
971
972
973
974 }, '3.3.0' ,{requires:['recordset-base','array-extras','plugin']});
975
976 YUI.add('recordset-indexer', function(Y) {
977
978 /**
979  * Provides the ability to store multiple custom hash tables referencing records in the recordset.
980  * @module recordset
981  * @submodule recordset-indexer
982  */
983 /**
984  * Plugin that provides the ability to store multiple custom hash tables referencing records in the recordset.
985  * This utility does not support any collision handling. New hash table entries with a used key overwrite older ones.
986  * @class RecordsetIndexer
987  */
988 function RecordsetIndexer(config) {
989     RecordsetIndexer.superclass.constructor.apply(this, arguments);
990 }
991
992 Y.mix(RecordsetIndexer, {
993     NS: "indexer",
994
995     NAME: "recordsetIndexer",
996
997     ATTRS: {
998         /**
999             * @description Collection of all the hashTables created by the plugin. 
1000                 * The individual tables can be accessed by the key they are hashing against. 
1001             *
1002             * @attribute hashTables
1003             * @public
1004             * @type object
1005             */
1006         hashTables: {
1007             value: {
1008
1009             }
1010         },
1011
1012
1013         keys: {
1014             value: {
1015
1016             }
1017         }
1018     }
1019 });
1020
1021
1022 Y.extend(RecordsetIndexer, Y.Plugin.Base, {
1023
1024     initializer: function(config) {
1025         var host = this.get('host');
1026
1027         //setup listeners on recordset events
1028         this.onHostEvent('add', Y.bind("_defAddHash", this), host);
1029         this.onHostEvent('remove', Y.bind('_defRemoveHash', this), host);
1030         this.onHostEvent('update', Y.bind('_defUpdateHash', this), host);
1031
1032     },
1033
1034     destructor: function(config) {
1035     
1036     },
1037
1038
1039     /**
1040      * @description Setup the hash table for a given key with all existing records in the recordset
1041      *
1042      * @method _setHashTable
1043      * @param key {string} A key to hash by.
1044          * @return obj {object} The created hash table
1045      * @private
1046      */
1047     _setHashTable: function(key) {
1048         var host = this.get('host'),
1049         obj = {},
1050         i = 0,
1051         len = host.getLength();
1052
1053         for (; i < len; i++) {
1054             obj[host._items[i].getValue(key)] = host._items[i];
1055         }
1056         return obj;
1057     },
1058
1059     //---------------------------------------------
1060     // Syncing Methods
1061     //---------------------------------------------
1062
1063     /**
1064      * @description Updates all hash tables when a record is added to the recordset
1065          *
1066      * @method _defAddHash
1067      * @private
1068      */
1069     _defAddHash: function(e) {
1070         var tbl = this.get('hashTables');
1071
1072
1073         //Go through every hashtable that is stored.
1074         //in each hashtable, look to see if the key is represented in the object being added.
1075         Y.each(tbl,
1076         function(v, key) {
1077             Y.each(e.added || e.updated,
1078             function(o) {
1079                 //if the object being added has a key which is being stored by hashtable v, add it into the table.
1080                 if (o.getValue(key)) {
1081                     v[o.getValue(key)] = o;
1082                 }
1083             });
1084         });
1085
1086     },
1087
1088     /**
1089      * @description Updates all hash tables when a record is removed from the recordset
1090          *
1091      * @method _defRemoveHash
1092      * @private
1093      */
1094     _defRemoveHash: function(e) {
1095         var tbl = this.get('hashTables'),
1096         reckey;
1097
1098         //Go through every hashtable that is stored.
1099         //in each hashtable, look to see if the key is represented in the object being deleted.
1100         Y.each(tbl,
1101         function(v, key) {
1102             Y.each(e.removed || e.overwritten,
1103             function(o) {
1104                 reckey = o.getValue(key);
1105
1106                 //if the hashtable has a key storing a record, and the key and the record both match the record being deleted, delete that row from the hashtable
1107                 if (reckey && v[reckey] === o) {
1108                     delete v[reckey];
1109                 }
1110             });
1111         });
1112     },
1113
1114     /**
1115      * @description Updates all hash tables when the recordset is updated (a combination of add and remove)
1116          *
1117      * @method _defUpdateHash
1118      * @private
1119      */
1120     _defUpdateHash: function(e) {
1121
1122         //TODO: It will be more performant to create a new method rather than using _defAddHash, _defRemoveHash, due to the number of loops. See commented code.
1123         e.added = e.updated;
1124         e.removed = e.overwritten;
1125         this._defAddHash(e);
1126         this._defRemoveHash(e);
1127
1128         /*
1129                                         var tbl = this.get('hashTables'), reckey;
1130                                         
1131                                         Y.each(tbl, function(v, key) {
1132                                                 Y.each(e.updated, function(o, i) {
1133                                                         
1134                                                         //delete record from hashtable if it has been overwritten
1135                                                         reckey = o.getValue(key);
1136                                                         
1137                                                         if (reckey) {
1138                                                                 v[reckey] = o;
1139                                                         }
1140                                                         
1141                                                         //the undefined case is if more records are updated than currently exist in the recordset. 
1142                                                         if (e.overwritten[i] && (v[e.overwritten[i].getValue(key)] === e.overwritten[i])) {
1143                                                                 delete v[e.overwritten[i].getValue(key)];
1144                                                         }
1145                                                         
1146                                                         // if (v[reckey] === o) {
1147                                                         //      delete v[reckey];
1148                                                         // }
1149                                                         //                              
1150                                                         // //add the new updated record if it has a key that corresponds to a hash table
1151                                                         // if (o.getValue(key)) {
1152                                                         //      v[o.getValue(key)] = o;
1153                                                         // }
1154                                                                                                                         
1155                                                 });
1156                                         });
1157                         */
1158     },
1159
1160     //---------------------------------------------
1161     // Public Methods
1162     //---------------------------------------------
1163
1164     /**
1165      * @description Creates a new hash table.
1166          *
1167      * @method createTable
1168      * @param key {string} A key to hash by.
1169          * @return tbls[key] {object} The created hash table
1170      * @public
1171      */
1172     createTable: function(key) {
1173         var tbls = this.get('hashTables');
1174         tbls[key] = this._setHashTable(key);
1175         this.set('hashTables', tbls);
1176
1177         return tbls[key];
1178     },
1179
1180
1181     /**
1182      * @description Get a hash table that hashes records by a given key.
1183          *
1184          * @method getTable
1185      * @param key {string} A key to hash by.
1186          * @return table {object} The created hash table
1187      * @public
1188      */
1189     getTable: function(key) {
1190         return this.get('hashTables')[key];
1191     }
1192
1193
1194
1195
1196
1197 });
1198 Y.namespace("Plugin").RecordsetIndexer = RecordsetIndexer;
1199
1200
1201
1202
1203 }, '3.3.0' ,{requires:['recordset-base','plugin']});
1204
1205
1206
1207 YUI.add('recordset', function(Y){}, '3.3.0' ,{use:['recordset-base','recordset-sort','recordset-filter','recordset-indexer']});
1208