/**
* @class Ext.data.Store
* @extends Ext.util.Observable
*The Store class encapsulates a client side cache of {@link Ext.data.Record Record}
* objects which provide input data for Components such as the {@link Ext.grid.GridPanel GridPanel},
* the {@link Ext.form.ComboBox ComboBox}, or the {@link Ext.DataView DataView}.
*Retrieving Data
*A Store object may access a data object using:
*- {@link #proxy configured implementation} of {@link Ext.data.DataProxy DataProxy}
*- {@link #data} to automatically pass in data
*- {@link #loadData} to manually pass in data
*
*Reading Data
*A Store object has no inherent knowledge of the format of the data object (it could be
* an Array, XML, or JSON). A Store object uses an appropriate {@link #reader configured implementation}
* of a {@link Ext.data.DataReader DataReader} to create {@link Ext.data.Record Record} instances from the data
* object.
*Store Types
*There are several implementations of Store available which are customized for use with
* a specific DataReader implementation. Here is an example using an ArrayStore which implicitly
* creates a reader commensurate to an Array data object.
*
var myStore = new Ext.data.ArrayStore({
fields: ['fullname', 'first'],
idIndex: 0 // id for each record will be the first element
});
*
*For custom implementations create a basic {@link Ext.data.Store} configured as needed:
*
// create a {@link Ext.data.Record Record} constructor:
var rt = Ext.data.Record.create([
{name: 'fullname'},
{name: 'first'}
]);
var myStore = new Ext.data.Store({
// explicitly create reader
reader: new Ext.data.ArrayReader(
{
idIndex: 0 // id for each record will be the first element
},
rt // recordType
)
});
*
*Load some data into store (note the data object is an array which corresponds to the reader):
*
var myData = [
[1, 'Fred Flintstone', 'Fred'], // note that id for the record is the first element
[2, 'Barney Rubble', 'Barney']
];
myStore.loadData(myData);
*
*Records are cached and made available through accessor functions. An example of adding
* a record to the store:
*
var defaultData = {
fullname: 'Full Name',
first: 'First Name'
};
var recId = 100; // provide unique id for the record
var r = new myStore.recordType(defaultData, ++recId); // create new record
myStore.{@link #insert}(0, r); // insert a new record into the store (also see {@link #add})
*
* @constructor
* Creates a new Store.
* @param {Object} config A config object containing the objects needed for the Store to access data,
* and read the data into Records.
* @xtype store
*/
Ext.data.Store = function(config){
this.data = new Ext.util.MixedCollection(false);
this.data.getKey = function(o){
return o.id;
};
/**
* See the{@link #baseParams corresponding configuration option}
* for a description of this property.
* To modify this property see{@link #setBaseParam}
.
* @property
*/
this.baseParams = {};
// temporary removed-records cache
this.removed = [];
if(config && config.data){
this.inlineData = config.data;
delete config.data;
}
Ext.apply(this, config);
this.paramNames = Ext.applyIf(this.paramNames || {}, this.defaultParamNames);
if(this.url && !this.proxy){
this.proxy = new Ext.data.HttpProxy({url: this.url});
}
// If Store is RESTful, so too is the DataProxy
if (this.restful === true && this.proxy) {
// When operating RESTfully, a unique transaction is generated for each record.
this.batch = false;
Ext.data.Api.restify(this.proxy);
}
if(this.reader){ // reader passed
if(!this.recordType){
this.recordType = this.reader.recordType;
}
if(this.reader.onMetaChange){
this.reader.onMetaChange = this.onMetaChange.createDelegate(this);
}
if (this.writer) { // writer passed
this.writer.meta = this.reader.meta;
this.pruneModifiedRecords = true;
}
}
/**
* The {@link Ext.data.Record Record} constructor as supplied to (or created by) the
* {@link Ext.data.DataReader Reader}. Read-only.
*If the Reader was constructed by passing in an Array of {@link Ext.data.Field} definition objects,
* instead of a Record constructor, it will implicitly create a Record constructor from that Array (see
* {@link Ext.data.Record}.{@link Ext.data.Record#create create} for additional details).
*This property may be used to create new Records of the type held in this Store, for example:
// create the data store
var store = new Ext.data.ArrayStore({
autoDestroy: true,
fields: [
{name: 'company'},
{name: 'price', type: 'float'},
{name: 'change', type: 'float'},
{name: 'pctChange', type: 'float'},
{name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
]
});
store.loadData(myData);
// create the Grid
var grid = new Ext.grid.EditorGridPanel({
store: store,
colModel: new Ext.grid.ColumnModel({
columns: [
{id:'company', header: 'Company', width: 160, dataIndex: 'company'},
{header: 'Price', renderer: 'usMoney', dataIndex: 'price'},
{header: 'Change', renderer: change, dataIndex: 'change'},
{header: '% Change', renderer: pctChange, dataIndex: 'pctChange'},
{header: 'Last Updated', width: 85,
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
dataIndex: 'lastChange'}
],
defaults: {
sortable: true,
width: 75
}
}),
autoExpandColumn: 'company', // match the id specified in the column model
height:350,
width:600,
title:'Array Grid',
tbar: [{
text: 'Add Record',
handler : function(){
var defaultData = {
change: 0,
company: 'New Company',
lastChange: (new Date()).clearTime(),
pctChange: 0,
price: 10
};
var recId = 3; // provide unique id
var p = new store.recordType(defaultData, recId); // create new record
grid.stopEditing();
store.{@link #insert}(0, p); // insert a new record into the store (also see {@link #add})
grid.startEditing(0, 0);
}
}]
});
*
* @property recordType
* @type Function
*/
if(this.recordType){
/**
* A {@link Ext.util.MixedCollection MixedCollection} containing the defined {@link Ext.data.Field Field}s
* for the {@link Ext.data.Record Records} stored in this Store. Read-only.
* @property fields
* @type Ext.util.MixedCollection
*/
this.fields = this.recordType.prototype.fields;
}
this.modified = [];
this.addEvents(
/**
* @event datachanged
* Fires when the data cache has changed in a bulk manner (e.g., it has been sorted, filtered, etc.) and a
* widget that is using this Store as a Record cache should refresh its view.
* @param {Store} this
*/
'datachanged',
/**
* @event metachange
* Fires when this store's reader provides new metadata (fields). This is currently only supported for JsonReaders.
* @param {Store} this
* @param {Object} meta The JSON metadata
*/
'metachange',
/**
* @event add
* Fires when Records have been {@link #add}ed to the Store
* @param {Store} this
* @param {Ext.data.Record[]} records The array of Records added
* @param {Number} index The index at which the record(s) were added
*/
'add',
/**
* @event remove
* Fires when a Record has been {@link #remove}d from the Store
* @param {Store} this
* @param {Ext.data.Record} record The Record that was removed
* @param {Number} index The index at which the record was removed
*/
'remove',
/**
* @event update
* Fires when a Record has been updated
* @param {Store} this
* @param {Ext.data.Record} record The Record that was updated
* @param {String} operation The update operation being performed. Value may be one of:
*
Ext.data.Record.EDIT
Ext.data.Record.REJECT
Ext.data.Record.COMMIT
*
*/
'update',
/**
* @event clear
* Fires when the data cache has been cleared.
* @param {Store} this
*/
'clear',
/**
* @event exception
*Fires if an exception occurs in the Proxy during a remote request.
/**
* This event is relayed through the corresponding {@link Ext.data.DataProxy}.
* See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
* for additional details.
* @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
* for description.
*/
'exception',
* @event beforeload
* Fires before a request is made for a new data object. If the beforeload handler returns
* false the {@link #load} action will be canceled.
* @param {Store} this
* @param {Object} options The loading options that were specified (see {@link #load} for details)
*/
'beforeload',
/**
* @event load
* Fires after a new set of Records has been loaded.
* @param {Store} this
* @param {Ext.data.Record[]} records The Records that were loaded
* @param {Object} options The loading options that were specified (see {@link #load} for details)
*/
'load',
/**
* @event loadexception
*This event is deprecated in favor of the catch-all
{@link #exception}
* event instead.
*This event is relayed through the corresponding {@link Ext.data.DataProxy}.
/**
* See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception}
* for additional details.
* @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception}
* for description.
*/
'loadexception',
* @event beforewrite
* @param {DataProxy} this
* @param {String} action [Ext.data.Api.actions.create|update|destroy]
* @param {Record/Array[Record]} rs
* @param {Object} options The loading options that were specified. Editoptions.params
to add Http parameters to the request. (see {@link #save} for details)
* @param {Object} arg The callback's arg object passed to the {@link #request} function
*/
'beforewrite',
/**
* @event write
* Fires if the server returns 200 after an Ext.data.Api.actions CRUD action.
* Success or failure of the action is available in theresult['successProperty']
property.
* The server-code might set thesuccessProperty
to false if a database validation
* failed, for example.
* @param {Ext.data.Store} store
* @param {String} action [Ext.data.Api.actions.create|update|destroy]
* @param {Object} result The 'data' picked-out out of the response for convenience.
* @param {Ext.Direct.Transaction} res
* @param {Record/Record[]} rs Store's records, the subject(s) of the write-action
*/
'write'
);
if(this.proxy){
this.relayEvents(this.proxy, ['loadexception', 'exception']);
}
// With a writer set for the Store, we want to listen to add/remove events to remotely create/destroy records.
if (this.writer) {
this.on({
scope: this,
add: this.createRecords,
remove: this.destroyRecord,
update: this.updateRecord
});
}
this.sortToggle = {};
if(this.sortField){
this.setDefaultSort(this.sortField, this.sortDir);
}else if(this.sortInfo){
this.setDefaultSort(this.sortInfo.field, this.sortInfo.direction);
}
Ext.data.Store.superclass.constructor.call(this);
if(this.id){
this.storeId = this.id;
delete this.id;
}
if(this.storeId){
Ext.StoreMgr.register(this);
}
if(this.inlineData){
this.loadData(this.inlineData);
delete this.inlineData;
}else if(this.autoLoad){
this.load.defer(10, this, [
typeof this.autoLoad == 'object' ?
this.autoLoad : undefined]);
}
};
Ext.extend(Ext.data.Store, Ext.util.Observable, {
/**
* @cfg {String} storeId If passed, the id to use to register with the {@link Ext.StoreMgr StoreMgr}.
*Note: if a (deprecated) {@link #id} is specified it will supersede the storeId
* assignment.
*/
/**
* @cfg {String} url If a {@link #proxy} is not specified the url will be used to
* implicitly configure a {@link Ext.data.HttpProxy HttpProxy} if an url is specified.
* Typically this option, or the{@link #data}
option will be specified.
*/
/**
* @cfg {Boolean/Object} autoLoad If {@link #data} is not specified, and if autoLoad
* is true or an Object, this store's {@link #load} method is automatically called
* after creation. If the value of autoLoad is an Object, this Object will
* be passed to the store's {@link #load} method.
*/
/**
* @cfg {Ext.data.DataProxy} proxy The {@link Ext.data.DataProxy DataProxy} object which provides
* access to a data object. See{@link #url}
.
*/
/**
* @cfg {Array} data An inline data object readable by the{@link #reader}
.
* Typically this option, or the{@link #url}
option will be specified.
*/
/**
* @cfg {Ext.data.DataReader} reader The {@link Ext.data.DataReader Reader} object which processes the
* data object and returns an Array of {@link Ext.data.Record} objects which are cached keyed by their
* {@link Ext.data.Record#id id} property.
*/
/**
* @cfg {Ext.data.DataWriter} writer
*The {@link Ext.data.DataWriter Writer} object which processes a record object for being written
* to the server-side database.
*When a writer is installed into a Store the {@link #add}, {@link #remove}, and {@link #update}
* events on the store are monitored in order to remotely {@link #createRecords create records},
* {@link #destroyRecord destroy records}, or {@link #updateRecord update records}.
*The proxy for this store will relay any {@link #writexception} events to this store.
*Sample implementation:
*
var writer = new {@link Ext.data.JsonWriter}({
encode: true,
writeAllFields: true // write all fields, not just those that changed
});
// Typical Store collecting the Proxy, Reader and Writer together.
var store = new Ext.data.Store({
storeId: 'user',
root: 'records',
proxy: proxy,
reader: reader,
writer: writer, // <-- plug a DataWriter into the store just as you would a Reader
paramsAsHash: true,
autoSave: false // <-- false to delay executing create, update, destroy requests
// until specifically told to do so.
});
*
*/
writer : undefined,
/**
* @cfg {Object} baseParams
*An object containing properties which are to be sent as parameters
* for every HTTP request.
*Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.
*Note:
baseParams
may be superseded by anyparams
* specified in a{@link #load}
request, see{@link #load}
* for more details.
* This property may be modified after creation using the{@link #setBaseParam}
* method.
* @property
*/
/**
* @cfg {Object} sortInfo A config object to specify the sort order in the request of a Store's
* {@link #load} operation. Note that for local sorting, the direction property is
* case-sensitive. See also {@link #remoteSort} and {@link #paramNames}.
* For example:
sortInfo: {
field: 'fieldName',
direction: 'ASC' // or 'DESC' (case sensitive for local sorting)
}
*/
/**
* @cfg {boolean} remoteSort true if sorting is to be handled by requesting the {@link #proxy Proxy}
* to provide a refreshed version of the data object in sorted order, as opposed to sorting the Record cache
* in place (defaults to false).
*If remoteSort is true, then clicking on a {@link Ext.grid.Column Grid Column}'s
* {@link Ext.grid.Column#header header} causes the current page to be requested from the server appending
* the following two parameters to the {@link #load params}:
*- sort : String
The name (as specified in the Record's
* {@link Ext.data.Field Field definition}) of the field to sort on.
*- dir : String
The direction of the sort, 'ASC' or 'DESC' (case-sensitive).
*
*/
remoteSort : false,
/**
* @cfg {Boolean} autoDestroy true to destroy the store when the component the store is bound
* to is destroyed (defaults to false).
*Note: this should be set to true when using stores that are bound to only 1 component.
*/
autoDestroy : false,
/**
* @cfg {Boolean} pruneModifiedRecords true to clear all modified record information each time
* the store is loaded or when a record is removed (defaults to false). See {@link #getModifiedRecords}
* for the accessor method to retrieve the modified records.
*/
pruneModifiedRecords : false,
/**
* Contains the last options object used as the parameter to the {@link #load} method. See {@link #load}
* for the details of what this may contain. This may be useful for accessing any params which were used
* to load the current Record cache.
* @property
*/
lastOptions : null,
/**
* @cfg {Boolean} autoSave
*Defaults to true causing the store to automatically {@link #save} records to
* the server when a record is modified (ie: becomes 'dirty'). Specify false to manually call {@link #save}
* to send all modifiedRecords to the server.
*Note: each CRUD action will be sent as a separate request.
*/
autoSave : true,
/**
* @cfg {Boolean} batch
*Defaults to true (unless
{@link #restful}:true
). Multiple
* requests for each CRUD action (CREATE, READ, UPDATE and DESTROY) will be combined
* and sent as one transaction. Only applies when{@link #autoSave}
is set
* to false.
*If Store is RESTful, the DataProxy is also RESTful, and a unique transaction is
* generated for each record.
*/
batch : true,
/**
* @cfg {Boolean} restful
* Defaults to false. Set to true to have the Store and the set
* Proxy operate in a RESTful manner. The store will automatically generate GET, POST,
* PUT and DELETE requests to the server. The HTTP method used for any given CRUD
* action is described in {@link Ext.data.Api#restActions}. For additional information
* see {@link Ext.data.DataProxy#restful}.
*Note: if
{@link #restful}:true
batch
will
* internally be set to false.
*/
restful: false,
/**
* @cfg {Object} paramNames
*An object containing properties which specify the names of the paging and
* sorting parameters passed to remote servers when loading blocks of data. By default, this
* object takes the following form:
{
start : 'start', // The parameter name which specifies the start row
limit : 'limit', // The parameter name which specifies number of rows to return
sort : 'sort', // The parameter name which specifies the column to sort on
dir : 'dir' // The parameter name which specifies the sort direction
}
*The server must produce the requested data block upon receipt of these parameter names.
* If different parameter names are required, this property can be overriden using a configuration
* property.
*A {@link Ext.PagingToolbar PagingToolbar} bound to this Store uses this property to determine
/**
* the parameter names to use in its {@link #load requests}.
*/
paramNames : undefined,
* @cfg {Object} defaultParamNames
* Provides the default values for the {@link #paramNames} property. To globally modify the parameters
* for all stores, this object should be changed on the store prototype.
*/
defaultParamNames : {
start : 'start',
limit : 'limit',
sort : 'sort',
dir : 'dir'
},
/**
* Destroys the store.
*/
destroy : function(){
if(this.storeId){
Ext.StoreMgr.unregister(this);
}
this.data = null;
Ext.destroy(this.proxy);
this.reader = this.writer = null;
this.purgeListeners();
},
/**
* Add Records to the Store and fires the {@link #add} event. To add Records
* to the store from a remote source use{@link #load}({add:true})
.
* See also{@link #recordType}
and{@link #insert}
.
* @param {Ext.data.Record[]} records An Array of Ext.data.Record objects
* to add to the cache. See {@link #recordType}.
*/
add : function(records){
records = [].concat(records);
if(records.length < 1){
return;
}
for(var i = 0, len = records.length; i < len; i++){
records[i].join(this);
}
var index = this.data.length;
this.data.addAll(records);
if(this.snapshot){
this.snapshot.addAll(records);
}
this.fireEvent('add', this, records, index);
},
/**
* (Local sort only) Inserts the passed Record into the Store at the index where it
* should go based on the current sort information.
* @param {Ext.data.Record} record
*/
addSorted : function(record){
var index = this.findInsertIndex(record);
this.insert(index, record);
},
/**
* Remove a Record from the Store and fires the {@link #remove} event.
* @param {Ext.data.Record} record The Ext.data.Record object to remove from the cache.
*/
remove : function(record){
var index = this.data.indexOf(record);
if(index > -1){
this.data.removeAt(index);
if(this.pruneModifiedRecords){
this.modified.remove(record);
}
if(this.snapshot){
this.snapshot.remove(record);
}
this.fireEvent('remove', this, record, index);
}
},
/**
* Remove a Record from the Store at the specified index. Fires the {@link #remove} event.
* @param {Number} index The index of the record to remove.
*/
removeAt : function(index){
this.remove(this.getAt(index));
},
/**
* Remove all Records from the Store and fires the {@link #clear} event.
*/
removeAll : function(){
this.data.clear();
if(this.snapshot){
this.snapshot.clear();
}
if(this.pruneModifiedRecords){
this.modified = [];
}
this.fireEvent('clear', this);
},
/**
* Inserts Records into the Store at the given index and fires the {@link #add} event.
* See also{@link #add}
and{@link #addSorted}
.
* @param {Number} index The start index at which to insert the passed Records.
* @param {Ext.data.Record[]} records An Array of Ext.data.Record objects to add to the cache.
*/
insert : function(index, records){
records = [].concat(records);
for(var i = 0, len = records.length; i < len; i++){
this.data.insert(index, records[i]);
records[i].join(this);
}
this.fireEvent('add', this, records, index);
},
/**
* Get the index within the cache of the passed Record.
* @param {Ext.data.Record} record The Ext.data.Record object to find.
* @return {Number} The index of the passed Record. Returns -1 if not found.
*/
indexOf : function(record){
return this.data.indexOf(record);
},
/**
* Get the index within the cache of the Record with the passed id.
* @param {String} id The id of the Record to find.
* @return {Number} The index of the Record. Returns -1 if not found.
*/
indexOfId : function(id){
return this.data.indexOfKey(id);
},
/**
* Get the Record with the specified id.
* @param {String} id The id of the Record to find.
* @return {Ext.data.Record} The Record with the passed id. Returns undefined if not found.
*/
getById : function(id){
return this.data.key(id);
},
/**
* Get the Record at the specified index.
* @param {Number} index The index of the Record to find.
* @return {Ext.data.Record} The Record at the passed index. Returns undefined if not found.
*/
getAt : function(index){
return this.data.itemAt(index);
},
/**
* Returns a range of Records between specified indices.
* @param {Number} startIndex (optional) The starting index (defaults to 0)
* @param {Number} endIndex (optional) The ending index (defaults to the last Record in the Store)
* @return {Ext.data.Record[]} An array of Records
*/
getRange : function(start, end){
return this.data.getRange(start, end);
},
// private
storeOptions : function(o){
o = Ext.apply({}, o);
delete o.callback;
delete o.scope;
this.lastOptions = o;
},
/**
*Loads the Record cache from the configured {@link #proxy} using the configured {@link #reader}.
*Notes:
*- Important: loading is asynchronous! This call will return before the new data has been
* loaded. To perform any post-processing where information from the load call is required, specify
* the callback function to be called, or use a {@link Ext.util.Observable#listeners a 'load' event handler}.
*- If using {@link Ext.PagingToolbar remote paging}, the first load call must specify the start and limit
* properties in theoptions.params
property to establish the initial position within the
* dataset, and the number of Records to cache on each read from the Proxy.
*- If using {@link #remoteSort remote sorting}, the configured
{@link #sortInfo}
* will be automatically included with the posted parameters according to the specified
*{@link #paramNames}
.
*
* @param {Object} options An object containing properties which control loading options:
An object containing properties to pass as HTTP
* parameters to a remote data source. Note: params
will override any
* {@link #baseParams}
of the same name.
Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.
A function to be called after the Records
* have been loaded. The callback is called after the load event and is passed the following arguments:
Scope with which to call the callback (defaults
* to the Store object)
Indicator to append loaded records rather than
* replace the current cache. Note: see note for {@link #loadData}
{@link #url}
will be used. * change url
* --------------- --------------------
* removed records Ext.data.Api.actions.destroy
* phantom records Ext.data.Api.actions.create
* {@link #getModifiedRecords modified records} Ext.data.Api.actions.update
*
Reloads the Record cache from the configured Proxy using the configured {@link Ext.data.Reader Reader} and
* the options from the last load operation performed.
Note: see the Important note in {@link #load}.
If using paging, this may not be the total size of the dataset. If the data object
* used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
* the dataset size. Note: see the Important note in {@link #load}.
If using paging, for this to be accurate, the data object used by the {@link #reader Reader}
* must contain the dataset size. For remote data sources, the value for this property
* (totalProperty for {@link Ext.data.JsonReader JsonReader},
* totalRecords for {@link Ext.data.XmlReader XmlReader}) shall be returned by a query on the server.
* Note: see the Important note in {@link #load}.
Note: this value is not updated when changing the contents of the Store locally.
The name of the field by which the Records are sorted.
The sort order, 'ASC' or 'DESC' (case-sensitive).
The {@link Ext.data.Record record}
* to test for filtering. Access field values using {@link Ext.data.Record#get}.
The ID of the Record passed.
The {@link Ext.data.Record record}
* to test for filtering. Access field values using {@link Ext.data.Record#get}.
The ID of the Record passed.
The {@link Ext.data.Record record}
* to test for filtering. Access field values using {@link Ext.data.Record#get}.
The ID of the Record passed.
myStore.setBaseParam('foo', {bar:3});