source: trunk/web/addons/job_monarch/lib/extjs/source/data/Store.js @ 619

Last change on this file since 619 was 619, checked in by ramonb, 15 years ago

lib/:

  • added new AJAX dependancies: ExtJS, pChart, Lightbox2
File size: 34.1 KB
Line 
1/*
2 * Ext JS Library 2.2.1
3 * Copyright(c) 2006-2009, Ext JS, LLC.
4 * licensing@extjs.com
5 *
6 * http://extjs.com/license
7 */
8
9/**
10 * @class Ext.data.Store
11 * @extends Ext.util.Observable
12 * The Store class encapsulates a client side cache of {@link Ext.data.Record Record}
13 * objects which provide input data for Components such as the {@link Ext.grid.GridPanel GridPanel},
14 * the {@link Ext.form.ComboBox ComboBox}, or the {@link Ext.DataView DataView}</p>
15 * <p>A Store object uses its {@link #proxy configured} implementation of {@link Ext.data.DataProxy DataProxy}
16 * to access a data object unless you call {@link #loadData} directly and pass in your data.</p>
17 * <p>A Store object has no knowledge of the format of the data returned by the Proxy.</p>
18 * <p>A Store object uses its {@link #reader configured} implementation of {@link Ext.data.DataReader DataReader}
19 * to create {@link Ext.data.Record Record} instances from the data object. These Records
20 * are cached and made available through accessor functions.</p>
21 * @constructor
22 * Creates a new Store.
23 * @param {Object} config A config object containing the objects needed for the Store to access data,
24 * and read the data into Records.
25 */
26Ext.data.Store = function(config){
27    this.data = new Ext.util.MixedCollection(false);
28    this.data.getKey = function(o){
29        return o.id;
30    };
31    /**
32     * An object containing properties which are used as parameters on any HTTP request.
33     * This property can be changed after creating the Store to send different parameters.
34     * @property
35     */
36    this.baseParams = {};
37    /**
38     * <p>An object containing properties which specify the names of the paging and
39     * sorting parameters passed to remote servers when loading blocks of data. By default, this
40     * object takes the following form:</p><pre><code>
41{
42    start : "start",    // The parameter name which specifies the start row
43    limit : "limit",    // The parameter name which specifies number of rows to return
44    sort : "sort",      // The parameter name which specifies the column to sort on
45    dir : "dir"         // The parameter name which specifies the sort direction
46}
47</code></pre>
48     * <p>The server must produce the requested data block upon receipt of these parameter names.
49     * If different parameter names are required, this property can be overriden using a configuration
50     * property.</p>
51     * <p>A {@link Ext.PagingToolbar PagingToolbar} bound to this grid uses this property to determine
52     * the parameter names to use in its requests.
53     * @property
54     */
55    this.paramNames = {
56        "start" : "start",
57        "limit" : "limit",
58        "sort" : "sort",
59        "dir" : "dir"
60    };
61
62    if(config && config.data){
63        this.inlineData = config.data;
64        delete config.data;
65    }
66
67    Ext.apply(this, config);
68
69    if(this.url && !this.proxy){
70        this.proxy = new Ext.data.HttpProxy({url: this.url});
71    }
72
73    if(this.reader){ // reader passed
74        if(!this.recordType){
75            this.recordType = this.reader.recordType;
76        }
77        if(this.reader.onMetaChange){
78            this.reader.onMetaChange = this.onMetaChange.createDelegate(this);
79        }
80    }
81
82    /**
83     * The {@link Ext.data.Record Record} constructor as supplied to (or created by) the {@link Ext.data.Reader#Reader Reader}.  Read-only.
84     * <p>If the Reader was constructed by passing in an Array of field definition objects, instead of an created
85     * Record constructor it will have {@link Ext.data.Record#create created a constructor} from that Array.</p>
86     * <p>This property may be used to create new Records of the type held in this Store.</p>
87     * @property recordType
88     * @type Function
89     */
90    if(this.recordType){
91        /**
92         * A MixedCollection containing the defined {@link Ext.data.Field Field}s for the Records stored in this Store.  Read-only.
93         * @property fields
94         * @type Ext.util.MixedCollection
95         */
96        this.fields = this.recordType.prototype.fields;
97    }
98    this.modified = [];
99
100    this.addEvents(
101        /**
102         * @event datachanged
103         * Fires when the data cache has changed in a bulk manner (e.g., it has been sorted, filtered, etc.) and a
104         * widget that is using this Store as a Record cache should refresh its view.
105         * @param {Store} this
106         */
107        'datachanged',
108        /**
109         * @event metachange
110         * Fires when this store's reader provides new metadata (fields). This is currently only supported for JsonReaders.
111         * @param {Store} this
112         * @param {Object} meta The JSON metadata
113         */
114        'metachange',
115        /**
116         * @event add
117         * Fires when Records have been added to the Store
118         * @param {Store} this
119         * @param {Ext.data.Record[]} records The array of Records added
120         * @param {Number} index The index at which the record(s) were added
121         */
122        'add',
123        /**
124         * @event remove
125         * Fires when a Record has been removed from the Store
126         * @param {Store} this
127         * @param {Ext.data.Record} record The Record that was removed
128         * @param {Number} index The index at which the record was removed
129         */
130        'remove',
131        /**
132         * @event update
133         * Fires when a Record has been updated
134         * @param {Store} this
135         * @param {Ext.data.Record} record The Record that was updated
136         * @param {String} operation The update operation being performed.  Value may be one of:
137         * <pre><code>
138 Ext.data.Record.EDIT
139 Ext.data.Record.REJECT
140 Ext.data.Record.COMMIT
141         * </code></pre>
142         */
143        'update',
144        /**
145         * @event clear
146         * Fires when the data cache has been cleared.
147         * @param {Store} this
148         */
149        'clear',
150        /**
151         * @event beforeload
152         * Fires before a request is made for a new data object.  If the beforeload handler returns false
153         * the load action will be canceled.
154         * @param {Store} this
155         * @param {Object} options The loading options that were specified (see {@link #load} for details)
156         */
157        'beforeload',
158        /**
159         * @event load
160         * Fires after a new set of Records has been loaded.
161         * @param {Store} this
162         * @param {Ext.data.Record[]} records The Records that were loaded
163         * @param {Object} options The loading options that were specified (see {@link #load} for details)
164         */
165        'load',
166        /**
167         * @event loadexception
168         * Fires if an exception occurs in the Proxy during loading.
169         * Called with the signature of the Proxy's "loadexception" event.
170         */
171        'loadexception'
172    );
173
174    if(this.proxy){
175        this.relayEvents(this.proxy,  ["loadexception"]);
176    }
177
178    this.sortToggle = {};
179    if(this.sortInfo){
180        this.setDefaultSort(this.sortInfo.field, this.sortInfo.direction);
181    }
182
183    Ext.data.Store.superclass.constructor.call(this);
184
185    if(this.storeId || this.id){
186        Ext.StoreMgr.register(this);
187    }
188    if(this.inlineData){
189        this.loadData(this.inlineData);
190        delete this.inlineData;
191    }else if(this.autoLoad){
192        this.load.defer(10, this, [
193            typeof this.autoLoad == 'object' ?
194                this.autoLoad : undefined]);
195    }
196};
197Ext.extend(Ext.data.Store, Ext.util.Observable, {
198    /**
199    * @cfg {String} storeId If passed, the id to use to register with the StoreMgr
200    */
201    /**
202    * @cfg {String} url If passed, an HttpProxy is created for the passed URL
203    */
204    /**
205    * @cfg {Boolean/Object} autoLoad If passed, this store's load method is automatically called after creation with the autoLoad object
206    */
207    /**
208    * @cfg {Ext.data.DataProxy} proxy The Proxy object which provides access to a data object.
209    */
210    /**
211    * @cfg {Array} data Inline data to be loaded when the store is initialized.
212    */
213    /**
214    * @cfg {Ext.data.DataReader} reader The DataReader object which processes the data object and returns
215    * an Array of Ext.data.Record objects which are cached keyed by their <em>id</em> property.
216    */
217    /**
218    * @cfg {Object} baseParams An object containing properties which are to be sent as parameters
219    * on any HTTP request
220    */
221    /**
222    * @cfg {Object} sortInfo A config object in the format: {field: "fieldName", direction: "ASC|DESC"} to
223    * specify the sort order in the request of a remote Store's {@link #load} operation.  Note that for
224    * local sorting, the direction property is case-sensitive.
225    */
226    /**
227    * @cfg {boolean} remoteSort True if sorting is to be handled by requesting the
228    * Proxy to provide a refreshed version of the data object in sorted order, as
229    * opposed to sorting the Record cache in place (defaults to false).
230    * <p>If remote sorting is specified, then clicking on a column header causes the
231    * current page to be requested from the server with the addition of the following
232    * two parameters:
233    * <div class="mdetail-params"><ul>
234    * <li><b>sort</b> : String<p class="sub-desc">The name (as specified in
235    * the Record's Field definition) of the field to sort on.</p></li>
236    * <li><b>dir</b> : String<p class="sub-desc">The direction of the sort, "ASC" or "DESC" (case-sensitive).</p></li>
237    * </ul></div></p>
238    */
239    remoteSort : false,
240
241    /**
242    * @cfg {boolean} pruneModifiedRecords True to clear all modified record information each time the store is
243     * loaded or when a record is removed. (defaults to false).
244    */
245    pruneModifiedRecords : false,
246
247    /**
248     * Contains the last options object used as the parameter to the load method. See {@link #load}
249     * for the details of what this may contain. This may be useful for accessing any params which
250     * were used to load the current Record cache.
251     * @property
252     */
253   lastOptions : null,
254
255    destroy : function(){
256        if(this.storeId || this.id){
257            Ext.StoreMgr.unregister(this);
258        }
259        this.data = null;
260        this.purgeListeners();
261    },
262
263    /**
264     * Add Records to the Store and fires the {@link #add} event.
265     * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects to add to the cache.
266     */
267    add : function(records){
268        records = [].concat(records);
269        if(records.length < 1){
270            return;
271        }
272        for(var i = 0, len = records.length; i < len; i++){
273            records[i].join(this);
274        }
275        var index = this.data.length;
276        this.data.addAll(records);
277        if(this.snapshot){
278            this.snapshot.addAll(records);
279        }
280        this.fireEvent("add", this, records, index);
281    },
282
283    /**
284     * (Local sort only) Inserts the passed Record into the Store at the index where it
285     * should go based on the current sort information.
286     * @param {Ext.data.Record} record
287     */
288    addSorted : function(record){
289        var index = this.findInsertIndex(record);
290        this.insert(index, record);
291    },
292
293    /**
294     * Remove a Record from the Store and fires the {@link #remove} event.
295     * @param {Ext.data.Record} record The Ext.data.Record object to remove from the cache.
296     */
297    remove : function(record){
298        var index = this.data.indexOf(record);
299        this.data.removeAt(index);
300        if(this.pruneModifiedRecords){
301            this.modified.remove(record);
302        }
303        if(this.snapshot){
304            this.snapshot.remove(record);
305        }
306        this.fireEvent("remove", this, record, index);
307    },
308   
309    /**
310     * Remove a Record from the Store at the specified index. Fires the {@link #remove} event.
311     * @param {Number} index The index of the record to remove.
312     */
313    removeAt : function(index){
314        this.remove(this.getAt(index));   
315    },
316
317    /**
318     * Remove all Records from the Store and fires the {@link #clear} event.
319     */
320    removeAll : function(){
321        this.data.clear();
322        if(this.snapshot){
323            this.snapshot.clear();
324        }
325        if(this.pruneModifiedRecords){
326            this.modified = [];
327        }
328        this.fireEvent("clear", this);
329    },
330
331    /**
332     * Inserts Records into the Store at the given index and fires the {@link #add} event.
333     * @param {Number} index The start index at which to insert the passed Records.
334     * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects to add to the cache.
335     */
336    insert : function(index, records){
337        records = [].concat(records);
338        for(var i = 0, len = records.length; i < len; i++){
339            this.data.insert(index, records[i]);
340            records[i].join(this);
341        }
342        this.fireEvent("add", this, records, index);
343    },
344
345    /**
346     * Get the index within the cache of the passed Record.
347     * @param {Ext.data.Record} record The Ext.data.Record object to find.
348     * @return {Number} The index of the passed Record. Returns -1 if not found.
349     */
350    indexOf : function(record){
351        return this.data.indexOf(record);
352    },
353
354    /**
355     * Get the index within the cache of the Record with the passed id.
356     * @param {String} id The id of the Record to find.
357     * @return {Number} The index of the Record. Returns -1 if not found.
358     */
359    indexOfId : function(id){
360        return this.data.indexOfKey(id);
361    },
362
363    /**
364     * Get the Record with the specified id.
365     * @param {String} id The id of the Record to find.
366     * @return {Ext.data.Record} The Record with the passed id. Returns undefined if not found.
367     */
368    getById : function(id){
369        return this.data.key(id);
370    },
371
372    /**
373     * Get the Record at the specified index.
374     * @param {Number} index The index of the Record to find.
375     * @return {Ext.data.Record} The Record at the passed index. Returns undefined if not found.
376     */
377    getAt : function(index){
378        return this.data.itemAt(index);
379    },
380
381    /**
382     * Returns a range of Records between specified indices.
383     * @param {Number} startIndex (optional) The starting index (defaults to 0)
384     * @param {Number} endIndex (optional) The ending index (defaults to the last Record in the Store)
385     * @return {Ext.data.Record[]} An array of Records
386     */
387    getRange : function(start, end){
388        return this.data.getRange(start, end);
389    },
390
391    // private
392    storeOptions : function(o){
393        o = Ext.apply({}, o);
394        delete o.callback;
395        delete o.scope;
396        this.lastOptions = o;
397    },
398
399    /**
400     * Loads the Record cache from the configured Proxy using the configured Reader.
401     * <p>If using remote paging, then the first load call must specify the <tt>start</tt>
402     * and <tt>limit</tt> properties in the options.params property to establish the initial
403     * position within the dataset, and the number of Records to cache on each read from the Proxy.</p>
404     * <p><b>It is important to note that for remote data sources, loading is asynchronous,
405     * and this call will return before the new data has been loaded. Perform any post-processing
406     * in a callback function, or in a "load" event handler.</b></p>
407     * @param {Object} options An object containing properties which control loading options:<ul>
408     * <li><b>params</b> :Object<p class="sub-desc">An object containing properties to pass as HTTP parameters to a remote data source.</p></li>
409     * <li><b>callback</b> : Function<p class="sub-desc">A function to be called after the Records have been loaded. The callback is
410     * passed the following arguments:<ul>
411     * <li>r : Ext.data.Record[]</li>
412     * <li>options: Options object from the load call</li>
413     * <li>success: Boolean success indicator</li></ul></p></li>
414     * <li><b>scope</b> : Object<p class="sub-desc">Scope with which to call the callback (defaults to the Store object)</p></li>
415     * <li><b>add</b> : Boolean<p class="sub-desc">Indicator to append loaded records rather than replace the current cache.</p></li>
416     * </ul>
417     * @return {Boolean} Whether the load fired (if beforeload failed).
418     */
419    load : function(options){
420        options = options || {};
421        if(this.fireEvent("beforeload", this, options) !== false){
422            this.storeOptions(options);
423            var p = Ext.apply(options.params || {}, this.baseParams);
424            if(this.sortInfo && this.remoteSort){
425                var pn = this.paramNames;
426                p[pn["sort"]] = this.sortInfo.field;
427                p[pn["dir"]] = this.sortInfo.direction;
428            }
429            this.proxy.load(p, this.reader, this.loadRecords, this, options);
430            return true;
431        } else {
432          return false;
433        }
434    },
435
436    /**
437     * <p>Reloads the Record cache from the configured Proxy using the configured Reader and
438     * the options from the last load operation performed.</p>
439     * <p><b>It is important to note that for remote data sources, loading is asynchronous,
440     * and this call will return before the new data has been loaded. Perform any post-processing
441     * in a callback function, or in a "load" event handler.</b></p>
442     * @param {Object} options (optional) An object containing loading options which may override the options
443     * used in the last load operation. See {@link #load} for details (defaults to null, in which case
444     * the most recently used options are reused).
445     */
446    reload : function(options){
447        this.load(Ext.applyIf(options||{}, this.lastOptions));
448    },
449
450    // private
451    // Called as a callback by the Reader during a load operation.
452    loadRecords : function(o, options, success){
453        if(!o || success === false){
454            if(success !== false){
455                this.fireEvent("load", this, [], options);
456            }
457            if(options.callback){
458                options.callback.call(options.scope || this, [], options, false);
459            }
460            return;
461        }
462        var r = o.records, t = o.totalRecords || r.length;
463        if(!options || options.add !== true){
464            if(this.pruneModifiedRecords){
465                this.modified = [];
466            }
467            for(var i = 0, len = r.length; i < len; i++){
468                r[i].join(this);
469            }
470            if(this.snapshot){
471                this.data = this.snapshot;
472                delete this.snapshot;
473            }
474            this.data.clear();
475            this.data.addAll(r);
476            this.totalLength = t;
477            this.applySort();
478            this.fireEvent("datachanged", this);
479        }else{
480            this.totalLength = Math.max(t, this.data.length+r.length);
481            this.add(r);
482        }
483        this.fireEvent("load", this, r, options);
484        if(options.callback){
485            options.callback.call(options.scope || this, r, options, true);
486        }
487    },
488
489    /**
490     * Loads data from a passed data block and fires the {@link #load} event. A Reader which understands the format of the data
491     * must have been configured in the constructor.
492     * @param {Object} data The data block from which to read the Records.  The format of the data expected
493     * is dependent on the type of Reader that is configured and should correspond to that Reader's readRecords parameter.
494     * @param {Boolean} add (Optional) True to add the new Records rather than replace the existing cache. <b>Remember that
495     * Records in a Store are keyed by their {@link Ext.data.Record#id id}, so added Records with ids which are already present in
496     * the Store will <i>replace</i> existing Records. Records with new, unique ids will be added.</b>
497     */
498    loadData : function(o, append){
499        var r = this.reader.readRecords(o);
500        this.loadRecords(r, {add: append}, true);
501    },
502
503    /**
504     * Gets the number of cached records.
505     * <p>If using paging, this may not be the total size of the dataset. If the data object
506     * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
507     * the dataset size.</p>
508     * @return {Number} The number of Records in the Store's cache.
509     */
510    getCount : function(){
511        return this.data.length || 0;
512    },
513
514    /**
515     * Gets the total number of records in the dataset as returned by the server.
516     * <p>If using paging, for this to be accurate, the data object used by the Reader must contain
517     * the dataset size. For remote data sources, this is provided by a query on the server.</p>
518     * @return {Number} The number of Records as specified in the data object passed to the Reader
519     * by the Proxy
520     * <p><b>This value is not updated when changing the contents of the Store locally.</b></p>
521     */
522    getTotalCount : function(){
523        return this.totalLength || 0;
524    },
525
526    /**
527     * Returns an object describing the current sort state of this Store.
528     * @return {Object} The sort state of the Store. An object with two properties:<ul>
529     * <li><b>field : String<p class="sub-desc">The name of the field by which the Records are sorted.</p></li>
530     * <li><b>direction : String<p class="sub-desc">The sort order, "ASC" or "DESC" (case-sensitive).</p></li>
531     * </ul>
532     */
533    getSortState : function(){
534        return this.sortInfo;
535    },
536
537    // private
538    applySort : function(){
539        if(this.sortInfo && !this.remoteSort){
540            var s = this.sortInfo, f = s.field;
541            this.sortData(f, s.direction);
542        }
543    },
544
545    // private
546    sortData : function(f, direction){
547        direction = direction || 'ASC';
548        var st = this.fields.get(f).sortType;
549        var fn = function(r1, r2){
550            var v1 = st(r1.data[f]), v2 = st(r2.data[f]);
551            return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
552        };
553        this.data.sort(direction, fn);
554        if(this.snapshot && this.snapshot != this.data){
555            this.snapshot.sort(direction, fn);
556        }
557    },
558
559    /**
560     * Sets the default sort column and order to be used by the next load operation.
561     * @param {String} fieldName The name of the field to sort by.
562     * @param {String} dir (optional) The sort order, "ASC" or "DESC" (case-sensitive, defaults to "ASC")
563     */
564    setDefaultSort : function(field, dir){
565        dir = dir ? dir.toUpperCase() : "ASC";
566        this.sortInfo = {field: field, direction: dir};
567        this.sortToggle[field] = dir;
568    },
569
570    /**
571     * Sort the Records.
572     * If remote sorting is used, the sort is performed on the server, and the cache is
573     * reloaded. If local sorting is used, the cache is sorted internally.
574     * @param {String} fieldName The name of the field to sort by.
575     * @param {String} dir (optional) The sort order, "ASC" or "DESC" (case-sensitive, defaults to "ASC")
576     */
577    sort : function(fieldName, dir){
578        var f = this.fields.get(fieldName);
579        if(!f){
580            return false;
581        }
582        if(!dir){
583            if(this.sortInfo && this.sortInfo.field == f.name){ // toggle sort dir
584                dir = (this.sortToggle[f.name] || "ASC").toggle("ASC", "DESC");
585            }else{
586                dir = f.sortDir;
587            }
588        }
589        var st = (this.sortToggle) ? this.sortToggle[f.name] : null;
590        var si = (this.sortInfo) ? this.sortInfo : null;
591
592        this.sortToggle[f.name] = dir;
593        this.sortInfo = {field: f.name, direction: dir};
594        if(!this.remoteSort){
595            this.applySort();
596            this.fireEvent("datachanged", this);
597        }else{
598            if (!this.load(this.lastOptions)) {
599                if (st) {
600                    this.sortToggle[f.name] = st;
601                }
602                if (si) {
603                    this.sortInfo = si;
604                }
605            }
606        }
607    },
608
609    /**
610     * Calls the specified function for each of the Records in the cache.
611     * @param {Function} fn The function to call. The Record is passed as the first parameter.
612     * Returning <tt>false</tt> aborts and exits the iteration.
613     * @param {Object} scope (optional) The scope in which to call the function (defaults to the Record).
614     */
615    each : function(fn, scope){
616        this.data.each(fn, scope);
617    },
618
619    /**
620     * Gets all records modified since the last commit.  Modified records are persisted across load operations
621     * (e.g., during paging).
622     * @return {Ext.data.Record[]} An array of Records containing outstanding modifications.
623     */
624    getModifiedRecords : function(){
625        return this.modified;
626    },
627
628    // private
629    createFilterFn : function(property, value, anyMatch, caseSensitive){
630        if(Ext.isEmpty(value, false)){
631            return false;
632        }
633        value = this.data.createValueMatcher(value, anyMatch, caseSensitive);
634        return function(r){
635            return value.test(r.data[property]);
636        };
637    },
638
639    /**
640     * Sums the value of <i>property</i> for each record between start and end and returns the result.
641     * @param {String} property A field on your records
642     * @param {Number} start The record index to start at (defaults to 0)
643     * @param {Number} end The last record index to include (defaults to length - 1)
644     * @return {Number} The sum
645     */
646    sum : function(property, start, end){
647        var rs = this.data.items, v = 0;
648        start = start || 0;
649        end = (end || end === 0) ? end : rs.length-1;
650
651        for(var i = start; i <= end; i++){
652            v += (rs[i].data[property] || 0);
653        }
654        return v;
655    },
656
657    /**
658     * Filter the records by a specified property.
659     * @param {String} field A field on your records
660     * @param {String/RegExp} value Either a string that the field
661     * should begin with, or a RegExp to test against the field.
662     * @param {Boolean} anyMatch (optional) True to match any part not just the beginning
663     * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
664     */
665    filter : function(property, value, anyMatch, caseSensitive){
666        var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
667        return fn ? this.filterBy(fn) : this.clearFilter();
668    },
669
670    /**
671     * Filter by a function. The specified function will be called for each
672     * Record in this Store. If the function returns <tt>true</tt> the Record is included,
673     * otherwise it is filtered out.
674     * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
675     * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
676     * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
677     * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
678     * </ul>
679     * @param {Object} scope (optional) The scope of the function (defaults to this)
680     */
681    filterBy : function(fn, scope){
682        this.snapshot = this.snapshot || this.data;
683        this.data = this.queryBy(fn, scope||this);
684        this.fireEvent("datachanged", this);
685    },
686
687    /**
688     * Query the records by a specified property.
689     * @param {String} field A field on your records
690     * @param {String/RegExp} value Either a string that the field
691     * should begin with, or a RegExp to test against the field.
692     * @param {Boolean} anyMatch (optional) True to match any part not just the beginning
693     * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
694     * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
695     */
696    query : function(property, value, anyMatch, caseSensitive){
697        var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
698        return fn ? this.queryBy(fn) : this.data.clone();
699    },
700
701    /**
702     * Query the cached records in this Store using a filtering function. The specified function
703     * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
704     * included in the results.
705     * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
706     * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
707     * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
708     * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
709     * </ul>
710     * @param {Object} scope (optional) The scope of the function (defaults to this)
711     * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
712     **/
713    queryBy : function(fn, scope){
714        var data = this.snapshot || this.data;
715        return data.filterBy(fn, scope||this);
716    },
717
718    /**
719     * Finds the index of the first matching record in this store by a specific property/value.
720     * @param {String} property A property on your objects
721     * @param {String/RegExp} value Either a string that the property value
722     * should begin with, or a RegExp to test against the property.
723     * @param {Number} startIndex (optional) The index to start searching at
724     * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
725     * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
726     * @return {Number} The matched index or -1
727     */
728    find : function(property, value, start, anyMatch, caseSensitive){
729        var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
730        return fn ? this.data.findIndexBy(fn, null, start) : -1;
731    },
732
733    /**
734     * Find the index of the first matching Record in this Store by a function.
735     * If the function returns <tt>true</tt> it is considered a match.
736     * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
737     * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
738     * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
739     * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
740     * </ul>
741     * @param {Object} scope (optional) The scope of the function (defaults to this)
742     * @param {Number} startIndex (optional) The index to start searching at
743     * @return {Number} The matched index or -1
744     */
745    findBy : function(fn, scope, start){
746        return this.data.findIndexBy(fn, scope, start);
747    },
748
749    /**
750     * Collects unique values for a particular dataIndex from this store.
751     * @param {String} dataIndex The property to collect
752     * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
753     * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
754     * @return {Array} An array of the unique values
755     **/
756    collect : function(dataIndex, allowNull, bypassFilter){
757        var d = (bypassFilter === true && this.snapshot) ?
758                this.snapshot.items : this.data.items;
759        var v, sv, r = [], l = {};
760        for(var i = 0, len = d.length; i < len; i++){
761            v = d[i].data[dataIndex];
762            sv = String(v);
763            if((allowNull || !Ext.isEmpty(v)) && !l[sv]){
764                l[sv] = true;
765                r[r.length] = v;
766            }
767        }
768        return r;
769    },
770
771    /**
772     * Revert to a view of the Record cache with no filtering applied.
773     * @param {Boolean} suppressEvent If true the filter is cleared silently without notifying listeners
774     */
775    clearFilter : function(suppressEvent){
776        if(this.isFiltered()){
777            this.data = this.snapshot;
778            delete this.snapshot;
779            if(suppressEvent !== true){
780                this.fireEvent("datachanged", this);
781            }
782        }
783    },
784
785    /**
786     * Returns true if this store is currently filtered
787     * @return {Boolean}
788     */
789    isFiltered : function(){
790        return this.snapshot && this.snapshot != this.data;
791    },
792
793    // private
794    afterEdit : function(record){
795        if(this.modified.indexOf(record) == -1){
796            this.modified.push(record);
797        }
798        this.fireEvent("update", this, record, Ext.data.Record.EDIT);
799    },
800
801    // private
802    afterReject : function(record){
803        this.modified.remove(record);
804        this.fireEvent("update", this, record, Ext.data.Record.REJECT);
805    },
806
807    // private
808    afterCommit : function(record){
809        this.modified.remove(record);
810        this.fireEvent("update", this, record, Ext.data.Record.COMMIT);
811    },
812
813    /**
814     * Commit all Records with outstanding changes. To handle updates for changes, subscribe to the
815     * Store's "update" event, and perform updating when the third parameter is Ext.data.Record.COMMIT.
816     */
817    commitChanges : function(){
818        var m = this.modified.slice(0);
819        this.modified = [];
820        for(var i = 0, len = m.length; i < len; i++){
821            m[i].commit();
822        }
823    },
824
825    /**
826     * Cancel outstanding changes on all changed records.
827     */
828    rejectChanges : function(){
829        var m = this.modified.slice(0);
830        this.modified = [];
831        for(var i = 0, len = m.length; i < len; i++){
832            m[i].reject();
833        }
834    },
835
836    // private
837    onMetaChange : function(meta, rtype, o){
838        this.recordType = rtype;
839        this.fields = rtype.prototype.fields;
840        delete this.snapshot;
841        this.sortInfo = meta.sortInfo;
842        this.modified = [];
843        this.fireEvent('metachange', this, this.reader.meta);
844    },
845
846    // private
847    findInsertIndex : function(record){
848        this.suspendEvents();
849        var data = this.data.clone();
850        this.data.add(record);
851        this.applySort();
852        var index = this.data.indexOf(record);
853        this.data = data;
854        this.resumeEvents();
855        return index;
856    }
857});
Note: See TracBrowser for help on using the repository browser.