Store.js
上传用户:shuoshiled
上传日期:2018-01-28
资源大小:10124k
文件大小:61k
源码类别:

中间件编程

开发平台:

JavaScript

  1. /*!
  2.  * Ext JS Library 3.0.0
  3.  * Copyright(c) 2006-2009 Ext JS, LLC
  4.  * licensing@extjs.com
  5.  * http://www.extjs.com/license
  6.  */
  7. /**
  8.  * @class Ext.data.Store
  9.  * @extends Ext.util.Observable
  10.  * <p>The Store class encapsulates a client side cache of {@link Ext.data.Record Record}
  11.  * objects which provide input data for Components such as the {@link Ext.grid.GridPanel GridPanel},
  12.  * the {@link Ext.form.ComboBox ComboBox}, or the {@link Ext.DataView DataView}.</p>
  13.  * <p><u>Retrieving Data</u></p>
  14.  * <p>A Store object may access a data object using:<div class="mdetail-params"><ul>
  15.  * <li>{@link #proxy configured implementation} of {@link Ext.data.DataProxy DataProxy}</li>
  16.  * <li>{@link #data} to automatically pass in data</li>
  17.  * <li>{@link #loadData} to manually pass in data</li>
  18.  * </ul></div></p>
  19.  * <p><u>Reading Data</u></p>
  20.  * <p>A Store object has no inherent knowledge of the format of the data object (it could be
  21.  * an Array, XML, or JSON). A Store object uses an appropriate {@link #reader configured implementation}
  22.  * of a {@link Ext.data.DataReader DataReader} to create {@link Ext.data.Record Record} instances from the data
  23.  * object.</p>
  24.  * <p><u>Store Types</u></p>
  25.  * <p>There are several implementations of Store available which are customized for use with
  26.  * a specific DataReader implementation.  Here is an example using an ArrayStore which implicitly
  27.  * creates a reader commensurate to an Array data object.</p>
  28.  * <pre><code>
  29. var myStore = new Ext.data.ArrayStore({
  30.     fields: ['fullname', 'first'],
  31.     idIndex: 0 // id for each record will be the first element
  32. });
  33.  * </code></pre>
  34.  * <p>For custom implementations create a basic {@link Ext.data.Store} configured as needed:</p>
  35.  * <pre><code>
  36. // create a {@link Ext.data.Record Record} constructor:
  37. var rt = Ext.data.Record.create([
  38.     {name: 'fullname'},
  39.     {name: 'first'}
  40. ]);
  41. var myStore = new Ext.data.Store({
  42.     // explicitly create reader
  43.     reader: new Ext.data.ArrayReader(
  44.         {
  45.             idIndex: 0  // id for each record will be the first element
  46.         },
  47.         rt // recordType
  48.     )
  49. });
  50.  * </code></pre>
  51.  * <p>Load some data into store (note the data object is an array which corresponds to the reader):</p>
  52.  * <pre><code>
  53. var myData = [
  54.     [1, 'Fred Flintstone', 'Fred'],  // note that id for the record is the first element
  55.     [2, 'Barney Rubble', 'Barney']
  56. ];
  57. myStore.loadData(myData);
  58.  * </code></pre>
  59.  * <p>Records are cached and made available through accessor functions.  An example of adding
  60.  * a record to the store:</p>
  61.  * <pre><code>
  62. var defaultData = {
  63.     fullname: 'Full Name',
  64.     first: 'First Name'
  65. };
  66. var recId = 100; // provide unique id for the record
  67. var r = new myStore.recordType(defaultData, ++recId); // create new record
  68. myStore.{@link #insert}(0, r); // insert a new record into the store (also see {@link #add})
  69.  * </code></pre>
  70.  * @constructor
  71.  * Creates a new Store.
  72.  * @param {Object} config A config object containing the objects needed for the Store to access data,
  73.  * and read the data into Records.
  74.  * @xtype store
  75.  */
  76. Ext.data.Store = function(config){
  77.     this.data = new Ext.util.MixedCollection(false);
  78.     this.data.getKey = function(o){
  79.         return o.id;
  80.     };
  81.     /**
  82.      * See the <code>{@link #baseParams corresponding configuration option}</code>
  83.      * for a description of this property.
  84.      * To modify this property see <code>{@link #setBaseParam}</code>.
  85.      * @property
  86.      */
  87.     this.baseParams = {};
  88.     // temporary removed-records cache
  89.     this.removed = [];
  90.     if(config && config.data){
  91.         this.inlineData = config.data;
  92.         delete config.data;
  93.     }
  94.     Ext.apply(this, config);
  95.     
  96.     this.paramNames = Ext.applyIf(this.paramNames || {}, this.defaultParamNames);
  97.     if(this.url && !this.proxy){
  98.         this.proxy = new Ext.data.HttpProxy({url: this.url});
  99.     }
  100.     // If Store is RESTful, so too is the DataProxy
  101.     if (this.restful === true && this.proxy) {
  102.         // When operating RESTfully, a unique transaction is generated for each record.
  103.         this.batch = false;
  104.         Ext.data.Api.restify(this.proxy);
  105.     }
  106.     if(this.reader){ // reader passed
  107.         if(!this.recordType){
  108.             this.recordType = this.reader.recordType;
  109.         }
  110.         if(this.reader.onMetaChange){
  111.             this.reader.onMetaChange = this.onMetaChange.createDelegate(this);
  112.         }
  113.         if (this.writer) { // writer passed
  114.             this.writer.meta = this.reader.meta;
  115.             this.pruneModifiedRecords = true;
  116.         }
  117.     }
  118.     /**
  119.      * The {@link Ext.data.Record Record} constructor as supplied to (or created by) the
  120.      * {@link Ext.data.DataReader Reader}. Read-only.
  121.      * <p>If the Reader was constructed by passing in an Array of {@link Ext.data.Field} definition objects,
  122.      * instead of a Record constructor, it will implicitly create a Record constructor from that Array (see
  123.      * {@link Ext.data.Record}.{@link Ext.data.Record#create create} for additional details).</p>
  124.      * <p>This property may be used to create new Records of the type held in this Store, for example:</p><pre><code>
  125. // create the data store
  126. var store = new Ext.data.ArrayStore({
  127.     autoDestroy: true,
  128.     fields: [
  129.        {name: 'company'},
  130.        {name: 'price', type: 'float'},
  131.        {name: 'change', type: 'float'},
  132.        {name: 'pctChange', type: 'float'},
  133.        {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
  134.     ]
  135. });
  136. store.loadData(myData);
  137. // create the Grid
  138. var grid = new Ext.grid.EditorGridPanel({
  139.     store: store,
  140.     colModel: new Ext.grid.ColumnModel({
  141.         columns: [
  142.             {id:'company', header: 'Company', width: 160, dataIndex: 'company'},
  143.             {header: 'Price', renderer: 'usMoney', dataIndex: 'price'},
  144.             {header: 'Change', renderer: change, dataIndex: 'change'},
  145.             {header: '% Change', renderer: pctChange, dataIndex: 'pctChange'},
  146.             {header: 'Last Updated', width: 85,
  147.                 renderer: Ext.util.Format.dateRenderer('m/d/Y'),
  148.                 dataIndex: 'lastChange'}
  149.         ],
  150.         defaults: {
  151.             sortable: true,
  152.             width: 75
  153.         }
  154.     }),
  155.     autoExpandColumn: 'company', // match the id specified in the column model
  156.     height:350,
  157.     width:600,
  158.     title:'Array Grid',
  159.     tbar: [{
  160.         text: 'Add Record',
  161.         handler : function(){
  162.             var defaultData = {
  163.                 change: 0,
  164.                 company: 'New Company',
  165.                 lastChange: (new Date()).clearTime(),
  166.                 pctChange: 0,
  167.                 price: 10
  168.             };
  169.             var recId = 3; // provide unique id
  170.             var p = new store.recordType(defaultData, recId); // create new record
  171.             grid.stopEditing();
  172.             store.{@link #insert}(0, p); // insert a new record into the store (also see {@link #add})
  173.             grid.startEditing(0, 0);
  174.         }
  175.     }]
  176. });
  177.      * </code></pre>
  178.      * @property recordType
  179.      * @type Function
  180.      */
  181.     if(this.recordType){
  182.         /**
  183.          * A {@link Ext.util.MixedCollection MixedCollection} containing the defined {@link Ext.data.Field Field}s
  184.          * for the {@link Ext.data.Record Records} stored in this Store. Read-only.
  185.          * @property fields
  186.          * @type Ext.util.MixedCollection
  187.          */
  188.         this.fields = this.recordType.prototype.fields;
  189.     }
  190.     this.modified = [];
  191.     this.addEvents(
  192.         /**
  193.          * @event datachanged
  194.          * Fires when the data cache has changed in a bulk manner (e.g., it has been sorted, filtered, etc.) and a
  195.          * widget that is using this Store as a Record cache should refresh its view.
  196.          * @param {Store} this
  197.          */
  198.         'datachanged',
  199.         /**
  200.          * @event metachange
  201.          * Fires when this store's reader provides new metadata (fields). This is currently only supported for JsonReaders.
  202.          * @param {Store} this
  203.          * @param {Object} meta The JSON metadata
  204.          */
  205.         'metachange',
  206.         /**
  207.          * @event add
  208.          * Fires when Records have been {@link #add}ed to the Store
  209.          * @param {Store} this
  210.          * @param {Ext.data.Record[]} records The array of Records added
  211.          * @param {Number} index The index at which the record(s) were added
  212.          */
  213.         'add',
  214.         /**
  215.          * @event remove
  216.          * Fires when a Record has been {@link #remove}d from the Store
  217.          * @param {Store} this
  218.          * @param {Ext.data.Record} record The Record that was removed
  219.          * @param {Number} index The index at which the record was removed
  220.          */
  221.         'remove',
  222.         /**
  223.          * @event update
  224.          * Fires when a Record has been updated
  225.          * @param {Store} this
  226.          * @param {Ext.data.Record} record The Record that was updated
  227.          * @param {String} operation The update operation being performed.  Value may be one of:
  228.          * <pre><code>
  229.  Ext.data.Record.EDIT
  230.  Ext.data.Record.REJECT
  231.  Ext.data.Record.COMMIT
  232.          * </code></pre>
  233.          */
  234.         'update',
  235.         /**
  236.          * @event clear
  237.          * Fires when the data cache has been cleared.
  238.          * @param {Store} this
  239.          */
  240.         'clear',
  241.         /**
  242.          * @event exception
  243.          * <p>Fires if an exception occurs in the Proxy during a remote request.
  244.          * This event is relayed through the corresponding {@link Ext.data.DataProxy}.
  245.          * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
  246.          * for additional details.
  247.          * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
  248.          * for description.
  249.          */
  250.         'exception',
  251.         /**
  252.          * @event beforeload
  253.          * Fires before a request is made for a new data object.  If the beforeload handler returns
  254.          * <tt>false</tt> the {@link #load} action will be canceled.
  255.          * @param {Store} this
  256.          * @param {Object} options The loading options that were specified (see {@link #load} for details)
  257.          */
  258.         'beforeload',
  259.         /**
  260.          * @event load
  261.          * Fires after a new set of Records has been loaded.
  262.          * @param {Store} this
  263.          * @param {Ext.data.Record[]} records The Records that were loaded
  264.          * @param {Object} options The loading options that were specified (see {@link #load} for details)
  265.          */
  266.         'load',
  267.         /**
  268.          * @event loadexception
  269.          * <p>This event is <b>deprecated</b> in favor of the catch-all <b><code>{@link #exception}</code></b>
  270.          * event instead.</p>
  271.          * <p>This event is relayed through the corresponding {@link Ext.data.DataProxy}.
  272.          * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception}
  273.          * for additional details.
  274.          * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception}
  275.          * for description.
  276.          */
  277.         'loadexception',
  278.         /**
  279.          * @event beforewrite
  280.          * @param {DataProxy} this
  281.          * @param {String} action [Ext.data.Api.actions.create|update|destroy]
  282.          * @param {Record/Array[Record]} rs
  283.          * @param {Object} options The loading options that were specified. Edit <code>options.params</code> to add Http parameters to the request.  (see {@link #save} for details)
  284.          * @param {Object} arg The callback's arg object passed to the {@link #request} function
  285.          */
  286.         'beforewrite',
  287.         /**
  288.          * @event write
  289.          * Fires if the server returns 200 after an Ext.data.Api.actions CRUD action.
  290.          * Success or failure of the action is available in the <code>result['successProperty']</code> property.
  291.          * The server-code might set the <code>successProperty</code> to <tt>false</tt> if a database validation
  292.          * failed, for example.
  293.          * @param {Ext.data.Store} store
  294.          * @param {String} action [Ext.data.Api.actions.create|update|destroy]
  295.          * @param {Object} result The 'data' picked-out out of the response for convenience.
  296.          * @param {Ext.Direct.Transaction} res
  297.          * @param {Record/Record[]} rs Store's records, the subject(s) of the write-action
  298.          */
  299.         'write'
  300.     );
  301.     if(this.proxy){
  302.         this.relayEvents(this.proxy,  ['loadexception', 'exception']);
  303.     }
  304.     // With a writer set for the Store, we want to listen to add/remove events to remotely create/destroy records.
  305.     if (this.writer) {
  306.         this.on({
  307.             scope: this,
  308.             add: this.createRecords,
  309.             remove: this.destroyRecord,
  310.             update: this.updateRecord
  311.         });
  312.     }
  313.     this.sortToggle = {};
  314.     if(this.sortField){
  315.         this.setDefaultSort(this.sortField, this.sortDir);
  316.     }else if(this.sortInfo){
  317.         this.setDefaultSort(this.sortInfo.field, this.sortInfo.direction);
  318.     }
  319.     Ext.data.Store.superclass.constructor.call(this);
  320.     if(this.id){
  321.         this.storeId = this.id;
  322.         delete this.id;
  323.     }
  324.     if(this.storeId){
  325.         Ext.StoreMgr.register(this);
  326.     }
  327.     if(this.inlineData){
  328.         this.loadData(this.inlineData);
  329.         delete this.inlineData;
  330.     }else if(this.autoLoad){
  331.         this.load.defer(10, this, [
  332.             typeof this.autoLoad == 'object' ?
  333.                 this.autoLoad : undefined]);
  334.     }
  335. };
  336. Ext.extend(Ext.data.Store, Ext.util.Observable, {
  337.     /**
  338.      * @cfg {String} storeId If passed, the id to use to register with the <b>{@link Ext.StoreMgr StoreMgr}</b>.
  339.      * <p><b>Note</b>: if a (deprecated) <tt>{@link #id}</tt> is specified it will supersede the <tt>storeId</tt>
  340.      * assignment.</p>
  341.      */
  342.     /**
  343.      * @cfg {String} url If a <tt>{@link #proxy}</tt> is not specified the <tt>url</tt> will be used to
  344.      * implicitly configure a {@link Ext.data.HttpProxy HttpProxy} if an <tt>url</tt> is specified.
  345.      * Typically this option, or the <code>{@link #data}</code> option will be specified.
  346.      */
  347.     /**
  348.      * @cfg {Boolean/Object} autoLoad If <tt>{@link #data}</tt> is not specified, and if <tt>autoLoad</tt>
  349.      * is <tt>true</tt> or an <tt>Object</tt>, this store's {@link #load} method is automatically called
  350.      * after creation. If the value of <tt>autoLoad</tt> is an <tt>Object</tt>, this <tt>Object</tt> will
  351.      * be passed to the store's {@link #load} method.
  352.      */
  353.     /**
  354.      * @cfg {Ext.data.DataProxy} proxy The {@link Ext.data.DataProxy DataProxy} object which provides
  355.      * access to a data object.  See <code>{@link #url}</code>.
  356.      */
  357.     /**
  358.      * @cfg {Array} data An inline data object readable by the <code>{@link #reader}</code>.
  359.      * Typically this option, or the <code>{@link #url}</code> option will be specified.
  360.      */
  361.     /**
  362.      * @cfg {Ext.data.DataReader} reader The {@link Ext.data.DataReader Reader} object which processes the
  363.      * data object and returns an Array of {@link Ext.data.Record} objects which are cached keyed by their
  364.      * <b><tt>{@link Ext.data.Record#id id}</tt></b> property.
  365.      */
  366.     /**
  367.      * @cfg {Ext.data.DataWriter} writer
  368.      * <p>The {@link Ext.data.DataWriter Writer} object which processes a record object for being written
  369.      * to the server-side database.</p>
  370.      * <br><p>When a writer is installed into a Store the {@link #add}, {@link #remove}, and {@link #update}
  371.      * events on the store are monitored in order to remotely {@link #createRecords create records},
  372.      * {@link #destroyRecord destroy records}, or {@link #updateRecord update records}.</p>
  373.      * <br><p>The proxy for this store will relay any {@link #writexception} events to this store.</p>
  374.      * <br><p>Sample implementation:
  375.      * <pre><code>
  376. var writer = new {@link Ext.data.JsonWriter}({
  377.     encode: true,
  378.     writeAllFields: true // write all fields, not just those that changed
  379. });
  380. // Typical Store collecting the Proxy, Reader and Writer together.
  381. var store = new Ext.data.Store({
  382.     storeId: 'user',
  383.     root: 'records',
  384.     proxy: proxy,
  385.     reader: reader,
  386.     writer: writer,     // <-- plug a DataWriter into the store just as you would a Reader
  387.     paramsAsHash: true,
  388.     autoSave: false    // <-- false to delay executing create, update, destroy requests
  389.                         //     until specifically told to do so.
  390. });
  391.      * </code></pre></p>
  392.      */
  393.     writer : undefined,
  394.     /**
  395.      * @cfg {Object} baseParams
  396.      * <p>An object containing properties which are to be sent as parameters
  397.      * for <i>every</i> HTTP request.</p>
  398.      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p>
  399.      * <p><b>Note</b>: <code>baseParams</code> may be superseded by any <code>params</code>
  400.      * specified in a <code>{@link #load}</code> request, see <code>{@link #load}</code>
  401.      * for more details.</p>
  402.      * This property may be modified after creation using the <code>{@link #setBaseParam}</code>
  403.      * method.
  404.      * @property
  405.      */
  406.     /**
  407.      * @cfg {Object} sortInfo A config object to specify the sort order in the request of a Store's
  408.      * {@link #load} operation.  Note that for local sorting, the <tt>direction</tt> property is
  409.      * case-sensitive. See also {@link #remoteSort} and {@link #paramNames}.
  410.      * For example:<pre><code>
  411. sortInfo: {
  412.     field: 'fieldName',
  413.     direction: 'ASC' // or 'DESC' (case sensitive for local sorting)
  414. }
  415. </code></pre>
  416.      */
  417.     /**
  418.      * @cfg {boolean} remoteSort <tt>true</tt> if sorting is to be handled by requesting the <tt>{@link #proxy Proxy}</tt>
  419.      * to provide a refreshed version of the data object in sorted order, as opposed to sorting the Record cache
  420.      * in place (defaults to <tt>false</tt>).
  421.      * <p>If <tt>remoteSort</tt> is <tt>true</tt>, then clicking on a {@link Ext.grid.Column Grid Column}'s
  422.      * {@link Ext.grid.Column#header header} causes the current page to be requested from the server appending
  423.      * the following two parameters to the <b><tt>{@link #load params}</tt></b>:<div class="mdetail-params"><ul>
  424.      * <li><b><tt>sort</tt></b> : String<p class="sub-desc">The <tt>name</tt> (as specified in the Record's
  425.      * {@link Ext.data.Field Field definition}) of the field to sort on.</p></li>
  426.      * <li><b><tt>dir</tt></b> : String<p class="sub-desc">The direction of the sort, 'ASC' or 'DESC' (case-sensitive).</p></li>
  427.      * </ul></div></p>
  428.      */
  429.     remoteSort : false,
  430.     /**
  431.      * @cfg {Boolean} autoDestroy <tt>true</tt> to destroy the store when the component the store is bound
  432.      * to is destroyed (defaults to <tt>false</tt>).
  433.      * <p><b>Note</b>: this should be set to true when using stores that are bound to only 1 component.</p>
  434.      */
  435.     autoDestroy : false,
  436.     /**
  437.      * @cfg {Boolean} pruneModifiedRecords <tt>true</tt> to clear all modified record information each time
  438.      * the store is loaded or when a record is removed (defaults to <tt>false</tt>). See {@link #getModifiedRecords}
  439.      * for the accessor method to retrieve the modified records.
  440.      */
  441.     pruneModifiedRecords : false,
  442.     /**
  443.      * Contains the last options object used as the parameter to the {@link #load} method. See {@link #load}
  444.      * for the details of what this may contain. This may be useful for accessing any params which were used
  445.      * to load the current Record cache.
  446.      * @property
  447.      */
  448.     lastOptions : null,
  449.     /**
  450.      * @cfg {Boolean} autoSave
  451.      * <p>Defaults to <tt>true</tt> causing the store to automatically {@link #save} records to
  452.      * the server when a record is modified (ie: becomes 'dirty'). Specify <tt>false</tt> to manually call {@link #save}
  453.      * to send all modifiedRecords to the server.</p>
  454.      * <br><p><b>Note</b>: each CRUD action will be sent as a separate request.</p>
  455.      */
  456.     autoSave : true,
  457.     /**
  458.      * @cfg {Boolean} batch
  459.      * <p>Defaults to <tt>true</tt> (unless <code>{@link #restful}:true</code>). Multiple
  460.      * requests for each CRUD action (CREATE, READ, UPDATE and DESTROY) will be combined
  461.      * and sent as one transaction. Only applies when <code>{@link #autoSave}</code> is set
  462.      * to <tt>false</tt>.</p>
  463.      * <br><p>If Store is RESTful, the DataProxy is also RESTful, and a unique transaction is
  464.      * generated for each record.</p>
  465.      */
  466.     batch : true,
  467.     /**
  468.      * @cfg {Boolean} restful
  469.      * Defaults to <tt>false</tt>.  Set to <tt>true</tt> to have the Store and the set
  470.      * Proxy operate in a RESTful manner. The store will automatically generate GET, POST,
  471.      * PUT and DELETE requests to the server. The HTTP method used for any given CRUD
  472.      * action is described in {@link Ext.data.Api#restActions}.  For additional information
  473.      * see {@link Ext.data.DataProxy#restful}.
  474.      * <p><b>Note</b>: if <code>{@link #restful}:true</code> <code>batch</code> will
  475.      * internally be set to <tt>false</tt>.</p>
  476.      */
  477.     restful: false,
  478.     
  479.     /**
  480.      * @cfg {Object} paramNames
  481.      * <p>An object containing properties which specify the names of the paging and
  482.      * sorting parameters passed to remote servers when loading blocks of data. By default, this
  483.      * object takes the following form:</p><pre><code>
  484. {
  485.     start : 'start',  // The parameter name which specifies the start row
  486.     limit : 'limit',  // The parameter name which specifies number of rows to return
  487.     sort : 'sort',    // The parameter name which specifies the column to sort on
  488.     dir : 'dir'       // The parameter name which specifies the sort direction
  489. }
  490. </code></pre>
  491.      * <p>The server must produce the requested data block upon receipt of these parameter names.
  492.      * If different parameter names are required, this property can be overriden using a configuration
  493.      * property.</p>
  494.      * <p>A {@link Ext.PagingToolbar PagingToolbar} bound to this Store uses this property to determine
  495.      * the parameter names to use in its {@link #load requests}.
  496.      */
  497.     paramNames : undefined,
  498.     
  499.     /**
  500.      * @cfg {Object} defaultParamNames
  501.      * Provides the default values for the {@link #paramNames} property. To globally modify the parameters
  502.      * for all stores, this object should be changed on the store prototype.
  503.      */
  504.     defaultParamNames : {
  505.         start : 'start',
  506.         limit : 'limit',
  507.         sort : 'sort',
  508.         dir : 'dir'
  509.     },
  510.     /**
  511.      * Destroys the store.
  512.      */
  513.     destroy : function(){
  514.         if(this.storeId){
  515.             Ext.StoreMgr.unregister(this);
  516.         }
  517.         this.data = null;
  518.         Ext.destroy(this.proxy);
  519.         this.reader = this.writer = null;
  520.         this.purgeListeners();
  521.     },
  522.     /**
  523.      * Add Records to the Store and fires the {@link #add} event.  To add Records
  524.      * to the store from a remote source use <code>{@link #load}({add:true})</code>.
  525.      * See also <code>{@link #recordType}</code> and <code>{@link #insert}</code>.
  526.      * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects
  527.      * to add to the cache. See {@link #recordType}.
  528.      */
  529.     add : function(records){
  530.         records = [].concat(records);
  531.         if(records.length < 1){
  532.             return;
  533.         }
  534.         for(var i = 0, len = records.length; i < len; i++){
  535.             records[i].join(this);
  536.         }
  537.         var index = this.data.length;
  538.         this.data.addAll(records);
  539.         if(this.snapshot){
  540.             this.snapshot.addAll(records);
  541.         }
  542.         this.fireEvent('add', this, records, index);
  543.     },
  544.     /**
  545.      * (Local sort only) Inserts the passed Record into the Store at the index where it
  546.      * should go based on the current sort information.
  547.      * @param {Ext.data.Record} record
  548.      */
  549.     addSorted : function(record){
  550.         var index = this.findInsertIndex(record);
  551.         this.insert(index, record);
  552.     },
  553.     /**
  554.      * Remove a Record from the Store and fires the {@link #remove} event.
  555.      * @param {Ext.data.Record} record The Ext.data.Record object to remove from the cache.
  556.      */
  557.     remove : function(record){
  558.         var index = this.data.indexOf(record);
  559.         if(index > -1){
  560.             this.data.removeAt(index);
  561.             if(this.pruneModifiedRecords){
  562.                 this.modified.remove(record);
  563.             }
  564.             if(this.snapshot){
  565.                 this.snapshot.remove(record);
  566.             }
  567.             this.fireEvent('remove', this, record, index);
  568.         }
  569.     },
  570.     /**
  571.      * Remove a Record from the Store at the specified index. Fires the {@link #remove} event.
  572.      * @param {Number} index The index of the record to remove.
  573.      */
  574.     removeAt : function(index){
  575.         this.remove(this.getAt(index));
  576.     },
  577.     /**
  578.      * Remove all Records from the Store and fires the {@link #clear} event.
  579.      */
  580.     removeAll : function(){
  581.         this.data.clear();
  582.         if(this.snapshot){
  583.             this.snapshot.clear();
  584.         }
  585.         if(this.pruneModifiedRecords){
  586.             this.modified = [];
  587.         }
  588.         this.fireEvent('clear', this);
  589.     },
  590.     /**
  591.      * Inserts Records into the Store at the given index and fires the {@link #add} event.
  592.      * See also <code>{@link #add}</code> and <code>{@link #addSorted}</code>.
  593.      * @param {Number} index The start index at which to insert the passed Records.
  594.      * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects to add to the cache.
  595.      */
  596.     insert : function(index, records){
  597.         records = [].concat(records);
  598.         for(var i = 0, len = records.length; i < len; i++){
  599.             this.data.insert(index, records[i]);
  600.             records[i].join(this);
  601.         }
  602.         this.fireEvent('add', this, records, index);
  603.     },
  604.     /**
  605.      * Get the index within the cache of the passed Record.
  606.      * @param {Ext.data.Record} record The Ext.data.Record object to find.
  607.      * @return {Number} The index of the passed Record. Returns -1 if not found.
  608.      */
  609.     indexOf : function(record){
  610.         return this.data.indexOf(record);
  611.     },
  612.     /**
  613.      * Get the index within the cache of the Record with the passed id.
  614.      * @param {String} id The id of the Record to find.
  615.      * @return {Number} The index of the Record. Returns -1 if not found.
  616.      */
  617.     indexOfId : function(id){
  618.         return this.data.indexOfKey(id);
  619.     },
  620.     /**
  621.      * Get the Record with the specified id.
  622.      * @param {String} id The id of the Record to find.
  623.      * @return {Ext.data.Record} The Record with the passed id. Returns undefined if not found.
  624.      */
  625.     getById : function(id){
  626.         return this.data.key(id);
  627.     },
  628.     /**
  629.      * Get the Record at the specified index.
  630.      * @param {Number} index The index of the Record to find.
  631.      * @return {Ext.data.Record} The Record at the passed index. Returns undefined if not found.
  632.      */
  633.     getAt : function(index){
  634.         return this.data.itemAt(index);
  635.     },
  636.     /**
  637.      * Returns a range of Records between specified indices.
  638.      * @param {Number} startIndex (optional) The starting index (defaults to 0)
  639.      * @param {Number} endIndex (optional) The ending index (defaults to the last Record in the Store)
  640.      * @return {Ext.data.Record[]} An array of Records
  641.      */
  642.     getRange : function(start, end){
  643.         return this.data.getRange(start, end);
  644.     },
  645.     // private
  646.     storeOptions : function(o){
  647.         o = Ext.apply({}, o);
  648.         delete o.callback;
  649.         delete o.scope;
  650.         this.lastOptions = o;
  651.     },
  652.     /**
  653.      * <p>Loads the Record cache from the configured <tt>{@link #proxy}</tt> using the configured <tt>{@link #reader}</tt>.</p>
  654.      * <br><p>Notes:</p><div class="mdetail-params"><ul>
  655.      * <li><b><u>Important</u></b>: loading is asynchronous! This call will return before the new data has been
  656.      * loaded. To perform any post-processing where information from the load call is required, specify
  657.      * the <tt>callback</tt> function to be called, or use a {@link Ext.util.Observable#listeners a 'load' event handler}.</li>
  658.      * <li>If using {@link Ext.PagingToolbar remote paging}, the first load call must specify the <tt>start</tt> and <tt>limit</tt>
  659.      * properties in the <code>options.params</code> property to establish the initial position within the
  660.      * dataset, and the number of Records to cache on each read from the Proxy.</li>
  661.      * <li>If using {@link #remoteSort remote sorting}, the configured <code>{@link #sortInfo}</code>
  662.      * will be automatically included with the posted parameters according to the specified
  663.      * <code>{@link #paramNames}</code>.</li>
  664.      * </ul></div>
  665.      * @param {Object} options An object containing properties which control loading options:<ul>
  666.      * <li><b><tt>params</tt></b> :Object<div class="sub-desc"><p>An object containing properties to pass as HTTP
  667.      * parameters to a remote data source. <b>Note</b>: <code>params</code> will override any
  668.      * <code>{@link #baseParams}</code> of the same name.</p>
  669.      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p></div></li>
  670.      * <li><b><tt>callback</tt></b> : Function<div class="sub-desc"><p>A function to be called after the Records
  671.      * have been loaded. The <tt>callback</tt> is called after the load event and is passed the following arguments:<ul>
  672.      * <li><tt>r</tt> : Ext.data.Record[]</li>
  673.      * <li><tt>options</tt>: Options object from the load call</li>
  674.      * <li><tt>success</tt>: Boolean success indicator</li></ul></p></div></li>
  675.      * <li><b><tt>scope</tt></b> : Object<div class="sub-desc"><p>Scope with which to call the callback (defaults
  676.      * to the Store object)</p></div></li>
  677.      * <li><b><tt>add</tt></b> : Boolean<div class="sub-desc"><p>Indicator to append loaded records rather than
  678.      * replace the current cache.  <b>Note</b>: see note for <tt>{@link #loadData}</tt></p></div></li>
  679.      * </ul>
  680.      * @return {Boolean} If the <i>developer</i> provided <tt>{@link #beforeload}</tt> event handler returns
  681.      * <tt>false</tt>, the load call will abort and will return <tt>false</tt>; otherwise will return <tt>true</tt>.
  682.      */
  683.     load : function(options) {
  684.         options = options || {};
  685.         this.storeOptions(options);
  686.         if(this.sortInfo && this.remoteSort){
  687.             var pn = this.paramNames;
  688.             options.params = options.params || {};
  689.             options.params[pn.sort] = this.sortInfo.field;
  690.             options.params[pn.dir] = this.sortInfo.direction;
  691.         }
  692.         try {
  693.             return this.execute('read', null, options); // <-- null represents rs.  No rs for load actions.
  694.         } catch(e) {
  695.             this.handleException(e);
  696.             return false;
  697.         }
  698.     },
  699.     /**
  700.      * updateRecord  Should not be used directly.  This method will be called automatically if a Writer is set.
  701.      * Listens to 'update' event.
  702.      * @param {Object} store
  703.      * @param {Object} record
  704.      * @param {Object} action
  705.      * @private
  706.      */
  707.     updateRecord : function(store, record, action) {
  708.         if (action == Ext.data.Record.EDIT && this.autoSave === true && (!record.phantom || (record.phantom && record.isValid))) {
  709.             this.save();
  710.         }
  711.     },
  712.     /**
  713.      * Should not be used directly.  Store#add will call this automatically if a Writer is set
  714.      * @param {Object} store
  715.      * @param {Object} rs
  716.      * @param {Object} index
  717.      * @private
  718.      */
  719.     createRecords : function(store, rs, index) {
  720.         for (var i = 0, len = rs.length; i < len; i++) {
  721.             if (rs[i].phantom && rs[i].isValid()) {
  722.                 rs[i].markDirty();  // <-- Mark new records dirty
  723.                 this.modified.push(rs[i]);  // <-- add to modified
  724.             }
  725.         }
  726.         if (this.autoSave === true) {
  727.             this.save();
  728.         }
  729.     },
  730.     /**
  731.      * Destroys a record or records.  Should not be used directly.  It's called by Store#remove if a Writer is set.
  732.      * @param {Store} this
  733.      * @param {Ext.data.Record/Ext.data.Record[]}
  734.      * @param {Number} index
  735.      * @private
  736.      */
  737.     destroyRecord : function(store, record, index) {
  738.         if (this.modified.indexOf(record) != -1) {  // <-- handled already if @cfg pruneModifiedRecords == true
  739.             this.modified.remove(record);
  740.         }
  741.         if (!record.phantom) {
  742.             this.removed.push(record);
  743.             // since the record has already been removed from the store but the server request has not yet been executed,
  744.             // must keep track of the last known index this record existed.  If a server error occurs, the record can be
  745.             // put back into the store.  @see Store#createCallback where the record is returned when response status === false
  746.             record.lastIndex = index;
  747.             if (this.autoSave === true) {
  748.                 this.save();
  749.             }
  750.         }
  751.     },
  752.     /**
  753.      * This method should generally not be used directly.  This method is called internally
  754.      * by {@link #load}, or if a Writer is set will be called automatically when {@link #add},
  755.      * {@link #remove}, or {@link #update} events fire.
  756.      * @param {String} action Action name ('read', 'create', 'update', or 'destroy')
  757.      * @param {Record/Record[]} rs
  758.      * @param {Object} options
  759.      * @throws Error
  760.      * @private
  761.      */
  762.     execute : function(action, rs, options) {
  763.         // blow up if action not Ext.data.CREATE, READ, UPDATE, DESTROY
  764.         if (!Ext.data.Api.isAction(action)) {
  765.             throw new Ext.data.Api.Error('execute', action);
  766.         }
  767.         // make sure options has a params key
  768.         options = Ext.applyIf(options||{}, {
  769.             params: {}
  770.         });
  771.         // have to separate before-events since load has a different signature than create,destroy and save events since load does not
  772.         // include the rs (record resultset) parameter.  Capture return values from the beforeaction into doRequest flag.
  773.         var doRequest = true;
  774.         if (action === 'read') {
  775.             doRequest = this.fireEvent('beforeload', this, options);
  776.         }
  777.         else {
  778.             // if Writer is configured as listful, force single-recoord rs to be [{}} instead of {}
  779.             if (this.writer.listful === true && this.restful !== true) {
  780.                 rs = (Ext.isArray(rs)) ? rs : [rs];
  781.             }
  782.             // if rs has just a single record, shift it off so that Writer writes data as '{}' rather than '[{}]'
  783.             else if (Ext.isArray(rs) && rs.length == 1) {
  784.                 rs = rs.shift();
  785.             }
  786.             // Write the action to options.params
  787.             if ((doRequest = this.fireEvent('beforewrite', this, action, rs, options)) !== false) {
  788.                 this.writer.write(action, options.params, rs);
  789.             }
  790.         }
  791.         if (doRequest !== false) {
  792.             // Send request to proxy.
  793.             var params = Ext.apply({}, options.params, this.baseParams);
  794.             if (this.writer && this.proxy.url && !this.proxy.restful && !Ext.data.Api.hasUniqueUrl(this.proxy, action)) {
  795.                 params.xaction = action;
  796.             }
  797.             // Note:  Up until this point we've been dealing with 'action' as a key from Ext.data.Api.actions.  We'll flip it now
  798.             // and send the value into DataProxy#request, since it's the value which maps to the DataProxy#api
  799.             this.proxy.request(Ext.data.Api.actions[action], rs, params, this.reader, this.createCallback(action, rs), this, options);
  800.         }
  801.         return doRequest;
  802.     },
  803.     /**
  804.      * Saves all pending changes to the store.  If the commensurate Ext.data.Api.actions action is not configured, then
  805.      * the configured <code>{@link #url}</code> will be used.
  806.      * <pre>
  807.      * change            url
  808.      * ---------------   --------------------
  809.      * removed records   Ext.data.Api.actions.destroy
  810.      * phantom records   Ext.data.Api.actions.create
  811.      * {@link #getModifiedRecords modified records}  Ext.data.Api.actions.update
  812.      * </pre>
  813.      * @TODO:  Create extensions of Error class and send associated Record with thrown exceptions.
  814.      * e.g.:  Ext.data.DataReader.Error or Ext.data.Error or Ext.data.DataProxy.Error, etc.
  815.      */
  816.     save : function() {
  817.         if (!this.writer) {
  818.             throw new Ext.data.Store.Error('writer-undefined');
  819.         }
  820.         // DESTROY:  First check for removed records.  Records in this.removed are guaranteed non-phantoms.  @see Store#remove
  821.         if (this.removed.length) {
  822.             this.doTransaction('destroy', this.removed);
  823.         }
  824.         // Check for modified records. Use a copy so Store#rejectChanges will work if server returns error.
  825.         var rs = [].concat(this.getModifiedRecords());
  826.         if (!rs.length) { // Bail-out if empty...
  827.             return true;
  828.         }
  829.         // CREATE:  Next check for phantoms within rs.  splice-off and execute create.
  830.         var phantoms = [];
  831.         for (var i = rs.length-1; i >= 0; i--) {
  832.             if (rs[i].phantom === true) {
  833.                 var rec = rs.splice(i, 1).shift();
  834.                 if (rec.isValid()) {
  835.                     phantoms.push(rec);
  836.                 }
  837.             } else if (!rs[i].isValid()) { // <-- while we're here, splice-off any !isValid real records
  838.                 rs.splice(i,1);
  839.             }
  840.         }
  841.         // If we have valid phantoms, create them...
  842.         if (phantoms.length) {
  843.             this.doTransaction('create', phantoms);
  844.         }
  845.         // UPDATE:  And finally, if we're still here after splicing-off phantoms and !isValid real records, update the rest...
  846.         if (rs.length) {
  847.             this.doTransaction('update', rs);
  848.         }
  849.         return true;
  850.     },
  851.     // private.  Simply wraps call to Store#execute in try/catch.  Defers to Store#handleException on error.  Loops if batch: false
  852.     doTransaction : function(action, rs) {
  853.         function transaction(records) {
  854.             try {
  855.                 this.execute(action, records);
  856.             } catch (e) {
  857.                 this.handleException(e);
  858.             }
  859.         }
  860.         if (this.batch === false) {
  861.             for (var i = 0, len = rs.length; i < len; i++) {
  862.                 transaction.call(this, rs[i]);
  863.             }
  864.         } else {
  865.             transaction.call(this, rs);
  866.         }
  867.     },
  868.     // @private callback-handler for remote CRUD actions
  869.     // Do not override -- override loadRecords, onCreateRecords, onDestroyRecords and onUpdateRecords instead.
  870.     createCallback : function(action, rs) {
  871.         var actions = Ext.data.Api.actions;
  872.         return (action == 'read') ? this.loadRecords : function(data, response, success) {
  873.             // calls: onCreateRecords | onUpdateRecords | onDestroyRecords
  874.             this['on' + Ext.util.Format.capitalize(action) + 'Records'](success, rs, data);
  875.             // If success === false here, exception will have been called in DataProxy
  876.             if (success === true) {
  877.                 this.fireEvent('write', this, action, data, response, rs);
  878.             }
  879.         };
  880.     },
  881.     // Clears records from modified array after an exception event.
  882.     // NOTE:  records are left marked dirty.  Do we want to commit them even though they were not updated/realized?
  883.     clearModified : function(rs) {
  884.         if (Ext.isArray(rs)) {
  885.             for (var n=rs.length-1;n>=0;n--) {
  886.                 this.modified.splice(this.modified.indexOf(rs[n]), 1);
  887.             }
  888.         } else {
  889.             this.modified.splice(this.modified.indexOf(rs), 1);
  890.         }
  891.     },
  892.     // remap record ids in MixedCollection after records have been realized.  @see Store#onCreateRecords, @see DataReader#realize
  893.     reMap : function(record) {
  894.         if (Ext.isArray(record)) {
  895.             for (var i = 0, len = record.length; i < len; i++) {
  896.                 this.reMap(record[i]);
  897.             }
  898.         } else {
  899.             delete this.data.map[record._phid];
  900.             this.data.map[record.id] = record;
  901.             var index = this.data.keys.indexOf(record._phid);
  902.             this.data.keys.splice(index, 1, record.id);
  903.             delete record._phid;
  904.         }
  905.     },
  906.     // @protected onCreateRecord proxy callback for create action
  907.     onCreateRecords : function(success, rs, data) {
  908.         if (success === true) {
  909.             try {
  910.                 this.reader.realize(rs, data);
  911.                 this.reMap(rs);
  912.             }
  913.             catch (e) {
  914.                 this.handleException(e);
  915.                 if (Ext.isArray(rs)) {
  916.                     // Recurse to run back into the try {}.  DataReader#realize splices-off the rs until empty.
  917.                     this.onCreateRecords(success, rs, data);
  918.                 }
  919.             }
  920.         }
  921.     },
  922.     // @protected, onUpdateRecords proxy callback for update action
  923.     onUpdateRecords : function(success, rs, data) {
  924.         if (success === true) {
  925.             try {
  926.                 this.reader.update(rs, data);
  927.             } catch (e) {
  928.                 this.handleException(e);
  929.                 if (Ext.isArray(rs)) {
  930.                     // Recurse to run back into the try {}.  DataReader#update splices-off the rs until empty.
  931.                     this.onUpdateRecords(success, rs, data);
  932.                 }
  933.             }
  934.         }
  935.     },
  936.     // @protected onDestroyRecords proxy callback for destroy action
  937.     onDestroyRecords : function(success, rs, data) {
  938.         // splice each rec out of this.removed
  939.         rs = (rs instanceof Ext.data.Record) ? [rs] : rs;
  940.         for (var i=0,len=rs.length;i<len;i++) {
  941.             this.removed.splice(this.removed.indexOf(rs[i]), 1);
  942.         }
  943.         if (success === false) {
  944.             // put records back into store if remote destroy fails.
  945.             // @TODO: Might want to let developer decide.
  946.             for (i=rs.length-1;i>=0;i--) {
  947.                 this.insert(rs[i].lastIndex, rs[i]);    // <-- lastIndex set in Store#destroyRecord
  948.             }
  949.         }
  950.     },
  951.     // protected handleException.  Possibly temporary until Ext framework has an exception-handler.
  952.     handleException : function(e) {
  953.         // @see core/Error.js
  954.         Ext.handleError(e);
  955.     },
  956.     /**
  957.      * <p>Reloads the Record cache from the configured Proxy using the configured {@link Ext.data.Reader Reader} and
  958.      * the options from the last load operation performed.</p>
  959.      * <p><b>Note</b>: see the Important note in {@link #load}.</p>
  960.      * @param {Object} options (optional) An <tt>Object</tt> containing {@link #load loading options} which may
  961.      * override the options used in the last {@link #load} operation. See {@link #load} for details (defaults to
  962.      * <tt>null</tt>, in which case the {@link #lastOptions} are used).
  963.      */
  964.     reload : function(options){
  965.         this.load(Ext.applyIf(options||{}, this.lastOptions));
  966.     },
  967.     // private
  968.     // Called as a callback by the Reader during a load operation.
  969.     loadRecords : function(o, options, success){
  970.         if(!o || success === false){
  971.             if(success !== false){
  972.                 this.fireEvent('load', this, [], options);
  973.             }
  974.             if(options.callback){
  975.                 options.callback.call(options.scope || this, [], options, false, o);
  976.             }
  977.             return;
  978.         }
  979.         var r = o.records, t = o.totalRecords || r.length;
  980.         if(!options || options.add !== true){
  981.             if(this.pruneModifiedRecords){
  982.                 this.modified = [];
  983.             }
  984.             for(var i = 0, len = r.length; i < len; i++){
  985.                 r[i].join(this);
  986.             }
  987.             if(this.snapshot){
  988.                 this.data = this.snapshot;
  989.                 delete this.snapshot;
  990.             }
  991.             this.data.clear();
  992.             this.data.addAll(r);
  993.             this.totalLength = t;
  994.             this.applySort();
  995.             this.fireEvent('datachanged', this);
  996.         }else{
  997.             this.totalLength = Math.max(t, this.data.length+r.length);
  998.             this.add(r);
  999.         }
  1000.         this.fireEvent('load', this, r, options);
  1001.         if(options.callback){
  1002.             options.callback.call(options.scope || this, r, options, true);
  1003.         }
  1004.     },
  1005.     /**
  1006.      * Loads data from a passed data block and fires the {@link #load} event. A {@link Ext.data.Reader Reader}
  1007.      * which understands the format of the data must have been configured in the constructor.
  1008.      * @param {Object} data The data block from which to read the Records.  The format of the data expected
  1009.      * is dependent on the type of {@link Ext.data.Reader Reader} that is configured and should correspond to
  1010.      * that {@link Ext.data.Reader Reader}'s <tt>{@link Ext.data.Reader#readRecords}</tt> parameter.
  1011.      * @param {Boolean} append (Optional) <tt>true</tt> to append the new Records rather the default to replace
  1012.      * the existing cache.
  1013.      * <b>Note</b>: that Records in a Store are keyed by their {@link Ext.data.Record#id id}, so added Records
  1014.      * with ids which are already present in the Store will <i>replace</i> existing Records. Only Records with
  1015.      * new, unique ids will be added.
  1016.      */
  1017.     loadData : function(o, append){
  1018.         var r = this.reader.readRecords(o);
  1019.         this.loadRecords(r, {add: append}, true);
  1020.     },
  1021.     /**
  1022.      * Gets the number of cached records.
  1023.      * <p>If using paging, this may not be the total size of the dataset. If the data object
  1024.      * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
  1025.      * the dataset size.  <b>Note</b>: see the Important note in {@link #load}.</p>
  1026.      * @return {Number} The number of Records in the Store's cache.
  1027.      */
  1028.     getCount : function(){
  1029.         return this.data.length || 0;
  1030.     },
  1031.     /**
  1032.      * Gets the total number of records in the dataset as returned by the server.
  1033.      * <p>If using paging, for this to be accurate, the data object used by the {@link #reader Reader}
  1034.      * must contain the dataset size. For remote data sources, the value for this property
  1035.      * (<tt>totalProperty</tt> for {@link Ext.data.JsonReader JsonReader},
  1036.      * <tt>totalRecords</tt> for {@link Ext.data.XmlReader XmlReader}) shall be returned by a query on the server.
  1037.      * <b>Note</b>: see the Important note in {@link #load}.</p>
  1038.      * @return {Number} The number of Records as specified in the data object passed to the Reader
  1039.      * by the Proxy.
  1040.      * <p><b>Note</b>: this value is not updated when changing the contents of the Store locally.</p>
  1041.      */
  1042.     getTotalCount : function(){
  1043.         return this.totalLength || 0;
  1044.     },
  1045.     /**
  1046.      * Returns an object describing the current sort state of this Store.
  1047.      * @return {Object} The sort state of the Store. An object with two properties:<ul>
  1048.      * <li><b>field : String<p class="sub-desc">The name of the field by which the Records are sorted.</p></li>
  1049.      * <li><b>direction : String<p class="sub-desc">The sort order, 'ASC' or 'DESC' (case-sensitive).</p></li>
  1050.      * </ul>
  1051.      * See <tt>{@link #sortInfo}</tt> for additional details.
  1052.      */
  1053.     getSortState : function(){
  1054.         return this.sortInfo;
  1055.     },
  1056.     // private
  1057.     applySort : function(){
  1058.         if(this.sortInfo && !this.remoteSort){
  1059.             var s = this.sortInfo, f = s.field;
  1060.             this.sortData(f, s.direction);
  1061.         }
  1062.     },
  1063.     // private
  1064.     sortData : function(f, direction){
  1065.         direction = direction || 'ASC';
  1066.         var st = this.fields.get(f).sortType;
  1067.         var fn = function(r1, r2){
  1068.             var v1 = st(r1.data[f]), v2 = st(r2.data[f]);
  1069.             return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
  1070.         };
  1071.         this.data.sort(direction, fn);
  1072.         if(this.snapshot && this.snapshot != this.data){
  1073.             this.snapshot.sort(direction, fn);
  1074.         }
  1075.     },
  1076.     /**
  1077.      * Sets the default sort column and order to be used by the next {@link #load} operation.
  1078.      * @param {String} fieldName The name of the field to sort by.
  1079.      * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
  1080.      */
  1081.     setDefaultSort : function(field, dir){
  1082.         dir = dir ? dir.toUpperCase() : 'ASC';
  1083.         this.sortInfo = {field: field, direction: dir};
  1084.         this.sortToggle[field] = dir;
  1085.     },
  1086.     /**
  1087.      * Sort the Records.
  1088.      * If remote sorting is used, the sort is performed on the server, and the cache is reloaded. If local
  1089.      * sorting is used, the cache is sorted internally. See also {@link #remoteSort} and {@link #paramNames}.
  1090.      * @param {String} fieldName The name of the field to sort by.
  1091.      * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
  1092.      */
  1093.     sort : function(fieldName, dir){
  1094.         var f = this.fields.get(fieldName);
  1095.         if(!f){
  1096.             return false;
  1097.         }
  1098.         if(!dir){
  1099.             if(this.sortInfo && this.sortInfo.field == f.name){ // toggle sort dir
  1100.                 dir = (this.sortToggle[f.name] || 'ASC').toggle('ASC', 'DESC');
  1101.             }else{
  1102.                 dir = f.sortDir;
  1103.             }
  1104.         }
  1105.         var st = (this.sortToggle) ? this.sortToggle[f.name] : null;
  1106.         var si = (this.sortInfo) ? this.sortInfo : null;
  1107.         this.sortToggle[f.name] = dir;
  1108.         this.sortInfo = {field: f.name, direction: dir};
  1109.         if(!this.remoteSort){
  1110.             this.applySort();
  1111.             this.fireEvent('datachanged', this);
  1112.         }else{
  1113.             if (!this.load(this.lastOptions)) {
  1114.                 if (st) {
  1115.                     this.sortToggle[f.name] = st;
  1116.                 }
  1117.                 if (si) {
  1118.                     this.sortInfo = si;
  1119.                 }
  1120.             }
  1121.         }
  1122.     },
  1123.     /**
  1124.      * Calls the specified function for each of the {@link Ext.data.Record Records} in the cache.
  1125.      * @param {Function} fn The function to call. The {@link Ext.data.Record Record} is passed as the first parameter.
  1126.      * Returning <tt>false</tt> aborts and exits the iteration.
  1127.      * @param {Object} scope (optional) The scope in which to call the function (defaults to the {@link Ext.data.Record Record}).
  1128.      */
  1129.     each : function(fn, scope){
  1130.         this.data.each(fn, scope);
  1131.     },
  1132.     /**
  1133.      * Gets all {@link Ext.data.Record records} modified since the last commit.  Modified records are
  1134.      * persisted across load operations (e.g., during paging). <b>Note</b>: deleted records are not
  1135.      * included.  See also <tt>{@link #pruneModifiedRecords}</tt> and
  1136.      * {@link Ext.data.Record}<tt>{@link Ext.data.Record#markDirty markDirty}.</tt>.
  1137.      * @return {Ext.data.Record[]} An array of {@link Ext.data.Record Records} containing outstanding
  1138.      * modifications.  To obtain modified fields within a modified record see
  1139.      *{@link Ext.data.Record}<tt>{@link Ext.data.Record#modified modified}.</tt>.
  1140.      */
  1141.     getModifiedRecords : function(){
  1142.         return this.modified;
  1143.     },
  1144.     // private
  1145.     createFilterFn : function(property, value, anyMatch, caseSensitive){
  1146.         if(Ext.isEmpty(value, false)){
  1147.             return false;
  1148.         }
  1149.         value = this.data.createValueMatcher(value, anyMatch, caseSensitive);
  1150.         return function(r){
  1151.             return value.test(r.data[property]);
  1152.         };
  1153.     },
  1154.     /**
  1155.      * Sums the value of <tt>property</tt> for each {@link Ext.data.Record record} between <tt>start</tt>
  1156.      * and <tt>end</tt> and returns the result.
  1157.      * @param {String} property A field in each record
  1158.      * @param {Number} start (optional) The record index to start at (defaults to <tt>0</tt>)
  1159.      * @param {Number} end (optional) The last record index to include (defaults to length - 1)
  1160.      * @return {Number} The sum
  1161.      */
  1162.     sum : function(property, start, end){
  1163.         var rs = this.data.items, v = 0;
  1164.         start = start || 0;
  1165.         end = (end || end === 0) ? end : rs.length-1;
  1166.         for(var i = start; i <= end; i++){
  1167.             v += (rs[i].data[property] || 0);
  1168.         }
  1169.         return v;
  1170.     },
  1171.     /**
  1172.      * Filter the {@link Ext.data.Record records} by a specified property.
  1173.      * @param {String} field A field on your records
  1174.      * @param {String/RegExp} value Either a string that the field should begin with, or a RegExp to test
  1175.      * against the field.
  1176.      * @param {Boolean} anyMatch (optional) <tt>true</tt> to match any part not just the beginning
  1177.      * @param {Boolean} caseSensitive (optional) <tt>true</tt> for case sensitive comparison
  1178.      */
  1179.     filter : function(property, value, anyMatch, caseSensitive){
  1180.         var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
  1181.         return fn ? this.filterBy(fn) : this.clearFilter();
  1182.     },
  1183.     /**
  1184.      * Filter by a function. The specified function will be called for each
  1185.      * Record in this Store. If the function returns <tt>true</tt> the Record is included,
  1186.      * otherwise it is filtered out.
  1187.      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
  1188.      * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
  1189.      * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
  1190.      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
  1191.      * </ul>
  1192.      * @param {Object} scope (optional) The scope of the function (defaults to this)
  1193.      */
  1194.     filterBy : function(fn, scope){
  1195.         this.snapshot = this.snapshot || this.data;
  1196.         this.data = this.queryBy(fn, scope||this);
  1197.         this.fireEvent('datachanged', this);
  1198.     },
  1199.     /**
  1200.      * Query the records by a specified property.
  1201.      * @param {String} field A field on your records
  1202.      * @param {String/RegExp} value Either a string that the field
  1203.      * should begin with, or a RegExp to test against the field.
  1204.      * @param {Boolean} anyMatch (optional) True to match any part not just the beginning
  1205.      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
  1206.      * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
  1207.      */
  1208.     query : function(property, value, anyMatch, caseSensitive){
  1209.         var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
  1210.         return fn ? this.queryBy(fn) : this.data.clone();
  1211.     },
  1212.     /**
  1213.      * Query the cached records in this Store using a filtering function. The specified function
  1214.      * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
  1215.      * included in the results.
  1216.      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
  1217.      * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
  1218.      * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
  1219.      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
  1220.      * </ul>
  1221.      * @param {Object} scope (optional) The scope of the function (defaults to this)
  1222.      * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
  1223.      **/
  1224.     queryBy : function(fn, scope){
  1225.         var data = this.snapshot || this.data;
  1226.         return data.filterBy(fn, scope||this);
  1227.     },
  1228.     /**
  1229.      * Finds the index of the first matching record in this store by a specific property/value.
  1230.      * @param {String} property A property on your objects
  1231.      * @param {String/RegExp} value Either a string that the property value
  1232.      * should begin with, or a RegExp to test against the property.
  1233.      * @param {Number} startIndex (optional) The index to start searching at
  1234.      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
  1235.      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
  1236.      * @return {Number} The matched index or -1
  1237.      */
  1238.     find : function(property, value, start, anyMatch, caseSensitive){
  1239.         var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
  1240.         return fn ? this.data.findIndexBy(fn, null, start) : -1;
  1241.     },
  1242.     /**
  1243.      * Finds the index of the first matching record in this store by a specific property/value.
  1244.      * @param {String} property A property on your objects
  1245.      * @param {String/RegExp} value The value to match against
  1246.      * @param {Number} startIndex (optional) The index to start searching at
  1247.      * @return {Number} The matched index or -1
  1248.      */
  1249.     findExact: function(property, value, start){
  1250.         return this.data.findIndexBy(function(rec){
  1251.             return rec.get(property) === value;
  1252.         }, this, start);
  1253.     },
  1254.     /**
  1255.      * Find the index of the first matching Record in this Store by a function.
  1256.      * If the function returns <tt>true</tt> it is considered a match.
  1257.      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
  1258.      * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
  1259.      * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
  1260.      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
  1261.      * </ul>
  1262.      * @param {Object} scope (optional) The scope of the function (defaults to this)
  1263.      * @param {Number} startIndex (optional) The index to start searching at
  1264.      * @return {Number} The matched index or -1
  1265.      */
  1266.     findBy : function(fn, scope, start){
  1267.         return this.data.findIndexBy(fn, scope, start);
  1268.     },
  1269.     /**
  1270.      * Collects unique values for a particular dataIndex from this store.
  1271.      * @param {String} dataIndex The property to collect
  1272.      * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
  1273.      * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
  1274.      * @return {Array} An array of the unique values
  1275.      **/
  1276.     collect : function(dataIndex, allowNull, bypassFilter){
  1277.         var d = (bypassFilter === true && this.snapshot) ?
  1278.                 this.snapshot.items : this.data.items;
  1279.         var v, sv, r = [], l = {};
  1280.         for(var i = 0, len = d.length; i < len; i++){
  1281.             v = d[i].data[dataIndex];
  1282.             sv = String(v);
  1283.             if((allowNull || !Ext.isEmpty(v)) && !l[sv]){
  1284.                 l[sv] = true;
  1285.                 r[r.length] = v;
  1286.             }
  1287.         }
  1288.         return r;
  1289.     },
  1290.     /**
  1291.      * Revert to a view of the Record cache with no filtering applied.
  1292.      * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the
  1293.      * {@link #datachanged} event.
  1294.      */
  1295.     clearFilter : function(suppressEvent){
  1296.         if(this.isFiltered()){
  1297.             this.data = this.snapshot;
  1298.             delete this.snapshot;
  1299.             if(suppressEvent !== true){
  1300.                 this.fireEvent('datachanged', this);
  1301.             }
  1302.         }
  1303.     },
  1304.     /**
  1305.      * Returns true if this store is currently filtered
  1306.      * @return {Boolean}
  1307.      */
  1308.     isFiltered : function(){
  1309.         return this.snapshot && this.snapshot != this.data;
  1310.     },
  1311.     // private
  1312.     afterEdit : function(record){
  1313.         if(this.modified.indexOf(record) == -1){
  1314.             this.modified.push(record);
  1315.         }
  1316.         this.fireEvent('update', this, record, Ext.data.Record.EDIT);
  1317.     },
  1318.     // private
  1319.     afterReject : function(record){
  1320.         this.modified.remove(record);
  1321.         this.fireEvent('update', this, record, Ext.data.Record.REJECT);
  1322.     },
  1323.     // private
  1324.     afterCommit : function(record){
  1325.         this.modified.remove(record);
  1326.         this.fireEvent('update', this, record, Ext.data.Record.COMMIT);
  1327.     },
  1328.     /**
  1329.      * Commit all Records with {@link #getModifiedRecords outstanding changes}. To handle updates for changes,
  1330.      * subscribe to the Store's {@link #update update event}, and perform updating when the third parameter is
  1331.      * Ext.data.Record.COMMIT.
  1332.      */
  1333.     commitChanges : function(){
  1334.         var m = this.modified.slice(0);
  1335.         this.modified = [];
  1336.         for(var i = 0, len = m.length; i < len; i++){
  1337.             m[i].commit();
  1338.         }
  1339.     },
  1340.     /**
  1341.      * {@link Ext.data.Record#reject Reject} outstanding changes on all {@link #getModifiedRecords modified records}.
  1342.      */
  1343.     rejectChanges : function(){
  1344.         var m = this.modified.slice(0);
  1345.         this.modified = [];
  1346.         for(var i = 0, len = m.length; i < len; i++){
  1347.             m[i].reject();
  1348.         }
  1349.     },
  1350.     // private
  1351.     onMetaChange : function(meta, rtype, o){
  1352.         this.recordType = rtype;
  1353.         this.fields = rtype.prototype.fields;
  1354.         delete this.snapshot;
  1355.         if(meta.sortInfo){
  1356.             this.sortInfo = meta.sortInfo;
  1357.         }else if(this.sortInfo  && !this.fields.get(this.sortInfo.field)){
  1358.             delete this.sortInfo;
  1359.         }
  1360.         this.modified = [];
  1361.         this.fireEvent('metachange', this, this.reader.meta);
  1362.     },
  1363.     // private
  1364.     findInsertIndex : function(record){
  1365.         this.suspendEvents();
  1366.         var data = this.data.clone();
  1367.         this.data.add(record);
  1368.         this.applySort();
  1369.         var index = this.data.indexOf(record);
  1370.         this.data = data;
  1371.         this.resumeEvents();
  1372.         return index;
  1373.     },
  1374.     /**
  1375.      * Set the value for a property name in this store's {@link #baseParams}.  Usage:</p><pre><code>
  1376. myStore.setBaseParam('foo', {bar:3});
  1377. </code></pre>
  1378.      * @param {String} name Name of the property to assign
  1379.      * @param {Mixed} value Value to assign the <tt>name</tt>d property
  1380.      **/
  1381.     setBaseParam : function (name, value){
  1382.         this.baseParams = this.baseParams || {};
  1383.         this.baseParams[name] = value;
  1384.     }
  1385. });
  1386. Ext.reg('store', Ext.data.Store);
  1387. /**
  1388.  * @class Ext.data.Store.Error
  1389.  * @extends Ext.Error
  1390.  * Store Error extension.
  1391.  * @param {String} name
  1392.  */
  1393. Ext.data.Store.Error = Ext.extend(Ext.Error, {
  1394.     name: 'Ext.data.Store'
  1395. });
  1396. Ext.apply(Ext.data.Store.Error.prototype, {
  1397.     lang: {
  1398.         'writer-undefined' : 'Attempted to execute a write-action without a DataWriter installed.'
  1399.     }
  1400. });