Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / pkgs / data-foundation-debug.js
1 /*!
2  * Ext JS Library 3.3.1
3  * Copyright(c) 2006-2010 Sencha Inc.
4  * licensing@sencha.com
5  * http://www.sencha.com/license
6  */
7
8 /**
9  * @class Ext.data.Api
10  * @extends Object
11  * Ext.data.Api is a singleton designed to manage the data API including methods
12  * for validating a developer's DataProxy API.  Defines variables for CRUD actions
13  * create, read, update and destroy in addition to a mapping of RESTful HTTP methods
14  * GET, POST, PUT and DELETE to CRUD actions.
15  * @singleton
16  */
17 Ext.data.Api = (function() {
18
19     // private validActions.  validActions is essentially an inverted hash of Ext.data.Api.actions, where value becomes the key.
20     // Some methods in this singleton (e.g.: getActions, getVerb) will loop through actions with the code <code>for (var verb in this.actions)</code>
21     // For efficiency, some methods will first check this hash for a match.  Those methods which do acces validActions will cache their result here.
22     // We cannot pre-define this hash since the developer may over-ride the actions at runtime.
23     var validActions = {};
24
25     return {
26         /**
27          * Defined actions corresponding to remote actions:
28          * <pre><code>
29 actions: {
30     create  : 'create',  // Text representing the remote-action to create records on server.
31     read    : 'read',    // Text representing the remote-action to read/load data from server.
32     update  : 'update',  // Text representing the remote-action to update records on server.
33     destroy : 'destroy'  // Text representing the remote-action to destroy records on server.
34 }
35          * </code></pre>
36          * @property actions
37          * @type Object
38          */
39         actions : {
40             create  : 'create',
41             read    : 'read',
42             update  : 'update',
43             destroy : 'destroy'
44         },
45
46         /**
47          * Defined {CRUD action}:{HTTP method} pairs to associate HTTP methods with the
48          * corresponding actions for {@link Ext.data.DataProxy#restful RESTful proxies}.
49          * Defaults to:
50          * <pre><code>
51 restActions : {
52     create  : 'POST',
53     read    : 'GET',
54     update  : 'PUT',
55     destroy : 'DELETE'
56 },
57          * </code></pre>
58          */
59         restActions : {
60             create  : 'POST',
61             read    : 'GET',
62             update  : 'PUT',
63             destroy : 'DELETE'
64         },
65
66         /**
67          * Returns true if supplied action-name is a valid API action defined in <code>{@link #actions}</code> constants
68          * @param {String} action Action to test for availability.
69          * @return {Boolean}
70          */
71         isAction : function(action) {
72             return (Ext.data.Api.actions[action]) ? true : false;
73         },
74
75         /**
76          * Returns the actual CRUD action KEY "create", "read", "update" or "destroy" from the supplied action-name.  This method is used internally and shouldn't generally
77          * need to be used directly.  The key/value pair of Ext.data.Api.actions will often be identical but this is not necessarily true.  A developer can override this naming
78          * convention if desired.  However, the framework internally calls methods based upon the KEY so a way of retreiving the the words "create", "read", "update" and "destroy" is
79          * required.  This method will cache discovered KEYS into the private validActions hash.
80          * @param {String} name The runtime name of the action.
81          * @return {String||null} returns the action-key, or verb of the user-action or null if invalid.
82          * @nodoc
83          */
84         getVerb : function(name) {
85             if (validActions[name]) {
86                 return validActions[name];  // <-- found in cache.  return immediately.
87             }
88             for (var verb in this.actions) {
89                 if (this.actions[verb] === name) {
90                     validActions[name] = verb;
91                     break;
92                 }
93             }
94             return (validActions[name] !== undefined) ? validActions[name] : null;
95         },
96
97         /**
98          * Returns true if the supplied API is valid; that is, check that all keys match defined actions
99          * otherwise returns an array of mistakes.
100          * @return {String[]|true}
101          */
102         isValid : function(api){
103             var invalid = [];
104             var crud = this.actions; // <-- cache a copy of the actions.
105             for (var action in api) {
106                 if (!(action in crud)) {
107                     invalid.push(action);
108                 }
109             }
110             return (!invalid.length) ? true : invalid;
111         },
112
113         /**
114          * Returns true if the supplied verb upon the supplied proxy points to a unique url in that none of the other api-actions
115          * point to the same url.  The question is important for deciding whether to insert the "xaction" HTTP parameter within an
116          * Ajax request.  This method is used internally and shouldn't generally need to be called directly.
117          * @param {Ext.data.DataProxy} proxy
118          * @param {String} verb
119          * @return {Boolean}
120          */
121         hasUniqueUrl : function(proxy, verb) {
122             var url = (proxy.api[verb]) ? proxy.api[verb].url : null;
123             var unique = true;
124             for (var action in proxy.api) {
125                 if ((unique = (action === verb) ? true : (proxy.api[action].url != url) ? true : false) === false) {
126                     break;
127                 }
128             }
129             return unique;
130         },
131
132         /**
133          * This method is used internally by <tt>{@link Ext.data.DataProxy DataProxy}</tt> and should not generally need to be used directly.
134          * Each action of a DataProxy api can be initially defined as either a String or an Object.  When specified as an object,
135          * one can explicitly define the HTTP method (GET|POST) to use for each CRUD action.  This method will prepare the supplied API, setting
136          * each action to the Object form.  If your API-actions do not explicitly define the HTTP method, the "method" configuration-parameter will
137          * be used.  If the method configuration parameter is not specified, POST will be used.
138          <pre><code>
139 new Ext.data.HttpProxy({
140     method: "POST",     // <-- default HTTP method when not specified.
141     api: {
142         create: 'create.php',
143         load: 'read.php',
144         save: 'save.php',
145         destroy: 'destroy.php'
146     }
147 });
148
149 // Alternatively, one can use the object-form to specify the API
150 new Ext.data.HttpProxy({
151     api: {
152         load: {url: 'read.php', method: 'GET'},
153         create: 'create.php',
154         destroy: 'destroy.php',
155         save: 'update.php'
156     }
157 });
158         </code></pre>
159          *
160          * @param {Ext.data.DataProxy} proxy
161          */
162         prepare : function(proxy) {
163             if (!proxy.api) {
164                 proxy.api = {}; // <-- No api?  create a blank one.
165             }
166             for (var verb in this.actions) {
167                 var action = this.actions[verb];
168                 proxy.api[action] = proxy.api[action] || proxy.url || proxy.directFn;
169                 if (typeof(proxy.api[action]) == 'string') {
170                     proxy.api[action] = {
171                         url: proxy.api[action],
172                         method: (proxy.restful === true) ? Ext.data.Api.restActions[action] : undefined
173                     };
174                 }
175             }
176         },
177
178         /**
179          * Prepares a supplied Proxy to be RESTful.  Sets the HTTP method for each api-action to be one of
180          * GET, POST, PUT, DELETE according to the defined {@link #restActions}.
181          * @param {Ext.data.DataProxy} proxy
182          */
183         restify : function(proxy) {
184             proxy.restful = true;
185             for (var verb in this.restActions) {
186                 proxy.api[this.actions[verb]].method ||
187                     (proxy.api[this.actions[verb]].method = this.restActions[verb]);
188             }
189             // TODO: perhaps move this interceptor elsewhere?  like into DataProxy, perhaps?  Placed here
190             // to satisfy initial 3.0 final release of REST features.
191             proxy.onWrite = proxy.onWrite.createInterceptor(function(action, o, response, rs) {
192                 var reader = o.reader;
193                 var res = new Ext.data.Response({
194                     action: action,
195                     raw: response
196                 });
197
198                 switch (response.status) {
199                     case 200:   // standard 200 response, send control back to HttpProxy#onWrite by returning true from this intercepted #onWrite
200                         return true;
201                         break;
202                     case 201:   // entity created but no response returned
203                         if (Ext.isEmpty(res.raw.responseText)) {
204                           res.success = true;
205                         } else {
206                           //if the response contains data, treat it like a 200
207                           return true;
208                         }
209                         break;
210                     case 204:  // no-content.  Create a fake response.
211                         res.success = true;
212                         res.data = null;
213                         break;
214                     default:
215                         return true;
216                         break;
217                 }
218                 if (res.success === true) {
219                     this.fireEvent("write", this, action, res.data, res, rs, o.request.arg);
220                 } else {
221                     this.fireEvent('exception', this, 'remote', action, o, res, rs);
222                 }
223                 o.request.callback.call(o.request.scope, res.data, res, res.success);
224
225                 return false;   // <-- false to prevent intercepted function from running.
226             }, proxy);
227         }
228     };
229 })();
230
231 /**
232  * Ext.data.Response
233  * Experimental.  Do not use directly.
234  */
235 Ext.data.Response = function(params, response) {
236     Ext.apply(this, params, {
237         raw: response
238     });
239 };
240 Ext.data.Response.prototype = {
241     message : null,
242     success : false,
243     status : null,
244     root : null,
245     raw : null,
246
247     getMessage : function() {
248         return this.message;
249     },
250     getSuccess : function() {
251         return this.success;
252     },
253     getStatus : function() {
254         return this.status;
255     },
256     getRoot : function() {
257         return this.root;
258     },
259     getRawResponse : function() {
260         return this.raw;
261     }
262 };
263
264 /**
265  * @class Ext.data.Api.Error
266  * @extends Ext.Error
267  * Error class for Ext.data.Api errors
268  */
269 Ext.data.Api.Error = Ext.extend(Ext.Error, {
270     constructor : function(message, arg) {
271         this.arg = arg;
272         Ext.Error.call(this, message);
273     },
274     name: 'Ext.data.Api'
275 });
276 Ext.apply(Ext.data.Api.Error.prototype, {
277     lang: {
278         'action-url-undefined': 'No fallback url defined for this action.  When defining a DataProxy api, please be sure to define an url for each CRUD action in Ext.data.Api.actions or define a default url in addition to your api-configuration.',
279         'invalid': 'received an invalid API-configuration.  Please ensure your proxy API-configuration contains only the actions defined in Ext.data.Api.actions',
280         'invalid-url': 'Invalid url.  Please review your proxy configuration.',
281         'execute': 'Attempted to execute an unknown action.  Valid API actions are defined in Ext.data.Api.actions"'
282     }
283 });
284
285
286
287 /**
288  * @class Ext.data.SortTypes
289  * @singleton
290  * Defines the default sorting (casting?) comparison functions used when sorting data.
291  */
292 Ext.data.SortTypes = {
293     /**
294      * Default sort that does nothing
295      * @param {Mixed} s The value being converted
296      * @return {Mixed} The comparison value
297      */
298     none : function(s){
299         return s;
300     },
301     
302     /**
303      * The regular expression used to strip tags
304      * @type {RegExp}
305      * @property
306      */
307     stripTagsRE : /<\/?[^>]+>/gi,
308     
309     /**
310      * Strips all HTML tags to sort on text only
311      * @param {Mixed} s The value being converted
312      * @return {String} The comparison value
313      */
314     asText : function(s){
315         return String(s).replace(this.stripTagsRE, "");
316     },
317     
318     /**
319      * Strips all HTML tags to sort on text only - Case insensitive
320      * @param {Mixed} s The value being converted
321      * @return {String} The comparison value
322      */
323     asUCText : function(s){
324         return String(s).toUpperCase().replace(this.stripTagsRE, "");
325     },
326     
327     /**
328      * Case insensitive string
329      * @param {Mixed} s The value being converted
330      * @return {String} The comparison value
331      */
332     asUCString : function(s) {
333         return String(s).toUpperCase();
334     },
335     
336     /**
337      * Date sorting
338      * @param {Mixed} s The value being converted
339      * @return {Number} The comparison value
340      */
341     asDate : function(s) {
342         if(!s){
343             return 0;
344         }
345         if(Ext.isDate(s)){
346             return s.getTime();
347         }
348         return Date.parse(String(s));
349     },
350     
351     /**
352      * Float sorting
353      * @param {Mixed} s The value being converted
354      * @return {Float} The comparison value
355      */
356     asFloat : function(s) {
357         var val = parseFloat(String(s).replace(/,/g, ""));
358         return isNaN(val) ? 0 : val;
359     },
360     
361     /**
362      * Integer sorting
363      * @param {Mixed} s The value being converted
364      * @return {Number} The comparison value
365      */
366     asInt : function(s) {
367         var val = parseInt(String(s).replace(/,/g, ""), 10);
368         return isNaN(val) ? 0 : val;
369     }
370 };/**
371  * @class Ext.data.Record
372  * <p>Instances of this class encapsulate both Record <em>definition</em> information, and Record
373  * <em>value</em> information for use in {@link Ext.data.Store} objects, or any code which needs
374  * to access Records cached in an {@link Ext.data.Store} object.</p>
375  * <p>Constructors for this class are generated by passing an Array of field definition objects to {@link #create}.
376  * Instances are usually only created by {@link Ext.data.Reader} implementations when processing unformatted data
377  * objects.</p>
378  * <p>Note that an instance of a Record class may only belong to one {@link Ext.data.Store Store} at a time.
379  * In order to copy data from one Store to another, use the {@link #copy} method to create an exact
380  * copy of the Record, and insert the new instance into the other Store.</p>
381  * <p>When serializing a Record for submission to the server, be aware that it contains many private
382  * properties, and also a reference to its owning Store which in turn holds references to its Records.
383  * This means that a whole Record may not be encoded using {@link Ext.util.JSON.encode}. Instead, use the
384  * <code>{@link #data}</code> and <code>{@link #id}</code> properties.</p>
385  * <p>Record objects generated by this constructor inherit all the methods of Ext.data.Record listed below.</p>
386  * @constructor
387  * <p>This constructor should not be used to create Record objects. Instead, use {@link #create} to
388  * generate a subclass of Ext.data.Record configured with information about its constituent fields.<p>
389  * <p><b>The generated constructor has the same signature as this constructor.</b></p>
390  * @param {Object} data (Optional) An object, the properties of which provide values for the new Record's
391  * fields. If not specified the <code>{@link Ext.data.Field#defaultValue defaultValue}</code>
392  * for each field will be assigned.
393  * @param {Object} id (Optional) The id of the Record. The id is used by the
394  * {@link Ext.data.Store} object which owns the Record to index its collection
395  * of Records (therefore this id should be unique within each store). If an
396  * <code>id</code> is not specified a <b><code>{@link #phantom}</code></b>
397  * Record will be created with an {@link #Record.id automatically generated id}.
398  */
399 Ext.data.Record = function(data, id){
400     // if no id, call the auto id method
401     this.id = (id || id === 0) ? id : Ext.data.Record.id(this);
402     this.data = data || {};
403 };
404
405 /**
406  * Generate a constructor for a specific Record layout.
407  * @param {Array} o An Array of <b>{@link Ext.data.Field Field}</b> definition objects.
408  * The constructor generated by this method may be used to create new Record instances. The data
409  * object must contain properties named after the {@link Ext.data.Field field}
410  * <b><tt>{@link Ext.data.Field#name}s</tt></b>.  Example usage:<pre><code>
411 // create a Record constructor from a description of the fields
412 var TopicRecord = Ext.data.Record.create([ // creates a subclass of Ext.data.Record
413     {{@link Ext.data.Field#name name}: 'title', {@link Ext.data.Field#mapping mapping}: 'topic_title'},
414     {name: 'author', mapping: 'username', allowBlank: false},
415     {name: 'totalPosts', mapping: 'topic_replies', type: 'int'},
416     {name: 'lastPost', mapping: 'post_time', type: 'date'},
417     {name: 'lastPoster', mapping: 'user2'},
418     {name: 'excerpt', mapping: 'post_text', allowBlank: false},
419     // In the simplest case, if no properties other than <tt>name</tt> are required,
420     // a field definition may consist of just a String for the field name.
421     'signature'
422 ]);
423
424 // create Record instance
425 var myNewRecord = new TopicRecord(
426     {
427         title: 'Do my job please',
428         author: 'noobie',
429         totalPosts: 1,
430         lastPost: new Date(),
431         lastPoster: 'Animal',
432         excerpt: 'No way dude!',
433         signature: ''
434     },
435     id // optionally specify the id of the record otherwise {@link #Record.id one is auto-assigned}
436 );
437 myStore.{@link Ext.data.Store#add add}(myNewRecord);
438 </code></pre>
439  * @method create
440  * @return {Function} A constructor which is used to create new Records according
441  * to the definition. The constructor has the same signature as {@link #Record}.
442  * @static
443  */
444 Ext.data.Record.create = function(o){
445     var f = Ext.extend(Ext.data.Record, {});
446     var p = f.prototype;
447     p.fields = new Ext.util.MixedCollection(false, function(field){
448         return field.name;
449     });
450     for(var i = 0, len = o.length; i < len; i++){
451         p.fields.add(new Ext.data.Field(o[i]));
452     }
453     f.getField = function(name){
454         return p.fields.get(name);
455     };
456     return f;
457 };
458
459 Ext.data.Record.PREFIX = 'ext-record';
460 Ext.data.Record.AUTO_ID = 1;
461 Ext.data.Record.EDIT = 'edit';
462 Ext.data.Record.REJECT = 'reject';
463 Ext.data.Record.COMMIT = 'commit';
464
465
466 /**
467  * Generates a sequential id. This method is typically called when a record is {@link #create}d
468  * and {@link #Record no id has been specified}. The returned id takes the form:
469  * <tt>&#123;PREFIX}-&#123;AUTO_ID}</tt>.<div class="mdetail-params"><ul>
470  * <li><b><tt>PREFIX</tt></b> : String<p class="sub-desc"><tt>Ext.data.Record.PREFIX</tt>
471  * (defaults to <tt>'ext-record'</tt>)</p></li>
472  * <li><b><tt>AUTO_ID</tt></b> : String<p class="sub-desc"><tt>Ext.data.Record.AUTO_ID</tt>
473  * (defaults to <tt>1</tt> initially)</p></li>
474  * </ul></div>
475  * @param {Record} rec The record being created.  The record does not exist, it's a {@link #phantom}.
476  * @return {String} auto-generated string id, <tt>"ext-record-i++'</tt>;
477  */
478 Ext.data.Record.id = function(rec) {
479     rec.phantom = true;
480     return [Ext.data.Record.PREFIX, '-', Ext.data.Record.AUTO_ID++].join('');
481 };
482
483 Ext.data.Record.prototype = {
484     /**
485      * <p><b>This property is stored in the Record definition's <u>prototype</u></b></p>
486      * A MixedCollection containing the defined {@link Ext.data.Field Field}s for this Record.  Read-only.
487      * @property fields
488      * @type Ext.util.MixedCollection
489      */
490     /**
491      * An object hash representing the data for this Record. Every field name in the Record definition
492      * is represented by a property of that name in this object. Note that unless you specified a field
493      * with {@link Ext.data.Field#name name} "id" in the Record definition, this will <b>not</b> contain
494      * an <tt>id</tt> property.
495      * @property data
496      * @type {Object}
497      */
498     /**
499      * The unique ID of the Record {@link #Record as specified at construction time}.
500      * @property id
501      * @type {Object}
502      */
503     /**
504      * <p><b>Only present if this Record was created by an {@link Ext.data.XmlReader XmlReader}</b>.</p>
505      * <p>The XML element which was the source of the data for this Record.</p>
506      * @property node
507      * @type {XMLElement}
508      */
509     /**
510      * <p><b>Only present if this Record was created by an {@link Ext.data.ArrayReader ArrayReader} or a {@link Ext.data.JsonReader JsonReader}</b>.</p>
511      * <p>The Array or object which was the source of the data for this Record.</p>
512      * @property json
513      * @type {Array|Object}
514      */
515     /**
516      * Readonly flag - true if this Record has been modified.
517      * @type Boolean
518      */
519     dirty : false,
520     editing : false,
521     error : null,
522     /**
523      * This object contains a key and value storing the original values of all modified
524      * fields or is null if no fields have been modified.
525      * @property modified
526      * @type {Object}
527      */
528     modified : null,
529     /**
530      * <tt>true</tt> when the record does not yet exist in a server-side database (see
531      * {@link #markDirty}).  Any record which has a real database pk set as its id property
532      * is NOT a phantom -- it's real.
533      * @property phantom
534      * @type {Boolean}
535      */
536     phantom : false,
537
538     // private
539     join : function(store){
540         /**
541          * The {@link Ext.data.Store} to which this Record belongs.
542          * @property store
543          * @type {Ext.data.Store}
544          */
545         this.store = store;
546     },
547
548     /**
549      * Set the {@link Ext.data.Field#name named field} to the specified value.  For example:
550      * <pre><code>
551 // record has a field named 'firstname'
552 var Employee = Ext.data.Record.{@link #create}([
553     {name: 'firstname'},
554     ...
555 ]);
556
557 // update the 2nd record in the store:
558 var rec = myStore.{@link Ext.data.Store#getAt getAt}(1);
559
560 // set the value (shows dirty flag):
561 rec.set('firstname', 'Betty');
562
563 // commit the change (removes dirty flag):
564 rec.{@link #commit}();
565
566 // update the record in the store, bypass setting dirty flag,
567 // and do not store the change in the {@link Ext.data.Store#getModifiedRecords modified records}
568 rec.{@link #data}['firstname'] = 'Wilma'; // updates record, but not the view
569 rec.{@link #commit}(); // updates the view
570      * </code></pre>
571      * <b>Notes</b>:<div class="mdetail-params"><ul>
572      * <li>If the store has a writer and <code>autoSave=true</code>, each set()
573      * will execute an XHR to the server.</li>
574      * <li>Use <code>{@link #beginEdit}</code> to prevent the store's <code>update</code>
575      * event firing while using set().</li>
576      * <li>Use <code>{@link #endEdit}</code> to have the store's <code>update</code>
577      * event fire.</li>
578      * </ul></div>
579      * @param {String} name The {@link Ext.data.Field#name name of the field} to set.
580      * @param {String/Object/Array} value The value to set the field to.
581      */
582     set : function(name, value){
583         var encode = Ext.isPrimitive(value) ? String : Ext.encode;
584         if(encode(this.data[name]) == encode(value)) {
585             return;
586         }        
587         this.dirty = true;
588         if(!this.modified){
589             this.modified = {};
590         }
591         if(this.modified[name] === undefined){
592             this.modified[name] = this.data[name];
593         }
594         this.data[name] = value;
595         if(!this.editing){
596             this.afterEdit();
597         }
598     },
599
600     // private
601     afterEdit : function(){
602         if (this.store != undefined && typeof this.store.afterEdit == "function") {
603             this.store.afterEdit(this);
604         }
605     },
606
607     // private
608     afterReject : function(){
609         if(this.store){
610             this.store.afterReject(this);
611         }
612     },
613
614     // private
615     afterCommit : function(){
616         if(this.store){
617             this.store.afterCommit(this);
618         }
619     },
620
621     /**
622      * Get the value of the {@link Ext.data.Field#name named field}.
623      * @param {String} name The {@link Ext.data.Field#name name of the field} to get the value of.
624      * @return {Object} The value of the field.
625      */
626     get : function(name){
627         return this.data[name];
628     },
629
630     /**
631      * Begin an edit. While in edit mode, no events (e.g.. the <code>update</code> event)
632      * are relayed to the containing store.
633      * See also: <code>{@link #endEdit}</code> and <code>{@link #cancelEdit}</code>.
634      */
635     beginEdit : function(){
636         this.editing = true;
637         this.modified = this.modified || {};
638     },
639
640     /**
641      * Cancels all changes made in the current edit operation.
642      */
643     cancelEdit : function(){
644         this.editing = false;
645         delete this.modified;
646     },
647
648     /**
649      * End an edit. If any data was modified, the containing store is notified
650      * (ie, the store's <code>update</code> event will fire).
651      */
652     endEdit : function(){
653         this.editing = false;
654         if(this.dirty){
655             this.afterEdit();
656         }
657     },
658
659     /**
660      * Usually called by the {@link Ext.data.Store} which owns the Record.
661      * Rejects all changes made to the Record since either creation, or the last commit operation.
662      * Modified fields are reverted to their original values.
663      * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
664      * to have their code notified of reject operations.</p>
665      * @param {Boolean} silent (optional) True to skip notification of the owning
666      * store of the change (defaults to false)
667      */
668     reject : function(silent){
669         var m = this.modified;
670         for(var n in m){
671             if(typeof m[n] != "function"){
672                 this.data[n] = m[n];
673             }
674         }
675         this.dirty = false;
676         delete this.modified;
677         this.editing = false;
678         if(silent !== true){
679             this.afterReject();
680         }
681     },
682
683     /**
684      * Usually called by the {@link Ext.data.Store} which owns the Record.
685      * Commits all changes made to the Record since either creation, or the last commit operation.
686      * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
687      * to have their code notified of commit operations.</p>
688      * @param {Boolean} silent (optional) True to skip notification of the owning
689      * store of the change (defaults to false)
690      */
691     commit : function(silent){
692         this.dirty = false;
693         delete this.modified;
694         this.editing = false;
695         if(silent !== true){
696             this.afterCommit();
697         }
698     },
699
700     /**
701      * Gets a hash of only the fields that have been modified since this Record was created or commited.
702      * @return Object
703      */
704     getChanges : function(){
705         var m = this.modified, cs = {};
706         for(var n in m){
707             if(m.hasOwnProperty(n)){
708                 cs[n] = this.data[n];
709             }
710         }
711         return cs;
712     },
713
714     // private
715     hasError : function(){
716         return this.error !== null;
717     },
718
719     // private
720     clearError : function(){
721         this.error = null;
722     },
723
724     /**
725      * Creates a copy (clone) of this Record.
726      * @param {String} id (optional) A new Record id, defaults to the id
727      * of the record being copied. See <code>{@link #id}</code>. 
728      * To generate a phantom record with a new id use:<pre><code>
729 var rec = record.copy(); // clone the record
730 Ext.data.Record.id(rec); // automatically generate a unique sequential id
731      * </code></pre>
732      * @return {Record}
733      */
734     copy : function(newId) {
735         return new this.constructor(Ext.apply({}, this.data), newId || this.id);
736     },
737
738     /**
739      * Returns <tt>true</tt> if the passed field name has been <code>{@link #modified}</code>
740      * since the load or last commit.
741      * @param {String} fieldName {@link Ext.data.Field.{@link Ext.data.Field#name}
742      * @return {Boolean}
743      */
744     isModified : function(fieldName){
745         return !!(this.modified && this.modified.hasOwnProperty(fieldName));
746     },
747
748     /**
749      * By default returns <tt>false</tt> if any {@link Ext.data.Field field} within the
750      * record configured with <tt>{@link Ext.data.Field#allowBlank} = false</tt> returns
751      * <tt>true</tt> from an {@link Ext}.{@link Ext#isEmpty isempty} test.
752      * @return {Boolean}
753      */
754     isValid : function() {
755         return this.fields.find(function(f) {
756             return (f.allowBlank === false && Ext.isEmpty(this.data[f.name])) ? true : false;
757         },this) ? false : true;
758     },
759
760     /**
761      * <p>Marks this <b>Record</b> as <code>{@link #dirty}</code>.  This method
762      * is used interally when adding <code>{@link #phantom}</code> records to a
763      * {@link Ext.data.Store#writer writer enabled store}.</p>
764      * <br><p>Marking a record <code>{@link #dirty}</code> causes the phantom to
765      * be returned by {@link Ext.data.Store#getModifiedRecords} where it will
766      * have a create action composed for it during {@link Ext.data.Store#save store save}
767      * operations.</p>
768      */
769     markDirty : function(){
770         this.dirty = true;
771         if(!this.modified){
772             this.modified = {};
773         }
774         this.fields.each(function(f) {
775             this.modified[f.name] = this.data[f.name];
776         },this);
777     }
778 };
779 /**
780  * @class Ext.StoreMgr
781  * @extends Ext.util.MixedCollection
782  * The default global group of stores.
783  * @singleton
784  */
785 Ext.StoreMgr = Ext.apply(new Ext.util.MixedCollection(), {
786     /**
787      * @cfg {Object} listeners @hide
788      */
789
790     /**
791      * Registers one or more Stores with the StoreMgr. You do not normally need to register stores
792      * manually.  Any store initialized with a {@link Ext.data.Store#storeId} will be auto-registered. 
793      * @param {Ext.data.Store} store1 A Store instance
794      * @param {Ext.data.Store} store2 (optional)
795      * @param {Ext.data.Store} etc... (optional)
796      */
797     register : function(){
798         for(var i = 0, s; (s = arguments[i]); i++){
799             this.add(s);
800         }
801     },
802
803     /**
804      * Unregisters one or more Stores with the StoreMgr
805      * @param {String/Object} id1 The id of the Store, or a Store instance
806      * @param {String/Object} id2 (optional)
807      * @param {String/Object} etc... (optional)
808      */
809     unregister : function(){
810         for(var i = 0, s; (s = arguments[i]); i++){
811             this.remove(this.lookup(s));
812         }
813     },
814
815     /**
816      * Gets a registered Store by id
817      * @param {String/Object} id The id of the Store, or a Store instance
818      * @return {Ext.data.Store}
819      */
820     lookup : function(id){
821         if(Ext.isArray(id)){
822             var fields = ['field1'], expand = !Ext.isArray(id[0]);
823             if(!expand){
824                 for(var i = 2, len = id[0].length; i <= len; ++i){
825                     fields.push('field' + i);
826                 }
827             }
828             return new Ext.data.ArrayStore({
829                 fields: fields,
830                 data: id,
831                 expandData: expand,
832                 autoDestroy: true,
833                 autoCreated: true
834
835             });
836         }
837         return Ext.isObject(id) ? (id.events ? id : Ext.create(id, 'store')) : this.get(id);
838     },
839
840     // getKey implementation for MixedCollection
841     getKey : function(o){
842          return o.storeId;
843     }
844 });/**
845  * @class Ext.data.Store
846  * @extends Ext.util.Observable
847  * <p>The Store class encapsulates a client side cache of {@link Ext.data.Record Record}
848  * objects which provide input data for Components such as the {@link Ext.grid.GridPanel GridPanel},
849  * the {@link Ext.form.ComboBox ComboBox}, or the {@link Ext.DataView DataView}.</p>
850  * <p><u>Retrieving Data</u></p>
851  * <p>A Store object may access a data object using:<div class="mdetail-params"><ul>
852  * <li>{@link #proxy configured implementation} of {@link Ext.data.DataProxy DataProxy}</li>
853  * <li>{@link #data} to automatically pass in data</li>
854  * <li>{@link #loadData} to manually pass in data</li>
855  * </ul></div></p>
856  * <p><u>Reading Data</u></p>
857  * <p>A Store object has no inherent knowledge of the format of the data object (it could be
858  * an Array, XML, or JSON). A Store object uses an appropriate {@link #reader configured implementation}
859  * of a {@link Ext.data.DataReader DataReader} to create {@link Ext.data.Record Record} instances from the data
860  * object.</p>
861  * <p><u>Store Types</u></p>
862  * <p>There are several implementations of Store available which are customized for use with
863  * a specific DataReader implementation.  Here is an example using an ArrayStore which implicitly
864  * creates a reader commensurate to an Array data object.</p>
865  * <pre><code>
866 var myStore = new Ext.data.ArrayStore({
867     fields: ['fullname', 'first'],
868     idIndex: 0 // id for each record will be the first element
869 });
870  * </code></pre>
871  * <p>For custom implementations create a basic {@link Ext.data.Store} configured as needed:</p>
872  * <pre><code>
873 // create a {@link Ext.data.Record Record} constructor:
874 var rt = Ext.data.Record.create([
875     {name: 'fullname'},
876     {name: 'first'}
877 ]);
878 var myStore = new Ext.data.Store({
879     // explicitly create reader
880     reader: new Ext.data.ArrayReader(
881         {
882             idIndex: 0  // id for each record will be the first element
883         },
884         rt // recordType
885     )
886 });
887  * </code></pre>
888  * <p>Load some data into store (note the data object is an array which corresponds to the reader):</p>
889  * <pre><code>
890 var myData = [
891     [1, 'Fred Flintstone', 'Fred'],  // note that id for the record is the first element
892     [2, 'Barney Rubble', 'Barney']
893 ];
894 myStore.loadData(myData);
895  * </code></pre>
896  * <p>Records are cached and made available through accessor functions.  An example of adding
897  * a record to the store:</p>
898  * <pre><code>
899 var defaultData = {
900     fullname: 'Full Name',
901     first: 'First Name'
902 };
903 var recId = 100; // provide unique id for the record
904 var r = new myStore.recordType(defaultData, ++recId); // create new record
905 myStore.{@link #insert}(0, r); // insert a new record into the store (also see {@link #add})
906  * </code></pre>
907  * <p><u>Writing Data</u></p>
908  * <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>
909  * along with <a href="http://extjs.com/deploy/dev/examples/restful/restful.html">RESTful features.</a>
910  * @constructor
911  * Creates a new Store.
912  * @param {Object} config A config object containing the objects needed for the Store to access data,
913  * and read the data into Records.
914  * @xtype store
915  */
916 Ext.data.Store = Ext.extend(Ext.util.Observable, {
917     /**
918      * @cfg {String} storeId If passed, the id to use to register with the <b>{@link Ext.StoreMgr StoreMgr}</b>.
919      * <p><b>Note</b>: if a (deprecated) <tt>{@link #id}</tt> is specified it will supersede the <tt>storeId</tt>
920      * assignment.</p>
921      */
922     /**
923      * @cfg {String} url If a <tt>{@link #proxy}</tt> is not specified the <tt>url</tt> will be used to
924      * implicitly configure a {@link Ext.data.HttpProxy HttpProxy} if an <tt>url</tt> is specified.
925      * Typically this option, or the <code>{@link #data}</code> option will be specified.
926      */
927     /**
928      * @cfg {Boolean/Object} autoLoad If <tt>{@link #data}</tt> is not specified, and if <tt>autoLoad</tt>
929      * is <tt>true</tt> or an <tt>Object</tt>, this store's {@link #load} method is automatically called
930      * after creation. If the value of <tt>autoLoad</tt> is an <tt>Object</tt>, this <tt>Object</tt> will
931      * be passed to the store's {@link #load} method.
932      */
933     /**
934      * @cfg {Ext.data.DataProxy} proxy The {@link Ext.data.DataProxy DataProxy} object which provides
935      * access to a data object.  See <code>{@link #url}</code>.
936      */
937     /**
938      * @cfg {Array} data An inline data object readable by the <code>{@link #reader}</code>.
939      * Typically this option, or the <code>{@link #url}</code> option will be specified.
940      */
941     /**
942      * @cfg {Ext.data.DataReader} reader The {@link Ext.data.DataReader Reader} object which processes the
943      * data object and returns an Array of {@link Ext.data.Record} objects which are cached keyed by their
944      * <b><tt>{@link Ext.data.Record#id id}</tt></b> property.
945      */
946     /**
947      * @cfg {Ext.data.DataWriter} writer
948      * <p>The {@link Ext.data.DataWriter Writer} object which processes a record object for being written
949      * to the server-side database.</p>
950      * <br><p>When a writer is installed into a Store the {@link #add}, {@link #remove}, and {@link #update}
951      * events on the store are monitored in order to remotely {@link #createRecords create records},
952      * {@link #destroyRecord destroy records}, or {@link #updateRecord update records}.</p>
953      * <br><p>The proxy for this store will relay any {@link #writexception} events to this store.</p>
954      * <br><p>Sample implementation:
955      * <pre><code>
956 var writer = new {@link Ext.data.JsonWriter}({
957     encode: true,
958     writeAllFields: true // write all fields, not just those that changed
959 });
960
961 // Typical Store collecting the Proxy, Reader and Writer together.
962 var store = new Ext.data.Store({
963     storeId: 'user',
964     root: 'records',
965     proxy: proxy,
966     reader: reader,
967     writer: writer,     // <-- plug a DataWriter into the store just as you would a Reader
968     paramsAsHash: true,
969     autoSave: false    // <-- false to delay executing create, update, destroy requests
970                         //     until specifically told to do so.
971 });
972      * </code></pre></p>
973      */
974     writer : undefined,
975     /**
976      * @cfg {Object} baseParams
977      * <p>An object containing properties which are to be sent as parameters
978      * for <i>every</i> HTTP request.</p>
979      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p>
980      * <p><b>Note</b>: <code>baseParams</code> may be superseded by any <code>params</code>
981      * specified in a <code>{@link #load}</code> request, see <code>{@link #load}</code>
982      * for more details.</p>
983      * This property may be modified after creation using the <code>{@link #setBaseParam}</code>
984      * method.
985      * @property
986      */
987     /**
988      * @cfg {Object} sortInfo A config object to specify the sort order in the request of a Store's
989      * {@link #load} operation.  Note that for local sorting, the <tt>direction</tt> property is
990      * case-sensitive. See also {@link #remoteSort} and {@link #paramNames}.
991      * For example:<pre><code>
992 sortInfo: {
993     field: 'fieldName',
994     direction: 'ASC' // or 'DESC' (case sensitive for local sorting)
995 }
996 </code></pre>
997      */
998     /**
999      * @cfg {boolean} remoteSort <tt>true</tt> if sorting is to be handled by requesting the <tt>{@link #proxy Proxy}</tt>
1000      * to provide a refreshed version of the data object in sorted order, as opposed to sorting the Record cache
1001      * in place (defaults to <tt>false</tt>).
1002      * <p>If <tt>remoteSort</tt> is <tt>true</tt>, then clicking on a {@link Ext.grid.Column Grid Column}'s
1003      * {@link Ext.grid.Column#header header} causes the current page to be requested from the server appending
1004      * the following two parameters to the <b><tt>{@link #load params}</tt></b>:<div class="mdetail-params"><ul>
1005      * <li><b><tt>sort</tt></b> : String<p class="sub-desc">The <tt>name</tt> (as specified in the Record's
1006      * {@link Ext.data.Field Field definition}) of the field to sort on.</p></li>
1007      * <li><b><tt>dir</tt></b> : String<p class="sub-desc">The direction of the sort, 'ASC' or 'DESC' (case-sensitive).</p></li>
1008      * </ul></div></p>
1009      */
1010     remoteSort : false,
1011
1012     /**
1013      * @cfg {Boolean} autoDestroy <tt>true</tt> to destroy the store when the component the store is bound
1014      * to is destroyed (defaults to <tt>false</tt>).
1015      * <p><b>Note</b>: this should be set to true when using stores that are bound to only 1 component.</p>
1016      */
1017     autoDestroy : false,
1018
1019     /**
1020      * @cfg {Boolean} pruneModifiedRecords <tt>true</tt> to clear all modified record information each time
1021      * the store is loaded or when a record is removed (defaults to <tt>false</tt>). See {@link #getModifiedRecords}
1022      * for the accessor method to retrieve the modified records.
1023      */
1024     pruneModifiedRecords : false,
1025
1026     /**
1027      * Contains the last options object used as the parameter to the {@link #load} method. See {@link #load}
1028      * for the details of what this may contain. This may be useful for accessing any params which were used
1029      * to load the current Record cache.
1030      * @property
1031      */
1032     lastOptions : null,
1033
1034     /**
1035      * @cfg {Boolean} autoSave
1036      * <p>Defaults to <tt>true</tt> causing the store to automatically {@link #save} records to
1037      * the server when a record is modified (ie: becomes 'dirty'). Specify <tt>false</tt> to manually call {@link #save}
1038      * to send all modifiedRecords to the server.</p>
1039      * <br><p><b>Note</b>: each CRUD action will be sent as a separate request.</p>
1040      */
1041     autoSave : true,
1042
1043     /**
1044      * @cfg {Boolean} batch
1045      * <p>Defaults to <tt>true</tt> (unless <code>{@link #restful}:true</code>). Multiple
1046      * requests for each CRUD action (CREATE, READ, UPDATE and DESTROY) will be combined
1047      * and sent as one transaction. Only applies when <code>{@link #autoSave}</code> is set
1048      * to <tt>false</tt>.</p>
1049      * <br><p>If Store is RESTful, the DataProxy is also RESTful, and a unique transaction is
1050      * generated for each record.</p>
1051      */
1052     batch : true,
1053
1054     /**
1055      * @cfg {Boolean} restful
1056      * Defaults to <tt>false</tt>.  Set to <tt>true</tt> to have the Store and the set
1057      * Proxy operate in a RESTful manner. The store will automatically generate GET, POST,
1058      * PUT and DELETE requests to the server. The HTTP method used for any given CRUD
1059      * action is described in {@link Ext.data.Api#restActions}.  For additional information
1060      * see {@link Ext.data.DataProxy#restful}.
1061      * <p><b>Note</b>: if <code>{@link #restful}:true</code> <code>batch</code> will
1062      * internally be set to <tt>false</tt>.</p>
1063      */
1064     restful: false,
1065
1066     /**
1067      * @cfg {Object} paramNames
1068      * <p>An object containing properties which specify the names of the paging and
1069      * sorting parameters passed to remote servers when loading blocks of data. By default, this
1070      * object takes the following form:</p><pre><code>
1071 {
1072     start : 'start',  // The parameter name which specifies the start row
1073     limit : 'limit',  // The parameter name which specifies number of rows to return
1074     sort : 'sort',    // The parameter name which specifies the column to sort on
1075     dir : 'dir'       // The parameter name which specifies the sort direction
1076 }
1077 </code></pre>
1078      * <p>The server must produce the requested data block upon receipt of these parameter names.
1079      * If different parameter names are required, this property can be overriden using a configuration
1080      * property.</p>
1081      * <p>A {@link Ext.PagingToolbar PagingToolbar} bound to this Store uses this property to determine
1082      * the parameter names to use in its {@link #load requests}.
1083      */
1084     paramNames : undefined,
1085
1086     /**
1087      * @cfg {Object} defaultParamNames
1088      * Provides the default values for the {@link #paramNames} property. To globally modify the parameters
1089      * for all stores, this object should be changed on the store prototype.
1090      */
1091     defaultParamNames : {
1092         start : 'start',
1093         limit : 'limit',
1094         sort : 'sort',
1095         dir : 'dir'
1096     },
1097
1098     isDestroyed: false,    
1099     hasMultiSort: false,
1100
1101     // private
1102     batchKey : '_ext_batch_',
1103
1104     constructor : function(config){
1105         /**
1106          * @property multiSort
1107          * @type Boolean
1108          * True if this store is currently sorted by more than one field/direction combination.
1109          */
1110         
1111         /**
1112          * @property isDestroyed
1113          * @type Boolean
1114          * True if the store has been destroyed already. Read only
1115          */
1116         
1117         this.data = new Ext.util.MixedCollection(false);
1118         this.data.getKey = function(o){
1119             return o.id;
1120         };
1121         
1122
1123         // temporary removed-records cache
1124         this.removed = [];
1125
1126         if(config && config.data){
1127             this.inlineData = config.data;
1128             delete config.data;
1129         }
1130
1131         Ext.apply(this, config);
1132
1133         /**
1134          * See the <code>{@link #baseParams corresponding configuration option}</code>
1135          * for a description of this property.
1136          * To modify this property see <code>{@link #setBaseParam}</code>.
1137          * @property
1138          */
1139         this.baseParams = Ext.isObject(this.baseParams) ? this.baseParams : {};
1140
1141         this.paramNames = Ext.applyIf(this.paramNames || {}, this.defaultParamNames);
1142
1143         if((this.url || this.api) && !this.proxy){
1144             this.proxy = new Ext.data.HttpProxy({url: this.url, api: this.api});
1145         }
1146         // If Store is RESTful, so too is the DataProxy
1147         if (this.restful === true && this.proxy) {
1148             // When operating RESTfully, a unique transaction is generated for each record.
1149             // TODO might want to allow implemention of faux REST where batch is possible using RESTful routes only.
1150             this.batch = false;
1151             Ext.data.Api.restify(this.proxy);
1152         }
1153
1154         if(this.reader){ // reader passed
1155             if(!this.recordType){
1156                 this.recordType = this.reader.recordType;
1157             }
1158             if(this.reader.onMetaChange){
1159                 this.reader.onMetaChange = this.reader.onMetaChange.createSequence(this.onMetaChange, this);
1160             }
1161             if (this.writer) { // writer passed
1162                 if (this.writer instanceof(Ext.data.DataWriter) === false) {    // <-- config-object instead of instance.
1163                     this.writer = this.buildWriter(this.writer);
1164                 }
1165                 this.writer.meta = this.reader.meta;
1166                 this.pruneModifiedRecords = true;
1167             }
1168         }
1169
1170         /**
1171          * The {@link Ext.data.Record Record} constructor as supplied to (or created by) the
1172          * {@link Ext.data.DataReader Reader}. Read-only.
1173          * <p>If the Reader was constructed by passing in an Array of {@link Ext.data.Field} definition objects,
1174          * instead of a Record constructor, it will implicitly create a Record constructor from that Array (see
1175          * {@link Ext.data.Record}.{@link Ext.data.Record#create create} for additional details).</p>
1176          * <p>This property may be used to create new Records of the type held in this Store, for example:</p><pre><code>
1177     // create the data store
1178     var store = new Ext.data.ArrayStore({
1179         autoDestroy: true,
1180         fields: [
1181            {name: 'company'},
1182            {name: 'price', type: 'float'},
1183            {name: 'change', type: 'float'},
1184            {name: 'pctChange', type: 'float'},
1185            {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
1186         ]
1187     });
1188     store.loadData(myData);
1189
1190     // create the Grid
1191     var grid = new Ext.grid.EditorGridPanel({
1192         store: store,
1193         colModel: new Ext.grid.ColumnModel({
1194             columns: [
1195                 {id:'company', header: 'Company', width: 160, dataIndex: 'company'},
1196                 {header: 'Price', renderer: 'usMoney', dataIndex: 'price'},
1197                 {header: 'Change', renderer: change, dataIndex: 'change'},
1198                 {header: '% Change', renderer: pctChange, dataIndex: 'pctChange'},
1199                 {header: 'Last Updated', width: 85,
1200                     renderer: Ext.util.Format.dateRenderer('m/d/Y'),
1201                     dataIndex: 'lastChange'}
1202             ],
1203             defaults: {
1204                 sortable: true,
1205                 width: 75
1206             }
1207         }),
1208         autoExpandColumn: 'company', // match the id specified in the column model
1209         height:350,
1210         width:600,
1211         title:'Array Grid',
1212         tbar: [{
1213             text: 'Add Record',
1214             handler : function(){
1215                 var defaultData = {
1216                     change: 0,
1217                     company: 'New Company',
1218                     lastChange: (new Date()).clearTime(),
1219                     pctChange: 0,
1220                     price: 10
1221                 };
1222                 var recId = 3; // provide unique id
1223                 var p = new store.recordType(defaultData, recId); // create new record
1224                 grid.stopEditing();
1225                 store.{@link #insert}(0, p); // insert a new record into the store (also see {@link #add})
1226                 grid.startEditing(0, 0);
1227             }
1228         }]
1229     });
1230          * </code></pre>
1231          * @property recordType
1232          * @type Function
1233          */
1234
1235         if(this.recordType){
1236             /**
1237              * A {@link Ext.util.MixedCollection MixedCollection} containing the defined {@link Ext.data.Field Field}s
1238              * for the {@link Ext.data.Record Records} stored in this Store. Read-only.
1239              * @property fields
1240              * @type Ext.util.MixedCollection
1241              */
1242             this.fields = this.recordType.prototype.fields;
1243         }
1244         this.modified = [];
1245
1246         this.addEvents(
1247             /**
1248              * @event datachanged
1249              * Fires when the data cache has changed in a bulk manner (e.g., it has been sorted, filtered, etc.) and a
1250              * widget that is using this Store as a Record cache should refresh its view.
1251              * @param {Store} this
1252              */
1253             'datachanged',
1254             /**
1255              * @event metachange
1256              * Fires when this store's reader provides new metadata (fields). This is currently only supported for JsonReaders.
1257              * @param {Store} this
1258              * @param {Object} meta The JSON metadata
1259              */
1260             'metachange',
1261             /**
1262              * @event add
1263              * Fires when Records have been {@link #add}ed to the Store
1264              * @param {Store} this
1265              * @param {Ext.data.Record[]} records The array of Records added
1266              * @param {Number} index The index at which the record(s) were added
1267              */
1268             'add',
1269             /**
1270              * @event remove
1271              * Fires when a Record has been {@link #remove}d from the Store
1272              * @param {Store} this
1273              * @param {Ext.data.Record} record The Record that was removed
1274              * @param {Number} index The index at which the record was removed
1275              */
1276             'remove',
1277             /**
1278              * @event update
1279              * Fires when a Record has been updated
1280              * @param {Store} this
1281              * @param {Ext.data.Record} record The Record that was updated
1282              * @param {String} operation The update operation being performed.  Value may be one of:
1283              * <pre><code>
1284      Ext.data.Record.EDIT
1285      Ext.data.Record.REJECT
1286      Ext.data.Record.COMMIT
1287              * </code></pre>
1288              */
1289             'update',
1290             /**
1291              * @event clear
1292              * Fires when the data cache has been cleared.
1293              * @param {Store} this
1294              * @param {Record[]} records The records that were cleared.
1295              */
1296             'clear',
1297             /**
1298              * @event exception
1299              * <p>Fires if an exception occurs in the Proxy during a remote request.
1300              * This event is relayed through the corresponding {@link Ext.data.DataProxy}.
1301              * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
1302              * for additional details.
1303              * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
1304              * for description.
1305              */
1306             'exception',
1307             /**
1308              * @event beforeload
1309              * Fires before a request is made for a new data object.  If the beforeload handler returns
1310              * <tt>false</tt> the {@link #load} action will be canceled.
1311              * @param {Store} this
1312              * @param {Object} options The loading options that were specified (see {@link #load} for details)
1313              */
1314             'beforeload',
1315             /**
1316              * @event load
1317              * Fires after a new set of Records has been loaded.
1318              * @param {Store} this
1319              * @param {Ext.data.Record[]} records The Records that were loaded
1320              * @param {Object} options The loading options that were specified (see {@link #load} for details)
1321              */
1322             'load',
1323             /**
1324              * @event loadexception
1325              * <p>This event is <b>deprecated</b> in favor of the catch-all <b><code>{@link #exception}</code></b>
1326              * event instead.</p>
1327              * <p>This event is relayed through the corresponding {@link Ext.data.DataProxy}.
1328              * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception}
1329              * for additional details.
1330              * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception}
1331              * for description.
1332              */
1333             'loadexception',
1334             /**
1335              * @event beforewrite
1336              * @param {Ext.data.Store} store
1337              * @param {String} action [Ext.data.Api.actions.create|update|destroy]
1338              * @param {Record/Record[]} rs The Record(s) being written.
1339              * @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)
1340              * @param {Object} arg The callback's arg object passed to the {@link #request} function
1341              */
1342             'beforewrite',
1343             /**
1344              * @event write
1345              * Fires if the server returns 200 after an Ext.data.Api.actions CRUD action.
1346              * Success of the action is determined in the <code>result['successProperty']</code>property (<b>NOTE</b> for RESTful stores,
1347              * a simple 20x response is sufficient for the actions "destroy" and "update".  The "create" action should should return 200 along with a database pk).
1348              * @param {Ext.data.Store} store
1349              * @param {String} action [Ext.data.Api.actions.create|update|destroy]
1350              * @param {Object} result The 'data' picked-out out of the response for convenience.
1351              * @param {Ext.Direct.Transaction} res
1352              * @param {Record/Record[]} rs Store's records, the subject(s) of the write-action
1353              */
1354             'write',
1355             /**
1356              * @event beforesave
1357              * Fires before a save action is called. A save encompasses destroying records, updating records and creating records.
1358              * @param {Ext.data.Store} store
1359              * @param {Object} data An object containing the data that is to be saved. The object will contain a key for each appropriate action,
1360              * with an array of records for each action.
1361              */
1362             'beforesave',
1363             /**
1364              * @event save
1365              * Fires after a save is completed. A save encompasses destroying records, updating records and creating records.
1366              * @param {Ext.data.Store} store
1367              * @param {Number} batch The identifier for the batch that was saved.
1368              * @param {Object} data An object containing the data that is to be saved. The object will contain a key for each appropriate action,
1369              * with an array of records for each action.
1370              */
1371             'save'
1372
1373         );
1374
1375         if(this.proxy){
1376             // TODO remove deprecated loadexception with ext-3.0.1
1377             this.relayEvents(this.proxy,  ['loadexception', 'exception']);
1378         }
1379         // With a writer set for the Store, we want to listen to add/remove events to remotely create/destroy records.
1380         if (this.writer) {
1381             this.on({
1382                 scope: this,
1383                 add: this.createRecords,
1384                 remove: this.destroyRecord,
1385                 update: this.updateRecord,
1386                 clear: this.onClear
1387             });
1388         }
1389
1390         this.sortToggle = {};
1391         if(this.sortField){
1392             this.setDefaultSort(this.sortField, this.sortDir);
1393         }else if(this.sortInfo){
1394             this.setDefaultSort(this.sortInfo.field, this.sortInfo.direction);
1395         }
1396
1397         Ext.data.Store.superclass.constructor.call(this);
1398
1399         if(this.id){
1400             this.storeId = this.id;
1401             delete this.id;
1402         }
1403         if(this.storeId){
1404             Ext.StoreMgr.register(this);
1405         }
1406         if(this.inlineData){
1407             this.loadData(this.inlineData);
1408             delete this.inlineData;
1409         }else if(this.autoLoad){
1410             this.load.defer(10, this, [
1411                 typeof this.autoLoad == 'object' ?
1412                     this.autoLoad : undefined]);
1413         }
1414         // used internally to uniquely identify a batch
1415         this.batchCounter = 0;
1416         this.batches = {};
1417     },
1418
1419     /**
1420      * builds a DataWriter instance when Store constructor is provided with a writer config-object instead of an instace.
1421      * @param {Object} config Writer configuration
1422      * @return {Ext.data.DataWriter}
1423      * @private
1424      */
1425     buildWriter : function(config) {
1426         var klass = undefined,
1427             type = (config.format || 'json').toLowerCase();
1428         switch (type) {
1429             case 'json':
1430                 klass = Ext.data.JsonWriter;
1431                 break;
1432             case 'xml':
1433                 klass = Ext.data.XmlWriter;
1434                 break;
1435             default:
1436                 klass = Ext.data.JsonWriter;
1437         }
1438         return new klass(config);
1439     },
1440
1441     /**
1442      * Destroys the store.
1443      */
1444     destroy : function(){
1445         if(!this.isDestroyed){
1446             if(this.storeId){
1447                 Ext.StoreMgr.unregister(this);
1448             }
1449             this.clearData();
1450             this.data = null;
1451             Ext.destroy(this.proxy);
1452             this.reader = this.writer = null;
1453             this.purgeListeners();
1454             this.isDestroyed = true;
1455         }
1456     },
1457
1458     /**
1459      * Add Records to the Store and fires the {@link #add} event.  To add Records
1460      * to the store from a remote source use <code>{@link #load}({add:true})</code>.
1461      * See also <code>{@link #recordType}</code> and <code>{@link #insert}</code>.
1462      * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects
1463      * to add to the cache. See {@link #recordType}.
1464      */
1465     add : function(records) {
1466         var i, len, record, index;
1467         
1468         records = [].concat(records);
1469         if (records.length < 1) {
1470             return;
1471         }
1472         
1473         for (i = 0, len = records.length; i < len; i++) {
1474             record = records[i];
1475             
1476             record.join(this);
1477             
1478             if (record.dirty || record.phantom) {
1479                 this.modified.push(record);
1480             }
1481         }
1482         
1483         index = this.data.length;
1484         this.data.addAll(records);
1485         
1486         if (this.snapshot) {
1487             this.snapshot.addAll(records);
1488         }
1489         
1490         this.fireEvent('add', this, records, index);
1491     },
1492
1493     /**
1494      * (Local sort only) Inserts the passed Record into the Store at the index where it
1495      * should go based on the current sort information.
1496      * @param {Ext.data.Record} record
1497      */
1498     addSorted : function(record){
1499         var index = this.findInsertIndex(record);
1500         this.insert(index, record);
1501     },
1502     
1503     /**
1504      * @private
1505      * Update a record within the store with a new reference
1506      */
1507     doUpdate : function(rec){
1508         this.data.replace(rec.id, rec);
1509         if(this.snapshot){
1510             this.snapshot.replace(rec.id, rec);
1511         }
1512         this.fireEvent('update', this, rec, Ext.data.Record.COMMIT);
1513     },
1514
1515     /**
1516      * Remove Records from the Store and fires the {@link #remove} event.
1517      * @param {Ext.data.Record/Ext.data.Record[]} record The record object or array of records to remove from the cache.
1518      */
1519     remove : function(record){
1520         if(Ext.isArray(record)){
1521             Ext.each(record, function(r){
1522                 this.remove(r);
1523             }, this);
1524             return;
1525         }
1526         var index = this.data.indexOf(record);
1527         if(index > -1){
1528             record.join(null);
1529             this.data.removeAt(index);
1530         }
1531         if(this.pruneModifiedRecords){
1532             this.modified.remove(record);
1533         }
1534         if(this.snapshot){
1535             this.snapshot.remove(record);
1536         }
1537         if(index > -1){
1538             this.fireEvent('remove', this, record, index);
1539         }
1540     },
1541
1542     /**
1543      * Remove a Record from the Store at the specified index. Fires the {@link #remove} event.
1544      * @param {Number} index The index of the record to remove.
1545      */
1546     removeAt : function(index){
1547         this.remove(this.getAt(index));
1548     },
1549
1550     /**
1551      * Remove all Records from the Store and fires the {@link #clear} event.
1552      * @param {Boolean} silent [false] Defaults to <tt>false</tt>.  Set <tt>true</tt> to not fire clear event.
1553      */
1554     removeAll : function(silent){
1555         var items = [];
1556         this.each(function(rec){
1557             items.push(rec);
1558         });
1559         this.clearData();
1560         if(this.snapshot){
1561             this.snapshot.clear();
1562         }
1563         if(this.pruneModifiedRecords){
1564             this.modified = [];
1565         }
1566         if (silent !== true) {  // <-- prevents write-actions when we just want to clear a store.
1567             this.fireEvent('clear', this, items);
1568         }
1569     },
1570
1571     // private
1572     onClear: function(store, records){
1573         Ext.each(records, function(rec, index){
1574             this.destroyRecord(this, rec, index);
1575         }, this);
1576     },
1577
1578     /**
1579      * Inserts Records into the Store at the given index and fires the {@link #add} event.
1580      * See also <code>{@link #add}</code> and <code>{@link #addSorted}</code>.
1581      * @param {Number} index The start index at which to insert the passed Records.
1582      * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects to add to the cache.
1583      */
1584     insert : function(index, records) {
1585         var i, len, record;
1586         
1587         records = [].concat(records);
1588         for (i = 0, len = records.length; i < len; i++) {
1589             record = records[i];
1590             
1591             this.data.insert(index + i, record);
1592             record.join(this);
1593             
1594             if (record.dirty || record.phantom) {
1595                 this.modified.push(record);
1596             }
1597         }
1598         
1599         if (this.snapshot) {
1600             this.snapshot.addAll(records);
1601         }
1602         
1603         this.fireEvent('add', this, records, index);
1604     },
1605
1606     /**
1607      * Get the index within the cache of the passed Record.
1608      * @param {Ext.data.Record} record The Ext.data.Record object to find.
1609      * @return {Number} The index of the passed Record. Returns -1 if not found.
1610      */
1611     indexOf : function(record){
1612         return this.data.indexOf(record);
1613     },
1614
1615     /**
1616      * Get the index within the cache of the Record with the passed id.
1617      * @param {String} id The id of the Record to find.
1618      * @return {Number} The index of the Record. Returns -1 if not found.
1619      */
1620     indexOfId : function(id){
1621         return this.data.indexOfKey(id);
1622     },
1623
1624     /**
1625      * Get the Record with the specified id.
1626      * @param {String} id The id of the Record to find.
1627      * @return {Ext.data.Record} The Record with the passed id. Returns undefined if not found.
1628      */
1629     getById : function(id){
1630         return (this.snapshot || this.data).key(id);
1631     },
1632
1633     /**
1634      * Get the Record at the specified index.
1635      * @param {Number} index The index of the Record to find.
1636      * @return {Ext.data.Record} The Record at the passed index. Returns undefined if not found.
1637      */
1638     getAt : function(index){
1639         return this.data.itemAt(index);
1640     },
1641
1642     /**
1643      * Returns a range of Records between specified indices.
1644      * @param {Number} startIndex (optional) The starting index (defaults to 0)
1645      * @param {Number} endIndex (optional) The ending index (defaults to the last Record in the Store)
1646      * @return {Ext.data.Record[]} An array of Records
1647      */
1648     getRange : function(start, end){
1649         return this.data.getRange(start, end);
1650     },
1651
1652     // private
1653     storeOptions : function(o){
1654         o = Ext.apply({}, o);
1655         delete o.callback;
1656         delete o.scope;
1657         this.lastOptions = o;
1658     },
1659
1660     // private
1661     clearData: function(){
1662         this.data.each(function(rec) {
1663             rec.join(null);
1664         });
1665         this.data.clear();
1666     },
1667
1668     /**
1669      * <p>Loads the Record cache from the configured <tt>{@link #proxy}</tt> using the configured <tt>{@link #reader}</tt>.</p>
1670      * <br><p>Notes:</p><div class="mdetail-params"><ul>
1671      * <li><b><u>Important</u></b>: loading is asynchronous! This call will return before the new data has been
1672      * loaded. To perform any post-processing where information from the load call is required, specify
1673      * the <tt>callback</tt> function to be called, or use a {@link Ext.util.Observable#listeners a 'load' event handler}.</li>
1674      * <li>If using {@link Ext.PagingToolbar remote paging}, the first load call must specify the <tt>start</tt> and <tt>limit</tt>
1675      * properties in the <code>options.params</code> property to establish the initial position within the
1676      * dataset, and the number of Records to cache on each read from the Proxy.</li>
1677      * <li>If using {@link #remoteSort remote sorting}, the configured <code>{@link #sortInfo}</code>
1678      * will be automatically included with the posted parameters according to the specified
1679      * <code>{@link #paramNames}</code>.</li>
1680      * </ul></div>
1681      * @param {Object} options An object containing properties which control loading options:<ul>
1682      * <li><b><tt>params</tt></b> :Object<div class="sub-desc"><p>An object containing properties to pass as HTTP
1683      * parameters to a remote data source. <b>Note</b>: <code>params</code> will override any
1684      * <code>{@link #baseParams}</code> of the same name.</p>
1685      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p></div></li>
1686      * <li><b>callback</b> : Function<div class="sub-desc"><p>A function to be called after the Records
1687      * have been loaded. The callback is called after the load event is fired, and is passed the following arguments:<ul>
1688      * <li>r : Ext.data.Record[] An Array of Records loaded.</li>
1689      * <li>options : Options object from the load call.</li>
1690      * <li>success : Boolean success indicator.</li></ul></p></div></li>
1691      * <li><b>scope</b> : Object<div class="sub-desc"><p>Scope with which to call the callback (defaults
1692      * to the Store object)</p></div></li>
1693      * <li><b>add</b> : Boolean<div class="sub-desc"><p>Indicator to append loaded records rather than
1694      * replace the current cache.  <b>Note</b>: see note for <tt>{@link #loadData}</tt></p></div></li>
1695      * </ul>
1696      * @return {Boolean} If the <i>developer</i> provided <tt>{@link #beforeload}</tt> event handler returns
1697      * <tt>false</tt>, the load call will abort and will return <tt>false</tt>; otherwise will return <tt>true</tt>.
1698      */
1699     load : function(options) {
1700         options = Ext.apply({}, options);
1701         this.storeOptions(options);
1702         if(this.sortInfo && this.remoteSort){
1703             var pn = this.paramNames;
1704             options.params = Ext.apply({}, options.params);
1705             options.params[pn.sort] = this.sortInfo.field;
1706             options.params[pn.dir] = this.sortInfo.direction;
1707         }
1708         try {
1709             return this.execute('read', null, options); // <-- null represents rs.  No rs for load actions.
1710         } catch(e) {
1711             this.handleException(e);
1712             return false;
1713         }
1714     },
1715
1716     /**
1717      * updateRecord  Should not be used directly.  This method will be called automatically if a Writer is set.
1718      * Listens to 'update' event.
1719      * @param {Object} store
1720      * @param {Object} record
1721      * @param {Object} action
1722      * @private
1723      */
1724     updateRecord : function(store, record, action) {
1725         if (action == Ext.data.Record.EDIT && this.autoSave === true && (!record.phantom || (record.phantom && record.isValid()))) {
1726             this.save();
1727         }
1728     },
1729
1730     /**
1731      * @private
1732      * Should not be used directly.  Store#add will call this automatically if a Writer is set
1733      * @param {Object} store
1734      * @param {Object} records
1735      * @param {Object} index
1736      */
1737     createRecords : function(store, records, index) {
1738         var modified = this.modified,
1739             length   = records.length,
1740             record, i;
1741         
1742         for (i = 0; i < length; i++) {
1743             record = records[i];
1744             
1745             if (record.phantom && record.isValid()) {
1746                 record.markDirty();  // <-- Mark new records dirty (Ed: why?)
1747                 
1748                 if (modified.indexOf(record) == -1) {
1749                     modified.push(record);
1750                 }
1751             }
1752         }
1753         if (this.autoSave === true) {
1754             this.save();
1755         }
1756     },
1757
1758     /**
1759      * Destroys a Record.  Should not be used directly.  It's called by Store#remove if a Writer is set.
1760      * @param {Store} store this
1761      * @param {Ext.data.Record} record
1762      * @param {Number} index
1763      * @private
1764      */
1765     destroyRecord : function(store, record, index) {
1766         if (this.modified.indexOf(record) != -1) {  // <-- handled already if @cfg pruneModifiedRecords == true
1767             this.modified.remove(record);
1768         }
1769         if (!record.phantom) {
1770             this.removed.push(record);
1771
1772             // since the record has already been removed from the store but the server request has not yet been executed,
1773             // must keep track of the last known index this record existed.  If a server error occurs, the record can be
1774             // put back into the store.  @see Store#createCallback where the record is returned when response status === false
1775             record.lastIndex = index;
1776
1777             if (this.autoSave === true) {
1778                 this.save();
1779             }
1780         }
1781     },
1782
1783     /**
1784      * This method should generally not be used directly.  This method is called internally
1785      * by {@link #load}, or if a Writer is set will be called automatically when {@link #add},
1786      * {@link #remove}, or {@link #update} events fire.
1787      * @param {String} action Action name ('read', 'create', 'update', or 'destroy')
1788      * @param {Record/Record[]} rs
1789      * @param {Object} options
1790      * @throws Error
1791      * @private
1792      */
1793     execute : function(action, rs, options, /* private */ batch) {
1794         // blow up if action not Ext.data.CREATE, READ, UPDATE, DESTROY
1795         if (!Ext.data.Api.isAction(action)) {
1796             throw new Ext.data.Api.Error('execute', action);
1797         }
1798         // make sure options has a fresh, new params hash
1799         options = Ext.applyIf(options||{}, {
1800             params: {}
1801         });
1802         if(batch !== undefined){
1803             this.addToBatch(batch);
1804         }
1805         // have to separate before-events since load has a different signature than create,destroy and save events since load does not
1806         // include the rs (record resultset) parameter.  Capture return values from the beforeaction into doRequest flag.
1807         var doRequest = true;
1808
1809         if (action === 'read') {
1810             doRequest = this.fireEvent('beforeload', this, options);
1811             Ext.applyIf(options.params, this.baseParams);
1812         }
1813         else {
1814             // if Writer is configured as listful, force single-record rs to be [{}] instead of {}
1815             // TODO Move listful rendering into DataWriter where the @cfg is defined.  Should be easy now.
1816             if (this.writer.listful === true && this.restful !== true) {
1817                 rs = (Ext.isArray(rs)) ? rs : [rs];
1818             }
1819             // if rs has just a single record, shift it off so that Writer writes data as '{}' rather than '[{}]'
1820             else if (Ext.isArray(rs) && rs.length == 1) {
1821                 rs = rs.shift();
1822             }
1823             // Write the action to options.params
1824             if ((doRequest = this.fireEvent('beforewrite', this, action, rs, options)) !== false) {
1825                 this.writer.apply(options.params, this.baseParams, action, rs);
1826             }
1827         }
1828         if (doRequest !== false) {
1829             // Send request to proxy.
1830             if (this.writer && this.proxy.url && !this.proxy.restful && !Ext.data.Api.hasUniqueUrl(this.proxy, action)) {
1831                 options.params.xaction = action;    // <-- really old, probaby unecessary.
1832             }
1833             // Note:  Up until this point we've been dealing with 'action' as a key from Ext.data.Api.actions.
1834             // We'll flip it now and send the value into DataProxy#request, since it's the value which maps to
1835             // the user's configured DataProxy#api
1836             // TODO Refactor all Proxies to accept an instance of Ext.data.Request (not yet defined) instead of this looooooong list
1837             // of params.  This method is an artifact from Ext2.
1838             this.proxy.request(Ext.data.Api.actions[action], rs, options.params, this.reader, this.createCallback(action, rs, batch), this, options);
1839         }
1840         return doRequest;
1841     },
1842
1843     /**
1844      * Saves all pending changes to the store.  If the commensurate Ext.data.Api.actions action is not configured, then
1845      * the configured <code>{@link #url}</code> will be used.
1846      * <pre>
1847      * change            url
1848      * ---------------   --------------------
1849      * removed records   Ext.data.Api.actions.destroy
1850      * phantom records   Ext.data.Api.actions.create
1851      * {@link #getModifiedRecords modified records}  Ext.data.Api.actions.update
1852      * </pre>
1853      * @TODO:  Create extensions of Error class and send associated Record with thrown exceptions.
1854      * e.g.:  Ext.data.DataReader.Error or Ext.data.Error or Ext.data.DataProxy.Error, etc.
1855      * @return {Number} batch Returns a number to uniquely identify the "batch" of saves occurring. -1 will be returned
1856      * if there are no items to save or the save was cancelled.
1857      */
1858     save : function() {
1859         if (!this.writer) {
1860             throw new Ext.data.Store.Error('writer-undefined');
1861         }
1862
1863         var queue = [],
1864             len,
1865             trans,
1866             batch,
1867             data = {},
1868             i;
1869         // DESTROY:  First check for removed records.  Records in this.removed are guaranteed non-phantoms.  @see Store#remove
1870         if(this.removed.length){
1871             queue.push(['destroy', this.removed]);
1872         }
1873
1874         // Check for modified records. Use a copy so Store#rejectChanges will work if server returns error.
1875         var rs = [].concat(this.getModifiedRecords());
1876         if(rs.length){
1877             // CREATE:  Next check for phantoms within rs.  splice-off and execute create.
1878             var phantoms = [];
1879             for(i = rs.length-1; i >= 0; i--){
1880                 if(rs[i].phantom === true){
1881                     var rec = rs.splice(i, 1).shift();
1882                     if(rec.isValid()){
1883                         phantoms.push(rec);
1884                     }
1885                 }else if(!rs[i].isValid()){ // <-- while we're here, splice-off any !isValid real records
1886                     rs.splice(i,1);
1887                 }
1888             }
1889             // If we have valid phantoms, create them...
1890             if(phantoms.length){
1891                 queue.push(['create', phantoms]);
1892             }
1893
1894             // UPDATE:  And finally, if we're still here after splicing-off phantoms and !isValid real records, update the rest...
1895             if(rs.length){
1896                 queue.push(['update', rs]);
1897             }
1898         }
1899         len = queue.length;
1900         if(len){
1901             batch = ++this.batchCounter;
1902             for(i = 0; i < len; ++i){
1903                 trans = queue[i];
1904                 data[trans[0]] = trans[1];
1905             }
1906             if(this.fireEvent('beforesave', this, data) !== false){
1907                 for(i = 0; i < len; ++i){
1908                     trans = queue[i];
1909                     this.doTransaction(trans[0], trans[1], batch);
1910                 }
1911                 return batch;
1912             }
1913         }
1914         return -1;
1915     },
1916
1917     // private.  Simply wraps call to Store#execute in try/catch.  Defers to Store#handleException on error.  Loops if batch: false
1918     doTransaction : function(action, rs, batch) {
1919         function transaction(records) {
1920             try{
1921                 this.execute(action, records, undefined, batch);
1922             }catch (e){
1923                 this.handleException(e);
1924             }
1925         }
1926         if(this.batch === false){
1927             for(var i = 0, len = rs.length; i < len; i++){
1928                 transaction.call(this, rs[i]);
1929             }
1930         }else{
1931             transaction.call(this, rs);
1932         }
1933     },
1934
1935     // private
1936     addToBatch : function(batch){
1937         var b = this.batches,
1938             key = this.batchKey + batch,
1939             o = b[key];
1940
1941         if(!o){
1942             b[key] = o = {
1943                 id: batch,
1944                 count: 0,
1945                 data: {}
1946             };
1947         }
1948         ++o.count;
1949     },
1950
1951     removeFromBatch : function(batch, action, data){
1952         var b = this.batches,
1953             key = this.batchKey + batch,
1954             o = b[key],
1955             arr;
1956
1957
1958         if(o){
1959             arr = o.data[action] || [];
1960             o.data[action] = arr.concat(data);
1961             if(o.count === 1){
1962                 data = o.data;
1963                 delete b[key];
1964                 this.fireEvent('save', this, batch, data);
1965             }else{
1966                 --o.count;
1967             }
1968         }
1969     },
1970
1971     // @private callback-handler for remote CRUD actions
1972     // Do not override -- override loadRecords, onCreateRecords, onDestroyRecords and onUpdateRecords instead.
1973     createCallback : function(action, rs, batch) {
1974         var actions = Ext.data.Api.actions;
1975         return (action == 'read') ? this.loadRecords : function(data, response, success) {
1976             // calls: onCreateRecords | onUpdateRecords | onDestroyRecords
1977             this['on' + Ext.util.Format.capitalize(action) + 'Records'](success, rs, [].concat(data));
1978             // If success === false here, exception will have been called in DataProxy
1979             if (success === true) {
1980                 this.fireEvent('write', this, action, data, response, rs);
1981             }
1982             this.removeFromBatch(batch, action, data);
1983         };
1984     },
1985
1986     // Clears records from modified array after an exception event.
1987     // NOTE:  records are left marked dirty.  Do we want to commit them even though they were not updated/realized?
1988     // TODO remove this method?
1989     clearModified : function(rs) {
1990         if (Ext.isArray(rs)) {
1991             for (var n=rs.length-1;n>=0;n--) {
1992                 this.modified.splice(this.modified.indexOf(rs[n]), 1);
1993             }
1994         } else {
1995             this.modified.splice(this.modified.indexOf(rs), 1);
1996         }
1997     },
1998
1999     // remap record ids in MixedCollection after records have been realized.  @see Store#onCreateRecords, @see DataReader#realize
2000     reMap : function(record) {
2001         if (Ext.isArray(record)) {
2002             for (var i = 0, len = record.length; i < len; i++) {
2003                 this.reMap(record[i]);
2004             }
2005         } else {
2006             delete this.data.map[record._phid];
2007             this.data.map[record.id] = record;
2008             var index = this.data.keys.indexOf(record._phid);
2009             this.data.keys.splice(index, 1, record.id);
2010             delete record._phid;
2011         }
2012     },
2013
2014     // @protected onCreateRecord proxy callback for create action
2015     onCreateRecords : function(success, rs, data) {
2016         if (success === true) {
2017             try {
2018                 this.reader.realize(rs, data);
2019                 this.reMap(rs);
2020             }
2021             catch (e) {
2022                 this.handleException(e);
2023                 if (Ext.isArray(rs)) {
2024                     // Recurse to run back into the try {}.  DataReader#realize splices-off the rs until empty.
2025                     this.onCreateRecords(success, rs, data);
2026                 }
2027             }
2028         }
2029     },
2030
2031     // @protected, onUpdateRecords proxy callback for update action
2032     onUpdateRecords : function(success, rs, data) {
2033         if (success === true) {
2034             try {
2035                 this.reader.update(rs, data);
2036             } catch (e) {
2037                 this.handleException(e);
2038                 if (Ext.isArray(rs)) {
2039                     // Recurse to run back into the try {}.  DataReader#update splices-off the rs until empty.
2040                     this.onUpdateRecords(success, rs, data);
2041                 }
2042             }
2043         }
2044     },
2045
2046     // @protected onDestroyRecords proxy callback for destroy action
2047     onDestroyRecords : function(success, rs, data) {
2048         // splice each rec out of this.removed
2049         rs = (rs instanceof Ext.data.Record) ? [rs] : [].concat(rs);
2050         for (var i=0,len=rs.length;i<len;i++) {
2051             this.removed.splice(this.removed.indexOf(rs[i]), 1);
2052         }
2053         if (success === false) {
2054             // put records back into store if remote destroy fails.
2055             // @TODO: Might want to let developer decide.
2056             for (i=rs.length-1;i>=0;i--) {
2057                 this.insert(rs[i].lastIndex, rs[i]);    // <-- lastIndex set in Store#destroyRecord
2058             }
2059         }
2060     },
2061
2062     // protected handleException.  Possibly temporary until Ext framework has an exception-handler.
2063     handleException : function(e) {
2064         // @see core/Error.js
2065         Ext.handleError(e);
2066     },
2067
2068     /**
2069      * <p>Reloads the Record cache from the configured Proxy using the configured
2070      * {@link Ext.data.Reader Reader} and the options from the last load operation
2071      * performed.</p>
2072      * <p><b>Note</b>: see the Important note in {@link #load}.</p>
2073      * @param {Object} options <p>(optional) An <tt>Object</tt> containing
2074      * {@link #load loading options} which may override the {@link #lastOptions options}
2075      * used in the last {@link #load} operation. See {@link #load} for details
2076      * (defaults to <tt>null</tt>, in which case the {@link #lastOptions} are
2077      * used).</p>
2078      * <br><p>To add new params to the existing params:</p><pre><code>
2079 lastOptions = myStore.lastOptions;
2080 Ext.apply(lastOptions.params, {
2081     myNewParam: true
2082 });
2083 myStore.reload(lastOptions);
2084      * </code></pre>
2085      */
2086     reload : function(options){
2087         this.load(Ext.applyIf(options||{}, this.lastOptions));
2088     },
2089
2090     // private
2091     // Called as a callback by the Reader during a load operation.
2092     loadRecords : function(o, options, success){
2093         var i, len;
2094         
2095         if (this.isDestroyed === true) {
2096             return;
2097         }
2098         if(!o || success === false){
2099             if(success !== false){
2100                 this.fireEvent('load', this, [], options);
2101             }
2102             if(options.callback){
2103                 options.callback.call(options.scope || this, [], options, false, o);
2104             }
2105             return;
2106         }
2107         var r = o.records, t = o.totalRecords || r.length;
2108         if(!options || options.add !== true){
2109             if(this.pruneModifiedRecords){
2110                 this.modified = [];
2111             }
2112             for(i = 0, len = r.length; i < len; i++){
2113                 r[i].join(this);
2114             }
2115             if(this.snapshot){
2116                 this.data = this.snapshot;
2117                 delete this.snapshot;
2118             }
2119             this.clearData();
2120             this.data.addAll(r);
2121             this.totalLength = t;
2122             this.applySort();
2123             this.fireEvent('datachanged', this);
2124         }else{
2125             var toAdd = [],
2126                 rec,
2127                 cnt = 0;
2128             for(i = 0, len = r.length; i < len; ++i){
2129                 rec = r[i];
2130                 if(this.indexOfId(rec.id) > -1){
2131                     this.doUpdate(rec);
2132                 }else{
2133                     toAdd.push(rec);
2134                     ++cnt;
2135                 }
2136             }
2137             this.totalLength = Math.max(t, this.data.length + cnt);
2138             this.add(toAdd);
2139         }
2140         this.fireEvent('load', this, r, options);
2141         if(options.callback){
2142             options.callback.call(options.scope || this, r, options, true);
2143         }
2144     },
2145
2146     /**
2147      * Loads data from a passed data block and fires the {@link #load} event. A {@link Ext.data.Reader Reader}
2148      * which understands the format of the data must have been configured in the constructor.
2149      * @param {Object} data The data block from which to read the Records.  The format of the data expected
2150      * is dependent on the type of {@link Ext.data.Reader Reader} that is configured and should correspond to
2151      * that {@link Ext.data.Reader Reader}'s <tt>{@link Ext.data.Reader#readRecords}</tt> parameter.
2152      * @param {Boolean} append (Optional) <tt>true</tt> to append the new Records rather the default to replace
2153      * the existing cache.
2154      * <b>Note</b>: that Records in a Store are keyed by their {@link Ext.data.Record#id id}, so added Records
2155      * with ids which are already present in the Store will <i>replace</i> existing Records. Only Records with
2156      * new, unique ids will be added.
2157      */
2158     loadData : function(o, append){
2159         var r = this.reader.readRecords(o);
2160         this.loadRecords(r, {add: append}, true);
2161     },
2162
2163     /**
2164      * Gets the number of cached records.
2165      * <p>If using paging, this may not be the total size of the dataset. If the data object
2166      * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
2167      * the dataset size.  <b>Note</b>: see the Important note in {@link #load}.</p>
2168      * @return {Number} The number of Records in the Store's cache.
2169      */
2170     getCount : function(){
2171         return this.data.length || 0;
2172     },
2173
2174     /**
2175      * Gets the total number of records in the dataset as returned by the server.
2176      * <p>If using paging, for this to be accurate, the data object used by the {@link #reader Reader}
2177      * must contain the dataset size. For remote data sources, the value for this property
2178      * (<tt>totalProperty</tt> for {@link Ext.data.JsonReader JsonReader},
2179      * <tt>totalRecords</tt> for {@link Ext.data.XmlReader XmlReader}) shall be returned by a query on the server.
2180      * <b>Note</b>: see the Important note in {@link #load}.</p>
2181      * @return {Number} The number of Records as specified in the data object passed to the Reader
2182      * by the Proxy.
2183      * <p><b>Note</b>: this value is not updated when changing the contents of the Store locally.</p>
2184      */
2185     getTotalCount : function(){
2186         return this.totalLength || 0;
2187     },
2188
2189     /**
2190      * Returns an object describing the current sort state of this Store.
2191      * @return {Object} The sort state of the Store. An object with two properties:<ul>
2192      * <li><b>field : String<p class="sub-desc">The name of the field by which the Records are sorted.</p></li>
2193      * <li><b>direction : String<p class="sub-desc">The sort order, 'ASC' or 'DESC' (case-sensitive).</p></li>
2194      * </ul>
2195      * See <tt>{@link #sortInfo}</tt> for additional details.
2196      */
2197     getSortState : function(){
2198         return this.sortInfo;
2199     },
2200
2201     /**
2202      * @private
2203      * Invokes sortData if we have sortInfo to sort on and are not sorting remotely
2204      */
2205     applySort : function(){
2206         if ((this.sortInfo || this.multiSortInfo) && !this.remoteSort) {
2207             this.sortData();
2208         }
2209     },
2210
2211     /**
2212      * @private
2213      * Performs the actual sorting of data. This checks to see if we currently have a multi sort or not. It applies
2214      * each sorter field/direction pair in turn by building an OR'ed master sorting function and running it against
2215      * the full dataset
2216      */
2217     sortData : function() {
2218         var sortInfo  = this.hasMultiSort ? this.multiSortInfo : this.sortInfo,
2219             direction = sortInfo.direction || "ASC",
2220             sorters   = sortInfo.sorters,
2221             sortFns   = [];
2222
2223         //if we just have a single sorter, pretend it's the first in an array
2224         if (!this.hasMultiSort) {
2225             sorters = [{direction: direction, field: sortInfo.field}];
2226         }
2227
2228         //create a sorter function for each sorter field/direction combo
2229         for (var i=0, j = sorters.length; i < j; i++) {
2230             sortFns.push(this.createSortFunction(sorters[i].field, sorters[i].direction));
2231         }
2232         
2233         if (sortFns.length == 0) {
2234             return;
2235         }
2236
2237         //the direction modifier is multiplied with the result of the sorting functions to provide overall sort direction
2238         //(as opposed to direction per field)
2239         var directionModifier = direction.toUpperCase() == "DESC" ? -1 : 1;
2240
2241         //create a function which ORs each sorter together to enable multi-sort
2242         var fn = function(r1, r2) {
2243           var result = sortFns[0].call(this, r1, r2);
2244
2245           //if we have more than one sorter, OR any additional sorter functions together
2246           if (sortFns.length > 1) {
2247               for (var i=1, j = sortFns.length; i < j; i++) {
2248                   result = result || sortFns[i].call(this, r1, r2);
2249               }
2250           }
2251
2252           return directionModifier * result;
2253         };
2254
2255         //sort the data
2256         this.data.sort(direction, fn);
2257         if (this.snapshot && this.snapshot != this.data) {
2258             this.snapshot.sort(direction, fn);
2259         }
2260     },
2261
2262     /**
2263      * @private
2264      * Creates and returns a function which sorts an array by the given field and direction
2265      * @param {String} field The field to create the sorter for
2266      * @param {String} direction The direction to sort by (defaults to "ASC")
2267      * @return {Function} A function which sorts by the field/direction combination provided
2268      */
2269     createSortFunction: function(field, direction) {
2270         direction = direction || "ASC";
2271         var directionModifier = direction.toUpperCase() == "DESC" ? -1 : 1;
2272
2273         var sortType = this.fields.get(field).sortType;
2274
2275         //create a comparison function. Takes 2 records, returns 1 if record 1 is greater,
2276         //-1 if record 2 is greater or 0 if they are equal
2277         return function(r1, r2) {
2278             var v1 = sortType(r1.data[field]),
2279                 v2 = sortType(r2.data[field]);
2280
2281             return directionModifier * (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
2282         };
2283     },
2284
2285     /**
2286      * Sets the default sort column and order to be used by the next {@link #load} operation.
2287      * @param {String} fieldName The name of the field to sort by.
2288      * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
2289      */
2290     setDefaultSort : function(field, dir) {
2291         dir = dir ? dir.toUpperCase() : 'ASC';
2292         this.sortInfo = {field: field, direction: dir};
2293         this.sortToggle[field] = dir;
2294     },
2295
2296     /**
2297      * Sort the Records.
2298      * If remote sorting is used, the sort is performed on the server, and the cache is reloaded. If local
2299      * sorting is used, the cache is sorted internally. See also {@link #remoteSort} and {@link #paramNames}.
2300      * This function accepts two call signatures - pass in a field name as the first argument to sort on a single
2301      * field, or pass in an array of sort configuration objects to sort by multiple fields.
2302      * Single sort example:
2303      * store.sort('name', 'ASC');
2304      * Multi sort example:
2305      * store.sort([
2306      *   {
2307      *     field    : 'name',
2308      *     direction: 'ASC'
2309      *   },
2310      *   {
2311      *     field    : 'salary',
2312      *     direction: 'DESC'
2313      *   }
2314      * ], 'ASC');
2315      * In this second form, the sort configs are applied in order, with later sorters sorting within earlier sorters' results.
2316      * For example, if two records with the same name are present they will also be sorted by salary if given the sort configs
2317      * above. Any number of sort configs can be added.
2318      * @param {String/Array} fieldName The name of the field to sort by, or an array of ordered sort configs
2319      * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
2320      */
2321     sort : function(fieldName, dir) {
2322         if (Ext.isArray(arguments[0])) {
2323             return this.multiSort.call(this, fieldName, dir);
2324         } else {
2325             return this.singleSort(fieldName, dir);
2326         }
2327     },
2328
2329     /**
2330      * Sorts the store contents by a single field and direction. This is called internally by {@link sort} and would
2331      * not usually be called manually
2332      * @param {String} fieldName The name of the field to sort by.
2333      * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
2334      */
2335     singleSort: function(fieldName, dir) {
2336         var field = this.fields.get(fieldName);
2337         if (!field) {
2338             return false;
2339         }
2340
2341         var name       = field.name,
2342             sortInfo   = this.sortInfo || null,
2343             sortToggle = this.sortToggle ? this.sortToggle[name] : null;
2344
2345         if (!dir) {
2346             if (sortInfo && sortInfo.field == name) { // toggle sort dir
2347                 dir = (this.sortToggle[name] || 'ASC').toggle('ASC', 'DESC');
2348             } else {
2349                 dir = field.sortDir;
2350             }
2351         }
2352
2353         this.sortToggle[name] = dir;
2354         this.sortInfo = {field: name, direction: dir};
2355         this.hasMultiSort = false;
2356
2357         if (this.remoteSort) {
2358             if (!this.load(this.lastOptions)) {
2359                 if (sortToggle) {
2360                     this.sortToggle[name] = sortToggle;
2361                 }
2362                 if (sortInfo) {
2363                     this.sortInfo = sortInfo;
2364                 }
2365             }
2366         } else {
2367             this.applySort();
2368             this.fireEvent('datachanged', this);
2369         }
2370         return true;
2371     },
2372
2373     /**
2374      * Sorts the contents of this store by multiple field/direction sorters. This is called internally by {@link sort}
2375      * and would not usually be called manually.
2376      * Multi sorting only currently applies to local datasets - multiple sort data is not currently sent to a proxy
2377      * if remoteSort is used.
2378      * @param {Array} sorters Array of sorter objects (field and direction)
2379      * @param {String} direction Overall direction to sort the ordered results by (defaults to "ASC")
2380      */
2381     multiSort: function(sorters, direction) {
2382         this.hasMultiSort = true;
2383         direction = direction || "ASC";
2384
2385         //toggle sort direction
2386         if (this.multiSortInfo && direction == this.multiSortInfo.direction) {
2387             direction = direction.toggle("ASC", "DESC");
2388         }
2389
2390         /**
2391          * Object containing overall sort direction and an ordered array of sorter configs used when sorting on multiple fields
2392          * @property multiSortInfo
2393          * @type Object
2394          */
2395         this.multiSortInfo = {
2396             sorters  : sorters,
2397             direction: direction
2398         };
2399         
2400         if (this.remoteSort) {
2401             this.singleSort(sorters[0].field, sorters[0].direction);
2402
2403         } else {
2404             this.applySort();
2405             this.fireEvent('datachanged', this);
2406         }
2407     },
2408
2409     /**
2410      * Calls the specified function for each of the {@link Ext.data.Record Records} in the cache.
2411      * @param {Function} fn The function to call. The {@link Ext.data.Record Record} is passed as the first parameter.
2412      * Returning <tt>false</tt> aborts and exits the iteration.
2413      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed.
2414      * Defaults to the current {@link Ext.data.Record Record} in the iteration.
2415      */
2416     each : function(fn, scope){
2417         this.data.each(fn, scope);
2418     },
2419
2420     /**
2421      * Gets all {@link Ext.data.Record records} modified since the last commit.  Modified records are
2422      * persisted across load operations (e.g., during paging). <b>Note</b>: deleted records are not
2423      * included.  See also <tt>{@link #pruneModifiedRecords}</tt> and
2424      * {@link Ext.data.Record}<tt>{@link Ext.data.Record#markDirty markDirty}.</tt>.
2425      * @return {Ext.data.Record[]} An array of {@link Ext.data.Record Records} containing outstanding
2426      * modifications.  To obtain modified fields within a modified record see
2427      *{@link Ext.data.Record}<tt>{@link Ext.data.Record#modified modified}.</tt>.
2428      */
2429     getModifiedRecords : function(){
2430         return this.modified;
2431     },
2432
2433     /**
2434      * Sums the value of <tt>property</tt> for each {@link Ext.data.Record record} between <tt>start</tt>
2435      * and <tt>end</tt> and returns the result.
2436      * @param {String} property A field in each record
2437      * @param {Number} start (optional) The record index to start at (defaults to <tt>0</tt>)
2438      * @param {Number} end (optional) The last record index to include (defaults to length - 1)
2439      * @return {Number} The sum
2440      */
2441     sum : function(property, start, end){
2442         var rs = this.data.items, v = 0;
2443         start = start || 0;
2444         end = (end || end === 0) ? end : rs.length-1;
2445
2446         for(var i = start; i <= end; i++){
2447             v += (rs[i].data[property] || 0);
2448         }
2449         return v;
2450     },
2451
2452     /**
2453      * @private
2454      * Returns a filter function used to test a the given property's value. Defers most of the work to
2455      * Ext.util.MixedCollection's createValueMatcher function
2456      * @param {String} property The property to create the filter function for
2457      * @param {String/RegExp} value The string/regex to compare the property value to
2458      * @param {Boolean} anyMatch True if we don't care if the filter value is not the full value (defaults to false)
2459      * @param {Boolean} caseSensitive True to create a case-sensitive regex (defaults to false)
2460      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
2461      */
2462     createFilterFn : function(property, value, anyMatch, caseSensitive, exactMatch){
2463         if(Ext.isEmpty(value, false)){
2464             return false;
2465         }
2466         value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
2467         return function(r) {
2468             return value.test(r.data[property]);
2469         };
2470     },
2471
2472     /**
2473      * @private
2474      * Given an array of filter functions (each with optional scope), constructs and returns a single function that returns
2475      * the result of all of the filters ANDed together
2476      * @param {Array} filters The array of filter objects (each object should contain an 'fn' and optional scope)
2477      * @return {Function} The multiple filter function
2478      */
2479     createMultipleFilterFn: function(filters) {
2480         return function(record) {
2481             var isMatch = true;
2482
2483             for (var i=0, j = filters.length; i < j; i++) {
2484                 var filter = filters[i],
2485                     fn     = filter.fn,
2486                     scope  = filter.scope;
2487
2488                 isMatch = isMatch && fn.call(scope, record);
2489             }
2490
2491             return isMatch;
2492         };
2493     },
2494
2495     /**
2496      * Filter the {@link Ext.data.Record records} by a specified property. Alternatively, pass an array of filter
2497      * options to filter by more than one property.
2498      * Single filter example:
2499      * store.filter('name', 'Ed', true, true); //finds all records containing the substring 'Ed'
2500      * Multiple filter example:
2501      * <pre><code>
2502      * store.filter([
2503      *   {
2504      *     property     : 'name',
2505      *     value        : 'Ed',
2506      *     anyMatch     : true, //optional, defaults to true
2507      *     caseSensitive: true  //optional, defaults to true
2508      *   },
2509      *
2510      *   //filter functions can also be passed
2511      *   {
2512      *     fn   : function(record) {
2513      *       return record.get('age') == 24
2514      *     },
2515      *     scope: this
2516      *   }
2517      * ]);
2518      * </code></pre>
2519      * @param {String|Array} field A field on your records, or an array containing multiple filter options
2520      * @param {String/RegExp} value Either a string that the field should begin with, or a RegExp to test
2521      * against the field.
2522      * @param {Boolean} anyMatch (optional) <tt>true</tt> to match any part not just the beginning
2523      * @param {Boolean} caseSensitive (optional) <tt>true</tt> for case sensitive comparison
2524      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
2525      */
2526     filter : function(property, value, anyMatch, caseSensitive, exactMatch){
2527         var fn;
2528         //we can accept an array of filter objects, or a single filter object - normalize them here
2529         if (Ext.isObject(property)) {
2530             property = [property];
2531         }
2532
2533         if (Ext.isArray(property)) {
2534             var filters = [];
2535
2536             //normalize the filters passed into an array of filter functions
2537             for (var i=0, j = property.length; i < j; i++) {
2538                 var filter = property[i],
2539                     func   = filter.fn,
2540                     scope  = filter.scope || this;
2541
2542                 //if we weren't given a filter function, construct one now
2543                 if (!Ext.isFunction(func)) {
2544                     func = this.createFilterFn(filter.property, filter.value, filter.anyMatch, filter.caseSensitive, filter.exactMatch);
2545                 }
2546
2547                 filters.push({fn: func, scope: scope});
2548             }
2549
2550             fn = this.createMultipleFilterFn(filters);
2551         } else {
2552             //classic single property filter
2553             fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
2554         }
2555
2556         return fn ? this.filterBy(fn) : this.clearFilter();
2557     },
2558
2559     /**
2560      * Filter by a function. The specified function will be called for each
2561      * Record in this Store. If the function returns <tt>true</tt> the Record is included,
2562      * otherwise it is filtered out.
2563      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
2564      * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
2565      * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
2566      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
2567      * </ul>
2568      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
2569      */
2570     filterBy : function(fn, scope){
2571         this.snapshot = this.snapshot || this.data;
2572         this.data = this.queryBy(fn, scope || this);
2573         this.fireEvent('datachanged', this);
2574     },
2575
2576     /**
2577      * Revert to a view of the Record cache with no filtering applied.
2578      * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the
2579      * {@link #datachanged} event.
2580      */
2581     clearFilter : function(suppressEvent){
2582         if(this.isFiltered()){
2583             this.data = this.snapshot;
2584             delete this.snapshot;
2585             if(suppressEvent !== true){
2586                 this.fireEvent('datachanged', this);
2587             }
2588         }
2589     },
2590
2591     /**
2592      * Returns true if this store is currently filtered
2593      * @return {Boolean}
2594      */
2595     isFiltered : function(){
2596         return !!this.snapshot && this.snapshot != this.data;
2597     },
2598
2599     /**
2600      * Query the records by a specified property.
2601      * @param {String} field A field on your records
2602      * @param {String/RegExp} value Either a string that the field
2603      * should begin with, or a RegExp to test against the field.
2604      * @param {Boolean} anyMatch (optional) True to match any part not just the beginning
2605      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
2606      * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
2607      */
2608     query : function(property, value, anyMatch, caseSensitive){
2609         var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
2610         return fn ? this.queryBy(fn) : this.data.clone();
2611     },
2612
2613     /**
2614      * Query the cached records in this Store using a filtering function. The specified function
2615      * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
2616      * included in the results.
2617      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
2618      * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
2619      * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
2620      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
2621      * </ul>
2622      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
2623      * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
2624      **/
2625     queryBy : function(fn, scope){
2626         var data = this.snapshot || this.data;
2627         return data.filterBy(fn, scope||this);
2628     },
2629
2630     /**
2631      * Finds the index of the first matching Record in this store by a specific field value.
2632      * @param {String} fieldName The name of the Record field to test.
2633      * @param {String/RegExp} value Either a string that the field value
2634      * should begin with, or a RegExp to test against the field.
2635      * @param {Number} startIndex (optional) The index to start searching at
2636      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
2637      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
2638      * @return {Number} The matched index or -1
2639      */
2640     find : function(property, value, start, anyMatch, caseSensitive){
2641         var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
2642         return fn ? this.data.findIndexBy(fn, null, start) : -1;
2643     },
2644
2645     /**
2646      * Finds the index of the first matching Record in this store by a specific field value.
2647      * @param {String} fieldName The name of the Record field to test.
2648      * @param {Mixed} value The value to match the field against.
2649      * @param {Number} startIndex (optional) The index to start searching at
2650      * @return {Number} The matched index or -1
2651      */
2652     findExact: function(property, value, start){
2653         return this.data.findIndexBy(function(rec){
2654             return rec.get(property) === value;
2655         }, this, start);
2656     },
2657
2658     /**
2659      * Find the index of the first matching Record in this Store by a function.
2660      * If the function returns <tt>true</tt> it is considered a match.
2661      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
2662      * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
2663      * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
2664      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
2665      * </ul>
2666      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
2667      * @param {Number} startIndex (optional) The index to start searching at
2668      * @return {Number} The matched index or -1
2669      */
2670     findBy : function(fn, scope, start){
2671         return this.data.findIndexBy(fn, scope, start);
2672     },
2673
2674     /**
2675      * Collects unique values for a particular dataIndex from this store.
2676      * @param {String} dataIndex The property to collect
2677      * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
2678      * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
2679      * @return {Array} An array of the unique values
2680      **/
2681     collect : function(dataIndex, allowNull, bypassFilter){
2682         var d = (bypassFilter === true && this.snapshot) ?
2683                 this.snapshot.items : this.data.items;
2684         var v, sv, r = [], l = {};
2685         for(var i = 0, len = d.length; i < len; i++){
2686             v = d[i].data[dataIndex];
2687             sv = String(v);
2688             if((allowNull || !Ext.isEmpty(v)) && !l[sv]){
2689                 l[sv] = true;
2690                 r[r.length] = v;
2691             }
2692         }
2693         return r;
2694     },
2695
2696     // private
2697     afterEdit : function(record){
2698         if(this.modified.indexOf(record) == -1){
2699             this.modified.push(record);
2700         }
2701         this.fireEvent('update', this, record, Ext.data.Record.EDIT);
2702     },
2703
2704     // private
2705     afterReject : function(record){
2706         this.modified.remove(record);
2707         this.fireEvent('update', this, record, Ext.data.Record.REJECT);
2708     },
2709
2710     // private
2711     afterCommit : function(record){
2712         this.modified.remove(record);
2713         this.fireEvent('update', this, record, Ext.data.Record.COMMIT);
2714     },
2715
2716     /**
2717      * Commit all Records with {@link #getModifiedRecords outstanding changes}. To handle updates for changes,
2718      * subscribe to the Store's {@link #update update event}, and perform updating when the third parameter is
2719      * Ext.data.Record.COMMIT.
2720      */
2721     commitChanges : function(){
2722         var modified = this.modified.slice(0),
2723             length   = modified.length,
2724             i;
2725             
2726         for (i = 0; i < length; i++){
2727             modified[i].commit();
2728         }
2729         
2730         this.modified = [];
2731         this.removed  = [];
2732     },
2733
2734     /**
2735      * {@link Ext.data.Record#reject Reject} outstanding changes on all {@link #getModifiedRecords modified records}.
2736      */
2737     rejectChanges : function() {
2738         var modified = this.modified.slice(0),
2739             removed  = this.removed.slice(0).reverse(),
2740             mLength  = modified.length,
2741             rLength  = removed.length,
2742             i;
2743         
2744         for (i = 0; i < mLength; i++) {
2745             modified[i].reject();
2746         }
2747         
2748         for (i = 0; i < rLength; i++) {
2749             this.insert(removed[i].lastIndex || 0, removed[i]);
2750             removed[i].reject();
2751         }
2752         
2753         this.modified = [];
2754         this.removed  = [];
2755     },
2756
2757     // private
2758     onMetaChange : function(meta){
2759         this.recordType = this.reader.recordType;
2760         this.fields = this.recordType.prototype.fields;
2761         delete this.snapshot;
2762         if(this.reader.meta.sortInfo){
2763             this.sortInfo = this.reader.meta.sortInfo;
2764         }else if(this.sortInfo  && !this.fields.get(this.sortInfo.field)){
2765             delete this.sortInfo;
2766         }
2767         if(this.writer){
2768             this.writer.meta = this.reader.meta;
2769         }
2770         this.modified = [];
2771         this.fireEvent('metachange', this, this.reader.meta);
2772     },
2773
2774     // private
2775     findInsertIndex : function(record){
2776         this.suspendEvents();
2777         var data = this.data.clone();
2778         this.data.add(record);
2779         this.applySort();
2780         var index = this.data.indexOf(record);
2781         this.data = data;
2782         this.resumeEvents();
2783         return index;
2784     },
2785
2786     /**
2787      * Set the value for a property name in this store's {@link #baseParams}.  Usage:</p><pre><code>
2788 myStore.setBaseParam('foo', {bar:3});
2789 </code></pre>
2790      * @param {String} name Name of the property to assign
2791      * @param {Mixed} value Value to assign the <tt>name</tt>d property
2792      **/
2793     setBaseParam : function (name, value){
2794         this.baseParams = this.baseParams || {};
2795         this.baseParams[name] = value;
2796     }
2797 });
2798
2799 Ext.reg('store', Ext.data.Store);
2800
2801 /**
2802  * @class Ext.data.Store.Error
2803  * @extends Ext.Error
2804  * Store Error extension.
2805  * @param {String} name
2806  */
2807 Ext.data.Store.Error = Ext.extend(Ext.Error, {
2808     name: 'Ext.data.Store'
2809 });
2810 Ext.apply(Ext.data.Store.Error.prototype, {
2811     lang: {
2812         'writer-undefined' : 'Attempted to execute a write-action without a DataWriter installed.'
2813     }
2814 });
2815 /**
2816  * @class Ext.data.Field
2817  * <p>This class encapsulates the field definition information specified in the field definition objects
2818  * passed to {@link Ext.data.Record#create}.</p>
2819  * <p>Developers do not need to instantiate this class. Instances are created by {@link Ext.data.Record.create}
2820  * and cached in the {@link Ext.data.Record#fields fields} property of the created Record constructor's <b>prototype.</b></p>
2821  */
2822 Ext.data.Field = Ext.extend(Object, {
2823     
2824     constructor : function(config){
2825         if(Ext.isString(config)){
2826             config = {name: config};
2827         }
2828         Ext.apply(this, config);
2829         
2830         var types = Ext.data.Types,
2831             st = this.sortType,
2832             t;
2833
2834         if(this.type){
2835             if(Ext.isString(this.type)){
2836                 this.type = Ext.data.Types[this.type.toUpperCase()] || types.AUTO;
2837             }
2838         }else{
2839             this.type = types.AUTO;
2840         }
2841
2842         // named sortTypes are supported, here we look them up
2843         if(Ext.isString(st)){
2844             this.sortType = Ext.data.SortTypes[st];
2845         }else if(Ext.isEmpty(st)){
2846             this.sortType = this.type.sortType;
2847         }
2848
2849         if(!this.convert){
2850             this.convert = this.type.convert;
2851         }
2852     },
2853     
2854     /**
2855      * @cfg {String} name
2856      * The name by which the field is referenced within the Record. This is referenced by, for example,
2857      * the <code>dataIndex</code> property in column definition objects passed to {@link Ext.grid.ColumnModel}.
2858      * <p>Note: In the simplest case, if no properties other than <code>name</code> are required, a field
2859      * definition may consist of just a String for the field name.</p>
2860      */
2861     /**
2862      * @cfg {Mixed} type
2863      * (Optional) The data type for automatic conversion from received data to the <i>stored</i> value if <code>{@link Ext.data.Field#convert convert}</code>
2864      * has not been specified. This may be specified as a string value. Possible values are
2865      * <div class="mdetail-params"><ul>
2866      * <li>auto (Default, implies no conversion)</li>
2867      * <li>string</li>
2868      * <li>int</li>
2869      * <li>float</li>
2870      * <li>boolean</li>
2871      * <li>date</li></ul></div>
2872      * <p>This may also be specified by referencing a member of the {@link Ext.data.Types} class.</p>
2873      * <p>Developers may create their own application-specific data types by defining new members of the
2874      * {@link Ext.data.Types} class.</p>
2875      */
2876     /**
2877      * @cfg {Function} convert
2878      * (Optional) A function which converts the value provided by the Reader into an object that will be stored
2879      * in the Record. It is passed the following parameters:<div class="mdetail-params"><ul>
2880      * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
2881      * the configured <code>{@link Ext.data.Field#defaultValue defaultValue}</code>.</div></li>
2882      * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
2883      * Depending on the Reader type, this could be an Array ({@link Ext.data.ArrayReader ArrayReader}), an object
2884      *  ({@link Ext.data.JsonReader JsonReader}), or an XML element ({@link Ext.data.XMLReader XMLReader}).</div></li>
2885      * </ul></div>
2886      * <pre><code>
2887 // example of convert function
2888 function fullName(v, record){
2889     return record.name.last + ', ' + record.name.first;
2890 }
2891
2892 function location(v, record){
2893     return !record.city ? '' : (record.city + ', ' + record.state);
2894 }
2895
2896 var Dude = Ext.data.Record.create([
2897     {name: 'fullname',  convert: fullName},
2898     {name: 'firstname', mapping: 'name.first'},
2899     {name: 'lastname',  mapping: 'name.last'},
2900     {name: 'city', defaultValue: 'homeless'},
2901     'state',
2902     {name: 'location',  convert: location}
2903 ]);
2904
2905 // create the data store
2906 var store = new Ext.data.Store({
2907     reader: new Ext.data.JsonReader(
2908         {
2909             idProperty: 'key',
2910             root: 'daRoot',
2911             totalProperty: 'total'
2912         },
2913         Dude  // recordType
2914     )
2915 });
2916
2917 var myData = [
2918     { key: 1,
2919       name: { first: 'Fat',    last:  'Albert' }
2920       // notice no city, state provided in data object
2921     },
2922     { key: 2,
2923       name: { first: 'Barney', last:  'Rubble' },
2924       city: 'Bedrock', state: 'Stoneridge'
2925     },
2926     { key: 3,
2927       name: { first: 'Cliff',  last:  'Claven' },
2928       city: 'Boston',  state: 'MA'
2929     }
2930 ];
2931      * </code></pre>
2932      */
2933     /**
2934      * @cfg {String} dateFormat
2935      * <p>(Optional) Used when converting received data into a Date when the {@link #type} is specified as <code>"date"</code>.</p>
2936      * <p>A format string for the {@link Date#parseDate Date.parseDate} function, or "timestamp" if the
2937      * value provided by the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a
2938      * javascript millisecond timestamp. See {@link Date}</p>
2939      */
2940     dateFormat: null,
2941     
2942     /**
2943      * @cfg {Boolean} useNull
2944      * <p>(Optional) Use when converting received data into a Number type (either int or float). If the value cannot be parsed,
2945      * null will be used if useNull is true, otherwise the value will be 0. Defaults to <tt>false</tt>
2946      */
2947     useNull: false,
2948     
2949     /**
2950      * @cfg {Mixed} defaultValue
2951      * (Optional) The default value used <b>when a Record is being created by a {@link Ext.data.Reader Reader}</b>
2952      * when the item referenced by the <code>{@link Ext.data.Field#mapping mapping}</code> does not exist in the data
2953      * object (i.e. undefined). (defaults to "")
2954      */
2955     defaultValue: "",
2956     /**
2957      * @cfg {String/Number} mapping
2958      * <p>(Optional) A path expression for use by the {@link Ext.data.DataReader} implementation
2959      * that is creating the {@link Ext.data.Record Record} to extract the Field value from the data object.
2960      * If the path expression is the same as the field name, the mapping may be omitted.</p>
2961      * <p>The form of the mapping expression depends on the Reader being used.</p>
2962      * <div class="mdetail-params"><ul>
2963      * <li>{@link Ext.data.JsonReader}<div class="sub-desc">The mapping is a string containing the javascript
2964      * expression to reference the data from an element of the data item's {@link Ext.data.JsonReader#root root} Array. Defaults to the field name.</div></li>
2965      * <li>{@link Ext.data.XmlReader}<div class="sub-desc">The mapping is an {@link Ext.DomQuery} path to the data
2966      * item relative to the DOM element that represents the {@link Ext.data.XmlReader#record record}. Defaults to the field name.</div></li>
2967      * <li>{@link Ext.data.ArrayReader}<div class="sub-desc">The mapping is a number indicating the Array index
2968      * of the field's value. Defaults to the field specification's Array position.</div></li>
2969      * </ul></div>
2970      * <p>If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
2971      * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
2972      * return the desired data.</p>
2973      */
2974     mapping: null,
2975     /**
2976      * @cfg {Function} sortType
2977      * (Optional) A function which converts a Field's value to a comparable value in order to ensure
2978      * correct sort ordering. Predefined functions are provided in {@link Ext.data.SortTypes}. A custom
2979      * sort example:<pre><code>
2980 // current sort     after sort we want
2981 // +-+------+          +-+------+
2982 // |1|First |          |1|First |
2983 // |2|Last  |          |3|Second|
2984 // |3|Second|          |2|Last  |
2985 // +-+------+          +-+------+
2986
2987 sortType: function(value) {
2988    switch (value.toLowerCase()) // native toLowerCase():
2989    {
2990       case 'first': return 1;
2991       case 'second': return 2;
2992       default: return 3;
2993    }
2994 }
2995      * </code></pre>
2996      */
2997     sortType : null,
2998     /**
2999      * @cfg {String} sortDir
3000      * (Optional) Initial direction to sort (<code>"ASC"</code> or  <code>"DESC"</code>).  Defaults to
3001      * <code>"ASC"</code>.
3002      */
3003     sortDir : "ASC",
3004     /**
3005      * @cfg {Boolean} allowBlank
3006      * (Optional) Used for validating a {@link Ext.data.Record record}, defaults to <code>true</code>.
3007      * An empty value here will cause {@link Ext.data.Record}.{@link Ext.data.Record#isValid isValid}
3008      * to evaluate to <code>false</code>.
3009      */
3010     allowBlank : true
3011 });
3012 /**
3013  * @class Ext.data.DataReader
3014  * Abstract base class for reading structured data from a data source and converting
3015  * it into an object containing {@link Ext.data.Record} objects and metadata for use
3016  * by an {@link Ext.data.Store}.  This class is intended to be extended and should not
3017  * be created directly. For existing implementations, see {@link Ext.data.ArrayReader},
3018  * {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}.
3019  * @constructor Create a new DataReader
3020  * @param {Object} meta Metadata configuration options (implementation-specific).
3021  * @param {Array/Object} recordType
3022  * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
3023  * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
3024  * constructor created using {@link Ext.data.Record#create}.</p>
3025  */
3026 Ext.data.DataReader = function(meta, recordType){
3027     /**
3028      * This DataReader's configured metadata as passed to the constructor.
3029      * @type Mixed
3030      * @property meta
3031      */
3032     this.meta = meta;
3033     /**
3034      * @cfg {Array/Object} fields
3035      * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
3036      * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
3037      * constructor created from {@link Ext.data.Record#create}.</p>
3038      */
3039     this.recordType = Ext.isArray(recordType) ?
3040         Ext.data.Record.create(recordType) : recordType;
3041
3042     // if recordType defined make sure extraction functions are defined
3043     if (this.recordType){
3044         this.buildExtractors();
3045     }
3046 };
3047
3048 Ext.data.DataReader.prototype = {
3049     /**
3050      * @cfg {String} messageProperty [undefined] Optional name of a property within a server-response that represents a user-feedback message.
3051      */
3052     /**
3053      * Abstract method created in extension's buildExtractors impl.
3054      */
3055     getTotal: Ext.emptyFn,
3056     /**
3057      * Abstract method created in extension's buildExtractors impl.
3058      */
3059     getRoot: Ext.emptyFn,
3060     /**
3061      * Abstract method created in extension's buildExtractors impl.
3062      */
3063     getMessage: Ext.emptyFn,
3064     /**
3065      * Abstract method created in extension's buildExtractors impl.
3066      */
3067     getSuccess: Ext.emptyFn,
3068     /**
3069      * Abstract method created in extension's buildExtractors impl.
3070      */
3071     getId: Ext.emptyFn,
3072     /**
3073      * Abstract method, overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}
3074      */
3075     buildExtractors : Ext.emptyFn,
3076     /**
3077      * Abstract method overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}
3078      */
3079     extractValues : Ext.emptyFn,
3080
3081     /**
3082      * Used for un-phantoming a record after a successful database insert.  Sets the records pk along with new data from server.
3083      * You <b>must</b> return at least the database pk using the idProperty defined in your DataReader configuration.  The incoming
3084      * data from server will be merged with the data in the local record.
3085      * In addition, you <b>must</b> return record-data from the server in the same order received.
3086      * Will perform a commit as well, un-marking dirty-fields.  Store's "update" event will be suppressed.
3087      * @param {Record/Record[]} record The phantom record to be realized.
3088      * @param {Object/Object[]} data The new record data to apply.  Must include the primary-key from database defined in idProperty field.
3089      */
3090     realize: function(rs, data){
3091         if (Ext.isArray(rs)) {
3092             for (var i = rs.length - 1; i >= 0; i--) {
3093                 // recurse
3094                 if (Ext.isArray(data)) {
3095                     this.realize(rs.splice(i,1).shift(), data.splice(i,1).shift());
3096                 }
3097                 else {
3098                     // weird...rs is an array but data isn't??  recurse but just send in the whole invalid data object.
3099                     // the else clause below will detect !this.isData and throw exception.
3100                     this.realize(rs.splice(i,1).shift(), data);
3101                 }
3102             }
3103         }
3104         else {
3105             // If rs is NOT an array but data IS, see if data contains just 1 record.  If so extract it and carry on.
3106             if (Ext.isArray(data) && data.length == 1) {
3107                 data = data.shift();
3108             }
3109             if (!this.isData(data)) {
3110                 // TODO: Let exception-handler choose to commit or not rather than blindly rs.commit() here.
3111                 //rs.commit();
3112                 throw new Ext.data.DataReader.Error('realize', rs);
3113             }
3114             rs.phantom = false; // <-- That's what it's all about
3115             rs._phid = rs.id;  // <-- copy phantom-id -> _phid, so we can remap in Store#onCreateRecords
3116             rs.id = this.getId(data);
3117             rs.data = data;
3118
3119             rs.commit();
3120         }
3121     },
3122
3123     /**
3124      * Used for updating a non-phantom or "real" record's data with fresh data from server after remote-save.
3125      * If returning data from multiple-records after a batch-update, you <b>must</b> return record-data from the server in
3126      * the same order received.  Will perform a commit as well, un-marking dirty-fields.  Store's "update" event will be
3127      * suppressed as the record receives fresh new data-hash
3128      * @param {Record/Record[]} rs
3129      * @param {Object/Object[]} data
3130      */
3131     update : function(rs, data) {
3132         if (Ext.isArray(rs)) {
3133             for (var i=rs.length-1; i >= 0; i--) {
3134                 if (Ext.isArray(data)) {
3135                     this.update(rs.splice(i,1).shift(), data.splice(i,1).shift());
3136                 }
3137                 else {
3138                     // weird...rs is an array but data isn't??  recurse but just send in the whole data object.
3139                     // the else clause below will detect !this.isData and throw exception.
3140                     this.update(rs.splice(i,1).shift(), data);
3141                 }
3142             }
3143         }
3144         else {
3145             // If rs is NOT an array but data IS, see if data contains just 1 record.  If so extract it and carry on.
3146             if (Ext.isArray(data) && data.length == 1) {
3147                 data = data.shift();
3148             }
3149             if (this.isData(data)) {
3150                 rs.data = Ext.apply(rs.data, data);
3151             }
3152             rs.commit();
3153         }
3154     },
3155
3156     /**
3157      * returns extracted, type-cast rows of data.  Iterates to call #extractValues for each row
3158      * @param {Object[]/Object} data-root from server response
3159      * @param {Boolean} returnRecords [false] Set true to return instances of Ext.data.Record
3160      * @private
3161      */
3162     extractData : function(root, returnRecords) {
3163         // A bit ugly this, too bad the Record's raw data couldn't be saved in a common property named "raw" or something.
3164         var rawName = (this instanceof Ext.data.JsonReader) ? 'json' : 'node';
3165
3166         var rs = [];
3167
3168         // Had to add Check for XmlReader, #isData returns true if root is an Xml-object.  Want to check in order to re-factor
3169         // #extractData into DataReader base, since the implementations are almost identical for JsonReader, XmlReader
3170         if (this.isData(root) && !(this instanceof Ext.data.XmlReader)) {
3171             root = [root];
3172         }
3173         var f       = this.recordType.prototype.fields,
3174             fi      = f.items,
3175             fl      = f.length,
3176             rs      = [];
3177         if (returnRecords === true) {
3178             var Record = this.recordType;
3179             for (var i = 0; i < root.length; i++) {
3180                 var n = root[i];
3181                 var record = new Record(this.extractValues(n, fi, fl), this.getId(n));
3182                 record[rawName] = n;    // <-- There's implementation of ugly bit, setting the raw record-data.
3183                 rs.push(record);
3184             }
3185         }
3186         else {
3187             for (var i = 0; i < root.length; i++) {
3188                 var data = this.extractValues(root[i], fi, fl);
3189                 data[this.meta.idProperty] = this.getId(root[i]);
3190                 rs.push(data);
3191             }
3192         }
3193         return rs;
3194     },
3195
3196     /**
3197      * Returns true if the supplied data-hash <b>looks</b> and quacks like data.  Checks to see if it has a key
3198      * corresponding to idProperty defined in your DataReader config containing non-empty pk.
3199      * @param {Object} data
3200      * @return {Boolean}
3201      */
3202     isData : function(data) {
3203         return (data && Ext.isObject(data) && !Ext.isEmpty(this.getId(data))) ? true : false;
3204     },
3205
3206     // private function a store will createSequence upon
3207     onMetaChange : function(meta){
3208         delete this.ef;
3209         this.meta = meta;
3210         this.recordType = Ext.data.Record.create(meta.fields);
3211         this.buildExtractors();
3212     }
3213 };
3214
3215 /**
3216  * @class Ext.data.DataReader.Error
3217  * @extends Ext.Error
3218  * General error class for Ext.data.DataReader
3219  */
3220 Ext.data.DataReader.Error = Ext.extend(Ext.Error, {
3221     constructor : function(message, arg) {
3222         this.arg = arg;
3223         Ext.Error.call(this, message);
3224     },
3225     name: 'Ext.data.DataReader'
3226 });
3227 Ext.apply(Ext.data.DataReader.Error.prototype, {
3228     lang : {
3229         'update': "#update received invalid data from server.  Please see docs for DataReader#update and review your DataReader configuration.",
3230         'realize': "#realize was called with invalid remote-data.  Please see the docs for DataReader#realize and review your DataReader configuration.",
3231         'invalid-response': "#readResponse received an invalid response from the server."
3232     }
3233 });
3234 /**
3235  * @class Ext.data.DataWriter
3236  * <p>Ext.data.DataWriter facilitates create, update, and destroy actions between
3237  * an Ext.data.Store and a server-side framework. A Writer enabled Store will
3238  * automatically manage the Ajax requests to perform CRUD actions on a Store.</p>
3239  * <p>Ext.data.DataWriter is an abstract base class which is intended to be extended
3240  * and should not be created directly. For existing implementations, see
3241  * {@link Ext.data.JsonWriter}.</p>
3242  * <p>Creating a writer is simple:</p>
3243  * <pre><code>
3244 var writer = new Ext.data.JsonWriter({
3245     encode: false   // &lt;--- false causes data to be printed to jsonData config-property of Ext.Ajax#reqeust
3246 });
3247  * </code></pre>
3248  * * <p>Same old JsonReader as Ext-2.x:</p>
3249  * <pre><code>
3250 var reader = new Ext.data.JsonReader({idProperty: 'id'}, [{name: 'first'}, {name: 'last'}, {name: 'email'}]);
3251  * </code></pre>
3252  *
3253  * <p>The proxy for a writer enabled store can be configured with a simple <code>url</code>:</p>
3254  * <pre><code>
3255 // Create a standard HttpProxy instance.
3256 var proxy = new Ext.data.HttpProxy({
3257     url: 'app.php/users'    // &lt;--- Supports "provides"-type urls, such as '/users.json', '/products.xml' (Hello Rails/Merb)
3258 });
3259  * </code></pre>
3260  * <p>For finer grained control, the proxy may also be configured with an <code>API</code>:</p>
3261  * <pre><code>
3262 // Maximum flexibility with the API-configuration
3263 var proxy = new Ext.data.HttpProxy({
3264     api: {
3265         read    : 'app.php/users/read',
3266         create  : 'app.php/users/create',
3267         update  : 'app.php/users/update',
3268         destroy : {  // &lt;--- Supports object-syntax as well
3269             url: 'app.php/users/destroy',
3270             method: "DELETE"
3271         }
3272     }
3273 });
3274  * </code></pre>
3275  * <p>Pulling it all together into a Writer-enabled Store:</p>
3276  * <pre><code>
3277 var store = new Ext.data.Store({
3278     proxy: proxy,
3279     reader: reader,
3280     writer: writer,
3281     autoLoad: true,
3282     autoSave: true  // -- Cell-level updates.
3283 });
3284  * </code></pre>
3285  * <p>Initiating write-actions <b>automatically</b>, using the existing Ext2.0 Store/Record API:</p>
3286  * <pre><code>
3287 var rec = store.getAt(0);
3288 rec.set('email', 'foo@bar.com');  // &lt;--- Immediately initiates an UPDATE action through configured proxy.
3289
3290 store.remove(rec);  // &lt;---- Immediately initiates a DESTROY action through configured proxy.
3291  * </code></pre>
3292  * <p>For <b>record/batch</b> updates, use the Store-configuration {@link Ext.data.Store#autoSave autoSave:false}</p>
3293  * <pre><code>
3294 var store = new Ext.data.Store({
3295     proxy: proxy,
3296     reader: reader,
3297     writer: writer,
3298     autoLoad: true,
3299     autoSave: false  // -- disable cell-updates
3300 });
3301
3302 var urec = store.getAt(0);
3303 urec.set('email', 'foo@bar.com');
3304
3305 var drec = store.getAt(1);
3306 store.remove(drec);
3307
3308 // Push the button!
3309 store.save();
3310  * </code></pre>
3311  * @constructor Create a new DataWriter
3312  * @param {Object} meta Metadata configuration options (implementation-specific)
3313  * @param {Object} recordType Either an Array of field definition objects as specified
3314  * in {@link Ext.data.Record#create}, or an {@link Ext.data.Record} object created
3315  * using {@link Ext.data.Record#create}.
3316  */
3317 Ext.data.DataWriter = function(config){
3318     Ext.apply(this, config);
3319 };
3320 Ext.data.DataWriter.prototype = {
3321
3322     /**
3323      * @cfg {Boolean} writeAllFields
3324      * <tt>false</tt> by default.  Set <tt>true</tt> to have DataWriter return ALL fields of a modified
3325      * record -- not just those that changed.
3326      * <tt>false</tt> to have DataWriter only request modified fields from a record.
3327      */
3328     writeAllFields : false,
3329     /**
3330      * @cfg {Boolean} listful
3331      * <tt>false</tt> by default.  Set <tt>true</tt> to have the DataWriter <b>always</b> write HTTP params as a list,
3332      * even when acting upon a single record.
3333      */
3334     listful : false,    // <-- listful is actually not used internally here in DataWriter.  @see Ext.data.Store#execute.
3335
3336     /**
3337      * Compiles a Store recordset into a data-format defined by an extension such as {@link Ext.data.JsonWriter} or {@link Ext.data.XmlWriter} in preparation for a {@link Ext.data.Api#actions server-write action}.  The first two params are similar similar in nature to {@link Ext#apply},
3338      * Where the first parameter is the <i>receiver</i> of paramaters and the second, baseParams, <i>the source</i>.
3339      * @param {Object} params The request-params receiver.
3340      * @param {Object} baseParams as defined by {@link Ext.data.Store#baseParams}.  The baseParms must be encoded by the extending class, eg: {@link Ext.data.JsonWriter}, {@link Ext.data.XmlWriter}.
3341      * @param {String} action [{@link Ext.data.Api#actions create|update|destroy}]
3342      * @param {Record/Record[]} rs The recordset to write, the subject(s) of the write action.
3343      */
3344     apply : function(params, baseParams, action, rs) {
3345         var data    = [],
3346         renderer    = action + 'Record';
3347         // TODO implement @cfg listful here
3348         if (Ext.isArray(rs)) {
3349             Ext.each(rs, function(rec){
3350                 data.push(this[renderer](rec));
3351             }, this);
3352         }
3353         else if (rs instanceof Ext.data.Record) {
3354             data = this[renderer](rs);
3355         }
3356         this.render(params, baseParams, data);
3357     },
3358
3359     /**
3360      * abstract method meant to be overridden by all DataWriter extensions.  It's the extension's job to apply the "data" to the "params".
3361      * The data-object provided to render is populated with data according to the meta-info defined in the user's DataReader config,
3362      * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
3363      * @param {Record[]} rs Store recordset
3364      * @param {Object} params Http params to be sent to server.
3365      * @param {Object} data object populated according to DataReader meta-data.
3366      */
3367     render : Ext.emptyFn,
3368
3369     /**
3370      * @cfg {Function} updateRecord Abstract method that should be implemented in all subclasses
3371      * (e.g.: {@link Ext.data.JsonWriter#updateRecord JsonWriter.updateRecord}
3372      */
3373     updateRecord : Ext.emptyFn,
3374
3375     /**
3376      * @cfg {Function} createRecord Abstract method that should be implemented in all subclasses
3377      * (e.g.: {@link Ext.data.JsonWriter#createRecord JsonWriter.createRecord})
3378      */
3379     createRecord : Ext.emptyFn,
3380
3381     /**
3382      * @cfg {Function} destroyRecord Abstract method that should be implemented in all subclasses
3383      * (e.g.: {@link Ext.data.JsonWriter#destroyRecord JsonWriter.destroyRecord})
3384      */
3385     destroyRecord : Ext.emptyFn,
3386
3387     /**
3388      * Converts a Record to a hash, taking into account the state of the Ext.data.Record along with configuration properties
3389      * related to its rendering, such as {@link #writeAllFields}, {@link Ext.data.Record#phantom phantom}, {@link Ext.data.Record#getChanges getChanges} and
3390      * {@link Ext.data.DataReader#idProperty idProperty}
3391      * @param {Ext.data.Record} rec The Record from which to create a hash.
3392      * @param {Object} config <b>NOT YET IMPLEMENTED</b>.  Will implement an exlude/only configuration for fine-control over which fields do/don't get rendered.
3393      * @return {Object}
3394      * @protected
3395      * TODO Implement excludes/only configuration with 2nd param?
3396      */
3397     toHash : function(rec, config) {
3398         var map = rec.fields.map,
3399             data = {},
3400             raw = (this.writeAllFields === false && rec.phantom === false) ? rec.getChanges() : rec.data,
3401             m;
3402         Ext.iterate(raw, function(prop, value){
3403             if((m = map[prop])){
3404                 data[m.mapping ? m.mapping : m.name] = value;
3405             }
3406         });
3407         // we don't want to write Ext auto-generated id to hash.  Careful not to remove it on Models not having auto-increment pk though.
3408         // We can tell its not auto-increment if the user defined a DataReader field for it *and* that field's value is non-empty.
3409         // we could also do a RegExp here for the Ext.data.Record AUTO_ID prefix.
3410         if (rec.phantom) {
3411             if (rec.fields.containsKey(this.meta.idProperty) && Ext.isEmpty(rec.data[this.meta.idProperty])) {
3412                 delete data[this.meta.idProperty];
3413             }
3414         } else {
3415             data[this.meta.idProperty] = rec.id;
3416         }
3417         return data;
3418     },
3419
3420     /**
3421      * Converts a {@link Ext.data.DataWriter#toHash Hashed} {@link Ext.data.Record} to fields-array array suitable
3422      * for encoding to xml via XTemplate, eg:
3423 <code><pre>&lt;tpl for=".">&lt;{name}>{value}&lt;/{name}&lt;/tpl></pre></code>
3424      * eg, <b>non-phantom</b>:
3425 <code><pre>{id: 1, first: 'foo', last: 'bar'} --> [{name: 'id', value: 1}, {name: 'first', value: 'foo'}, {name: 'last', value: 'bar'}]</pre></code>
3426      * {@link Ext.data.Record#phantom Phantom} records will have had their idProperty omitted in {@link #toHash} if determined to be auto-generated.
3427      * Non AUTOINCREMENT pks should have been protected.
3428      * @param {Hash} data Hashed by Ext.data.DataWriter#toHash
3429      * @return {[Object]} Array of attribute-objects.
3430      * @protected
3431      */
3432     toArray : function(data) {
3433         var fields = [];
3434         Ext.iterate(data, function(k, v) {fields.push({name: k, value: v});},this);
3435         return fields;
3436     }
3437 };/**
3438  * @class Ext.data.DataProxy
3439  * @extends Ext.util.Observable
3440  * <p>Abstract base class for implementations which provide retrieval of unformatted data objects.
3441  * This class is intended to be extended and should not be created directly. For existing implementations,
3442  * see {@link Ext.data.DirectProxy}, {@link Ext.data.HttpProxy}, {@link Ext.data.ScriptTagProxy} and
3443  * {@link Ext.data.MemoryProxy}.</p>
3444  * <p>DataProxy implementations are usually used in conjunction with an implementation of {@link Ext.data.DataReader}
3445  * (of the appropriate type which knows how to parse the data object) to provide a block of
3446  * {@link Ext.data.Records} to an {@link Ext.data.Store}.</p>
3447  * <p>The parameter to a DataProxy constructor may be an {@link Ext.data.Connection} or can also be the
3448  * config object to an {@link Ext.data.Connection}.</p>
3449  * <p>Custom implementations must implement either the <code><b>doRequest</b></code> method (preferred) or the
3450  * <code>load</code> method (deprecated). See
3451  * {@link Ext.data.HttpProxy}.{@link Ext.data.HttpProxy#doRequest doRequest} or
3452  * {@link Ext.data.HttpProxy}.{@link Ext.data.HttpProxy#load load} for additional details.</p>
3453  * <p><b><u>Example 1</u></b></p>
3454  * <pre><code>
3455 proxy: new Ext.data.ScriptTagProxy({
3456     {@link Ext.data.Connection#url url}: 'http://extjs.com/forum/topics-remote.php'
3457 }),
3458  * </code></pre>
3459  * <p><b><u>Example 2</u></b></p>
3460  * <pre><code>
3461 proxy : new Ext.data.HttpProxy({
3462     {@link Ext.data.Connection#method method}: 'GET',
3463     {@link Ext.data.HttpProxy#prettyUrls prettyUrls}: false,
3464     {@link Ext.data.Connection#url url}: 'local/default.php', // see options parameter for {@link Ext.Ajax#request}
3465     {@link #api}: {
3466         // all actions except the following will use above url
3467         create  : 'local/new.php',
3468         update  : 'local/update.php'
3469     }
3470 }),
3471  * </code></pre>
3472  * <p>And <b>new in Ext version 3</b>, attach centralized event-listeners upon the DataProxy class itself!  This is a great place
3473  * to implement a <i>messaging system</i> to centralize your application's user-feedback and error-handling.</p>
3474  * <pre><code>
3475 // Listen to all "beforewrite" event fired by all proxies.
3476 Ext.data.DataProxy.on('beforewrite', function(proxy, action) {
3477     console.log('beforewrite: ', action);
3478 });
3479
3480 // Listen to "write" event fired by all proxies
3481 Ext.data.DataProxy.on('write', function(proxy, action, data, res, rs) {
3482     console.info('write: ', action);
3483 });
3484
3485 // Listen to "exception" event fired by all proxies
3486 Ext.data.DataProxy.on('exception', function(proxy, type, action, exception) {
3487     console.error(type + action + ' exception);
3488 });
3489  * </code></pre>
3490  * <b>Note:</b> These three events are all fired with the signature of the corresponding <i>DataProxy instance</i> event {@link #beforewrite beforewrite}, {@link #write write} and {@link #exception exception}.
3491  */
3492 Ext.data.DataProxy = function(conn){
3493     // make sure we have a config object here to support ux proxies.
3494     // All proxies should now send config into superclass constructor.
3495     conn = conn || {};
3496
3497     // This line caused a bug when people use custom Connection object having its own request method.
3498     // http://extjs.com/forum/showthread.php?t=67194.  Have to set DataProxy config
3499     //Ext.applyIf(this, conn);
3500
3501     this.api     = conn.api;
3502     this.url     = conn.url;
3503     this.restful = conn.restful;
3504     this.listeners = conn.listeners;
3505
3506     // deprecated
3507     this.prettyUrls = conn.prettyUrls;
3508
3509     /**
3510      * @cfg {Object} api
3511      * Specific urls to call on CRUD action methods "read", "create", "update" and "destroy".
3512      * Defaults to:<pre><code>
3513 api: {
3514     read    : undefined,
3515     create  : undefined,
3516     update  : undefined,
3517     destroy : undefined
3518 }
3519      * </code></pre>
3520      * <p>The url is built based upon the action being executed <tt>[load|create|save|destroy]</tt>
3521      * using the commensurate <tt>{@link #api}</tt> property, or if undefined default to the
3522      * configured {@link Ext.data.Store}.{@link Ext.data.Store#url url}.</p><br>
3523      * <p>For example:</p>
3524      * <pre><code>
3525 api: {
3526     load :    '/controller/load',
3527     create :  '/controller/new',  // Server MUST return idProperty of new record
3528     save :    '/controller/update',
3529     destroy : '/controller/destroy_action'
3530 }
3531
3532 // Alternatively, one can use the object-form to specify each API-action
3533 api: {
3534     load: {url: 'read.php', method: 'GET'},
3535     create: 'create.php',
3536     destroy: 'destroy.php',
3537     save: 'update.php'
3538 }
3539      * </code></pre>
3540      * <p>If the specific URL for a given CRUD action is undefined, the CRUD action request
3541      * will be directed to the configured <tt>{@link Ext.data.Connection#url url}</tt>.</p>
3542      * <br><p><b>Note</b>: To modify the URL for an action dynamically the appropriate API
3543      * property should be modified before the action is requested using the corresponding before
3544      * action event.  For example to modify the URL associated with the load action:
3545      * <pre><code>
3546 // modify the url for the action
3547 myStore.on({
3548     beforeload: {
3549         fn: function (store, options) {
3550             // use <tt>{@link Ext.data.HttpProxy#setUrl setUrl}</tt> to change the URL for *just* this request.
3551             store.proxy.setUrl('changed1.php');
3552
3553             // set optional second parameter to true to make this URL change
3554             // permanent, applying this URL for all subsequent requests.
3555             store.proxy.setUrl('changed1.php', true);
3556
3557             // Altering the proxy API should be done using the public
3558             // method <tt>{@link Ext.data.DataProxy#setApi setApi}</tt>.
3559             store.proxy.setApi('read', 'changed2.php');
3560
3561             // Or set the entire API with a config-object.
3562             // When using the config-object option, you must redefine the <b>entire</b>
3563             // API -- not just a specific action of it.
3564             store.proxy.setApi({
3565                 read    : 'changed_read.php',
3566                 create  : 'changed_create.php',
3567                 update  : 'changed_update.php',
3568                 destroy : 'changed_destroy.php'
3569             });
3570         }
3571     }
3572 });
3573      * </code></pre>
3574      * </p>
3575      */
3576
3577     this.addEvents(
3578         /**
3579          * @event exception
3580          * <p>Fires if an exception occurs in the Proxy during a remote request. This event is relayed
3581          * through a corresponding {@link Ext.data.Store}.{@link Ext.data.Store#exception exception},
3582          * so any Store instance may observe this event.</p>
3583          * <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
3584          * through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of exception events from <b>all</b>
3585          * DataProxies by attaching a listener to the Ext.data.DataProxy class itself.</p>
3586          * <p>This event can be fired for one of two reasons:</p>
3587          * <div class="mdetail-params"><ul>
3588          * <li>remote-request <b>failed</b> : <div class="sub-desc">
3589          * The server did not return status === 200.
3590          * </div></li>
3591          * <li>remote-request <b>succeeded</b> : <div class="sub-desc">
3592          * The remote-request succeeded but the reader could not read the response.
3593          * This means the server returned data, but the configured Reader threw an
3594          * error while reading the response.  In this case, this event will be
3595          * raised and the caught error will be passed along into this event.
3596          * </div></li>
3597          * </ul></div>
3598          * <br><p>This event fires with two different contexts based upon the 2nd
3599          * parameter <tt>type [remote|response]</tt>.  The first four parameters
3600          * are identical between the two contexts -- only the final two parameters
3601          * differ.</p>
3602          * @param {DataProxy} this The proxy that sent the request
3603          * @param {String} type
3604          * <p>The value of this parameter will be either <tt>'response'</tt> or <tt>'remote'</tt>.</p>
3605          * <div class="mdetail-params"><ul>
3606          * <li><b><tt>'response'</tt></b> : <div class="sub-desc">
3607          * <p>An <b>invalid</b> response from the server was returned: either 404,
3608          * 500 or the response meta-data does not match that defined in the DataReader
3609          * (e.g.: root, idProperty, successProperty).</p>
3610          * </div></li>
3611          * <li><b><tt>'remote'</tt></b> : <div class="sub-desc">
3612          * <p>A <b>valid</b> response was returned from the server having
3613          * successProperty === false.  This response might contain an error-message
3614          * sent from the server.  For example, the user may have failed
3615          * authentication/authorization or a database validation error occurred.</p>
3616          * </div></li>
3617          * </ul></div>
3618          * @param {String} action Name of the action (see {@link Ext.data.Api#actions}.
3619          * @param {Object} options The options for the action that were specified in the {@link #request}.
3620          * @param {Object} response
3621          * <p>The value of this parameter depends on the value of the <code>type</code> parameter:</p>
3622          * <div class="mdetail-params"><ul>
3623          * <li><b><tt>'response'</tt></b> : <div class="sub-desc">
3624          * <p>The raw browser response object (e.g.: XMLHttpRequest)</p>
3625          * </div></li>
3626          * <li><b><tt>'remote'</tt></b> : <div class="sub-desc">
3627          * <p>The decoded response object sent from the server.</p>
3628          * </div></li>
3629          * </ul></div>
3630          * @param {Mixed} arg
3631          * <p>The type and value of this parameter depends on the value of the <code>type</code> parameter:</p>
3632          * <div class="mdetail-params"><ul>
3633          * <li><b><tt>'response'</tt></b> : Error<div class="sub-desc">
3634          * <p>The JavaScript Error object caught if the configured Reader could not read the data.
3635          * If the remote request returns success===false, this parameter will be null.</p>
3636          * </div></li>
3637          * <li><b><tt>'remote'</tt></b> : Record/Record[]<div class="sub-desc">
3638          * <p>This parameter will only exist if the <tt>action</tt> was a <b>write</b> action
3639          * (Ext.data.Api.actions.create|update|destroy).</p>
3640          * </div></li>
3641          * </ul></div>
3642          */
3643         'exception',
3644         /**
3645          * @event beforeload
3646          * Fires before a request to retrieve a data object.
3647          * @param {DataProxy} this The proxy for the request
3648          * @param {Object} params The params object passed to the {@link #request} function
3649          */
3650         'beforeload',
3651         /**
3652          * @event load
3653          * Fires before the load method's callback is called.
3654          * @param {DataProxy} this The proxy for the request
3655          * @param {Object} o The request transaction object
3656          * @param {Object} options The callback's <tt>options</tt> property as passed to the {@link #request} function
3657          */
3658         'load',
3659         /**
3660          * @event loadexception
3661          * <p>This event is <b>deprecated</b>.  The signature of the loadexception event
3662          * varies depending on the proxy, use the catch-all {@link #exception} event instead.
3663          * This event will fire in addition to the {@link #exception} event.</p>
3664          * @param {misc} misc See {@link #exception}.
3665          * @deprecated
3666          */
3667         'loadexception',
3668         /**
3669          * @event beforewrite
3670          * <p>Fires before a request is generated for one of the actions Ext.data.Api.actions.create|update|destroy</p>
3671          * <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
3672          * through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of beforewrite events from <b>all</b>
3673          * DataProxies by attaching a listener to the Ext.data.DataProxy class itself.</p>
3674          * @param {DataProxy} this The proxy for the request
3675          * @param {String} action [Ext.data.Api.actions.create|update|destroy]
3676          * @param {Record/Record[]} rs The Record(s) to create|update|destroy.
3677          * @param {Object} params The request <code>params</code> object.  Edit <code>params</code> to add parameters to the request.
3678          */
3679         'beforewrite',
3680         /**
3681          * @event write
3682          * <p>Fires before the request-callback is called</p>
3683          * <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
3684          * through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of write events from <b>all</b>
3685          * DataProxies by attaching a listener to the Ext.data.DataProxy class itself.</p>
3686          * @param {DataProxy} this The proxy that sent the request
3687          * @param {String} action [Ext.data.Api.actions.create|upate|destroy]
3688          * @param {Object} data The data object extracted from the server-response
3689          * @param {Object} response The decoded response from server
3690          * @param {Record/Record[]} rs The Record(s) from Store
3691          * @param {Object} options The callback's <tt>options</tt> property as passed to the {@link #request} function
3692          */
3693         'write'
3694     );
3695     Ext.data.DataProxy.superclass.constructor.call(this);
3696
3697     // Prepare the proxy api.  Ensures all API-actions are defined with the Object-form.
3698     try {
3699         Ext.data.Api.prepare(this);
3700     } catch (e) {
3701         if (e instanceof Ext.data.Api.Error) {
3702             e.toConsole();
3703         }
3704     }
3705     // relay each proxy's events onto Ext.data.DataProxy class for centralized Proxy-listening
3706     Ext.data.DataProxy.relayEvents(this, ['beforewrite', 'write', 'exception']);
3707 };
3708
3709 Ext.extend(Ext.data.DataProxy, Ext.util.Observable, {
3710     /**
3711      * @cfg {Boolean} restful
3712      * <p>Defaults to <tt>false</tt>.  Set to <tt>true</tt> to operate in a RESTful manner.</p>
3713      * <br><p> Note: this parameter will automatically be set to <tt>true</tt> if the
3714      * {@link Ext.data.Store} it is plugged into is set to <code>restful: true</code>. If the
3715      * Store is RESTful, there is no need to set this option on the proxy.</p>
3716      * <br><p>RESTful implementations enable the serverside framework to automatically route
3717      * actions sent to one url based upon the HTTP method, for example:
3718      * <pre><code>
3719 store: new Ext.data.Store({
3720     restful: true,
3721     proxy: new Ext.data.HttpProxy({url:'/users'}); // all requests sent to /users
3722     ...
3723 )}
3724      * </code></pre>
3725      * If there is no <code>{@link #api}</code> specified in the configuration of the proxy,
3726      * all requests will be marshalled to a single RESTful url (/users) so the serverside
3727      * framework can inspect the HTTP Method and act accordingly:
3728      * <pre>
3729 <u>Method</u>   <u>url</u>        <u>action</u>
3730 POST     /users     create
3731 GET      /users     read
3732 PUT      /users/23  update
3733 DESTROY  /users/23  delete
3734      * </pre></p>
3735      * <p>If set to <tt>true</tt>, a {@link Ext.data.Record#phantom non-phantom} record's
3736      * {@link Ext.data.Record#id id} will be appended to the url. Some MVC (e.g., Ruby on Rails,
3737      * Merb and Django) support segment based urls where the segments in the URL follow the
3738      * Model-View-Controller approach:<pre><code>
3739      * someSite.com/controller/action/id
3740      * </code></pre>
3741      * Where the segments in the url are typically:<div class="mdetail-params"><ul>
3742      * <li>The first segment : represents the controller class that should be invoked.</li>
3743      * <li>The second segment : represents the class function, or method, that should be called.</li>
3744      * <li>The third segment : represents the ID (a variable typically passed to the method).</li>
3745      * </ul></div></p>
3746      * <br><p>Refer to <code>{@link Ext.data.DataProxy#api}</code> for additional information.</p>
3747      */
3748     restful: false,
3749
3750     /**
3751      * <p>Redefines the Proxy's API or a single action of an API. Can be called with two method signatures.</p>
3752      * <p>If called with an object as the only parameter, the object should redefine the <b>entire</b> API, e.g.:</p><pre><code>
3753 proxy.setApi({
3754     read    : '/users/read',
3755     create  : '/users/create',
3756     update  : '/users/update',
3757     destroy : '/users/destroy'
3758 });
3759 </code></pre>
3760      * <p>If called with two parameters, the first parameter should be a string specifying the API action to
3761      * redefine and the second parameter should be the URL (or function if using DirectProxy) to call for that action, e.g.:</p><pre><code>
3762 proxy.setApi(Ext.data.Api.actions.read, '/users/new_load_url');
3763 </code></pre>
3764      * @param {String/Object} api An API specification object, or the name of an action.
3765      * @param {String/Function} url The URL (or function if using DirectProxy) to call for the action.
3766      */
3767     setApi : function() {
3768         if (arguments.length == 1) {
3769             var valid = Ext.data.Api.isValid(arguments[0]);
3770             if (valid === true) {
3771                 this.api = arguments[0];
3772             }
3773             else {
3774                 throw new Ext.data.Api.Error('invalid', valid);
3775             }
3776         }
3777         else if (arguments.length == 2) {
3778             if (!Ext.data.Api.isAction(arguments[0])) {
3779                 throw new Ext.data.Api.Error('invalid', arguments[0]);
3780             }
3781             this.api[arguments[0]] = arguments[1];
3782         }
3783         Ext.data.Api.prepare(this);
3784     },
3785
3786     /**
3787      * Returns true if the specified action is defined as a unique action in the api-config.
3788      * request.  If all API-actions are routed to unique urls, the xaction parameter is unecessary.  However, if no api is defined
3789      * and all Proxy actions are routed to DataProxy#url, the server-side will require the xaction parameter to perform a switch to
3790      * the corresponding code for CRUD action.
3791      * @param {String [Ext.data.Api.CREATE|READ|UPDATE|DESTROY]} action
3792      * @return {Boolean}
3793      */
3794     isApiAction : function(action) {
3795         return (this.api[action]) ? true : false;
3796     },
3797
3798     /**
3799      * All proxy actions are executed through this method.  Automatically fires the "before" + action event
3800      * @param {String} action Name of the action
3801      * @param {Ext.data.Record/Ext.data.Record[]/null} rs Will be null when action is 'load'
3802      * @param {Object} params
3803      * @param {Ext.data.DataReader} reader
3804      * @param {Function} callback
3805      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the Proxy object.
3806      * @param {Object} options Any options specified for the action (e.g. see {@link Ext.data.Store#load}.
3807      */
3808     request : function(action, rs, params, reader, callback, scope, options) {
3809         if (!this.api[action] && !this.load) {
3810             throw new Ext.data.DataProxy.Error('action-undefined', action);
3811         }
3812         params = params || {};
3813         if ((action === Ext.data.Api.actions.read) ? this.fireEvent("beforeload", this, params) : this.fireEvent("beforewrite", this, action, rs, params) !== false) {
3814             this.doRequest.apply(this, arguments);
3815         }
3816         else {
3817             callback.call(scope || this, null, options, false);
3818         }
3819     },
3820
3821
3822     /**
3823      * <b>Deprecated</b> load method using old method signature. See {@doRequest} for preferred method.
3824      * @deprecated
3825      * @param {Object} params
3826      * @param {Object} reader
3827      * @param {Object} callback
3828      * @param {Object} scope
3829      * @param {Object} arg
3830      */
3831     load : null,
3832
3833     /**
3834      * @cfg {Function} doRequest Abstract method that should be implemented in all subclasses.  <b>Note:</b> Should only be used by custom-proxy developers.
3835      * (e.g.: {@link Ext.data.HttpProxy#doRequest HttpProxy.doRequest},
3836      * {@link Ext.data.DirectProxy#doRequest DirectProxy.doRequest}).
3837      */
3838     doRequest : function(action, rs, params, reader, callback, scope, options) {
3839         // default implementation of doRequest for backwards compatibility with 2.0 proxies.
3840         // If we're executing here, the action is probably "load".
3841         // Call with the pre-3.0 method signature.
3842         this.load(params, reader, callback, scope, options);
3843     },
3844
3845     /**
3846      * @cfg {Function} onRead Abstract method that should be implemented in all subclasses.  <b>Note:</b> Should only be used by custom-proxy developers.  Callback for read {@link Ext.data.Api#actions action}.
3847      * @param {String} action Action name as per {@link Ext.data.Api.actions#read}.
3848      * @param {Object} o The request transaction object
3849      * @param {Object} res The server response
3850      * @fires loadexception (deprecated)
3851      * @fires exception
3852      * @fires load
3853      * @protected
3854      */
3855     onRead : Ext.emptyFn,
3856     /**
3857      * @cfg {Function} onWrite Abstract method that should be implemented in all subclasses.  <b>Note:</b> Should only be used by custom-proxy developers.  Callback for <i>create, update and destroy</i> {@link Ext.data.Api#actions actions}.
3858      * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
3859      * @param {Object} trans The request transaction object
3860      * @param {Object} res The server response
3861      * @fires exception
3862      * @fires write
3863      * @protected
3864      */
3865     onWrite : Ext.emptyFn,
3866     /**
3867      * buildUrl
3868      * Sets the appropriate url based upon the action being executed.  If restful is true, and only a single record is being acted upon,
3869      * url will be built Rails-style, as in "/controller/action/32".  restful will aply iff the supplied record is an
3870      * instance of Ext.data.Record rather than an Array of them.
3871      * @param {String} action The api action being executed [read|create|update|destroy]
3872      * @param {Ext.data.Record/Ext.data.Record[]} record The record or Array of Records being acted upon.
3873      * @return {String} url
3874      * @private
3875      */
3876     buildUrl : function(action, record) {
3877         record = record || null;
3878
3879         // conn.url gets nullified after each request.  If it's NOT null here, that means the user must have intervened with a call
3880         // to DataProxy#setUrl or DataProxy#setApi and changed it before the request was executed.  If that's the case, use conn.url,
3881         // otherwise, build the url from the api or this.url.
3882         var url = (this.conn && this.conn.url) ? this.conn.url : (this.api[action]) ? this.api[action].url : this.url;
3883         if (!url) {
3884             throw new Ext.data.Api.Error('invalid-url', action);
3885         }
3886
3887         // look for urls having "provides" suffix used in some MVC frameworks like Rails/Merb and others.  The provides suffice informs
3888         // the server what data-format the client is dealing with and returns data in the same format (eg: application/json, application/xml, etc)
3889         // e.g.: /users.json, /users.xml, etc.
3890         // with restful routes, we need urls like:
3891         // PUT /users/1.json
3892         // DELETE /users/1.json
3893         var provides = null;
3894         var m = url.match(/(.*)(\.json|\.xml|\.html)$/);
3895         if (m) {
3896             provides = m[2];    // eg ".json"
3897             url      = m[1];    // eg: "/users"
3898         }
3899         // prettyUrls is deprectated in favor of restful-config
3900         if ((this.restful === true || this.prettyUrls === true) && record instanceof Ext.data.Record && !record.phantom) {
3901             url += '/' + record.id;
3902         }
3903         return (provides === null) ? url : url + provides;
3904     },
3905
3906     /**
3907      * Destroys the proxy by purging any event listeners and cancelling any active requests.
3908      */
3909     destroy: function(){
3910         this.purgeListeners();
3911     }
3912 });
3913
3914 // Apply the Observable prototype to the DataProxy class so that proxy instances can relay their
3915 // events to the class.  Allows for centralized listening of all proxy instances upon the DataProxy class.
3916 Ext.apply(Ext.data.DataProxy, Ext.util.Observable.prototype);
3917 Ext.util.Observable.call(Ext.data.DataProxy);
3918
3919 /**
3920  * @class Ext.data.DataProxy.Error
3921  * @extends Ext.Error
3922  * DataProxy Error extension.
3923  * constructor
3924  * @param {String} message Message describing the error.
3925  * @param {Record/Record[]} arg
3926  */
3927 Ext.data.DataProxy.Error = Ext.extend(Ext.Error, {
3928     constructor : function(message, arg) {
3929         this.arg = arg;
3930         Ext.Error.call(this, message);
3931     },
3932     name: 'Ext.data.DataProxy'
3933 });
3934 Ext.apply(Ext.data.DataProxy.Error.prototype, {
3935     lang: {
3936         'action-undefined': "DataProxy attempted to execute an API-action but found an undefined url / function.  Please review your Proxy url/api-configuration.",
3937         'api-invalid': 'Recieved an invalid API-configuration.  Please ensure your proxy API-configuration contains only the actions from Ext.data.Api.actions.'
3938     }
3939 });
3940
3941
3942 /**
3943  * @class Ext.data.Request
3944  * A simple Request class used internally to the data package to provide more generalized remote-requests
3945  * to a DataProxy.
3946  * TODO Not yet implemented.  Implement in Ext.data.Store#execute
3947  */
3948 Ext.data.Request = function(params) {
3949     Ext.apply(this, params);
3950 };
3951 Ext.data.Request.prototype = {
3952     /**
3953      * @cfg {String} action
3954      */
3955     action : undefined,
3956     /**
3957      * @cfg {Ext.data.Record[]/Ext.data.Record} rs The Store recordset associated with the request.
3958      */
3959     rs : undefined,
3960     /**
3961      * @cfg {Object} params HTTP request params
3962      */
3963     params: undefined,
3964     /**
3965      * @cfg {Function} callback The function to call when request is complete
3966      */
3967     callback : Ext.emptyFn,
3968     /**
3969      * @cfg {Object} scope The scope of the callback funtion
3970      */
3971     scope : undefined,
3972     /**
3973      * @cfg {Ext.data.DataReader} reader The DataReader instance which will parse the received response
3974      */
3975     reader : undefined
3976 };
3977 /**
3978  * @class Ext.data.Response
3979  * A generic response class to normalize response-handling internally to the framework.
3980  */
3981 Ext.data.Response = function(params) {
3982     Ext.apply(this, params);
3983 };
3984 Ext.data.Response.prototype = {
3985     /**
3986      * @cfg {String} action {@link Ext.data.Api#actions}
3987      */
3988     action: undefined,
3989     /**
3990      * @cfg {Boolean} success
3991      */
3992     success : undefined,
3993     /**
3994      * @cfg {String} message
3995      */
3996     message : undefined,
3997     /**
3998      * @cfg {Array/Object} data
3999      */
4000     data: undefined,
4001     /**
4002      * @cfg {Object} raw The raw response returned from server-code
4003      */
4004     raw: undefined,
4005     /**
4006      * @cfg {Ext.data.Record/Ext.data.Record[]} records related to the Request action
4007      */
4008     records: undefined
4009 };
4010 /**
4011  * @class Ext.data.ScriptTagProxy
4012  * @extends Ext.data.DataProxy
4013  * An implementation of Ext.data.DataProxy that reads a data object from a URL which may be in a domain
4014  * other than the originating domain of the running page.<br>
4015  * <p>
4016  * <b>Note that if you are retrieving data from a page that is in a domain that is NOT the same as the originating domain
4017  * of the running page, you must use this class, rather than HttpProxy.</b><br>
4018  * <p>
4019  * The content passed back from a server resource requested by a ScriptTagProxy <b>must</b> be executable JavaScript
4020  * source code because it is used as the source inside a &lt;script> tag.<br>
4021  * <p>
4022  * In order for the browser to process the returned data, the server must wrap the data object
4023  * with a call to a callback function, the name of which is passed as a parameter by the ScriptTagProxy.
4024  * Below is a Java example for a servlet which returns data for either a ScriptTagProxy, or an HttpProxy
4025  * depending on whether the callback name was passed:
4026  * <p>
4027  * <pre><code>
4028 boolean scriptTag = false;
4029 String cb = request.getParameter("callback");
4030 if (cb != null) {
4031     scriptTag = true;
4032     response.setContentType("text/javascript");
4033 } else {
4034     response.setContentType("application/x-json");
4035 }
4036 Writer out = response.getWriter();
4037 if (scriptTag) {
4038     out.write(cb + "(");
4039 }
4040 out.print(dataBlock.toJsonString());
4041 if (scriptTag) {
4042     out.write(");");
4043 }
4044 </code></pre>
4045  * <p>Below is a PHP example to do the same thing:</p><pre><code>
4046 $callback = $_REQUEST['callback'];
4047
4048 // Create the output object.
4049 $output = array('a' => 'Apple', 'b' => 'Banana');
4050
4051 //start output
4052 if ($callback) {
4053     header('Content-Type: text/javascript');
4054     echo $callback . '(' . json_encode($output) . ');';
4055 } else {
4056     header('Content-Type: application/x-json');
4057     echo json_encode($output);
4058 }
4059 </code></pre>
4060  * <p>Below is the ASP.Net code to do the same thing:</p><pre><code>
4061 String jsonString = "{success: true}";
4062 String cb = Request.Params.Get("callback");
4063 String responseString = "";
4064 if (!String.IsNullOrEmpty(cb)) {
4065     responseString = cb + "(" + jsonString + ")";
4066 } else {
4067     responseString = jsonString;
4068 }
4069 Response.Write(responseString);
4070 </code></pre>
4071  *
4072  * @constructor
4073  * @param {Object} config A configuration object.
4074  */
4075 Ext.data.ScriptTagProxy = function(config){
4076     Ext.apply(this, config);
4077
4078     Ext.data.ScriptTagProxy.superclass.constructor.call(this, config);
4079
4080     this.head = document.getElementsByTagName("head")[0];
4081
4082     /**
4083      * @event loadexception
4084      * <b>Deprecated</b> in favor of 'exception' event.
4085      * Fires if an exception occurs in the Proxy during data loading.  This event can be fired for one of two reasons:
4086      * <ul><li><b>The load call timed out.</b>  This means the load callback did not execute within the time limit
4087      * specified by {@link #timeout}.  In this case, this event will be raised and the
4088      * fourth parameter (read error) will be null.</li>
4089      * <li><b>The load succeeded but the reader could not read the response.</b>  This means the server returned
4090      * data, but the configured Reader threw an error while reading the data.  In this case, this event will be
4091      * raised and the caught error will be passed along as the fourth parameter of this event.</li></ul>
4092      * Note that this event is also relayed through {@link Ext.data.Store}, so you can listen for it directly
4093      * on any Store instance.
4094      * @param {Object} this
4095      * @param {Object} options The loading options that were specified (see {@link #load} for details).  If the load
4096      * call timed out, this parameter will be null.
4097      * @param {Object} arg The callback's arg object passed to the {@link #load} function
4098      * @param {Error} e The JavaScript Error object caught if the configured Reader could not read the data.
4099      * If the remote request returns success: false, this parameter will be null.
4100      */
4101 };
4102
4103 Ext.data.ScriptTagProxy.TRANS_ID = 1000;
4104
4105 Ext.extend(Ext.data.ScriptTagProxy, Ext.data.DataProxy, {
4106     /**
4107      * @cfg {String} url The URL from which to request the data object.
4108      */
4109     /**
4110      * @cfg {Number} timeout (optional) The number of milliseconds to wait for a response. Defaults to 30 seconds.
4111      */
4112     timeout : 30000,
4113     /**
4114      * @cfg {String} callbackParam (Optional) The name of the parameter to pass to the server which tells
4115      * the server the name of the callback function set up by the load call to process the returned data object.
4116      * Defaults to "callback".<p>The server-side processing must read this parameter value, and generate
4117      * javascript output which calls this named function passing the data object as its only parameter.
4118      */
4119     callbackParam : "callback",
4120     /**
4121      *  @cfg {Boolean} nocache (optional) Defaults to true. Disable caching by adding a unique parameter
4122      * name to the request.
4123      */
4124     nocache : true,
4125
4126     /**
4127      * HttpProxy implementation of DataProxy#doRequest
4128      * @param {String} action
4129      * @param {Ext.data.Record/Ext.data.Record[]} rs If action is <tt>read</tt>, rs will be null
4130      * @param {Object} params An object containing properties which are to be used as HTTP parameters
4131      * for the request to the remote server.
4132      * @param {Ext.data.DataReader} reader The Reader object which converts the data
4133      * object into a block of Ext.data.Records.
4134      * @param {Function} callback The function into which to pass the block of Ext.data.Records.
4135      * The function must be passed <ul>
4136      * <li>The Record block object</li>
4137      * <li>The "arg" argument from the load function</li>
4138      * <li>A boolean success indicator</li>
4139      * </ul>
4140      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
4141      * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
4142      */
4143     doRequest : function(action, rs, params, reader, callback, scope, arg) {
4144         var p = Ext.urlEncode(Ext.apply(params, this.extraParams));
4145
4146         var url = this.buildUrl(action, rs);
4147         if (!url) {
4148             throw new Ext.data.Api.Error('invalid-url', url);
4149         }
4150         url = Ext.urlAppend(url, p);
4151
4152         if(this.nocache){
4153             url = Ext.urlAppend(url, '_dc=' + (new Date().getTime()));
4154         }
4155         var transId = ++Ext.data.ScriptTagProxy.TRANS_ID;
4156         var trans = {
4157             id : transId,
4158             action: action,
4159             cb : "stcCallback"+transId,
4160             scriptId : "stcScript"+transId,
4161             params : params,
4162             arg : arg,
4163             url : url,
4164             callback : callback,
4165             scope : scope,
4166             reader : reader
4167         };
4168         window[trans.cb] = this.createCallback(action, rs, trans);
4169         url += String.format("&{0}={1}", this.callbackParam, trans.cb);
4170         if(this.autoAbort !== false){
4171             this.abort();
4172         }
4173
4174         trans.timeoutId = this.handleFailure.defer(this.timeout, this, [trans]);
4175
4176         var script = document.createElement("script");
4177         script.setAttribute("src", url);
4178         script.setAttribute("type", "text/javascript");
4179         script.setAttribute("id", trans.scriptId);
4180         this.head.appendChild(script);
4181
4182         this.trans = trans;
4183     },
4184
4185     // @private createCallback
4186     createCallback : function(action, rs, trans) {
4187         var self = this;
4188         return function(res) {
4189             self.trans = false;
4190             self.destroyTrans(trans, true);
4191             if (action === Ext.data.Api.actions.read) {
4192                 self.onRead.call(self, action, trans, res);
4193             } else {
4194                 self.onWrite.call(self, action, trans, res, rs);
4195             }
4196         };
4197     },
4198     /**
4199      * Callback for read actions
4200      * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
4201      * @param {Object} trans The request transaction object
4202      * @param {Object} res The server response
4203      * @protected
4204      */
4205     onRead : function(action, trans, res) {
4206         var result;
4207         try {
4208             result = trans.reader.readRecords(res);
4209         }catch(e){
4210             // @deprecated: fire loadexception
4211             this.fireEvent("loadexception", this, trans, res, e);
4212
4213             this.fireEvent('exception', this, 'response', action, trans, res, e);
4214             trans.callback.call(trans.scope||window, null, trans.arg, false);
4215             return;
4216         }
4217         if (result.success === false) {
4218             // @deprecated: fire old loadexception for backwards-compat.
4219             this.fireEvent('loadexception', this, trans, res);
4220
4221             this.fireEvent('exception', this, 'remote', action, trans, res, null);
4222         } else {
4223             this.fireEvent("load", this, res, trans.arg);
4224         }
4225         trans.callback.call(trans.scope||window, result, trans.arg, result.success);
4226     },
4227     /**
4228      * Callback for write actions
4229      * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
4230      * @param {Object} trans The request transaction object
4231      * @param {Object} res The server response
4232      * @protected
4233      */
4234     onWrite : function(action, trans, response, rs) {
4235         var reader = trans.reader;
4236         try {
4237             // though we already have a response object here in STP, run through readResponse to catch any meta-data exceptions.
4238             var res = reader.readResponse(action, response);
4239         } catch (e) {
4240             this.fireEvent('exception', this, 'response', action, trans, res, e);
4241             trans.callback.call(trans.scope||window, null, res, false);
4242             return;
4243         }
4244         if(!res.success === true){
4245             this.fireEvent('exception', this, 'remote', action, trans, res, rs);
4246             trans.callback.call(trans.scope||window, null, res, false);
4247             return;
4248         }
4249         this.fireEvent("write", this, action, res.data, res, rs, trans.arg );
4250         trans.callback.call(trans.scope||window, res.data, res, true);
4251     },
4252
4253     // private
4254     isLoading : function(){
4255         return this.trans ? true : false;
4256     },
4257
4258     /**
4259      * Abort the current server request.
4260      */
4261     abort : function(){
4262         if(this.isLoading()){
4263             this.destroyTrans(this.trans);
4264         }
4265     },
4266
4267     // private
4268     destroyTrans : function(trans, isLoaded){
4269         this.head.removeChild(document.getElementById(trans.scriptId));
4270         clearTimeout(trans.timeoutId);
4271         if(isLoaded){
4272             window[trans.cb] = undefined;
4273             try{
4274                 delete window[trans.cb];
4275             }catch(e){}
4276         }else{
4277             // if hasn't been loaded, wait for load to remove it to prevent script error
4278             window[trans.cb] = function(){
4279                 window[trans.cb] = undefined;
4280                 try{
4281                     delete window[trans.cb];
4282                 }catch(e){}
4283             };
4284         }
4285     },
4286
4287     // private
4288     handleFailure : function(trans){
4289         this.trans = false;
4290         this.destroyTrans(trans, false);
4291         if (trans.action === Ext.data.Api.actions.read) {
4292             // @deprecated firing loadexception
4293             this.fireEvent("loadexception", this, null, trans.arg);
4294         }
4295
4296         this.fireEvent('exception', this, 'response', trans.action, {
4297             response: null,
4298             options: trans.arg
4299         });
4300         trans.callback.call(trans.scope||window, null, trans.arg, false);
4301     },
4302
4303     // inherit docs
4304     destroy: function(){
4305         this.abort();
4306         Ext.data.ScriptTagProxy.superclass.destroy.call(this);
4307     }
4308 });/**
4309  * @class Ext.data.HttpProxy
4310  * @extends Ext.data.DataProxy
4311  * <p>An implementation of {@link Ext.data.DataProxy} that processes data requests within the same
4312  * domain of the originating page.</p>
4313  * <p><b>Note</b>: this class cannot be used to retrieve data from a domain other
4314  * than the domain from which the running page was served. For cross-domain requests, use a
4315  * {@link Ext.data.ScriptTagProxy ScriptTagProxy}.</p>
4316  * <p>Be aware that to enable the browser to parse an XML document, the server must set
4317  * the Content-Type header in the HTTP response to "<tt>text/xml</tt>".</p>
4318  * @constructor
4319  * @param {Object} conn
4320  * An {@link Ext.data.Connection} object, or options parameter to {@link Ext.Ajax#request}.
4321  * <p>Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the
4322  * Store's call to {@link #load} will override any specified <tt>callback</tt> and <tt>params</tt>
4323  * options. In this case, use the Store's {@link Ext.data.Store#events events} to modify parameters,
4324  * or react to loading events. The Store's {@link Ext.data.Store#baseParams baseParams} may also be
4325  * used to pass parameters known at instantiation time.</p>
4326  * <p>If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make
4327  * the request.</p>
4328  */
4329 Ext.data.HttpProxy = function(conn){
4330     Ext.data.HttpProxy.superclass.constructor.call(this, conn);
4331
4332     /**
4333      * The Connection object (Or options parameter to {@link Ext.Ajax#request}) which this HttpProxy
4334      * uses to make requests to the server. Properties of this object may be changed dynamically to
4335      * change the way data is requested.
4336      * @property
4337      */
4338     this.conn = conn;
4339
4340     // nullify the connection url.  The url param has been copied to 'this' above.  The connection
4341     // url will be set during each execution of doRequest when buildUrl is called.  This makes it easier for users to override the
4342     // connection url during beforeaction events (ie: beforeload, beforewrite, etc).
4343     // Url is always re-defined during doRequest.
4344     this.conn.url = null;
4345
4346     this.useAjax = !conn || !conn.events;
4347
4348     // A hash containing active requests, keyed on action [Ext.data.Api.actions.create|read|update|destroy]
4349     var actions = Ext.data.Api.actions;
4350     this.activeRequest = {};
4351     for (var verb in actions) {
4352         this.activeRequest[actions[verb]] = undefined;
4353     }
4354 };
4355
4356 Ext.extend(Ext.data.HttpProxy, Ext.data.DataProxy, {
4357     /**
4358      * Return the {@link Ext.data.Connection} object being used by this Proxy.
4359      * @return {Connection} The Connection object. This object may be used to subscribe to events on
4360      * a finer-grained basis than the DataProxy events.
4361      */
4362     getConnection : function() {
4363         return this.useAjax ? Ext.Ajax : this.conn;
4364     },
4365
4366     /**
4367      * Used for overriding the url used for a single request.  Designed to be called during a beforeaction event.  Calling setUrl
4368      * will override any urls set via the api configuration parameter.  Set the optional parameter makePermanent to set the url for
4369      * all subsequent requests.  If not set to makePermanent, the next request will use the same url or api configuration defined
4370      * in the initial proxy configuration.
4371      * @param {String} url
4372      * @param {Boolean} makePermanent (Optional) [false]
4373      *
4374      * (e.g.: beforeload, beforesave, etc).
4375      */
4376     setUrl : function(url, makePermanent) {
4377         this.conn.url = url;
4378         if (makePermanent === true) {
4379             this.url = url;
4380             this.api = null;
4381             Ext.data.Api.prepare(this);
4382         }
4383     },
4384
4385     /**
4386      * HttpProxy implementation of DataProxy#doRequest
4387      * @param {String} action The crud action type (create, read, update, destroy)
4388      * @param {Ext.data.Record/Ext.data.Record[]} rs If action is load, rs will be null
4389      * @param {Object} params An object containing properties which are to be used as HTTP parameters
4390      * for the request to the remote server.
4391      * @param {Ext.data.DataReader} reader The Reader object which converts the data
4392      * object into a block of Ext.data.Records.
4393      * @param {Function} callback
4394      * <div class="sub-desc"><p>A function to be called after the request.
4395      * The <tt>callback</tt> is passed the following arguments:<ul>
4396      * <li><tt>r</tt> : Ext.data.Record[] The block of Ext.data.Records.</li>
4397      * <li><tt>options</tt>: Options object from the action request</li>
4398      * <li><tt>success</tt>: Boolean success indicator</li></ul></p></div>
4399      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
4400      * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
4401      * @protected
4402      */
4403     doRequest : function(action, rs, params, reader, cb, scope, arg) {
4404         var  o = {
4405             method: (this.api[action]) ? this.api[action]['method'] : undefined,
4406             request: {
4407                 callback : cb,
4408                 scope : scope,
4409                 arg : arg
4410             },
4411             reader: reader,
4412             callback : this.createCallback(action, rs),
4413             scope: this
4414         };
4415
4416         // If possible, transmit data using jsonData || xmlData on Ext.Ajax.request (An installed DataWriter would have written it there.).
4417         // Use std HTTP params otherwise.
4418         if (params.jsonData) {
4419             o.jsonData = params.jsonData;
4420         } else if (params.xmlData) {
4421             o.xmlData = params.xmlData;
4422         } else {
4423             o.params = params || {};
4424         }
4425         // Set the connection url.  If this.conn.url is not null here,
4426         // the user must have overridden the url during a beforewrite/beforeload event-handler.
4427         // this.conn.url is nullified after each request.
4428         this.conn.url = this.buildUrl(action, rs);
4429
4430         if(this.useAjax){
4431
4432             Ext.applyIf(o, this.conn);
4433
4434             // If a currently running request is found for this action, abort it.
4435             if (this.activeRequest[action]) {
4436                 ////
4437                 // Disabled aborting activeRequest while implementing REST.  activeRequest[action] will have to become an array
4438                 // TODO ideas anyone?
4439                 //
4440                 //Ext.Ajax.abort(this.activeRequest[action]);
4441             }
4442             this.activeRequest[action] = Ext.Ajax.request(o);
4443         }else{
4444             this.conn.request(o);
4445         }
4446         // request is sent, nullify the connection url in preparation for the next request
4447         this.conn.url = null;
4448     },
4449
4450     /**
4451      * Returns a callback function for a request.  Note a special case is made for the
4452      * read action vs all the others.
4453      * @param {String} action [create|update|delete|load]
4454      * @param {Ext.data.Record[]} rs The Store-recordset being acted upon
4455      * @private
4456      */
4457     createCallback : function(action, rs) {
4458         return function(o, success, response) {
4459             this.activeRequest[action] = undefined;
4460             if (!success) {
4461                 if (action === Ext.data.Api.actions.read) {
4462                     // @deprecated: fire loadexception for backwards compat.
4463                     // TODO remove
4464                     this.fireEvent('loadexception', this, o, response);
4465                 }
4466                 this.fireEvent('exception', this, 'response', action, o, response);
4467                 o.request.callback.call(o.request.scope, null, o.request.arg, false);
4468                 return;
4469             }
4470             if (action === Ext.data.Api.actions.read) {
4471                 this.onRead(action, o, response);
4472             } else {
4473                 this.onWrite(action, o, response, rs);
4474             }
4475         };
4476     },
4477
4478     /**
4479      * Callback for read action
4480      * @param {String} action Action name as per {@link Ext.data.Api.actions#read}.
4481      * @param {Object} o The request transaction object
4482      * @param {Object} res The server response
4483      * @fires loadexception (deprecated)
4484      * @fires exception
4485      * @fires load
4486      * @protected
4487      */
4488     onRead : function(action, o, response) {
4489         var result;
4490         try {
4491             result = o.reader.read(response);
4492         }catch(e){
4493             // @deprecated: fire old loadexception for backwards-compat.
4494             // TODO remove
4495             this.fireEvent('loadexception', this, o, response, e);
4496
4497             this.fireEvent('exception', this, 'response', action, o, response, e);
4498             o.request.callback.call(o.request.scope, null, o.request.arg, false);
4499             return;
4500         }
4501         if (result.success === false) {
4502             // @deprecated: fire old loadexception for backwards-compat.
4503             // TODO remove
4504             this.fireEvent('loadexception', this, o, response);
4505
4506             // Get DataReader read-back a response-object to pass along to exception event
4507             var res = o.reader.readResponse(action, response);
4508             this.fireEvent('exception', this, 'remote', action, o, res, null);
4509         }
4510         else {
4511             this.fireEvent('load', this, o, o.request.arg);
4512         }
4513         // TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance
4514         // the calls to request.callback(...) in each will have to be made identical.
4515         // NOTE reader.readResponse does not currently return Ext.data.Response
4516         o.request.callback.call(o.request.scope, result, o.request.arg, result.success);
4517     },
4518     /**
4519      * Callback for write actions
4520      * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
4521      * @param {Object} trans The request transaction object
4522      * @param {Object} res The server response
4523      * @fires exception
4524      * @fires write
4525      * @protected
4526      */
4527     onWrite : function(action, o, response, rs) {
4528         var reader = o.reader;
4529         var res;
4530         try {
4531             res = reader.readResponse(action, response);
4532         } catch (e) {
4533             this.fireEvent('exception', this, 'response', action, o, response, e);
4534             o.request.callback.call(o.request.scope, null, o.request.arg, false);
4535             return;
4536         }
4537         if (res.success === true) {
4538             this.fireEvent('write', this, action, res.data, res, rs, o.request.arg);
4539         } else {
4540             this.fireEvent('exception', this, 'remote', action, o, res, rs);
4541         }
4542         // TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance
4543         // the calls to request.callback(...) in each will have to be made similar.
4544         // NOTE reader.readResponse does not currently return Ext.data.Response
4545         o.request.callback.call(o.request.scope, res.data, res, res.success);
4546     },
4547
4548     // inherit docs
4549     destroy: function(){
4550         if(!this.useAjax){
4551             this.conn.abort();
4552         }else if(this.activeRequest){
4553             var actions = Ext.data.Api.actions;
4554             for (var verb in actions) {
4555                 if(this.activeRequest[actions[verb]]){
4556                     Ext.Ajax.abort(this.activeRequest[actions[verb]]);
4557                 }
4558             }
4559         }
4560         Ext.data.HttpProxy.superclass.destroy.call(this);
4561     }
4562 });/**
4563  * @class Ext.data.MemoryProxy
4564  * @extends Ext.data.DataProxy
4565  * An implementation of Ext.data.DataProxy that simply passes the data specified in its constructor
4566  * to the Reader when its load method is called.
4567  * @constructor
4568  * @param {Object} data The data object which the Reader uses to construct a block of Ext.data.Records.
4569  */
4570 Ext.data.MemoryProxy = function(data){
4571     // Must define a dummy api with "read" action to satisfy DataProxy#doRequest and Ext.data.Api#prepare *before* calling super
4572     var api = {};
4573     api[Ext.data.Api.actions.read] = true;
4574     Ext.data.MemoryProxy.superclass.constructor.call(this, {
4575         api: api
4576     });
4577     this.data = data;
4578 };
4579
4580 Ext.extend(Ext.data.MemoryProxy, Ext.data.DataProxy, {
4581     /**
4582      * @event loadexception
4583      * Fires if an exception occurs in the Proxy during data loading. Note that this event is also relayed
4584      * through {@link Ext.data.Store}, so you can listen for it directly on any Store instance.
4585      * @param {Object} this
4586      * @param {Object} arg The callback's arg object passed to the {@link #load} function
4587      * @param {Object} null This parameter does not apply and will always be null for MemoryProxy
4588      * @param {Error} e The JavaScript Error object caught if the configured Reader could not read the data
4589      */
4590
4591        /**
4592      * MemoryProxy implementation of DataProxy#doRequest
4593      * @param {String} action
4594      * @param {Ext.data.Record/Ext.data.Record[]} rs If action is load, rs will be null
4595      * @param {Object} params An object containing properties which are to be used as HTTP parameters
4596      * for the request to the remote server.
4597      * @param {Ext.data.DataReader} reader The Reader object which converts the data
4598      * object into a block of Ext.data.Records.
4599      * @param {Function} callback The function into which to pass the block of Ext.data.Records.
4600      * The function must be passed <ul>
4601      * <li>The Record block object</li>
4602      * <li>The "arg" argument from the load function</li>
4603      * <li>A boolean success indicator</li>
4604      * </ul>
4605      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
4606      * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
4607      */
4608     doRequest : function(action, rs, params, reader, callback, scope, arg) {
4609         // No implementation for CRUD in MemoryProxy.  Assumes all actions are 'load'
4610         params = params || {};
4611         var result;
4612         try {
4613             result = reader.readRecords(this.data);
4614         }catch(e){
4615             // @deprecated loadexception
4616             this.fireEvent("loadexception", this, null, arg, e);
4617
4618             this.fireEvent('exception', this, 'response', action, arg, null, e);
4619             callback.call(scope, null, arg, false);
4620             return;
4621         }
4622         callback.call(scope, result, arg, true);
4623     }
4624 });/**
4625  * @class Ext.data.Types
4626  * <p>This is s static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
4627  * <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
4628  * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
4629  * of this class.</p>
4630  * <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
4631  * each type definition must contain three properties:</p>
4632  * <div class="mdetail-params"><ul>
4633  * <li><code>convert</code> : <i>Function</i><div class="sub-desc">A function to convert raw data values from a data block into the data
4634  * to be stored in the Field. The function is passed the collowing parameters:
4635  * <div class="mdetail-params"><ul>
4636  * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
4637  * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
4638  * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
4639  * Depending on the Reader type, this could be an Array ({@link Ext.data.ArrayReader ArrayReader}), an object
4640  * ({@link Ext.data.JsonReader JsonReader}), or an XML element ({@link Ext.data.XMLReader XMLReader}).</div></li>
4641  * </ul></div></div></li>
4642  * <li><code>sortType</code> : <i>Function</i> <div class="sub-desc">A function to convert the stored data into comparable form, as defined by {@link Ext.data.SortTypes}.</div></li>
4643  * <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
4644  * </ul></div>
4645  * <p>For example, to create a VELatLong field (See the Microsoft Bing Mapping API) containing the latitude/longitude value of a datapoint on a map from a JsonReader data block
4646  * which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
4647  *<pre><code>
4648 // Add a new Field data type which stores a VELatLong object in the Record.
4649 Ext.data.Types.VELATLONG = {
4650     convert: function(v, data) {
4651         return new VELatLong(data.lat, data.long);
4652     },
4653     sortType: function(v) {
4654         return v.Latitude;  // When sorting, order by latitude
4655     },
4656     type: 'VELatLong'
4657 };
4658 </code></pre>
4659  * <p>Then, when declaring a Record, use <pre><code>
4660 var types = Ext.data.Types; // allow shorthand type access
4661 UnitRecord = Ext.data.Record.create([
4662     { name: 'unitName', mapping: 'UnitName' },
4663     { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
4664     { name: 'latitude', mapping: 'lat', type: types.FLOAT },
4665     { name: 'latitude', mapping: 'lat', type: types.FLOAT },
4666     { name: 'position', type: types.VELATLONG }
4667 ]);
4668 </code></pre>
4669  * @singleton
4670  */
4671 Ext.data.Types = new function(){
4672     var st = Ext.data.SortTypes;
4673     Ext.apply(this, {
4674         /**
4675          * @type Regexp
4676          * @property stripRe
4677          * A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
4678          * This should be overridden for localization.
4679          */
4680         stripRe: /[\$,%]/g,
4681         
4682         /**
4683          * @type Object.
4684          * @property AUTO
4685          * This data type means that no conversion is applied to the raw data before it is placed into a Record.
4686          */
4687         AUTO: {
4688             convert: function(v){ return v; },
4689             sortType: st.none,
4690             type: 'auto'
4691         },
4692
4693         /**
4694          * @type Object.
4695          * @property STRING
4696          * This data type means that the raw data is converted into a String before it is placed into a Record.
4697          */
4698         STRING: {
4699             convert: function(v){ return (v === undefined || v === null) ? '' : String(v); },
4700             sortType: st.asUCString,
4701             type: 'string'
4702         },
4703
4704         /**
4705          * @type Object.
4706          * @property INT
4707          * This data type means that the raw data is converted into an integer before it is placed into a Record.
4708          * <p>The synonym <code>INTEGER</code> is equivalent.</p>
4709          */
4710         INT: {
4711             convert: function(v){
4712                 return v !== undefined && v !== null && v !== '' ?
4713                     parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
4714             },
4715             sortType: st.none,
4716             type: 'int'
4717         },
4718         
4719         /**
4720          * @type Object.
4721          * @property FLOAT
4722          * This data type means that the raw data is converted into a number before it is placed into a Record.
4723          * <p>The synonym <code>NUMBER</code> is equivalent.</p>
4724          */
4725         FLOAT: {
4726             convert: function(v){
4727                 return v !== undefined && v !== null && v !== '' ?
4728                     parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
4729             },
4730             sortType: st.none,
4731             type: 'float'
4732         },
4733         
4734         /**
4735          * @type Object.
4736          * @property BOOL
4737          * <p>This data type means that the raw data is converted into a boolean before it is placed into
4738          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
4739          * <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
4740          */
4741         BOOL: {
4742             convert: function(v){ return v === true || v === 'true' || v == 1; },
4743             sortType: st.none,
4744             type: 'bool'
4745         },
4746         
4747         /**
4748          * @type Object.
4749          * @property DATE
4750          * This data type means that the raw data is converted into a Date before it is placed into a Record.
4751          * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
4752          * being applied.
4753          */
4754         DATE: {
4755             convert: function(v){
4756                 var df = this.dateFormat;
4757                 if(!v){
4758                     return null;
4759                 }
4760                 if(Ext.isDate(v)){
4761                     return v;
4762                 }
4763                 if(df){
4764                     if(df == 'timestamp'){
4765                         return new Date(v*1000);
4766                     }
4767                     if(df == 'time'){
4768                         return new Date(parseInt(v, 10));
4769                     }
4770                     return Date.parseDate(v, df);
4771                 }
4772                 var parsed = Date.parse(v);
4773                 return parsed ? new Date(parsed) : null;
4774             },
4775             sortType: st.asDate,
4776             type: 'date'
4777         }
4778     });
4779     
4780     Ext.apply(this, {
4781         /**
4782          * @type Object.
4783          * @property BOOLEAN
4784          * <p>This data type means that the raw data is converted into a boolean before it is placed into
4785          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
4786          * <p>The synonym <code>BOOL</code> is equivalent.</p>
4787          */
4788         BOOLEAN: this.BOOL,
4789         /**
4790          * @type Object.
4791          * @property INTEGER
4792          * This data type means that the raw data is converted into an integer before it is placed into a Record.
4793          * <p>The synonym <code>INT</code> is equivalent.</p>
4794          */
4795         INTEGER: this.INT,
4796         /**
4797          * @type Object.
4798          * @property NUMBER
4799          * This data type means that the raw data is converted into a number before it is placed into a Record.
4800          * <p>The synonym <code>FLOAT</code> is equivalent.</p>
4801          */
4802         NUMBER: this.FLOAT    
4803     });
4804 };