Store.js
上传用户:dawnssy
上传日期:2022-08-06
资源大小:9345k
文件大小:68k
源码类别:

JavaScript

开发平台:

JavaScript

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