~mortenoh/+junk/dhis2-detailed-import-export

« back to all changes in this revision

Viewing changes to gis/dhis-gis-geostat/mfbase/ext/source/data/Store.js

  • Committer: larshelge at gmail
  • Date: 2009-03-03 16:46:36 UTC
  • Revision ID: larshelge@gmail.com-20090303164636-2sjlrquo7ib1gf7r
Initial check-in

Show diffs side-by-side

added added

removed removed

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