]>
git.proxmox.com Git - sencha-touch.git/blob - src/src/data/Store.js
4 * The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load
5 * data via a {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting},
6 * {@link #filter filtering} and querying the {@link Ext.data.Model model} instances contained within it.
8 * Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:
10 * // Set up a {@link Ext.data.Model model} to use in our Store
11 * Ext.define("User", {
12 * extend: "Ext.data.Model",
15 * {name: "firstName", type: "string"},
16 * {name: "lastName", type: "string"},
17 * {name: "age", type: "int"},
18 * {name: "eyeColor", type: "string"}
23 * var myStore = Ext.create("Ext.data.Store", {
27 * url : "/users.json",
30 * rootProperty: "users"
36 * Ext.create("Ext.List", {
39 * itemTpl: "{lastName}, {firstName} ({age})"
42 * In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy
43 * to use a {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object -
44 * {@link Ext.data.reader.Json see the docs on JsonReader} for details.
46 * The external data file, _/users.json_, is as follows:
52 * "firstName": "Tommy",
53 * "lastName": "Maintz",
58 * "firstName": "Aaron",
59 * "lastName": "Conran",
64 * "firstName": "Jamie",
65 * "lastName": "Avins",
74 * Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #cfg-data}
75 * into Model instances:
78 * // Set up a model to use in our Store
79 * Ext.define('User', {
80 * extend: 'Ext.data.Model',
83 * {name: 'firstName', type: 'string'},
84 * {name: 'lastName', type: 'string'},
85 * {name: 'age', type: 'int'},
86 * {name: 'eyeColor', type: 'string'}
91 * Ext.create("Ext.data.Store", {
92 * storeId: "usersStore",
95 * {firstName: "Ed", lastName: "Spencer"},
96 * {firstName: "Tommy", lastName: "Maintz"},
97 * {firstName: "Aaron", lastName: "Conran"},
98 * {firstName: "Jamie", lastName: "Avins"}
102 * Ext.create("Ext.List", {
104 * store: "usersStore",
105 * itemTpl: "{lastName}, {firstName}"
108 * Loading inline data using the method above is great if the data is in the correct format already (e.g. it doesn't need
109 * to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode the data structure,
110 * use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory MemoryProxy} docs for an example).
112 * Additional data can also be loaded locally using {@link #method-add}.
114 * ## Loading Nested Data
116 * Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
117 * Instead of issuing an AJAX request for the User and a series of additional AJAX requests for each Order, we can load a nested dataset
118 * and allow the Reader to automatically populate the associated models. Below is a brief example, see the {@link Ext.data.reader.Reader} intro
119 * documentation for a full explanation:
121 * // Set up a model to use in our Store
122 * Ext.define('User', {
123 * extend: 'Ext.data.Model',
126 * {name: 'name', type: 'string'},
127 * {name: 'id', type: 'int'}
132 * var store = Ext.create('Ext.data.Store', {
137 * url : 'users.json',
140 * rootProperty: 'users'
145 * Ext.create("Ext.List", {
148 * itemTpl: "{name} (id: {id})"
151 * Which would consume a response like this:
162 * "status": "invoiced"
167 * "status": "shipped"
184 * "status": "shipped"
191 * See the {@link Ext.data.reader.Reader} intro docs for a full explanation.
193 * ## Filtering and Sorting
195 * Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and {@link #filters} are
196 * held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage. Usually it is sufficient to
197 * either just specify sorters and filters in the Store configuration or call {@link #sort} or {@link #filter}:
199 * // Set up a model to use in our Store
200 * Ext.define('User', {
201 * extend: 'Ext.data.Model',
204 * {name: 'firstName', type: 'string'},
205 * {name: 'lastName', type: 'string'},
206 * {name: 'age', type: 'int'}
211 * var store = Ext.create("Ext.data.Store", {
216 * url : "users.json",
219 * rootProperty: "users"
228 * property : "firstName",
234 * property: "firstName",
240 * Ext.create("Ext.List", {
243 * itemTpl: "{lastName}, {firstName} ({age})"
246 * And the data file, _users.json_, is as follows:
252 * "firstName": "Tommy",
253 * "lastName": "Maintz",
257 * "firstName": "Aaron",
258 * "lastName": "Conran",
262 * "firstName": "Jamie",
263 * "lastName": "Avins",
269 * The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By default, sorting
270 * and filtering are both performed locally by the Store - see {@link #remoteSort} and {@link #remoteFilter} to allow the server to
271 * perform these operations instead.
273 * Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter to the Store
274 * and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all existing filters). Note that by
275 * default your sorters are automatically reapplied if using local sorting.
277 * store.filter('eyeColor', 'Brown');
279 * Change the sorting at any time by calling {@link #sort}:
281 * store.sort('height', 'ASC');
283 * Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no arguments,
284 * the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new ones, just add them
285 * to the MixedCollection:
287 * store.sorters.add(new Ext.util.Sorter({
288 * property : 'shoeSize',
294 * ## Registering with StoreManager
296 * Any Store that is instantiated with a {@link #storeId} will automatically be registered with the {@link Ext.data.StoreManager StoreManager}.
297 * This makes it easy to reuse the same store in multiple views:
299 * // this store can be used several times
300 * Ext.create('Ext.data.Store', {
302 * storeId: 'usersStore'
305 * Ext.create('Ext.List', {
306 * store: 'usersStore'
307 * // other config goes here
310 * Ext.create('Ext.view.View', {
311 * store: 'usersStore'
312 * // other config goes here
317 * Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
318 * pieces and how they fit together, see:
320 * - {@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used
321 * - {@link Ext.data.Model Model} - the core class in the data package
322 * - {@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response
324 Ext
. define ( 'Ext.data.Store' , {
325 alias
: 'store.store' ,
327 extend
: 'Ext.Evented' ,
330 'Ext.util.Collection' ,
331 'Ext.data.Operation' ,
332 'Ext.data.proxy.Memory' ,
334 'Ext.data.StoreManager' ,
340 * Fired when one or more new Model instances have been added to this Store. You should listen
341 * for this event if you have to update a representation of the records in this store in your UI.
342 * If you need the indices of the records that were added please use the store.indexOf(record) method.
343 * @param {Ext.data.Store} store The store
344 * @param {Ext.data.Model[]} records The Model instances that were added
348 * @event removerecords
349 * Fired when one or more Model instances have been removed from this Store. You should listen
350 * for this event if you have to update a representation of the records in this store in your UI.
351 * @param {Ext.data.Store} store The Store object
352 * @param {Ext.data.Model[]} records The Model instances that was removed
353 * @param {Number[]} indices The indices of the records that were removed. These indices already
354 * take into account any potential earlier records that you remove. This means that if you loop
355 * over the records, you can get its current index in your data representation from this array.
359 * @event updaterecord
360 * Fires when a Model instance has been updated
361 * @param {Ext.data.Store} this
362 * @param {Ext.data.Model} record The Model instance that was updated
363 * @param {Number} newIndex If the update changed the index of the record (due to sorting for example), then
364 * this gives you the new index in the store.
365 * @param {Number} oldIndex If the update changed the index of the record (due to sorting for example), then
366 * this gives you the old index in the store.
367 * @param {Array} modifiedFieldNames An array containing the field names that have been modified since the
368 * record was committed or created
369 * @param {Object} modifiedValues An object where each key represents a field name that had it's value modified,
370 * and where the value represents the old value for that field. To get the new value in a listener
371 * you should use the {@link Ext.data.Model#get get} method.
376 * @inheritdoc Ext.data.Store#updaterecord
377 * @removed 2.0 Listen to #updaterecord instead.
382 * Fires whenever the records in the Store have changed in a way that your representation of the records
383 * need to be entirely refreshed.
384 * @param {Ext.data.Store} this The data store
385 * @param {Ext.util.Collection} data The data collection containing all the records
390 * Fires before a request is made for a new data object. If the beforeload handler returns false the load
391 * action will be canceled. Note that you should not listen for this event in order to refresh the
392 * data view. Use the {@link #refresh} event for this instead.
393 * @param {Ext.data.Store} store This Store
394 * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
400 * Fires whenever records have been loaded into the store. Note that you should not listen
401 * for this event in order to refresh the data view. Use the {@link #refresh} event for this instead.
402 * @param {Ext.data.Store} this
403 * @param {Ext.data.Model[]} records An array of records
404 * @param {Boolean} successful `true` if the operation was successful.
405 * @param {Ext.data.Operation} operation The associated operation.
410 * Fires whenever a successful write has been made via the configured {@link #proxy Proxy}
411 * @param {Ext.data.Store} store This Store
412 * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object that was used in
418 * Fired before a call to {@link #sync} is executed. Return `false` from any listener to cancel the sync
419 * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
424 * Fired after the {@link #removeAll} method is called. Note that you should not listen for this event in order
425 * to refresh the data view. Use the {@link #refresh} event for this instead.
426 * @param {Ext.data.Store} this
427 * @return {Ext.data.Store}
431 create : function ( store
) {
432 if (! store
. isStore
) {
434 store
. type
= 'store' ;
436 store
= Ext
. createByAlias ( 'store.' + store
. type
, store
);
446 * @cfg {String} storeId
447 * Unique identifier for this store. If present, this Store will be registered with the {@link Ext.data.StoreManager},
448 * making it easy to reuse elsewhere.
454 * @cfg {Object[]/Ext.data.Model[]} data
455 * Array of Model instances or data objects to load locally. See "Inline data" above for details.
461 * @cfg {Boolean/Object} [autoLoad=false]
462 * If data is not specified, and if `autoLoad` is `true` or an Object, this store's load method is automatically called
463 * after creation. If the value of `autoLoad` is an Object, this Object will be passed to the store's `load()` method.
469 * @cfg {Boolean} autoSync
470 * `true` to automatically sync the Store with its Proxy after every edit to one of its Records.
476 * @cfg {String} model
477 * Returns Ext.data.Model and not a String.
478 * Name of the {@link Ext.data.Model Model} associated with this store.
479 * The string is used as an argument for {@link Ext.ModelManager#getModel}.
485 * @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
486 * object or a Proxy instance - see {@link #setProxy} for details.
492 * @cfg {Object[]/Ext.util.Collection} fields
493 * Returns Ext.util.Collection not just an Object.
494 * Use in place of specifying a {@link #model} configuration. The fields should be a
495 * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
496 * with these fields. In general this configuration option should be avoided, it exists for the purposes of
497 * backwards compatibility. For anything more complicated, such as specifying a particular id property or
498 * associations, a {@link Ext.data.Model} should be defined and specified for the {@link #model}
500 * @return Ext.util.Collection
506 * @cfg {Boolean} remoteSort
507 * `true` to defer any sorting operation to the server. If `false`, sorting is done locally on the client.
509 * If this is set to `true`, you will have to manually call the {@link #method-load} method after you {@link #method-sort}, to retrieve the sorted
510 * data from the server.
512 * {@link #buffered Buffered} stores automatically set this to `true`. Buffered stores contain an abitrary
513 * subset of the full dataset which depends upon various configurations and which pages have been requested
514 * for rendering. Such *sparse* datasets are ineligible for local sorting.
520 * @cfg {Boolean} remoteFilter
521 * `true` to defer any filtering operation to the server. If `false`, filtering is done locally on the client.
523 * If this is set to `true`, you will have to manually call the {@link #method-load} method after you {@link #method-filter} to retrieve the filtered
524 * data from the server.
526 * {@link #buffered Buffered} stores automatically set this to `true`. Buffered stores contain an abitrary
527 * subset of the full dataset which depends upon various configurations and which pages have been requested
528 * for rendering. Such *sparse* datasets are ineligible for local filtering.
534 * @cfg {Boolean} remoteGroup
535 * `true` to defer any grouping operation to the server. If `false`, grouping is done locally on the client.
537 * {@link #buffered Buffered} stores automatically set this to `true`. Buffered stores contain an abitrary
538 * subset of the full dataset which depends upon various configurations and which pages have been requested
539 * for rendering. Such *sparse* datasets are ineligible for local grouping.
545 * @cfg {Object[]} filters
546 * Array of {@link Ext.util.Filter Filters} for this store. This configuration is handled by the
547 * {@link Ext.mixin.Filterable Filterable} mixin of the {@link Ext.util.Collection data} collection.
553 * @cfg {Object[]} sorters
554 * Array of {@link Ext.util.Sorter Sorters} for this store. This configuration is handled by the
555 * {@link Ext.mixin.Sortable Sortable} mixin of the {@link Ext.util.Collection data} collection.
556 * See also the {@link #sort} method.
562 * @cfg {Object} grouper
563 * A configuration object for this Store's {@link Ext.util.Grouper grouper}.
565 * For example, to group a store's items by the first letter of the last name:
567 * Ext.define('People', {
568 * extend: 'Ext.data.Store',
571 * fields: ['first_name', 'last_name'],
574 * groupFn: function(record) {
575 * return record.get('last_name').substr(0, 1);
577 * sortProperty: 'last_name'
587 * @cfg {String} groupField
588 * The (optional) field by which to group data in the store. Internally, grouping is very similar to sorting - the
589 * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
590 * level of grouping, and groups can be fetched via the {@link #getGroups} method.
596 * @cfg {String} groupDir
597 * The direction in which sorting should be applied when grouping. If you specify a grouper by using the {@link #groupField}
598 * configuration, this will automatically default to "ASC" - the other supported value is "DESC"
604 * @cfg {Function} getGroupString This function will be passed to the {@link #grouper} configuration as it's `groupFn`.
605 * Note that this configuration is deprecated and grouper: `{groupFn: yourFunction}}` is preferred.
609 getGroupString
: null ,
612 * @cfg {Number} pageSize
613 * The number of records considered to form a 'page'. This is used to power the built-in
614 * paging using the nextPage and previousPage functions.
616 * If this Store is {@link #buffered}, pages are loaded into a page cache before the Store's
617 * data is updated from the cache. The pageSize is the number of rows loaded into the cache in one request.
618 * This will not affect the rendering of a buffered grid, but a larger page size will mean fewer loads.
620 * In a buffered grid, scrolling is monitored, and the page cache is kept primed with data ahead of the
621 * direction of scroll to provide rapid access to data when scrolling causes it to be required. Several pages
622 * in advance may be requested depending on various parameters.
624 * It is recommended to tune the {@link #pageSize}, {@link #trailingBufferZone} and
625 * {@link #leadingBufferZone} configurations based upon the conditions pertaining in your deployed application.
630 * @cfg {Number} totalCount The total number of records in the full dataset, as indicated by a server. If the
631 * server-side dataset contains 5000 records but only returns pages of 50 at a time, `totalCount` will be set to
632 * 5000 and {@link #getCount} will return 50
637 * @cfg {Boolean} clearOnPageLoad `true` to empty the store when loading another page via {@link #loadPage},
638 * {@link #nextPage} or {@link #previousPage}. Setting to `false` keeps existing records, allowing
639 * large data sets to be loaded one page at a time but rendered all together.
642 clearOnPageLoad
: true ,
645 * @cfg {Object} params Parameters to send into the proxy for any CRUD operations
654 * @cfg {Boolean} autoDestroy This is a private configuration used in the framework whether this Store
661 * @cfg {Boolean} syncRemovedRecords This configuration allows you to disable the synchronization of
662 * removed records on this Store. By default, when you call `removeAll()` or `remove()`, records will be added
663 * to an internal removed array. When you then sync the Store, we send a destroy request for these records.
664 * If you don't want this to happen, you can set this configuration to `false`.
666 syncRemovedRecords
: true ,
669 * @cfg {Boolean} destroyRemovedRecords This configuration allows you to prevent destroying record
670 * instances when they are removed from this store and are not in any other store.
672 destroyRemovedRecords
: true ,
675 * @cfg {Boolean} buffered
676 * Allows the Store to prefetch and cache in a **page cache**, pages of Records, and to then satisfy
677 * loading requirements from this page cache.
679 * To use buffered Stores, initiate the process by loading the first page. The number of rows rendered are
680 * determined automatically, and the range of pages needed to keep the cache primed for scrolling is
681 * requested and cached.
684 * myStore.loadPage(1); // Load page 1
686 * A {@link Ext.grid.plugin.BufferedRenderer BufferedRenderer} is instantiated which will monitor the scrolling in the grid, and
687 * refresh the view's rows from the page cache as needed. It will also pull new data into the page
688 * cache when scrolling of the view draws upon data near either end of the prefetched data.
690 * The margins which trigger view refreshing from the prefetched data are {@link Ext.grid.plugin.BufferedRenderer#numFromEdge},
691 * {@link Ext.grid.plugin.BufferedRenderer#leadingBufferZone} and {@link Ext.grid.plugin.BufferedRenderer#trailingBufferZone}.
693 * The margins which trigger loading more data into the page cache are, {@link #leadingBufferZone} and
694 * {@link #trailingBufferZone}.
696 * By default, only 5 pages of data are cached in the page cache, with pages "scrolling" out of the buffer
697 * as the view moves down through the dataset.
698 * Setting this value to zero means that no pages are *ever* scrolled out of the page cache, and
699 * that eventually the whole dataset may become present in the page cache. This is sometimes desirable
700 * as long as datasets do not reach astronomical proportions.
702 * Selection state may be maintained across page boundaries by configuring the SelectionModel not to discard
703 * records from its collection when those Records cycle out of the Store's primary collection. This is done
704 * by configuring the SelectionModel like this:
707 * pruneRemoved: false
714 * @cfg {Object/Array} plugins
716 * An object or array of objects that will provide custom functionality for this component. The only
717 * requirement for a valid plugin is that it contain an init method that accepts a reference of type Ext.Component.
719 * When a component is created, if any plugins are available, the component will call the init method on each
720 * plugin, passing a reference to itself. Each plugin can then call methods or respond to events on the
721 * component as needed to provide its functionality.
723 * For examples of plugins, see Ext.plugin.PullRefresh and Ext.plugin.ListPaging
729 * Ext.create('Ext.dataview.List', {
731 * plugins: 'listpaging',
732 * itemTpl: '<div class="item">{title}</div>',
737 * Multiple plugins by alias:
739 * Ext.create('Ext.dataview.List', {
741 * plugins: ['listpaging', 'pullrefresh'],
742 * itemTpl: '<div class="item">{title}</div>',
747 * Single plugin by class name with config options:
749 * Ext.create('Ext.dataview.List', {
752 * xclass: 'Ext.plugin.ListPaging', // Reference plugin by class
756 * itemTpl: '<div class="item">{title}</div>',
761 * Multiple plugins by class name with config options:
763 * Ext.create('Ext.dataview.List', {
767 * xclass: 'Ext.plugin.PullRefresh',
768 * pullRefreshText: 'Pull to refresh...'
771 * xclass: 'Ext.plugin.ListPaging',
776 * itemTpl: '<div class="item">{title}</div>',
786 * @property {Number} currentPage
787 * The page that the Store has most recently loaded (see {@link #loadPage})
791 constructor : function ( config
) {
792 config
= config
|| {};
794 this . data
= this . _data
= this . createDataCollection ();
796 this . data
. setSortRoot ( 'data' );
797 this . data
. setFilterRoot ( 'data' );
801 if ( config
. id
&& ! config
. storeId
) {
802 config
. storeId
= config
. id
;
806 // <deprecated product=touch since=2.0>
808 if ( config
. hasOwnProperty ( 'sortOnLoad' )) {
809 Ext
. Logger
. deprecate (
810 '[Ext.data.Store] sortOnLoad is always activated in Sencha Touch 2 so your Store is always fully ' +
811 'sorted after loading. The only exception is if you are using remoteSort and change sorting after ' +
812 'the Store as loaded, in which case you need to call store.load() to fetch the sorted data from the server.'
816 if ( config
. hasOwnProperty ( 'filterOnLoad' )) {
817 Ext
. Logger
. deprecate (
818 '[Ext.data.Store] filterOnLoad is always activated in Sencha Touch 2 so your Store is always fully ' +
819 'sorted after loading. The only exception is if you are using remoteFilter and change filtering after ' +
820 'the Store as loaded, in which case you need to call store.load() to fetch the filtered data from the server.'
824 if ( config
. hasOwnProperty ( 'sortOnFilter' )) {
825 Ext
. Logger
. deprecate (
826 '[Ext.data.Store] sortOnFilter is deprecated and is always effectively true when sorting and filtering locally'
832 this . initConfig ( config
);
834 this . callParent ( arguments
);
837 applyPlugins : function ( config
) {
838 var ln
, i
, configObj
;
844 config
= []. concat ( config
);
846 for ( i
= 0 , ln
= config
. length
; i
< ln
; i
++) {
847 configObj
= config
[ i
];
848 config
[ i
] = Ext
. factory ( configObj
, 'Ext.plugin.Plugin' , null , 'plugin' );
854 updatePlugins : function ( newPlugins
, oldPlugins
) {
858 for ( i
= 0 , ln
= newPlugins
. length
; i
< ln
; i
++) {
859 newPlugins
[ i
]. init ( this );
864 for ( i
= 0 , ln
= oldPlugins
. length
; i
< ln
; i
++) {
865 Ext
. destroy ( oldPlugins
[ i
]);
872 * @return {Ext.util.Collection}
874 createDataCollection : function () {
875 return new Ext
. util
. Collection ( function ( record
) {
876 return record
. getId ();
880 applyStoreId : function ( storeId
) {
881 if ( storeId
=== undefined || storeId
=== null ) {
882 storeId
= this . getUniqueId ();
887 updateStoreId : function ( storeId
, oldStoreId
) {
889 Ext
. data
. StoreManager
. unregister ( this );
892 Ext
. data
. StoreManager
. register ( this );
896 applyModel : function ( model
) {
897 if ( typeof model
== 'string' ) {
898 var registeredModel
= Ext
. data
. ModelManager
. getModel ( model
);
899 if (! registeredModel
) {
900 Ext
. Logger
. error ( 'Model with name "' + model
+ '" does not exist.' );
902 model
= registeredModel
;
905 if ( model
&& ! model
. prototype . isModel
&& Ext
. isObject ( model
)) {
906 model
= Ext
. data
. ModelManager
. registerType ( model
. storeId
|| model
. id
|| Ext
. id (), model
);
910 var fields
= this . getFields (),
911 data
= this . config
. data
;
913 if (! fields
&& data
&& data
. length
) {
914 fields
= Ext
. Object
. getKeys ( data
[ 0 ]);
918 model
= Ext
. define ( 'Ext.data.Store.ImplicitModel-' + ( this . getStoreId () || Ext
. id ()), {
919 extend
: 'Ext.data.Model' ,
923 proxy
: this . getProxy ()
927 this . implicitModel
= true ;
930 if (! model
&& this . getProxy ()) {
931 model
= this . getProxy (). getModel ();
936 Ext
. Logger
. warn ( 'Unless you define your model through metadata, a store needs to have a model defined on either itself or on its proxy' );
943 updateModel : function ( model
) {
944 var proxy
= this . getProxy ();
946 if ( proxy
&& ! proxy
. getModel ()) {
947 proxy
. setModel ( model
);
951 applyProxy : function ( proxy
, currentProxy
) {
952 proxy
= Ext
. factory ( proxy
, Ext
. data
. Proxy
, currentProxy
, 'proxy' );
954 if (! proxy
&& this . getModel ()) {
955 proxy
= this . getModel (). getProxy ();
959 proxy
= new Ext
. data
. proxy
. Memory ({
960 model
: this . getModel ()
964 if ( proxy
. isMemoryProxy
) {
965 this . setSyncRemovedRecords ( false );
971 updateProxy : function ( proxy
, oldProxy
) {
973 if (! proxy
. getModel ()) {
974 proxy
. setModel ( this . getModel ());
976 proxy
. on ( 'metachange' , 'onMetaChange' , this );
979 proxy
. un ( 'metachange' , 'onMetaChange' , this );
984 * We are using applyData so that we can return nothing and prevent the `this.data`
985 * property to be overridden.
986 * @param {Array/Object} data
988 applyData : function ( data
) {
992 proxy
= me
. getProxy ();
993 if ( proxy
instanceof Ext
. data
. proxy
. Memory
) {
998 // We make it silent because we don't want to fire a refresh event
1001 // This means we have to fire a clear event though
1002 me
. fireEvent ( 'clear' , me
);
1004 // We don't want to fire addrecords event since we will be firing
1005 // a refresh event later which will already take care of updating
1006 // any views bound to this store
1011 // We set this to true so isAutoLoading to try
1012 me
. dataLoaded
= true ;
1017 // This means we have to fire a clear event though
1018 me
. fireEvent ( 'clear' , me
);
1021 me
. fireEvent ( 'refresh' , me
, me
. data
);
1024 clearData : function () {
1029 * Uses the configured {@link Ext.data.reader.Reader reader} to convert the data into records
1030 * and adds it to the Store. Use this when you have raw data that needs to pass trough converters,
1031 * mappings and other extra logic from the reader.
1033 * If your data is already formated and ready for consumption, use {@link #add} method.
1035 * @param {Object[]} data Array of data to load
1037 addData : function ( data
) {
1038 var reader
= this . getProxy (). getReader (),
1039 resultSet
= reader
. read ( data
),
1040 records
= resultSet
. getRecords ();
1045 updateAutoLoad : function ( autoLoad
) {
1046 var proxy
= this . getProxy ();
1047 if ( autoLoad
&& ( proxy
&& ! proxy
. isMemoryProxy
)) {
1048 this . load ( Ext
. isObject ( autoLoad
) ? autoLoad
: null );
1053 * Returns `true` if the Store is set to {@link #autoLoad} or is a type which loads upon instantiation.
1056 isAutoLoading : function () {
1057 var proxy
= this . getProxy ();
1058 return ( this . getAutoLoad () || ( proxy
&& proxy
. isMemoryProxy
) || this . dataLoaded
);
1061 updateGroupField : function ( groupField
) {
1062 var grouper
= this . getGrouper ();
1066 property
: groupField
,
1067 direction
: this . getGroupDir () || 'ASC'
1070 grouper
. setProperty ( groupField
);
1072 } else if ( grouper
) {
1073 this . setGrouper ( null );
1077 updateGroupDir : function ( groupDir
) {
1078 var grouper
= this . getGrouper ();
1080 grouper
. setDirection ( groupDir
);
1084 applyGetGroupString : function ( getGroupStringFn
) {
1085 var grouper
= this . getGrouper ();
1086 if ( getGroupStringFn
) {
1088 Ext
. Logger
. warn ( 'Specifying getGroupString on a store has been deprecated. Please use grouper: {groupFn: yourFunction}' );
1092 grouper
. setGroupFn ( getGroupStringFn
);
1095 groupFn
: getGroupStringFn
1098 } else if ( grouper
) {
1099 this . setGrouper ( null );
1103 applyGrouper : function ( grouper
) {
1104 if ( typeof grouper
== 'string' ) {
1109 else if ( typeof grouper
== 'function' ) {
1115 grouper
= Ext
. factory ( grouper
, Ext
. util
. Grouper
);
1119 updateGrouper : function ( grouper
, oldGrouper
) {
1120 var data
= this . data
;
1122 data
. removeSorter ( oldGrouper
);
1124 data
. getSorters (). removeSorter ( 'isGrouper' );
1128 data
. insertSorter ( 0 , grouper
);
1130 data
. getSorters (). addSorter ({
1132 property
: 'isGrouper' ,
1133 transform : function ( value
) {
1134 return ( value
=== true ) ? 1 : - 1 ;
1139 this . fireEvent ( 'refresh' , this , data
);
1143 * This method tells you if this store has a grouper defined on it.
1144 * @return {Boolean} `true` if this store has a grouper defined.
1146 isGrouped : function () {
1147 return !! this . getGrouper ();
1150 updateSorters : function ( sorters
) {
1151 var grouper
= this . getGrouper (),
1153 autoSort
= data
. getAutoSort ();
1155 // While we remove/add sorters we don't want to automatically sort because we still need
1156 // to apply any field sortTypes as transforms on the Sorters after we have added them.
1157 data
. setAutoSort ( false );
1159 data
. setSorters ( sorters
);
1161 data
. insertSorter ( 0 , grouper
);
1164 this . updateSortTypes ();
1166 // Now we put back autoSort on the Collection to the value it had before. If it was
1167 // auto sorted, setting this back will cause it to sort right away.
1168 data
. setAutoSort ( autoSort
);
1171 updateSortTypes : function () {
1172 var model
= this . getModel (),
1173 fields
= model
&& model
. getFields (),
1176 // We loop over each sorter and set it's transform method to the every field's sortType.
1178 data
. getSorters (). each ( function ( sorter
) {
1179 var property
= sorter
. getProperty (),
1182 if (! sorter
. isGrouper
&& property
&& ! sorter
. getTransform ()) {
1183 field
= fields
. get ( property
);
1185 sorter
. setTransform ( field
. getSortType ());
1192 updateFilters : function ( filters
) {
1193 this . data
. setFilters ( filters
);
1197 * Adds Model instance to the Store. This method accepts either:
1199 * - An array of Model instances or Model configuration objects.
1200 * - Any number of Model instance or Model configuration object arguments.
1202 * The new Model instances will be added at the end of the existing collection.
1206 * myStore.add({some: 'data2'}, {some: 'other data2'});
1208 * Use {@link #addData} method instead if you have raw data that need to pass
1209 * through the data reader.
1211 * @param {Ext.data.Model[]/Ext.data.Model...} model An array of Model instances
1212 * or Model configuration objects, or variable number of Model instance or config arguments.
1213 * @return {Ext.data.Model[]} The model instances that were added.
1215 add : function ( records
) {
1216 if (! Ext
. isArray ( records
)) {
1217 records
= Array
. prototype . slice
. call ( arguments
);
1220 return this . insert ( this . data
. length
, records
);
1224 * Inserts Model instances into the Store at the given index and fires the {@link #add} event.
1225 * See also `{@link #add}`.
1226 * @param {Number} index The start index at which to insert the passed Records.
1227 * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the cache.
1230 insert : function ( index
, records
) {
1231 if (! Ext
. isArray ( records
)) {
1232 records
= Array
. prototype . slice
. call ( arguments
, 1 );
1238 ln
= records
. length
,
1239 Model
= this . getModel (),
1240 modelDefaults
= me
. getModelDefaults (),
1244 records
= records
. slice ();
1246 for ( i
= 0 ; i
< ln
; i
++) {
1247 record
= records
[ i
];
1248 if (! record
. isModel
) {
1249 record
= new Model ( record
);
1251 // If we are adding a record that is already an instance which was still in the
1252 // removed array, then we remove it from the removed array
1253 else if ( this . removed
. indexOf ( record
) != - 1 ) {
1254 Ext
. Array
. remove ( this . removed
, record
);
1257 record
. set ( modelDefaults
);
1260 records
[ i
] = record
;
1262 // If this is a newly created record, then we might want to sync it later
1263 sync
= sync
|| ( record
. phantom
=== true );
1266 // Now we insert all these records in one go to the collection. Saves many function
1267 // calls to data.insert. Does however create two loops over the records we are adding.
1268 if ( records
. length
=== 1 ) {
1269 added
= data
. insert ( index
, records
[ 0 ]);
1274 added
= data
. insertAll ( index
, records
);
1278 me
. fireEvent ( 'addrecords' , me
, added
);
1281 if ( me
. getAutoSync () && sync
) {
1289 * Removes the given record from the Store, firing the `removerecords` event passing all the instances that are removed.
1290 * @param {Ext.data.Model/Ext.data.Model[]} records Model instance or array of instances to remove.
1292 remove : function ( records
) {
1293 if ( records
. isModel
) {
1294 records
= [ records
];
1300 autoSync
= this . getAutoSync (),
1301 syncRemovedRecords
= me
. getSyncRemovedRecords (),
1302 destroyRemovedRecords
= this . getDestroyRemovedRecords (),
1303 ln
= records
. length
,
1307 items
= me
. data
. items
,
1310 for (; i
< ln
; i
++) {
1311 record
= records
[ i
];
1313 if ( me
. data
. contains ( record
)) {
1314 isPhantom
= ( record
. phantom
=== true );
1316 index
= items
. indexOf ( record
);
1318 removed
. push ( record
);
1319 indices
. push ( index
);
1323 me
. data
. remove ( record
);
1325 if ( destroyRemovedRecords
&& ! syncRemovedRecords
&& ! record
. stores
. length
) {
1328 else if (! isPhantom
&& syncRemovedRecords
) {
1329 // don't push phantom records onto removed
1330 me
. removed
. push ( record
);
1333 sync
= sync
|| ! isPhantom
;
1337 me
. fireEvent ( 'removerecords' , me
, removed
, indices
);
1339 if ( autoSync
&& sync
) {
1345 * Removes the model instance at the given index.
1346 * @param {Number} index The record index.
1348 removeAt : function ( index
) {
1349 var record
= this . getAt ( index
);
1352 this . remove ( record
);
1357 * Remove all items from the store.
1358 * @param {Boolean} [silent] Prevent the `clear` event from being fired.
1360 removeAll : function ( silent
) {
1361 if ( silent
!== true && this . eventFiringSuspended
!== true ) {
1362 this . fireAction ( 'clear' , [ this ], 'doRemoveAll' );
1364 this . doRemoveAll
. call ( this , true );
1368 doRemoveAll : function ( silent
) {
1370 destroyRemovedRecords
= this . getDestroyRemovedRecords (),
1371 syncRemovedRecords
= this . getSyncRemovedRecords (),
1372 records
= me
. data
. all
. slice (),
1373 ln
= records
. length
,
1376 for ( i
= 0 ; i
< ln
; i
++) {
1377 record
= records
[ i
];
1380 if ( destroyRemovedRecords
&& ! syncRemovedRecords
&& ! record
. stores
. length
) {
1383 else if ( record
. phantom
!== true && syncRemovedRecords
) {
1384 me
. removed
. push ( record
);
1390 if ( silent
!== true ) {
1391 me
. fireEvent ( 'refresh' , me
, me
. data
);
1394 if ( me
. getAutoSync ()) {
1400 * Calls the specified function for each of the {@link Ext.data.Model Records} in the cache.
1402 * // Set up a model to use in our Store
1403 * Ext.define('User', {
1404 * extend: 'Ext.data.Model',
1407 * {name: 'firstName', type: 'string'},
1408 * {name: 'lastName', type: 'string'}
1413 * var store = Ext.create('Ext.data.Store', {
1416 * {firstName: 'Ed', lastName: 'Spencer'},
1417 * {firstName: 'Tommy', lastName: 'Maintz'},
1418 * {firstName: 'Aaron', lastName: 'Conran'},
1419 * {firstName: 'Jamie', lastName: 'Avins'}
1423 * store.each(function (item, index, length) {
1424 * console.log(item.get('firstName'), index);
1427 * @param {Function} fn The function to call. Returning `false` aborts and exits the iteration.
1428 * @param {Ext.data.Model} fn.item
1429 * @param {Number} fn.index
1430 * @param {Number} fn.length
1431 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
1432 * Defaults to the current {@link Ext.data.Model Record} in the iteration.
1434 each : function ( fn
, scope
) {
1435 this . data
. each ( fn
, scope
);
1439 * Gets the number of cached records. Note that filtered records are not included in this count.
1440 * If using paging, this may not be the total size of the dataset.
1441 * @return {Number} The number of Records in the Store's cache.
1443 getCount : function () {
1444 return this . data
. items
. length
|| 0 ;
1448 * Gets the number of all cached records including the ones currently filtered.
1449 * If using paging, this may not be the total size of the dataset.
1450 * @return {Number} The number of all Records in the Store's cache.
1452 getAllCount : function () {
1453 return this . data
. all
. length
|| 0 ;
1457 * Get the Record at the specified index.
1458 * @param {Number} index The index of the Record to find.
1459 * @return {Ext.data.Model/undefined} The Record at the passed index. Returns `undefined` if not found.
1461 getAt : function ( index
) {
1462 return this . data
. getAt ( index
);
1466 * Returns a range of Records between specified indices. Note that if the store is filtered, only filtered results
1468 * @param {Number} [startIndex=0] (optional) The starting index.
1469 * @param {Number} [endIndex=-1] (optional) The ending index (defaults to the last Record in the Store).
1470 * @return {Ext.data.Model[]} An array of Records.
1472 getRange : function ( start
, end
) {
1473 return this . data
. getRange ( start
, end
);
1477 * Get the Record with the specified id.
1478 * @param {String} id The id of the Record to find.
1479 * @return {Ext.data.Model/undefined} The Record with the passed id. Returns `undefined` if not found.
1481 getById : function ( id
) {
1482 return this . data
. findBy ( function ( record
) {
1483 return record
. getId () == id
;
1488 * Get the index within the cache of the passed Record.
1489 * @param {Ext.data.Model} record The Ext.data.Model object to find.
1490 * @return {Number} The index of the passed Record. Returns -1 if not found.
1492 indexOf : function ( record
) {
1493 return this . data
. indexOf ( record
);
1497 * Get the index within the cache of the Record with the passed id.
1498 * @param {String} id The id of the Record to find.
1499 * @return {Number} The index of the Record. Returns -1 if not found.
1501 indexOfId : function ( id
) {
1502 return this . data
. indexOfKey ( id
);
1507 * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
1508 * @param {Ext.data.Model} record The model instance that was edited.
1509 * @param {String[]} modifiedFieldNames Array of field names changed during edit.
1510 * @param {Object} modified
1512 afterEdit : function ( record
, modifiedFieldNames
, modified
) {
1515 currentId
= modified
[ record
. getIdProperty ()] || record
. getId (),
1516 currentIndex
= data
. keys
. indexOf ( currentId
),
1519 if ( currentIndex
=== - 1 && data
. map
[ currentId
] === undefined ) {
1523 if ( me
. getAutoSync ()) {
1527 if ( currentId
!== record
. getId ()) {
1528 data
. replace ( currentId
, record
);
1530 data
. replace ( record
);
1533 newIndex
= data
. indexOf ( record
);
1534 if ( currentIndex
=== - 1 && newIndex
!== - 1 ) {
1535 me
. fireEvent ( 'addrecords' , me
, [ record
]);
1537 else if ( currentIndex
!== - 1 && newIndex
=== - 1 ) {
1538 me
. fireEvent ( 'removerecords' , me
, [ record
], [ currentIndex
]);
1540 else if ( newIndex
!== - 1 ) {
1541 me
. fireEvent ( 'updaterecord' , me
, record
, newIndex
, currentIndex
, modifiedFieldNames
, modified
);
1547 * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
1548 * @param {Ext.data.Model} record The model instance that was edited.
1550 afterReject : function ( record
) {
1551 var index
= this . data
. indexOf ( record
);
1552 this . fireEvent ( 'updaterecord' , this , record
, index
, index
, [], {});
1557 * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
1558 * @param {Ext.data.Model} record The model instance that was edited.
1559 * @param {String[]} modifiedFieldNames
1560 * @param {Object} modified
1562 afterCommit : function ( record
, modifiedFieldNames
, modified
) {
1565 currentId
= modified
[ record
. getIdProperty ()] || record
. getId (),
1566 currentIndex
= data
. keys
. indexOf ( currentId
),
1569 if ( currentIndex
=== - 1 && data
. map
[ currentId
] === undefined ) {
1573 if ( currentId
!== record
. getId ()) {
1574 data
. replace ( currentId
, record
);
1576 data
. replace ( record
);
1579 newIndex
= data
. indexOf ( record
);
1580 if ( currentIndex
=== - 1 && newIndex
!== - 1 ) {
1581 me
. fireEvent ( 'addrecords' , me
, [ record
]);
1583 else if ( currentIndex
!== - 1 && newIndex
=== - 1 ) {
1584 me
. fireEvent ( 'removerecords' , me
, [ record
], [ currentIndex
]);
1586 else if ( newIndex
!== - 1 ) {
1587 me
. fireEvent ( 'updaterecord' , me
, record
, newIndex
, currentIndex
, modifiedFieldNames
, modified
);
1592 * This gets called by a record after is gets erased from the server.
1593 * @param {Ext.data.Model} record
1596 afterErase : function ( record
) {
1599 index
= data
. indexOf ( record
);
1602 data
. remove ( record
);
1603 me
. fireEvent ( 'removerecords' , me
, [ record
], [ index
]);
1607 applyRemoteFilter : function ( value
) {
1608 var proxy
= this . getProxy ();
1609 return value
|| ( proxy
&& proxy
. isSQLProxy
=== true );
1612 applyRemoteSort : function ( value
) {
1613 var proxy
= this . getProxy ();
1614 return value
|| ( proxy
&& proxy
. isSQLProxy
=== true );
1617 applyRemoteGroup : function ( value
) {
1618 var proxy
= this . getProxy ();
1619 return value
|| ( proxy
&& proxy
. isSQLProxy
=== true );
1622 updateRemoteFilter : function ( remoteFilter
) {
1623 this . data
. setAutoFilter (! remoteFilter
);
1626 updateRemoteSort : function ( remoteSort
) {
1627 this . data
. setAutoSort (! remoteSort
);
1631 * Sorts the data in the Store by one or more of its properties. Example usage:
1633 * // sort by a single field
1634 * myStore.sort('myField', 'DESC');
1636 * // sorting by multiple fields
1643 * property : 'name',
1648 * Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
1649 * the actual sorting to its internal {@link Ext.util.Collection}.
1651 * When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
1653 * store.sort('myField');
1654 * store.sort('myField');
1656 * is equivalent to this code:
1658 * store.sort('myField', 'ASC');
1659 * store.sort('myField', 'DESC');
1661 * because Store handles the toggling automatically.
1663 * If the {@link #remoteSort} configuration has been set to `true`, you will have to manually call the {@link #method-load}
1664 * method after you sort to retrieve the sorted data from the server.
1666 * @param {String/Ext.util.Sorter[]} sorters Either a string name of one of the fields in this Store's configured
1667 * {@link Ext.data.Model Model}, or an array of sorter configurations.
1668 * @param {String} [defaultDirection=ASC] The default overall direction to sort the data by.
1669 * @param {String} where (Optional) This can be either `'prepend'` or `'append'`. If you leave this undefined
1670 * it will clear the current sorters.
1672 sort : function ( sorters
, defaultDirection
, where
) {
1673 var data
= this . data
,
1674 grouper
= this . getGrouper (),
1675 autoSort
= data
. getAutoSort ();
1678 // While we are adding sorters we don't want to sort right away
1679 // since we need to update sortTypes on the sorters.
1680 data
. setAutoSort ( false );
1681 if ( typeof where
=== 'string' ) {
1682 if ( where
== 'prepend' ) {
1683 data
. insertSorters ( grouper
? 1 : 0 , sorters
, defaultDirection
);
1685 data
. addSorters ( sorters
, defaultDirection
);
1688 data
. setSorters ( null );
1690 data
. addSorters ( grouper
);
1692 data
. addSorters ( sorters
, defaultDirection
);
1694 this . updateSortTypes ();
1695 // Setting back autoSort to true (if it was like that before) will
1696 // instantly sort the data again.
1697 data
. setAutoSort ( autoSort
);
1700 if (! this . getRemoteSort ()) {
1701 // If we haven't added any new sorters we have to manually call sort
1706 this . fireEvent ( 'sort' , this , this . data
, this . data
. getSorters ());
1708 this . fireEvent ( 'refresh' , this , this . data
);
1714 * Filters the loaded set of records by a given set of filters.
1716 * Filtering by single field:
1718 * store.filter("email", /\.com$/);
1720 * Using multiple filters:
1723 * {property: "email", value: /\.com$/},
1724 * {filterFn: function(item) { return item.get("age") > 10; }}
1727 * Using Ext.util.Filter instances instead of config objects
1728 * (note that we need to specify the {@link Ext.util.Filter#root root} config option in this case):
1731 * Ext.create('Ext.util.Filter', {property: "email", value: /\.com$/, root: 'data'}),
1732 * Ext.create('Ext.util.Filter', {filterFn: function(item) { return item.get("age") > 10; }, root: 'data'})
1735 * If the {@link #remoteFilter} configuration has been set to `true`, you will have to manually call the {@link #method-load}
1736 * method after you filter to retrieve the filtered data from the server.
1738 * @param {Object[]/Ext.util.Filter[]/String} filters The set of filters to apply to the data.
1739 * These are stored internally on the store, but the filtering itself is done on the Store's
1740 * {@link Ext.util.MixedCollection MixedCollection}. See MixedCollection's
1741 * {@link Ext.util.MixedCollection#filter filter} method for filter syntax.
1742 * Alternatively, pass in a property string.
1743 * @param {String} [value] value to filter by (only if using a property string as the first argument).
1744 * @param {Boolean} [anyMatch=false] `true` to allow any match, false to anchor regex beginning with `^`.
1745 * @param {Boolean} [caseSensitive=false] `true` to make the filtering regex case sensitive.
1747 filter : function ( property
, value
, anyMatch
, caseSensitive
) {
1748 var data
= this . data
,
1752 if ( Ext
. isFunction ( property
)) {
1753 filter
= { filterFn
: property
};
1755 else if ( Ext
. isArray ( property
) || property
. isFilter
) {
1760 property
: property
,
1762 anyMatch
: anyMatch
,
1763 caseSensitive
: caseSensitive
,
1769 if ( this . getRemoteFilter ()) {
1770 data
. addFilters ( filter
);
1772 data
. filter ( filter
);
1773 this . fireEvent ( 'filter' , this , data
, data
. getFilters ());
1774 this . fireEvent ( 'refresh' , this , data
);
1779 * Filter by a function. The specified function will be called for each
1780 * Record in this Store. If the function returns `true` the Record is included,
1781 * otherwise it is filtered out.
1782 * @param {Function} fn The function to be called. It will be passed the following parameters:
1783 * @param {Ext.data.Model} fn.record The {@link Ext.data.Model record}
1784 * to test for filtering. Access field values using {@link Ext.data.Model#get}.
1785 * @param {Object} fn.id The ID of the Record passed.
1786 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to this Store.
1788 filterBy : function ( fn
, scope
) {
1794 filterFn : function ( record
) {
1795 return fn
. call ( scope
|| me
, record
, record
. getId ());
1799 this . fireEvent ( 'filter' , this , data
, data
. getFilters ());
1801 if ( data
. length
!== ln
) {
1802 this . fireEvent ( 'refresh' , this , data
);
1807 * Query the cached records in this Store using a filtering function. The specified function
1808 * will be called with each record in this Store. If the function returns `true` the record is
1809 * included in the results.
1810 * @param {Function} fn The function to be called. It will be passed the following parameters:
1811 * @param {Ext.data.Model} fn.record The {@link Ext.data.Model record}
1812 * to test for filtering. Access field values using {@link Ext.data.Model#get}.
1813 * @param {Object} fn.id The ID of the Record passed.
1814 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to this Store.
1815 * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records.
1817 queryBy : function ( fn
, scope
) {
1818 return this . data
. filterBy ( fn
, scope
|| this );
1822 * Reverts to a view of the Record cache with no filtering applied.
1823 * @param {Boolean} [suppressEvent=false] `true` to clear silently without firing the `refresh` event.
1825 clearFilter : function ( suppressEvent
) {
1826 var ln
= this . data
. length
;
1827 if ( suppressEvent
) {
1828 this . suspendEvents ();
1830 this . data
. setFilters ( null );
1831 if ( suppressEvent
) {
1832 this . resumeEvents ( true );
1833 } else if ( ln
!== this . data
. length
) {
1834 this . fireEvent ( 'refresh' , this , this . data
);
1839 * Returns `true` if this store is currently filtered.
1842 isFiltered : function () {
1843 return this . data
. filtered
;
1847 * Returns `true` if this store is currently sorted.
1850 isSorted : function () {
1851 return this . data
. sorted
;
1854 getSorters : function () {
1855 var sorters
= this . data
. getSorters ();
1856 return ( sorters
) ? sorters
. items
: [];
1859 getFilters : function () {
1860 var filters
= this . data
. getFilters ();
1861 return ( filters
) ? filters
. items
: [];
1865 * Returns an array containing the result of applying the grouper to the records in this store. See {@link #groupField},
1866 * {@link #groupDir} and {@link #grouper}. Example for a store containing records with a color field:
1868 * var myStore = Ext.create('Ext.data.Store', {
1869 * groupField: 'color',
1873 * myStore.getGroups(); //returns:
1878 * //all records where the color field is 'yellow'
1884 * //all records where the color field is 'red'
1889 * @param {String} groupName (Optional) Pass in an optional `groupName` argument to access a specific group as defined by {@link #grouper}.
1890 * @return {Object/Object[]} The grouped data.
1892 getGroups : function ( requestGroupString
) {
1893 var records
= this . data
. items
,
1894 length
= records
. length
,
1895 grouper
= this . getGrouper (),
1905 Ext
. Logger
. error ( 'Trying to get groups for a store that has no grouper' );
1909 for ( i
= 0 ; i
< length
; i
++) {
1910 record
= records
[ i
];
1911 groupStr
= grouper
. getGroupString ( record
);
1912 group
= pointers
[ groupStr
];
1914 if ( group
=== undefined ) {
1921 pointers
[ groupStr
] = group
;
1924 group
. children
. push ( record
);
1927 return requestGroupString
? pointers
[ requestGroupString
] : groups
;
1931 * @param {Ext.data.Model} record
1934 getGroupString : function ( record
) {
1935 var grouper
= this . getGrouper ();
1937 return grouper
. getGroupString ( record
);
1943 * Finds the index of the first matching Record in this store by a specific field value.
1944 * @param {String} fieldName The name of the Record field to test.
1945 * @param {String/RegExp} value Either a string that the field value
1946 * should begin with, or a RegExp to test against the field.
1947 * @param {Number} startIndex (optional) The index to start searching at.
1948 * @param {Boolean} anyMatch (optional) `true` to match any part of the string, not just the beginning.
1949 * @param {Boolean} caseSensitive (optional) `true` for case sensitive comparison.
1950 * @param {Boolean} [exactMatch=false] (optional) `true` to force exact match (^ and $ characters added to the regex).
1951 * @return {Number} The matched index or -1
1953 find : function ( fieldName
, value
, startIndex
, anyMatch
, caseSensitive
, exactMatch
) {
1954 var filter
= Ext
. create ( 'Ext.util.Filter' , {
1955 property
: fieldName
,
1958 caseSensitive
: caseSensitive
,
1959 exactMatch
: exactMatch
,
1962 return this . data
. findIndexBy ( filter
. getFilterFn (), null , startIndex
);
1966 * Finds the first matching Record in this store by a specific field value.
1967 * @param {String} fieldName The name of the Record field to test.
1968 * @param {String/RegExp} value Either a string that the field value
1969 * should begin with, or a RegExp to test against the field.
1970 * @param {Number} startIndex (optional) The index to start searching at.
1971 * @param {Boolean} anyMatch (optional) `true` to match any part of the string, not just the beginning.
1972 * @param {Boolean} caseSensitive (optional) `true` for case sensitive comparison.
1973 * @param {Boolean} [exactMatch=false] (optional) `true` to force exact match (^ and $ characters added to the regex).
1974 * @return {Ext.data.Model} The matched record or `null`.
1976 findRecord : function () {
1978 index
= me
. find
. apply ( me
, arguments
);
1979 return index
!== - 1 ? me
. getAt ( index
) : null ;
1983 * Finds the index of the first matching Record in this store by a specific field value.
1984 * @param {String} fieldName The name of the Record field to test.
1985 * @param {Object} value The value to match the field against.
1986 * @param {Number} startIndex (optional) The index to start searching at.
1987 * @return {Number} The matched index or -1.
1989 findExact : function ( fieldName
, value
, startIndex
) {
1990 return this . data
. findIndexBy ( function ( record
) {
1991 return record
. get ( fieldName
) === value
;
1992 }, this , startIndex
);
1996 * Find the index of the first matching Record in this Store by a function.
1997 * If the function returns `true` it is considered a match.
1998 * @param {Function} fn The function to be called. It will be passed the following parameters:
1999 * @param {Ext.data.Model} fn.record The {@link Ext.data.Model record}
2000 * to test for filtering. Access field values using {@link Ext.data.Model#get}.
2001 * @param {Object} fn.id The ID of the Record passed.
2002 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to this Store.
2003 * @param {Number} startIndex (optional) The index to start searching at.
2004 * @return {Number} The matched index or -1.
2006 findBy : function ( fn
, scope
, startIndex
) {
2007 return this . data
. findIndexBy ( fn
, scope
, startIndex
);
2011 * Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
2012 * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
2013 * instances into the Store and calling an optional callback if required. Example usage:
2016 * callback: function(records, operation, success) {
2017 * // the {@link Ext.data.Operation operation} object contains all of the details of the load operation
2018 * console.log(records);
2023 * If only the callback and scope options need to be specified, then one can call it simply like so:
2025 * store.load(function(records, operation, success) {
2026 * console.log('loaded records');
2029 * @param {Object/Function} [options] config object, passed into the {@link Ext.data.Operation} object before loading.
2030 * @param {Object} [scope] Scope for the function.
2033 load : function ( options
, scope
) {
2036 currentPage
= me
. currentPage
,
2037 pageSize
= me
. getPageSize ();
2039 options
= options
|| {};
2041 if ( Ext
. isFunction ( options
)) {
2044 scope
: scope
|| this
2048 if ( me
. getRemoteSort ()) {
2049 options
. sorters
= options
. sorters
|| this . getSorters ();
2052 if ( me
. getRemoteFilter ()) {
2053 options
. filters
= options
. filters
|| this . getFilters ();
2056 if ( me
. getRemoteGroup ()) {
2057 options
. grouper
= options
. grouper
|| this . getGrouper ();
2060 Ext
. applyIf ( options
, {
2062 start
: ( currentPage
- 1 ) * pageSize
,
2066 params
: this . getParams (),
2067 model
: this . getModel ()
2070 operation
= Ext
. create ( 'Ext.data.Operation' , options
);
2072 if ( me
. fireEvent ( 'beforeload' , me
, operation
) !== false ) {
2074 me
. getProxy (). read ( operation
, me
. onProxyLoad
, me
);
2081 * Returns `true` if the Store is currently performing a load operation.
2082 * @return {Boolean} `true` if the Store is currently loading.
2084 isLoading : function () {
2085 return Boolean ( this . loading
);
2089 * Returns `true` if the Store has been loaded.
2090 * @return {Boolean} `true` if the Store has been loaded.
2092 isLoaded : function () {
2093 return Boolean ( this . loaded
);
2097 * Synchronizes the Store with its Proxy. This asks the Proxy to batch together any new, updated
2098 * and deleted records in the store, updating the Store's internal representation of the records
2099 * as each operation completes.
2101 * @return {Object} return.added
2102 * @return {Object} return.updated
2103 * @return {Object} return.removed
2105 sync : function ( options
) {
2108 toCreate
= me
. getNewRecords (),
2109 toUpdate
= me
. getUpdatedRecords (),
2110 toDestroy
= me
. getRemovedRecords (),
2113 if ( toCreate
. length
> 0 ) {
2114 operations
. create
= toCreate
;
2118 if ( toUpdate
. length
> 0 ) {
2119 operations
. update
= toUpdate
;
2123 if ( toDestroy
. length
> 0 ) {
2124 operations
. destroy
= toDestroy
;
2128 if ( needsSync
&& me
. fireEvent ( 'beforesync' , this , operations
) !== false ) {
2129 me
. getProxy (). batch ( Ext
. merge ({
2130 operations
: operations
,
2131 listeners
: me
. getBatchListeners ()
2143 * Convenience function for getting the first model instance in the store.
2144 * @return {Ext.data.Model/undefined} The first model instance in the store, or `undefined`.
2147 return this . data
. first ();
2151 * Convenience function for getting the last model instance in the store.
2152 * @return {Ext.data.Model/undefined} The last model instance in the store, or `undefined`.
2155 return this . data
. last ();
2159 * Sums the value of `property` for each {@link Ext.data.Model record} between `start`
2160 * and `end` and returns the result.
2161 * @param {String} field The field in each record.
2162 * @return {Number} The sum.
2164 sum : function ( field
) {
2167 records
= this . data
. items
,
2168 len
= records
. length
;
2170 for (; i
< len
; ++ i
) {
2171 total
+= records
[ i
]. get ( field
);
2178 * Gets the minimum value in the store.
2179 * @param {String} field The field in each record.
2180 * @return {Object/undefined} The minimum value, if no items exist, `undefined`.
2182 min : function ( field
) {
2184 records
= this . data
. items
,
2185 len
= records
. length
,
2189 min
= records
[ 0 ]. get ( field
);
2192 for (; i
< len
; ++ i
) {
2193 value
= records
[ i
]. get ( field
);
2202 * Gets the maximum value in the store.
2203 * @param {String} field The field in each record.
2204 * @return {Object/undefined} The maximum value, if no items exist, `undefined`.
2206 max : function ( field
) {
2208 records
= this . data
. items
,
2209 len
= records
. length
,
2214 max
= records
[ 0 ]. get ( field
);
2217 for (; i
< len
; ++ i
) {
2218 value
= records
[ i
]. get ( field
);
2227 * Gets the average value in the store.
2228 * @param {String} field The field in each record you want to get the average for.
2229 * @return {Number} The average value, if no items exist, 0.
2231 average : function ( field
) {
2233 records
= this . data
. items
,
2234 len
= records
. length
,
2237 if ( records
. length
> 0 ) {
2238 for (; i
< len
; ++ i
) {
2239 sum
+= records
[ i
]. get ( field
);
2248 * Returns an object which is passed in as the listeners argument to `proxy.batch` inside `this.sync`.
2249 * This is broken out into a separate function to allow for customization of the listeners.
2250 * @return {Object} The listeners object.
2251 * @return {Object} return.scope
2252 * @return {Object} return.exception
2253 * @return {Object} return.complete
2255 getBatchListeners : function () {
2258 exception
: this . onBatchException
,
2259 complete
: this . onBatchComplete
2265 * Attached as the `complete` event listener to a proxy's Batch object. Iterates over the batch operations
2266 * and updates the Store's internal data MixedCollection.
2268 onBatchComplete : function ( batch
) {
2270 operations
= batch
. operations
,
2271 length
= operations
. length
,
2274 for ( i
= 0 ; i
< length
; i
++) {
2275 me
. onProxyWrite ( operations
[ i
]);
2279 onBatchException : function ( batch
, operation
) {
2280 // //decide what to do... could continue with the next operation
2283 // //or retry the last operation
2289 * Called internally when a Proxy has completed a load request.
2291 onProxyLoad : function ( operation
) {
2293 records
= operation
. getRecords (),
2294 resultSet
= operation
. getResultSet (),
2295 successful
= operation
. wasSuccessful ();
2298 me
. setTotalCount ( resultSet
. getTotal ());
2302 this . fireAction ( 'datarefresh' , [ this , this . data
, operation
], 'doDataRefresh' );
2307 me
. fireEvent ( 'load' , this , records
, successful
, operation
);
2309 //this is a callback that would have been passed to the 'read' function and is optional
2310 Ext
. callback ( operation
. getCallback (), operation
. getScope () || me
, [ records
, operation
, successful
]);
2313 doDataRefresh : function ( store
, data
, operation
) {
2314 var records
= operation
. getRecords (),
2316 destroyRemovedRecords
= me
. getDestroyRemovedRecords (),
2317 currentRecords
= data
. all
. slice (),
2318 ln
= currentRecords
. length
,
2319 ln2
= records
. length
,
2323 if ( operation
. getAddRecords () !== true ) {
2324 for ( i
= 0 ; i
< ln2
; i
++) {
2325 ids
[ records
[ i
]. id
] = true ;
2327 for ( i
= 0 ; i
< ln
; i
++) {
2328 record
= currentRecords
[ i
];
2331 // If the record we are removing is not part of the records we are about to add to the store then handle
2332 // the destroying or removing of the record to avoid memory leaks.
2333 if ( ids
[ record
. id
] !== true && destroyRemovedRecords
&& ! record
. stores
. length
) {
2339 // This means we have to fire a clear event though
2340 me
. fireEvent ( 'clear' , me
);
2342 if ( records
&& records
. length
) {
2343 // Now lets add the records without firing an addrecords event
2346 me
. resumeEvents ( true ); // true to discard the queue
2349 me
. fireEvent ( 'refresh' , me
, data
);
2354 * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
2355 * the updates provided by the Proxy.
2357 onProxyWrite : function ( operation
) {
2359 success
= operation
. wasSuccessful (),
2360 records
= operation
. getRecords ();
2362 switch ( operation
. getAction ()) {
2364 me
. onCreateRecords ( records
, operation
, success
);
2367 me
. onUpdateRecords ( records
, operation
, success
);
2370 me
. onDestroyRecords ( records
, operation
, success
);
2375 me
. fireEvent ( 'write' , me
, operation
);
2377 //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
2378 Ext
. callback ( operation
. getCallback (), operation
. getScope () || me
, [ records
, operation
, success
]);
2381 // These methods are now just template methods since updating the records etc is all taken care of
2382 // by the operation itself.
2383 onCreateRecords : function ( records
, operation
, success
) {},
2384 onUpdateRecords : function ( records
, operation
, success
) {},
2386 onDestroyRecords : function ( records
, operation
, success
) {
2390 onMetaChange : function ( data
) {
2391 var model
= this . getProxy (). getModel ();
2392 if (! this . getModel () && model
) {
2393 this . setModel ( model
);
2398 * Fires whenever the server has sent back new metadata to reconfigure the Reader.
2399 * @param {Ext.data.Store} this
2400 * @param {Object} data The metadata sent back from the server.
2402 this . fireEvent ( 'metachange' , this , data
);
2406 * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
2407 * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one).
2408 * @return {Ext.data.Model[]} The Model instances.
2410 getNewRecords : function () {
2411 return this . data
. filterBy ( function ( item
) {
2412 // only want phantom records that are valid
2413 return item
. phantom
=== true && item
. isValid ();
2418 * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy.
2419 * @return {Ext.data.Model[]} The updated Model instances.
2421 getUpdatedRecords : function () {
2422 return this . data
. filterBy ( function ( item
) {
2423 // only want dirty records, not phantoms that are valid
2424 return item
. dirty
=== true && item
. phantom
!== true && item
. isValid ();
2429 * Returns any records that have been removed from the store but not yet destroyed on the proxy.
2430 * @return {Ext.data.Model[]} The removed Model instances.
2432 getRemovedRecords : function () {
2433 return this . removed
;
2438 * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
2439 * load operation, passing in calculated `start` and `limit` params.
2440 * @param {Number} page The number of the page to load.
2441 * @param {Object} options See options for {@link #method-load}.
2442 * @param {Object} scope
2444 loadPage : function ( page
, options
, scope
) {
2445 if ( typeof options
=== 'function' ) {
2448 scope
: scope
|| this
2453 pageSize
= me
. getPageSize (),
2454 clearOnPageLoad
= me
. getClearOnPageLoad ();
2456 options
= Ext
. apply ({}, options
);
2458 me
. currentPage
= page
;
2460 me
. load ( Ext
. applyIf ( options
, {
2462 start
: ( page
- 1 ) * pageSize
,
2464 addRecords
: ! clearOnPageLoad
2469 * Loads the next 'page' in the current data set.
2470 * @param {Object} options See options for {@link #method-load}.
2472 nextPage : function ( options
) {
2473 this . loadPage ( this . currentPage
+ 1 , options
);
2477 * Loads the previous 'page' in the current data set.
2478 * @param {Object} options See options for {@link #method-load}.
2480 previousPage : function ( options
) {
2481 this . loadPage ( this . currentPage
- 1 , options
);
2484 destroy : function () {
2486 var proxy
= this . getProxy ();
2490 Ext
. data
. StoreManager
. unregister ( this );
2492 Ext
. destroy ( this . getPlugins ());
2494 if ( this . implicitModel
&& this . getModel ()) {
2495 delete Ext
. data
. ModelManager
. types
[ this . getModel (). getName ()];
2497 Ext
. destroy ( this . data
);
2499 this . callParent ( arguments
);
2502 // <deprecated product=touch since=2.0>
2503 , onClassExtended : function ( cls
, data
) {
2504 var prototype = this . prototype ,
2505 defaultConfig
= prototype . config
,
2506 config
= data
. config
|| {},
2509 // Convert deprecated properties in application into a config object
2510 for ( key
in defaultConfig
) {
2511 if ( key
!= "control" && key
in data
) {
2512 config
[ key
] = data
[ key
];
2515 Ext
. Logger
. deprecate ( key
+ ' is deprecated as a property directly on the ' + this .$ className
+
2516 ' prototype. Please put it inside the config object.' );
2521 data
. config
= config
;
2525 * Loads an array of data straight into the Store.
2526 * @param {Ext.data.Model[]/Object[]} data Array of data to load. Any non-model instances will be cast into model instances first.
2527 * @param {Boolean} append `true` to add the records to the existing records in the store, `false` to remove the old ones first.
2528 * @deprecated 2.0 Please use #add or #setData instead.
2532 loadData : function ( data
, append
) {
2533 Ext
. Logger
. deprecate ( "loadData is deprecated, please use either add or setData" );
2542 doAddListener : function ( name
, fn
, scope
, options
, order
) {
2546 Ext
. Logger
. warn ( 'The update event on Store has been removed. Please use the updaterecord event from now on.' );
2549 Ext
. Logger
. warn ( 'The add event on Store has been removed. Please use the addrecords event from now on.' );
2552 Ext
. Logger
. warn ( 'The remove event on Store has been removed. Please use the removerecords event from now on.' );
2555 Ext
. Logger
. warn ( 'The datachanged event on Store has been removed. Please use the refresh event from now on.' );
2561 return this . callParent ( arguments
);
2566 * @member Ext.data.Store
2567 * @method loadRecords
2568 * @inheritdoc Ext.data.Store#add
2569 * @deprecated 2.0.0 Please use {@link #add} instead.
2571 Ext
. deprecateMethod ( this , 'loadRecords' , 'add' , "Ext.data.Store#loadRecords has been deprecated. Please use the add method." );