Upgrade to ExtJS 3.2.1 - Released 04/27/2010
[extjs.git] / pkgs / data-foundation-debug.js
1 /*!
2  * Ext JS Library 3.2.1
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.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     /**
1099      * @property isDestroyed
1100      * @type Boolean
1101      * True if the store has been destroyed already. Read only
1102      */
1103     isDestroyed: false,
1104
1105     /**
1106      * @property hasMultiSort
1107      * @type Boolean
1108      * True if this store is currently sorted by more than one field/direction combination.
1109      */
1110     hasMultiSort: false,
1111
1112     // private
1113     batchKey : '_ext_batch_',
1114
1115     constructor : function(config){
1116         this.data = new Ext.util.MixedCollection(false);
1117         this.data.getKey = function(o){
1118             return o.id;
1119         };
1120
1121
1122         // temporary removed-records cache
1123         this.removed = [];
1124
1125         if(config && config.data){
1126             this.inlineData = config.data;
1127             delete config.data;
1128         }
1129
1130         Ext.apply(this, config);
1131
1132         /**
1133          * See the <code>{@link #baseParams corresponding configuration option}</code>
1134          * for a description of this property.
1135          * To modify this property see <code>{@link #setBaseParam}</code>.
1136          * @property
1137          */
1138         this.baseParams = Ext.isObject(this.baseParams) ? this.baseParams : {};
1139
1140         this.paramNames = Ext.applyIf(this.paramNames || {}, this.defaultParamNames);
1141
1142         if((this.url || this.api) && !this.proxy){
1143             this.proxy = new Ext.data.HttpProxy({url: this.url, api: this.api});
1144         }
1145         // If Store is RESTful, so too is the DataProxy
1146         if (this.restful === true && this.proxy) {
1147             // When operating RESTfully, a unique transaction is generated for each record.
1148             // TODO might want to allow implemention of faux REST where batch is possible using RESTful routes only.
1149             this.batch = false;
1150             Ext.data.Api.restify(this.proxy);
1151         }
1152
1153         if(this.reader){ // reader passed
1154             if(!this.recordType){
1155                 this.recordType = this.reader.recordType;
1156             }
1157             if(this.reader.onMetaChange){
1158                 this.reader.onMetaChange = this.reader.onMetaChange.createSequence(this.onMetaChange, this);
1159             }
1160             if (this.writer) { // writer passed
1161                 if (this.writer instanceof(Ext.data.DataWriter) === false) {    // <-- config-object instead of instance.
1162                     this.writer = this.buildWriter(this.writer);
1163                 }
1164                 this.writer.meta = this.reader.meta;
1165                 this.pruneModifiedRecords = true;
1166             }
1167         }
1168
1169         /**
1170          * The {@link Ext.data.Record Record} constructor as supplied to (or created by) the
1171          * {@link Ext.data.DataReader Reader}. Read-only.
1172          * <p>If the Reader was constructed by passing in an Array of {@link Ext.data.Field} definition objects,
1173          * instead of a Record constructor, it will implicitly create a Record constructor from that Array (see
1174          * {@link Ext.data.Record}.{@link Ext.data.Record#create create} for additional details).</p>
1175          * <p>This property may be used to create new Records of the type held in this Store, for example:</p><pre><code>
1176     // create the data store
1177     var store = new Ext.data.ArrayStore({
1178         autoDestroy: true,
1179         fields: [
1180            {name: 'company'},
1181            {name: 'price', type: 'float'},
1182            {name: 'change', type: 'float'},
1183            {name: 'pctChange', type: 'float'},
1184            {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
1185         ]
1186     });
1187     store.loadData(myData);
1188
1189     // create the Grid
1190     var grid = new Ext.grid.EditorGridPanel({
1191         store: store,
1192         colModel: new Ext.grid.ColumnModel({
1193             columns: [
1194                 {id:'company', header: 'Company', width: 160, dataIndex: 'company'},
1195                 {header: 'Price', renderer: 'usMoney', dataIndex: 'price'},
1196                 {header: 'Change', renderer: change, dataIndex: 'change'},
1197                 {header: '% Change', renderer: pctChange, dataIndex: 'pctChange'},
1198                 {header: 'Last Updated', width: 85,
1199                     renderer: Ext.util.Format.dateRenderer('m/d/Y'),
1200                     dataIndex: 'lastChange'}
1201             ],
1202             defaults: {
1203                 sortable: true,
1204                 width: 75
1205             }
1206         }),
1207         autoExpandColumn: 'company', // match the id specified in the column model
1208         height:350,
1209         width:600,
1210         title:'Array Grid',
1211         tbar: [{
1212             text: 'Add Record',
1213             handler : function(){
1214                 var defaultData = {
1215                     change: 0,
1216                     company: 'New Company',
1217                     lastChange: (new Date()).clearTime(),
1218                     pctChange: 0,
1219                     price: 10
1220                 };
1221                 var recId = 3; // provide unique id
1222                 var p = new store.recordType(defaultData, recId); // create new record
1223                 grid.stopEditing();
1224                 store.{@link #insert}(0, p); // insert a new record into the store (also see {@link #add})
1225                 grid.startEditing(0, 0);
1226             }
1227         }]
1228     });
1229          * </code></pre>
1230          * @property recordType
1231          * @type Function
1232          */
1233
1234         if(this.recordType){
1235             /**
1236              * A {@link Ext.util.MixedCollection MixedCollection} containing the defined {@link Ext.data.Field Field}s
1237              * for the {@link Ext.data.Record Records} stored in this Store. Read-only.
1238              * @property fields
1239              * @type Ext.util.MixedCollection
1240              */
1241             this.fields = this.recordType.prototype.fields;
1242         }
1243         this.modified = [];
1244
1245         this.addEvents(
1246             /**
1247              * @event datachanged
1248              * Fires when the data cache has changed in a bulk manner (e.g., it has been sorted, filtered, etc.) and a
1249              * widget that is using this Store as a Record cache should refresh its view.
1250              * @param {Store} this
1251              */
1252             'datachanged',
1253             /**
1254              * @event metachange
1255              * Fires when this store's reader provides new metadata (fields). This is currently only supported for JsonReaders.
1256              * @param {Store} this
1257              * @param {Object} meta The JSON metadata
1258              */
1259             'metachange',
1260             /**
1261              * @event add
1262              * Fires when Records have been {@link #add}ed to the Store
1263              * @param {Store} this
1264              * @param {Ext.data.Record[]} records The array of Records added
1265              * @param {Number} index The index at which the record(s) were added
1266              */
1267             'add',
1268             /**
1269              * @event remove
1270              * Fires when a Record has been {@link #remove}d from the Store
1271              * @param {Store} this
1272              * @param {Ext.data.Record} record The Record that was removed
1273              * @param {Number} index The index at which the record was removed
1274              */
1275             'remove',
1276             /**
1277              * @event update
1278              * Fires when a Record has been updated
1279              * @param {Store} this
1280              * @param {Ext.data.Record} record The Record that was updated
1281              * @param {String} operation The update operation being performed.  Value may be one of:
1282              * <pre><code>
1283      Ext.data.Record.EDIT
1284      Ext.data.Record.REJECT
1285      Ext.data.Record.COMMIT
1286              * </code></pre>
1287              */
1288             'update',
1289             /**
1290              * @event clear
1291              * Fires when the data cache has been cleared.
1292              * @param {Store} this
1293              * @param {Record[]} The records that were cleared.
1294              */
1295             'clear',
1296             /**
1297              * @event exception
1298              * <p>Fires if an exception occurs in the Proxy during a remote request.
1299              * This event is relayed through the corresponding {@link Ext.data.DataProxy}.
1300              * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
1301              * for additional details.
1302              * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
1303              * for description.
1304              */
1305             'exception',
1306             /**
1307              * @event beforeload
1308              * Fires before a request is made for a new data object.  If the beforeload handler returns
1309              * <tt>false</tt> the {@link #load} action will be canceled.
1310              * @param {Store} this
1311              * @param {Object} options The loading options that were specified (see {@link #load} for details)
1312              */
1313             'beforeload',
1314             /**
1315              * @event load
1316              * Fires after a new set of Records has been loaded.
1317              * @param {Store} this
1318              * @param {Ext.data.Record[]} records The Records that were loaded
1319              * @param {Object} options The loading options that were specified (see {@link #load} for details)
1320              */
1321             'load',
1322             /**
1323              * @event loadexception
1324              * <p>This event is <b>deprecated</b> in favor of the catch-all <b><code>{@link #exception}</code></b>
1325              * event instead.</p>
1326              * <p>This event is relayed through the corresponding {@link Ext.data.DataProxy}.
1327              * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception}
1328              * for additional details.
1329              * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception}
1330              * for description.
1331              */
1332             'loadexception',
1333             /**
1334              * @event beforewrite
1335              * @param {Ext.data.Store} store
1336              * @param {String} action [Ext.data.Api.actions.create|update|destroy]
1337              * @param {Record/Record[]} rs The Record(s) being written.
1338              * @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)
1339              * @param {Object} arg The callback's arg object passed to the {@link #request} function
1340              */
1341             'beforewrite',
1342             /**
1343              * @event write
1344              * Fires if the server returns 200 after an Ext.data.Api.actions CRUD action.
1345              * Success of the action is determined in the <code>result['successProperty']</code>property (<b>NOTE</b> for RESTful stores,
1346              * a simple 20x response is sufficient for the actions "destroy" and "update".  The "create" action should should return 200 along with a database pk).
1347              * @param {Ext.data.Store} store
1348              * @param {String} action [Ext.data.Api.actions.create|update|destroy]
1349              * @param {Object} result The 'data' picked-out out of the response for convenience.
1350              * @param {Ext.Direct.Transaction} res
1351              * @param {Record/Record[]} rs Store's records, the subject(s) of the write-action
1352              */
1353             'write',
1354             /**
1355              * @event beforesave
1356              * Fires before a save action is called. A save encompasses destroying records, updating records and creating records.
1357              * @param {Ext.data.Store} store
1358              * @param {Object} data An object containing the data that is to be saved. The object will contain a key for each appropriate action,
1359              * with an array of records for each action.
1360              */
1361             'beforesave',
1362             /**
1363              * @event save
1364              * Fires after a save is completed. A save encompasses destroying records, updating records and creating records.
1365              * @param {Ext.data.Store} store
1366              * @param {Number} batch The identifier for the batch that was saved.
1367              * @param {Object} data An object containing the data that is to be saved. The object will contain a key for each appropriate action,
1368              * with an array of records for each action.
1369              */
1370             'save'
1371
1372         );
1373
1374         if(this.proxy){
1375             // TODO remove deprecated loadexception with ext-3.0.1
1376             this.relayEvents(this.proxy,  ['loadexception', 'exception']);
1377         }
1378         // With a writer set for the Store, we want to listen to add/remove events to remotely create/destroy records.
1379         if (this.writer) {
1380             this.on({
1381                 scope: this,
1382                 add: this.createRecords,
1383                 remove: this.destroyRecord,
1384                 update: this.updateRecord,
1385                 clear: this.onClear
1386             });
1387         }
1388
1389         this.sortToggle = {};
1390         if(this.sortField){
1391             this.setDefaultSort(this.sortField, this.sortDir);
1392         }else if(this.sortInfo){
1393             this.setDefaultSort(this.sortInfo.field, this.sortInfo.direction);
1394         }
1395
1396         Ext.data.Store.superclass.constructor.call(this);
1397
1398         if(this.id){
1399             this.storeId = this.id;
1400             delete this.id;
1401         }
1402         if(this.storeId){
1403             Ext.StoreMgr.register(this);
1404         }
1405         if(this.inlineData){
1406             this.loadData(this.inlineData);
1407             delete this.inlineData;
1408         }else if(this.autoLoad){
1409             this.load.defer(10, this, [
1410                 typeof this.autoLoad == 'object' ?
1411                     this.autoLoad : undefined]);
1412         }
1413         // used internally to uniquely identify a batch
1414         this.batchCounter = 0;
1415         this.batches = {};
1416     },
1417
1418     /**
1419      * builds a DataWriter instance when Store constructor is provided with a writer config-object instead of an instace.
1420      * @param {Object} config Writer configuration
1421      * @return {Ext.data.DataWriter}
1422      * @private
1423      */
1424     buildWriter : function(config) {
1425         var klass = undefined,
1426             type = (config.format || 'json').toLowerCase();
1427         switch (type) {
1428             case 'json':
1429                 klass = Ext.data.JsonWriter;
1430                 break;
1431             case 'xml':
1432                 klass = Ext.data.XmlWriter;
1433                 break;
1434             default:
1435                 klass = Ext.data.JsonWriter;
1436         }
1437         return new klass(config);
1438     },
1439
1440     /**
1441      * Destroys the store.
1442      */
1443     destroy : function(){
1444         if(!this.isDestroyed){
1445             if(this.storeId){
1446                 Ext.StoreMgr.unregister(this);
1447             }
1448             this.clearData();
1449             this.data = null;
1450             Ext.destroy(this.proxy);
1451             this.reader = this.writer = null;
1452             this.purgeListeners();
1453             this.isDestroyed = true;
1454         }
1455     },
1456
1457     /**
1458      * Add Records to the Store and fires the {@link #add} event.  To add Records
1459      * to the store from a remote source use <code>{@link #load}({add:true})</code>.
1460      * See also <code>{@link #recordType}</code> and <code>{@link #insert}</code>.
1461      * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects
1462      * to add to the cache. See {@link #recordType}.
1463      */
1464     add : function(records){
1465         records = [].concat(records);
1466         if(records.length < 1){
1467             return;
1468         }
1469         for(var i = 0, len = records.length; i < len; i++){
1470             records[i].join(this);
1471         }
1472         var index = this.data.length;
1473         this.data.addAll(records);
1474         if(this.snapshot){
1475             this.snapshot.addAll(records);
1476         }
1477         this.fireEvent('add', this, records, index);
1478     },
1479
1480     /**
1481      * (Local sort only) Inserts the passed Record into the Store at the index where it
1482      * should go based on the current sort information.
1483      * @param {Ext.data.Record} record
1484      */
1485     addSorted : function(record){
1486         var index = this.findInsertIndex(record);
1487         this.insert(index, record);
1488     },
1489
1490     /**
1491      * Remove Records from the Store and fires the {@link #remove} event.
1492      * @param {Ext.data.Record/Ext.data.Record[]} record The record object or array of records to remove from the cache.
1493      */
1494     remove : function(record){
1495         if(Ext.isArray(record)){
1496             Ext.each(record, function(r){
1497                 this.remove(r);
1498             }, this);
1499             return;
1500         }
1501         var index = this.data.indexOf(record);
1502         if(index > -1){
1503             record.join(null);
1504             this.data.removeAt(index);
1505         }
1506         if(this.pruneModifiedRecords){
1507             this.modified.remove(record);
1508         }
1509         if(this.snapshot){
1510             this.snapshot.remove(record);
1511         }
1512         if(index > -1){
1513             this.fireEvent('remove', this, record, index);
1514         }
1515     },
1516
1517     /**
1518      * Remove a Record from the Store at the specified index. Fires the {@link #remove} event.
1519      * @param {Number} index The index of the record to remove.
1520      */
1521     removeAt : function(index){
1522         this.remove(this.getAt(index));
1523     },
1524
1525     /**
1526      * Remove all Records from the Store and fires the {@link #clear} event.
1527      * @param {Boolean} silent [false] Defaults to <tt>false</tt>.  Set <tt>true</tt> to not fire clear event.
1528      */
1529     removeAll : function(silent){
1530         var items = [];
1531         this.each(function(rec){
1532             items.push(rec);
1533         });
1534         this.clearData();
1535         if(this.snapshot){
1536             this.snapshot.clear();
1537         }
1538         if(this.pruneModifiedRecords){
1539             this.modified = [];
1540         }
1541         if (silent !== true) {  // <-- prevents write-actions when we just want to clear a store.
1542             this.fireEvent('clear', this, items);
1543         }
1544     },
1545
1546     // private
1547     onClear: function(store, records){
1548         Ext.each(records, function(rec, index){
1549             this.destroyRecord(this, rec, index);
1550         }, this);
1551     },
1552
1553     /**
1554      * Inserts Records into the Store at the given index and fires the {@link #add} event.
1555      * See also <code>{@link #add}</code> and <code>{@link #addSorted}</code>.
1556      * @param {Number} index The start index at which to insert the passed Records.
1557      * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects to add to the cache.
1558      */
1559     insert : function(index, records){
1560         records = [].concat(records);
1561         for(var i = 0, len = records.length; i < len; i++){
1562             this.data.insert(index, records[i]);
1563             records[i].join(this);
1564         }
1565         if(this.snapshot){
1566             this.snapshot.addAll(records);
1567         }
1568         this.fireEvent('add', this, records, index);
1569     },
1570
1571     /**
1572      * Get the index within the cache of the passed Record.
1573      * @param {Ext.data.Record} record The Ext.data.Record object to find.
1574      * @return {Number} The index of the passed Record. Returns -1 if not found.
1575      */
1576     indexOf : function(record){
1577         return this.data.indexOf(record);
1578     },
1579
1580     /**
1581      * Get the index within the cache of the Record with the passed id.
1582      * @param {String} id The id of the Record to find.
1583      * @return {Number} The index of the Record. Returns -1 if not found.
1584      */
1585     indexOfId : function(id){
1586         return this.data.indexOfKey(id);
1587     },
1588
1589     /**
1590      * Get the Record with the specified id.
1591      * @param {String} id The id of the Record to find.
1592      * @return {Ext.data.Record} The Record with the passed id. Returns undefined if not found.
1593      */
1594     getById : function(id){
1595         return (this.snapshot || this.data).key(id);
1596     },
1597
1598     /**
1599      * Get the Record at the specified index.
1600      * @param {Number} index The index of the Record to find.
1601      * @return {Ext.data.Record} The Record at the passed index. Returns undefined if not found.
1602      */
1603     getAt : function(index){
1604         return this.data.itemAt(index);
1605     },
1606
1607     /**
1608      * Returns a range of Records between specified indices.
1609      * @param {Number} startIndex (optional) The starting index (defaults to 0)
1610      * @param {Number} endIndex (optional) The ending index (defaults to the last Record in the Store)
1611      * @return {Ext.data.Record[]} An array of Records
1612      */
1613     getRange : function(start, end){
1614         return this.data.getRange(start, end);
1615     },
1616
1617     // private
1618     storeOptions : function(o){
1619         o = Ext.apply({}, o);
1620         delete o.callback;
1621         delete o.scope;
1622         this.lastOptions = o;
1623     },
1624
1625     // private
1626     clearData: function(){
1627         this.data.each(function(rec) {
1628             rec.join(null);
1629         });
1630         this.data.clear();
1631     },
1632
1633     /**
1634      * <p>Loads the Record cache from the configured <tt>{@link #proxy}</tt> using the configured <tt>{@link #reader}</tt>.</p>
1635      * <br><p>Notes:</p><div class="mdetail-params"><ul>
1636      * <li><b><u>Important</u></b>: loading is asynchronous! This call will return before the new data has been
1637      * loaded. To perform any post-processing where information from the load call is required, specify
1638      * the <tt>callback</tt> function to be called, or use a {@link Ext.util.Observable#listeners a 'load' event handler}.</li>
1639      * <li>If using {@link Ext.PagingToolbar remote paging}, the first load call must specify the <tt>start</tt> and <tt>limit</tt>
1640      * properties in the <code>options.params</code> property to establish the initial position within the
1641      * dataset, and the number of Records to cache on each read from the Proxy.</li>
1642      * <li>If using {@link #remoteSort remote sorting}, the configured <code>{@link #sortInfo}</code>
1643      * will be automatically included with the posted parameters according to the specified
1644      * <code>{@link #paramNames}</code>.</li>
1645      * </ul></div>
1646      * @param {Object} options An object containing properties which control loading options:<ul>
1647      * <li><b><tt>params</tt></b> :Object<div class="sub-desc"><p>An object containing properties to pass as HTTP
1648      * parameters to a remote data source. <b>Note</b>: <code>params</code> will override any
1649      * <code>{@link #baseParams}</code> of the same name.</p>
1650      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p></div></li>
1651      * <li><b>callback</b> : Function<div class="sub-desc"><p>A function to be called after the Records
1652      * have been loaded. The callback is called after the load event is fired, and is passed the following arguments:<ul>
1653      * <li>r : Ext.data.Record[] An Array of Records loaded.</li>
1654      * <li>options : Options object from the load call.</li>
1655      * <li>success : Boolean success indicator.</li></ul></p></div></li>
1656      * <li><b>scope</b> : Object<div class="sub-desc"><p>Scope with which to call the callback (defaults
1657      * to the Store object)</p></div></li>
1658      * <li><b>add</b> : Boolean<div class="sub-desc"><p>Indicator to append loaded records rather than
1659      * replace the current cache.  <b>Note</b>: see note for <tt>{@link #loadData}</tt></p></div></li>
1660      * </ul>
1661      * @return {Boolean} If the <i>developer</i> provided <tt>{@link #beforeload}</tt> event handler returns
1662      * <tt>false</tt>, the load call will abort and will return <tt>false</tt>; otherwise will return <tt>true</tt>.
1663      */
1664     load : function(options) {
1665         options = Ext.apply({}, options);
1666         this.storeOptions(options);
1667         if(this.sortInfo && this.remoteSort){
1668             var pn = this.paramNames;
1669             options.params = Ext.apply({}, options.params);
1670             options.params[pn.sort] = this.sortInfo.field;
1671             options.params[pn.dir] = this.sortInfo.direction;
1672         }
1673         try {
1674             return this.execute('read', null, options); // <-- null represents rs.  No rs for load actions.
1675         } catch(e) {
1676             this.handleException(e);
1677             return false;
1678         }
1679     },
1680
1681     /**
1682      * updateRecord  Should not be used directly.  This method will be called automatically if a Writer is set.
1683      * Listens to 'update' event.
1684      * @param {Object} store
1685      * @param {Object} record
1686      * @param {Object} action
1687      * @private
1688      */
1689     updateRecord : function(store, record, action) {
1690         if (action == Ext.data.Record.EDIT && this.autoSave === true && (!record.phantom || (record.phantom && record.isValid()))) {
1691             this.save();
1692         }
1693     },
1694
1695     /**
1696      * Should not be used directly.  Store#add will call this automatically if a Writer is set
1697      * @param {Object} store
1698      * @param {Object} rs
1699      * @param {Object} index
1700      * @private
1701      */
1702     createRecords : function(store, rs, index) {
1703         for (var i = 0, len = rs.length; i < len; i++) {
1704             if (rs[i].phantom && rs[i].isValid()) {
1705                 rs[i].markDirty();  // <-- Mark new records dirty
1706                 this.modified.push(rs[i]);  // <-- add to modified
1707             }
1708         }
1709         if (this.autoSave === true) {
1710             this.save();
1711         }
1712     },
1713
1714     /**
1715      * Destroys a Record.  Should not be used directly.  It's called by Store#remove if a Writer is set.
1716      * @param {Store} store this
1717      * @param {Ext.data.Record} record
1718      * @param {Number} index
1719      * @private
1720      */
1721     destroyRecord : function(store, record, index) {
1722         if (this.modified.indexOf(record) != -1) {  // <-- handled already if @cfg pruneModifiedRecords == true
1723             this.modified.remove(record);
1724         }
1725         if (!record.phantom) {
1726             this.removed.push(record);
1727
1728             // since the record has already been removed from the store but the server request has not yet been executed,
1729             // must keep track of the last known index this record existed.  If a server error occurs, the record can be
1730             // put back into the store.  @see Store#createCallback where the record is returned when response status === false
1731             record.lastIndex = index;
1732
1733             if (this.autoSave === true) {
1734                 this.save();
1735             }
1736         }
1737     },
1738
1739     /**
1740      * This method should generally not be used directly.  This method is called internally
1741      * by {@link #load}, or if a Writer is set will be called automatically when {@link #add},
1742      * {@link #remove}, or {@link #update} events fire.
1743      * @param {String} action Action name ('read', 'create', 'update', or 'destroy')
1744      * @param {Record/Record[]} rs
1745      * @param {Object} options
1746      * @throws Error
1747      * @private
1748      */
1749     execute : function(action, rs, options, /* private */ batch) {
1750         // blow up if action not Ext.data.CREATE, READ, UPDATE, DESTROY
1751         if (!Ext.data.Api.isAction(action)) {
1752             throw new Ext.data.Api.Error('execute', action);
1753         }
1754         // make sure options has a fresh, new params hash
1755         options = Ext.applyIf(options||{}, {
1756             params: {}
1757         });
1758         if(batch !== undefined){
1759             this.addToBatch(batch);
1760         }
1761         // have to separate before-events since load has a different signature than create,destroy and save events since load does not
1762         // include the rs (record resultset) parameter.  Capture return values from the beforeaction into doRequest flag.
1763         var doRequest = true;
1764
1765         if (action === 'read') {
1766             doRequest = this.fireEvent('beforeload', this, options);
1767             Ext.applyIf(options.params, this.baseParams);
1768         }
1769         else {
1770             // if Writer is configured as listful, force single-record rs to be [{}] instead of {}
1771             // TODO Move listful rendering into DataWriter where the @cfg is defined.  Should be easy now.
1772             if (this.writer.listful === true && this.restful !== true) {
1773                 rs = (Ext.isArray(rs)) ? rs : [rs];
1774             }
1775             // if rs has just a single record, shift it off so that Writer writes data as '{}' rather than '[{}]'
1776             else if (Ext.isArray(rs) && rs.length == 1) {
1777                 rs = rs.shift();
1778             }
1779             // Write the action to options.params
1780             if ((doRequest = this.fireEvent('beforewrite', this, action, rs, options)) !== false) {
1781                 this.writer.apply(options.params, this.baseParams, action, rs);
1782             }
1783         }
1784         if (doRequest !== false) {
1785             // Send request to proxy.
1786             if (this.writer && this.proxy.url && !this.proxy.restful && !Ext.data.Api.hasUniqueUrl(this.proxy, action)) {
1787                 options.params.xaction = action;    // <-- really old, probaby unecessary.
1788             }
1789             // Note:  Up until this point we've been dealing with 'action' as a key from Ext.data.Api.actions.
1790             // We'll flip it now and send the value into DataProxy#request, since it's the value which maps to
1791             // the user's configured DataProxy#api
1792             // TODO Refactor all Proxies to accept an instance of Ext.data.Request (not yet defined) instead of this looooooong list
1793             // of params.  This method is an artifact from Ext2.
1794             this.proxy.request(Ext.data.Api.actions[action], rs, options.params, this.reader, this.createCallback(action, rs, batch), this, options);
1795         }
1796         return doRequest;
1797     },
1798
1799     /**
1800      * Saves all pending changes to the store.  If the commensurate Ext.data.Api.actions action is not configured, then
1801      * the configured <code>{@link #url}</code> will be used.
1802      * <pre>
1803      * change            url
1804      * ---------------   --------------------
1805      * removed records   Ext.data.Api.actions.destroy
1806      * phantom records   Ext.data.Api.actions.create
1807      * {@link #getModifiedRecords modified records}  Ext.data.Api.actions.update
1808      * </pre>
1809      * @TODO:  Create extensions of Error class and send associated Record with thrown exceptions.
1810      * e.g.:  Ext.data.DataReader.Error or Ext.data.Error or Ext.data.DataProxy.Error, etc.
1811      * @return {Number} batch Returns a number to uniquely identify the "batch" of saves occurring. -1 will be returned
1812      * if there are no items to save or the save was cancelled.
1813      */
1814     save : function() {
1815         if (!this.writer) {
1816             throw new Ext.data.Store.Error('writer-undefined');
1817         }
1818
1819         var queue = [],
1820             len,
1821             trans,
1822             batch,
1823             data = {};
1824         // DESTROY:  First check for removed records.  Records in this.removed are guaranteed non-phantoms.  @see Store#remove
1825         if(this.removed.length){
1826             queue.push(['destroy', this.removed]);
1827         }
1828
1829         // Check for modified records. Use a copy so Store#rejectChanges will work if server returns error.
1830         var rs = [].concat(this.getModifiedRecords());
1831         if(rs.length){
1832             // CREATE:  Next check for phantoms within rs.  splice-off and execute create.
1833             var phantoms = [];
1834             for(var i = rs.length-1; i >= 0; i--){
1835                 if(rs[i].phantom === true){
1836                     var rec = rs.splice(i, 1).shift();
1837                     if(rec.isValid()){
1838                         phantoms.push(rec);
1839                     }
1840                 }else if(!rs[i].isValid()){ // <-- while we're here, splice-off any !isValid real records
1841                     rs.splice(i,1);
1842                 }
1843             }
1844             // If we have valid phantoms, create them...
1845             if(phantoms.length){
1846                 queue.push(['create', phantoms]);
1847             }
1848
1849             // UPDATE:  And finally, if we're still here after splicing-off phantoms and !isValid real records, update the rest...
1850             if(rs.length){
1851                 queue.push(['update', rs]);
1852             }
1853         }
1854         len = queue.length;
1855         if(len){
1856             batch = ++this.batchCounter;
1857             for(var i = 0; i < len; ++i){
1858                 trans = queue[i];
1859                 data[trans[0]] = trans[1];
1860             }
1861             if(this.fireEvent('beforesave', this, data) !== false){
1862                 for(var i = 0; i < len; ++i){
1863                     trans = queue[i];
1864                     this.doTransaction(trans[0], trans[1], batch);
1865                 }
1866                 return batch;
1867             }
1868         }
1869         return -1;
1870     },
1871
1872     // private.  Simply wraps call to Store#execute in try/catch.  Defers to Store#handleException on error.  Loops if batch: false
1873     doTransaction : function(action, rs, batch) {
1874         function transaction(records) {
1875             try{
1876                 this.execute(action, records, undefined, batch);
1877             }catch (e){
1878                 this.handleException(e);
1879             }
1880         }
1881         if(this.batch === false){
1882             for(var i = 0, len = rs.length; i < len; i++){
1883                 transaction.call(this, rs[i]);
1884             }
1885         }else{
1886             transaction.call(this, rs);
1887         }
1888     },
1889
1890     // private
1891     addToBatch : function(batch){
1892         var b = this.batches,
1893             key = this.batchKey + batch,
1894             o = b[key];
1895
1896         if(!o){
1897             b[key] = o = {
1898                 id: batch,
1899                 count: 0,
1900                 data: {}
1901             };
1902         }
1903         ++o.count;
1904     },
1905
1906     removeFromBatch : function(batch, action, data){
1907         var b = this.batches,
1908             key = this.batchKey + batch,
1909             o = b[key],
1910             data,
1911             arr;
1912
1913
1914         if(o){
1915             arr = o.data[action] || [];
1916             o.data[action] = arr.concat(data);
1917             if(o.count === 1){
1918                 data = o.data;
1919                 delete b[key];
1920                 this.fireEvent('save', this, batch, data);
1921             }else{
1922                 --o.count;
1923             }
1924         }
1925     },
1926
1927     // @private callback-handler for remote CRUD actions
1928     // Do not override -- override loadRecords, onCreateRecords, onDestroyRecords and onUpdateRecords instead.
1929     createCallback : function(action, rs, batch) {
1930         var actions = Ext.data.Api.actions;
1931         return (action == 'read') ? this.loadRecords : function(data, response, success) {
1932             // calls: onCreateRecords | onUpdateRecords | onDestroyRecords
1933             this['on' + Ext.util.Format.capitalize(action) + 'Records'](success, rs, [].concat(data));
1934             // If success === false here, exception will have been called in DataProxy
1935             if (success === true) {
1936                 this.fireEvent('write', this, action, data, response, rs);
1937             }
1938             this.removeFromBatch(batch, action, data);
1939         };
1940     },
1941
1942     // Clears records from modified array after an exception event.
1943     // NOTE:  records are left marked dirty.  Do we want to commit them even though they were not updated/realized?
1944     // TODO remove this method?
1945     clearModified : function(rs) {
1946         if (Ext.isArray(rs)) {
1947             for (var n=rs.length-1;n>=0;n--) {
1948                 this.modified.splice(this.modified.indexOf(rs[n]), 1);
1949             }
1950         } else {
1951             this.modified.splice(this.modified.indexOf(rs), 1);
1952         }
1953     },
1954
1955     // remap record ids in MixedCollection after records have been realized.  @see Store#onCreateRecords, @see DataReader#realize
1956     reMap : function(record) {
1957         if (Ext.isArray(record)) {
1958             for (var i = 0, len = record.length; i < len; i++) {
1959                 this.reMap(record[i]);
1960             }
1961         } else {
1962             delete this.data.map[record._phid];
1963             this.data.map[record.id] = record;
1964             var index = this.data.keys.indexOf(record._phid);
1965             this.data.keys.splice(index, 1, record.id);
1966             delete record._phid;
1967         }
1968     },
1969
1970     // @protected onCreateRecord proxy callback for create action
1971     onCreateRecords : function(success, rs, data) {
1972         if (success === true) {
1973             try {
1974                 this.reader.realize(rs, data);
1975                 this.reMap(rs);
1976             }
1977             catch (e) {
1978                 this.handleException(e);
1979                 if (Ext.isArray(rs)) {
1980                     // Recurse to run back into the try {}.  DataReader#realize splices-off the rs until empty.
1981                     this.onCreateRecords(success, rs, data);
1982                 }
1983             }
1984         }
1985     },
1986
1987     // @protected, onUpdateRecords proxy callback for update action
1988     onUpdateRecords : function(success, rs, data) {
1989         if (success === true) {
1990             try {
1991                 this.reader.update(rs, data);
1992             } catch (e) {
1993                 this.handleException(e);
1994                 if (Ext.isArray(rs)) {
1995                     // Recurse to run back into the try {}.  DataReader#update splices-off the rs until empty.
1996                     this.onUpdateRecords(success, rs, data);
1997                 }
1998             }
1999         }
2000     },
2001
2002     // @protected onDestroyRecords proxy callback for destroy action
2003     onDestroyRecords : function(success, rs, data) {
2004         // splice each rec out of this.removed
2005         rs = (rs instanceof Ext.data.Record) ? [rs] : [].concat(rs);
2006         for (var i=0,len=rs.length;i<len;i++) {
2007             this.removed.splice(this.removed.indexOf(rs[i]), 1);
2008         }
2009         if (success === false) {
2010             // put records back into store if remote destroy fails.
2011             // @TODO: Might want to let developer decide.
2012             for (i=rs.length-1;i>=0;i--) {
2013                 this.insert(rs[i].lastIndex, rs[i]);    // <-- lastIndex set in Store#destroyRecord
2014             }
2015         }
2016     },
2017
2018     // protected handleException.  Possibly temporary until Ext framework has an exception-handler.
2019     handleException : function(e) {
2020         // @see core/Error.js
2021         Ext.handleError(e);
2022     },
2023
2024     /**
2025      * <p>Reloads the Record cache from the configured Proxy using the configured
2026      * {@link Ext.data.Reader Reader} and the options from the last load operation
2027      * performed.</p>
2028      * <p><b>Note</b>: see the Important note in {@link #load}.</p>
2029      * @param {Object} options <p>(optional) An <tt>Object</tt> containing
2030      * {@link #load loading options} which may override the {@link #lastOptions options}
2031      * used in the last {@link #load} operation. See {@link #load} for details
2032      * (defaults to <tt>null</tt>, in which case the {@link #lastOptions} are
2033      * used).</p>
2034      * <br><p>To add new params to the existing params:</p><pre><code>
2035 lastOptions = myStore.lastOptions;
2036 Ext.apply(lastOptions.params, {
2037     myNewParam: true
2038 });
2039 myStore.reload(lastOptions);
2040      * </code></pre>
2041      */
2042     reload : function(options){
2043         this.load(Ext.applyIf(options||{}, this.lastOptions));
2044     },
2045
2046     // private
2047     // Called as a callback by the Reader during a load operation.
2048     loadRecords : function(o, options, success){
2049         if (this.isDestroyed === true) {
2050             return;
2051         }
2052         if(!o || success === false){
2053             if(success !== false){
2054                 this.fireEvent('load', this, [], options);
2055             }
2056             if(options.callback){
2057                 options.callback.call(options.scope || this, [], options, false, o);
2058             }
2059             return;
2060         }
2061         var r = o.records, t = o.totalRecords || r.length;
2062         if(!options || options.add !== true){
2063             if(this.pruneModifiedRecords){
2064                 this.modified = [];
2065             }
2066             for(var i = 0, len = r.length; i < len; i++){
2067                 r[i].join(this);
2068             }
2069             if(this.snapshot){
2070                 this.data = this.snapshot;
2071                 delete this.snapshot;
2072             }
2073             this.clearData();
2074             this.data.addAll(r);
2075             this.totalLength = t;
2076             this.applySort();
2077             this.fireEvent('datachanged', this);
2078         }else{
2079             this.totalLength = Math.max(t, this.data.length+r.length);
2080             this.add(r);
2081         }
2082         this.fireEvent('load', this, r, options);
2083         if(options.callback){
2084             options.callback.call(options.scope || this, r, options, true);
2085         }
2086     },
2087
2088     /**
2089      * Loads data from a passed data block and fires the {@link #load} event. A {@link Ext.data.Reader Reader}
2090      * which understands the format of the data must have been configured in the constructor.
2091      * @param {Object} data The data block from which to read the Records.  The format of the data expected
2092      * is dependent on the type of {@link Ext.data.Reader Reader} that is configured and should correspond to
2093      * that {@link Ext.data.Reader Reader}'s <tt>{@link Ext.data.Reader#readRecords}</tt> parameter.
2094      * @param {Boolean} append (Optional) <tt>true</tt> to append the new Records rather the default to replace
2095      * the existing cache.
2096      * <b>Note</b>: that Records in a Store are keyed by their {@link Ext.data.Record#id id}, so added Records
2097      * with ids which are already present in the Store will <i>replace</i> existing Records. Only Records with
2098      * new, unique ids will be added.
2099      */
2100     loadData : function(o, append){
2101         var r = this.reader.readRecords(o);
2102         this.loadRecords(r, {add: append}, true);
2103     },
2104
2105     /**
2106      * Gets the number of cached records.
2107      * <p>If using paging, this may not be the total size of the dataset. If the data object
2108      * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
2109      * the dataset size.  <b>Note</b>: see the Important note in {@link #load}.</p>
2110      * @return {Number} The number of Records in the Store's cache.
2111      */
2112     getCount : function(){
2113         return this.data.length || 0;
2114     },
2115
2116     /**
2117      * Gets the total number of records in the dataset as returned by the server.
2118      * <p>If using paging, for this to be accurate, the data object used by the {@link #reader Reader}
2119      * must contain the dataset size. For remote data sources, the value for this property
2120      * (<tt>totalProperty</tt> for {@link Ext.data.JsonReader JsonReader},
2121      * <tt>totalRecords</tt> for {@link Ext.data.XmlReader XmlReader}) shall be returned by a query on the server.
2122      * <b>Note</b>: see the Important note in {@link #load}.</p>
2123      * @return {Number} The number of Records as specified in the data object passed to the Reader
2124      * by the Proxy.
2125      * <p><b>Note</b>: this value is not updated when changing the contents of the Store locally.</p>
2126      */
2127     getTotalCount : function(){
2128         return this.totalLength || 0;
2129     },
2130
2131     /**
2132      * Returns an object describing the current sort state of this Store.
2133      * @return {Object} The sort state of the Store. An object with two properties:<ul>
2134      * <li><b>field : String<p class="sub-desc">The name of the field by which the Records are sorted.</p></li>
2135      * <li><b>direction : String<p class="sub-desc">The sort order, 'ASC' or 'DESC' (case-sensitive).</p></li>
2136      * </ul>
2137      * See <tt>{@link #sortInfo}</tt> for additional details.
2138      */
2139     getSortState : function(){
2140         return this.sortInfo;
2141     },
2142
2143     /**
2144      * @private
2145      * Invokes sortData if we have sortInfo to sort on and are not sorting remotely
2146      */
2147     applySort : function(){
2148         if ((this.sortInfo || this.multiSortInfo) && !this.remoteSort) {
2149             this.sortData();
2150         }
2151     },
2152
2153     /**
2154      * @private
2155      * Performs the actual sorting of data. This checks to see if we currently have a multi sort or not. It applies
2156      * each sorter field/direction pair in turn by building an OR'ed master sorting function and running it against
2157      * the full dataset
2158      */
2159     sortData : function() {
2160         var sortInfo  = this.hasMultiSort ? this.multiSortInfo : this.sortInfo,
2161             direction = sortInfo.direction || "ASC",
2162             sorters   = sortInfo.sorters,
2163             sortFns   = [];
2164
2165         //if we just have a single sorter, pretend it's the first in an array
2166         if (!this.hasMultiSort) {
2167             sorters = [{direction: direction, field: sortInfo.field}];
2168         }
2169
2170         //create a sorter function for each sorter field/direction combo
2171         for (var i=0, j = sorters.length; i < j; i++) {
2172             sortFns.push(this.createSortFunction(sorters[i].field, sorters[i].direction));
2173         }
2174         
2175         if (sortFns.length == 0) {
2176             return;
2177         }
2178
2179         //the direction modifier is multiplied with the result of the sorting functions to provide overall sort direction
2180         //(as opposed to direction per field)
2181         var directionModifier = direction.toUpperCase() == "DESC" ? -1 : 1;
2182
2183         //create a function which ORs each sorter together to enable multi-sort
2184         var fn = function(r1, r2) {
2185           var result = sortFns[0].call(this, r1, r2);
2186
2187           //if we have more than one sorter, OR any additional sorter functions together
2188           if (sortFns.length > 1) {
2189               for (var i=1, j = sortFns.length; i < j; i++) {
2190                   result = result || sortFns[i].call(this, r1, r2);
2191               }
2192           }
2193
2194           return directionModifier * result;
2195         };
2196
2197         //sort the data
2198         this.data.sort(direction, fn);
2199         if (this.snapshot && this.snapshot != this.data) {
2200             this.snapshot.sort(direction, fn);
2201         }
2202     },
2203
2204     /**
2205      * @private
2206      * Creates and returns a function which sorts an array by the given field and direction
2207      * @param {String} field The field to create the sorter for
2208      * @param {String} direction The direction to sort by (defaults to "ASC")
2209      * @return {Function} A function which sorts by the field/direction combination provided
2210      */
2211     createSortFunction: function(field, direction) {
2212         direction = direction || "ASC";
2213         var directionModifier = direction.toUpperCase() == "DESC" ? -1 : 1;
2214
2215         var sortType = this.fields.get(field).sortType;
2216
2217         //create a comparison function. Takes 2 records, returns 1 if record 1 is greater,
2218         //-1 if record 2 is greater or 0 if they are equal
2219         return function(r1, r2) {
2220             var v1 = sortType(r1.data[field]),
2221                 v2 = sortType(r2.data[field]);
2222
2223             return directionModifier * (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
2224         };
2225     },
2226
2227     /**
2228      * Sets the default sort column and order to be used by the next {@link #load} operation.
2229      * @param {String} fieldName The name of the field to sort by.
2230      * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
2231      */
2232     setDefaultSort : function(field, dir) {
2233         dir = dir ? dir.toUpperCase() : 'ASC';
2234         this.sortInfo = {field: field, direction: dir};
2235         this.sortToggle[field] = dir;
2236     },
2237
2238     /**
2239      * Sort the Records.
2240      * If remote sorting is used, the sort is performed on the server, and the cache is reloaded. If local
2241      * sorting is used, the cache is sorted internally. See also {@link #remoteSort} and {@link #paramNames}.
2242      * This function accepts two call signatures - pass in a field name as the first argument to sort on a single
2243      * field, or pass in an array of sort configuration objects to sort by multiple fields.
2244      * Single sort example:
2245      * store.sort('name', 'ASC');
2246      * Multi sort example:
2247      * store.sort([
2248      *   {
2249      *     field    : 'name',
2250      *     direction: 'ASC'
2251      *   },
2252      *   {
2253      *     field    : 'salary',
2254      *     direction: 'DESC'
2255      *   }
2256      * ], 'ASC');
2257      * In this second form, the sort configs are applied in order, with later sorters sorting within earlier sorters' results.
2258      * For example, if two records with the same name are present they will also be sorted by salary if given the sort configs
2259      * above. Any number of sort configs can be added.
2260      * @param {String/Array} fieldName The name of the field to sort by, or an array of ordered sort configs
2261      * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
2262      */
2263     sort : function(fieldName, dir) {
2264         if (Ext.isArray(arguments[0])) {
2265             return this.multiSort.call(this, fieldName, dir);
2266         } else {
2267             return this.singleSort(fieldName, dir);
2268         }
2269     },
2270
2271     /**
2272      * Sorts the store contents by a single field and direction. This is called internally by {@link sort} and would
2273      * not usually be called manually
2274      * @param {String} fieldName The name of the field to sort by.
2275      * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
2276      */
2277     singleSort: function(fieldName, dir) {
2278         var field = this.fields.get(fieldName);
2279         if (!field) return false;
2280
2281         var name       = field.name,
2282             sortInfo   = this.sortInfo || null,
2283             sortToggle = this.sortToggle ? this.sortToggle[name] : null;
2284
2285         if (!dir) {
2286             if (sortInfo && sortInfo.field == name) { // toggle sort dir
2287                 dir = (this.sortToggle[name] || 'ASC').toggle('ASC', 'DESC');
2288             } else {
2289                 dir = field.sortDir;
2290             }
2291         }
2292
2293         this.sortToggle[name] = dir;
2294         this.sortInfo = {field: name, direction: dir};
2295         this.hasMultiSort = false;
2296
2297         if (this.remoteSort) {
2298             if (!this.load(this.lastOptions)) {
2299                 if (sortToggle) {
2300                     this.sortToggle[name] = sortToggle;
2301                 }
2302                 if (sortInfo) {
2303                     this.sortInfo = sortInfo;
2304                 }
2305             }
2306         } else {
2307             this.applySort();
2308             this.fireEvent('datachanged', this);
2309         }
2310     },
2311
2312     /**
2313      * Sorts the contents of this store by multiple field/direction sorters. This is called internally by {@link sort}
2314      * and would not usually be called manually.
2315      * Multi sorting only currently applies to local datasets - multiple sort data is not currently sent to a proxy
2316      * if remoteSort is used.
2317      * @param {Array} sorters Array of sorter objects (field and direction)
2318      * @param {String} direction Overall direction to sort the ordered results by (defaults to "ASC")
2319      */
2320     multiSort: function(sorters, direction) {
2321         this.hasMultiSort = true;
2322         direction = direction || "ASC";
2323
2324         //toggle sort direction
2325         if (this.multiSortInfo && direction == this.multiSortInfo.direction) {
2326             direction = direction.toggle("ASC", "DESC");
2327         }
2328
2329         /**
2330          * @property multiSortInfo
2331          * @type Object
2332          * Object containing overall sort direction and an ordered array of sorter configs used when sorting on multiple fields
2333          */
2334         this.multiSortInfo = {
2335             sorters  : sorters,
2336             direction: direction
2337         };
2338         
2339         if (this.remoteSort) {
2340             this.singleSort(sorters[0].field, sorters[0].direction);
2341
2342         } else {
2343             this.applySort();
2344             this.fireEvent('datachanged', this);
2345         }
2346     },
2347
2348     /**
2349      * Calls the specified function for each of the {@link Ext.data.Record Records} in the cache.
2350      * @param {Function} fn The function to call. The {@link Ext.data.Record Record} is passed as the first parameter.
2351      * Returning <tt>false</tt> aborts and exits the iteration.
2352      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed.
2353      * Defaults to the current {@link Ext.data.Record Record} in the iteration.
2354      */
2355     each : function(fn, scope){
2356         this.data.each(fn, scope);
2357     },
2358
2359     /**
2360      * Gets all {@link Ext.data.Record records} modified since the last commit.  Modified records are
2361      * persisted across load operations (e.g., during paging). <b>Note</b>: deleted records are not
2362      * included.  See also <tt>{@link #pruneModifiedRecords}</tt> and
2363      * {@link Ext.data.Record}<tt>{@link Ext.data.Record#markDirty markDirty}.</tt>.
2364      * @return {Ext.data.Record[]} An array of {@link Ext.data.Record Records} containing outstanding
2365      * modifications.  To obtain modified fields within a modified record see
2366      *{@link Ext.data.Record}<tt>{@link Ext.data.Record#modified modified}.</tt>.
2367      */
2368     getModifiedRecords : function(){
2369         return this.modified;
2370     },
2371
2372     /**
2373      * Sums the value of <tt>property</tt> for each {@link Ext.data.Record record} between <tt>start</tt>
2374      * and <tt>end</tt> and returns the result.
2375      * @param {String} property A field in each record
2376      * @param {Number} start (optional) The record index to start at (defaults to <tt>0</tt>)
2377      * @param {Number} end (optional) The last record index to include (defaults to length - 1)
2378      * @return {Number} The sum
2379      */
2380     sum : function(property, start, end){
2381         var rs = this.data.items, v = 0;
2382         start = start || 0;
2383         end = (end || end === 0) ? end : rs.length-1;
2384
2385         for(var i = start; i <= end; i++){
2386             v += (rs[i].data[property] || 0);
2387         }
2388         return v;
2389     },
2390
2391     /**
2392      * @private
2393      * Returns a filter function used to test a the given property's value. Defers most of the work to
2394      * Ext.util.MixedCollection's createValueMatcher function
2395      * @param {String} property The property to create the filter function for
2396      * @param {String/RegExp} value The string/regex to compare the property value to
2397      * @param {Boolean} anyMatch True if we don't care if the filter value is not the full value (defaults to false)
2398      * @param {Boolean} caseSensitive True to create a case-sensitive regex (defaults to false)
2399      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
2400      */
2401     createFilterFn : function(property, value, anyMatch, caseSensitive, exactMatch){
2402         if(Ext.isEmpty(value, false)){
2403             return false;
2404         }
2405         value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
2406         return function(r) {
2407             return value.test(r.data[property]);
2408         };
2409     },
2410
2411     /**
2412      * @private
2413      * Given an array of filter functions (each with optional scope), constructs and returns a single function that returns
2414      * the result of all of the filters ANDed together
2415      * @param {Array} filters The array of filter objects (each object should contain an 'fn' and optional scope)
2416      * @return {Function} The multiple filter function
2417      */
2418     createMultipleFilterFn: function(filters) {
2419         return function(record) {
2420             var isMatch = true;
2421
2422             for (var i=0, j = filters.length; i < j; i++) {
2423                 var filter = filters[i],
2424                     fn     = filter.fn,
2425                     scope  = filter.scope;
2426
2427                 isMatch = isMatch && fn.call(scope, record);
2428             }
2429
2430             return isMatch;
2431         };
2432     },
2433
2434     /**
2435      * Filter the {@link Ext.data.Record records} by a specified property. Alternatively, pass an array of filter
2436      * options to filter by more than one property.
2437      * Single filter example:
2438      * store.filter('name', 'Ed', true, true); //finds all records containing the substring 'Ed'
2439      * Multiple filter example:
2440      * <pre><code>
2441      * store.filter([
2442      *   {
2443      *     property     : 'name',
2444      *     value        : 'Ed',
2445      *     anyMatch     : true, //optional, defaults to true
2446      *     caseSensitive: true  //optional, defaults to true
2447      *   },
2448      *
2449      *   //filter functions can also be passed
2450      *   {
2451      *     fn   : function(record) {
2452      *       return record.get('age') == 24
2453      *     },
2454      *     scope: this
2455      *   }
2456      * ]);
2457      * </code></pre>
2458      * @param {String|Array} field A field on your records, or an array containing multiple filter options
2459      * @param {String/RegExp} value Either a string that the field should begin with, or a RegExp to test
2460      * against the field.
2461      * @param {Boolean} anyMatch (optional) <tt>true</tt> to match any part not just the beginning
2462      * @param {Boolean} caseSensitive (optional) <tt>true</tt> for case sensitive comparison
2463      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
2464      */
2465     filter : function(property, value, anyMatch, caseSensitive, exactMatch){
2466         //we can accept an array of filter objects, or a single filter object - normalize them here
2467         if (Ext.isObject(property)) {
2468             property = [property];
2469         }
2470
2471         if (Ext.isArray(property)) {
2472             var filters = [];
2473
2474             //normalize the filters passed into an array of filter functions
2475             for (var i=0, j = property.length; i < j; i++) {
2476                 var filter = property[i],
2477                     func   = filter.fn,
2478                     scope  = filter.scope || this;
2479
2480                 //if we weren't given a filter function, construct one now
2481                 if (!Ext.isFunction(func)) {
2482                     func = this.createFilterFn(filter.property, filter.value, filter.anyMatch, filter.caseSensitive, filter.exactMatch);
2483                 }
2484
2485                 filters.push({fn: func, scope: scope});
2486             }
2487
2488             var fn = this.createMultipleFilterFn(filters);
2489         } else {
2490             //classic single property filter
2491             var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
2492         }
2493
2494         return fn ? this.filterBy(fn) : this.clearFilter();
2495     },
2496
2497     /**
2498      * Filter by a function. The specified function will be called for each
2499      * Record in this Store. If the function returns <tt>true</tt> the Record is included,
2500      * otherwise it is filtered out.
2501      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
2502      * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
2503      * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
2504      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
2505      * </ul>
2506      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
2507      */
2508     filterBy : function(fn, scope){
2509         this.snapshot = this.snapshot || this.data;
2510         this.data = this.queryBy(fn, scope||this);
2511         this.fireEvent('datachanged', this);
2512     },
2513
2514     /**
2515      * Revert to a view of the Record cache with no filtering applied.
2516      * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the
2517      * {@link #datachanged} event.
2518      */
2519     clearFilter : function(suppressEvent){
2520         if(this.isFiltered()){
2521             this.data = this.snapshot;
2522             delete this.snapshot;
2523             if(suppressEvent !== true){
2524                 this.fireEvent('datachanged', this);
2525             }
2526         }
2527     },
2528
2529     /**
2530      * Returns true if this store is currently filtered
2531      * @return {Boolean}
2532      */
2533     isFiltered : function(){
2534         return !!this.snapshot && this.snapshot != this.data;
2535     },
2536
2537     /**
2538      * Query the records by a specified property.
2539      * @param {String} field A field on your records
2540      * @param {String/RegExp} value Either a string that the field
2541      * should begin with, or a RegExp to test against the field.
2542      * @param {Boolean} anyMatch (optional) True to match any part not just the beginning
2543      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
2544      * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
2545      */
2546     query : function(property, value, anyMatch, caseSensitive){
2547         var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
2548         return fn ? this.queryBy(fn) : this.data.clone();
2549     },
2550
2551     /**
2552      * Query the cached records in this Store using a filtering function. The specified function
2553      * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
2554      * included in the results.
2555      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
2556      * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
2557      * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
2558      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
2559      * </ul>
2560      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
2561      * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
2562      **/
2563     queryBy : function(fn, scope){
2564         var data = this.snapshot || this.data;
2565         return data.filterBy(fn, scope||this);
2566     },
2567
2568     /**
2569      * Finds the index of the first matching Record in this store by a specific field value.
2570      * @param {String} fieldName The name of the Record field to test.
2571      * @param {String/RegExp} value Either a string that the field value
2572      * should begin with, or a RegExp to test against the field.
2573      * @param {Number} startIndex (optional) The index to start searching at
2574      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
2575      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
2576      * @return {Number} The matched index or -1
2577      */
2578     find : function(property, value, start, anyMatch, caseSensitive){
2579         var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
2580         return fn ? this.data.findIndexBy(fn, null, start) : -1;
2581     },
2582
2583     /**
2584      * Finds the index of the first matching Record in this store by a specific field value.
2585      * @param {String} fieldName The name of the Record field to test.
2586      * @param {Mixed} value The value to match the field against.
2587      * @param {Number} startIndex (optional) The index to start searching at
2588      * @return {Number} The matched index or -1
2589      */
2590     findExact: function(property, value, start){
2591         return this.data.findIndexBy(function(rec){
2592             return rec.get(property) === value;
2593         }, this, start);
2594     },
2595
2596     /**
2597      * Find the index of the first matching Record in this Store by a function.
2598      * If the function returns <tt>true</tt> it is considered a match.
2599      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
2600      * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
2601      * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
2602      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
2603      * </ul>
2604      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
2605      * @param {Number} startIndex (optional) The index to start searching at
2606      * @return {Number} The matched index or -1
2607      */
2608     findBy : function(fn, scope, start){
2609         return this.data.findIndexBy(fn, scope, start);
2610     },
2611
2612     /**
2613      * Collects unique values for a particular dataIndex from this store.
2614      * @param {String} dataIndex The property to collect
2615      * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
2616      * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
2617      * @return {Array} An array of the unique values
2618      **/
2619     collect : function(dataIndex, allowNull, bypassFilter){
2620         var d = (bypassFilter === true && this.snapshot) ?
2621                 this.snapshot.items : this.data.items;
2622         var v, sv, r = [], l = {};
2623         for(var i = 0, len = d.length; i < len; i++){
2624             v = d[i].data[dataIndex];
2625             sv = String(v);
2626             if((allowNull || !Ext.isEmpty(v)) && !l[sv]){
2627                 l[sv] = true;
2628                 r[r.length] = v;
2629             }
2630         }
2631         return r;
2632     },
2633
2634     // private
2635     afterEdit : function(record){
2636         if(this.modified.indexOf(record) == -1){
2637             this.modified.push(record);
2638         }
2639         this.fireEvent('update', this, record, Ext.data.Record.EDIT);
2640     },
2641
2642     // private
2643     afterReject : function(record){
2644         this.modified.remove(record);
2645         this.fireEvent('update', this, record, Ext.data.Record.REJECT);
2646     },
2647
2648     // private
2649     afterCommit : function(record){
2650         this.modified.remove(record);
2651         this.fireEvent('update', this, record, Ext.data.Record.COMMIT);
2652     },
2653
2654     /**
2655      * Commit all Records with {@link #getModifiedRecords outstanding changes}. To handle updates for changes,
2656      * subscribe to the Store's {@link #update update event}, and perform updating when the third parameter is
2657      * Ext.data.Record.COMMIT.
2658      */
2659     commitChanges : function(){
2660         var m = this.modified.slice(0);
2661         this.modified = [];
2662         for(var i = 0, len = m.length; i < len; i++){
2663             m[i].commit();
2664         }
2665     },
2666
2667     /**
2668      * {@link Ext.data.Record#reject Reject} outstanding changes on all {@link #getModifiedRecords modified records}.
2669      */
2670     rejectChanges : function(){
2671         var m = this.modified.slice(0);
2672         this.modified = [];
2673         for(var i = 0, len = m.length; i < len; i++){
2674             m[i].reject();
2675         }
2676         var m = this.removed.slice(0).reverse();
2677         this.removed = [];
2678         for(var i = 0, len = m.length; i < len; i++){
2679             this.insert(m[i].lastIndex||0, m[i]);
2680             m[i].reject();
2681         }
2682     },
2683
2684     // private
2685     onMetaChange : function(meta){
2686         this.recordType = this.reader.recordType;
2687         this.fields = this.recordType.prototype.fields;
2688         delete this.snapshot;
2689         if(this.reader.meta.sortInfo){
2690             this.sortInfo = this.reader.meta.sortInfo;
2691         }else if(this.sortInfo  && !this.fields.get(this.sortInfo.field)){
2692             delete this.sortInfo;
2693         }
2694         if(this.writer){
2695             this.writer.meta = this.reader.meta;
2696         }
2697         this.modified = [];
2698         this.fireEvent('metachange', this, this.reader.meta);
2699     },
2700
2701     // private
2702     findInsertIndex : function(record){
2703         this.suspendEvents();
2704         var data = this.data.clone();
2705         this.data.add(record);
2706         this.applySort();
2707         var index = this.data.indexOf(record);
2708         this.data = data;
2709         this.resumeEvents();
2710         return index;
2711     },
2712
2713     /**
2714      * Set the value for a property name in this store's {@link #baseParams}.  Usage:</p><pre><code>
2715 myStore.setBaseParam('foo', {bar:3});
2716 </code></pre>
2717      * @param {String} name Name of the property to assign
2718      * @param {Mixed} value Value to assign the <tt>name</tt>d property
2719      **/
2720     setBaseParam : function (name, value){
2721         this.baseParams = this.baseParams || {};
2722         this.baseParams[name] = value;
2723     }
2724 });
2725
2726 Ext.reg('store', Ext.data.Store);
2727
2728 /**
2729  * @class Ext.data.Store.Error
2730  * @extends Ext.Error
2731  * Store Error extension.
2732  * @param {String} name
2733  */
2734 Ext.data.Store.Error = Ext.extend(Ext.Error, {
2735     name: 'Ext.data.Store'
2736 });
2737 Ext.apply(Ext.data.Store.Error.prototype, {
2738     lang: {
2739         'writer-undefined' : 'Attempted to execute a write-action without a DataWriter installed.'
2740     }
2741 });
2742 /**
2743  * @class Ext.data.Field
2744  * <p>This class encapsulates the field definition information specified in the field definition objects
2745  * passed to {@link Ext.data.Record#create}.</p>
2746  * <p>Developers do not need to instantiate this class. Instances are created by {@link Ext.data.Record.create}
2747  * and cached in the {@link Ext.data.Record#fields fields} property of the created Record constructor's <b>prototype.</b></p>
2748  */
2749 Ext.data.Field = Ext.extend(Object, {
2750     
2751     constructor : function(config){
2752         if(Ext.isString(config)){
2753             config = {name: config};
2754         }
2755         Ext.apply(this, config);
2756         
2757         var types = Ext.data.Types,
2758             st = this.sortType,
2759             t;
2760
2761         if(this.type){
2762             if(Ext.isString(this.type)){
2763                 this.type = Ext.data.Types[this.type.toUpperCase()] || types.AUTO;
2764             }
2765         }else{
2766             this.type = types.AUTO;
2767         }
2768
2769         // named sortTypes are supported, here we look them up
2770         if(Ext.isString(st)){
2771             this.sortType = Ext.data.SortTypes[st];
2772         }else if(Ext.isEmpty(st)){
2773             this.sortType = this.type.sortType;
2774         }
2775
2776         if(!this.convert){
2777             this.convert = this.type.convert;
2778         }
2779     },
2780     
2781     /**
2782      * @cfg {String} name
2783      * The name by which the field is referenced within the Record. This is referenced by, for example,
2784      * the <code>dataIndex</code> property in column definition objects passed to {@link Ext.grid.ColumnModel}.
2785      * <p>Note: In the simplest case, if no properties other than <code>name</code> are required, a field
2786      * definition may consist of just a String for the field name.</p>
2787      */
2788     /**
2789      * @cfg {Mixed} type
2790      * (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>
2791      * has not been specified. This may be specified as a string value. Possible values are
2792      * <div class="mdetail-params"><ul>
2793      * <li>auto (Default, implies no conversion)</li>
2794      * <li>string</li>
2795      * <li>int</li>
2796      * <li>float</li>
2797      * <li>boolean</li>
2798      * <li>date</li></ul></div>
2799      * <p>This may also be specified by referencing a member of the {@link Ext.data.Types} class.</p>
2800      * <p>Developers may create their own application-specific data types by defining new members of the
2801      * {@link Ext.data.Types} class.</p>
2802      */
2803     /**
2804      * @cfg {Function} convert
2805      * (Optional) A function which converts the value provided by the Reader into an object that will be stored
2806      * in the Record. It is passed the following parameters:<div class="mdetail-params"><ul>
2807      * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
2808      * the configured <code>{@link Ext.data.Field#defaultValue defaultValue}</code>.</div></li>
2809      * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
2810      * Depending on the Reader type, this could be an Array ({@link Ext.data.ArrayReader ArrayReader}), an object
2811      *  ({@link Ext.data.JsonReader JsonReader}), or an XML element ({@link Ext.data.XMLReader XMLReader}).</div></li>
2812      * </ul></div>
2813      * <pre><code>
2814 // example of convert function
2815 function fullName(v, record){
2816     return record.name.last + ', ' + record.name.first;
2817 }
2818
2819 function location(v, record){
2820     return !record.city ? '' : (record.city + ', ' + record.state);
2821 }
2822
2823 var Dude = Ext.data.Record.create([
2824     {name: 'fullname',  convert: fullName},
2825     {name: 'firstname', mapping: 'name.first'},
2826     {name: 'lastname',  mapping: 'name.last'},
2827     {name: 'city', defaultValue: 'homeless'},
2828     'state',
2829     {name: 'location',  convert: location}
2830 ]);
2831
2832 // create the data store
2833 var store = new Ext.data.Store({
2834     reader: new Ext.data.JsonReader(
2835         {
2836             idProperty: 'key',
2837             root: 'daRoot',
2838             totalProperty: 'total'
2839         },
2840         Dude  // recordType
2841     )
2842 });
2843
2844 var myData = [
2845     { key: 1,
2846       name: { first: 'Fat',    last:  'Albert' }
2847       // notice no city, state provided in data object
2848     },
2849     { key: 2,
2850       name: { first: 'Barney', last:  'Rubble' },
2851       city: 'Bedrock', state: 'Stoneridge'
2852     },
2853     { key: 3,
2854       name: { first: 'Cliff',  last:  'Claven' },
2855       city: 'Boston',  state: 'MA'
2856     }
2857 ];
2858      * </code></pre>
2859      */
2860     /**
2861      * @cfg {String} dateFormat
2862      * <p>(Optional) Used when converting received data into a Date when the {@link #type} is specified as <code>"date"</code>.</p>
2863      * <p>A format string for the {@link Date#parseDate Date.parseDate} function, or "timestamp" if the
2864      * value provided by the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a
2865      * javascript millisecond timestamp. See {@link Date}</p>
2866      */
2867     dateFormat: null,
2868     /**
2869      * @cfg {Mixed} defaultValue
2870      * (Optional) The default value used <b>when a Record is being created by a {@link Ext.data.Reader Reader}</b>
2871      * when the item referenced by the <code>{@link Ext.data.Field#mapping mapping}</code> does not exist in the data
2872      * object (i.e. undefined). (defaults to "")
2873      */
2874     defaultValue: "",
2875     /**
2876      * @cfg {String/Number} mapping
2877      * <p>(Optional) A path expression for use by the {@link Ext.data.DataReader} implementation
2878      * that is creating the {@link Ext.data.Record Record} to extract the Field value from the data object.
2879      * If the path expression is the same as the field name, the mapping may be omitted.</p>
2880      * <p>The form of the mapping expression depends on the Reader being used.</p>
2881      * <div class="mdetail-params"><ul>
2882      * <li>{@link Ext.data.JsonReader}<div class="sub-desc">The mapping is a string containing the javascript
2883      * 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>
2884      * <li>{@link Ext.data.XmlReader}<div class="sub-desc">The mapping is an {@link Ext.DomQuery} path to the data
2885      * item relative to the DOM element that represents the {@link Ext.data.XmlReader#record record}. Defaults to the field name.</div></li>
2886      * <li>{@link Ext.data.ArrayReader}<div class="sub-desc">The mapping is a number indicating the Array index
2887      * of the field's value. Defaults to the field specification's Array position.</div></li>
2888      * </ul></div>
2889      * <p>If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
2890      * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
2891      * return the desired data.</p>
2892      */
2893     mapping: null,
2894     /**
2895      * @cfg {Function} sortType
2896      * (Optional) A function which converts a Field's value to a comparable value in order to ensure
2897      * correct sort ordering. Predefined functions are provided in {@link Ext.data.SortTypes}. A custom
2898      * sort example:<pre><code>
2899 // current sort     after sort we want
2900 // +-+------+          +-+------+
2901 // |1|First |          |1|First |
2902 // |2|Last  |          |3|Second|
2903 // |3|Second|          |2|Last  |
2904 // +-+------+          +-+------+
2905
2906 sortType: function(value) {
2907    switch (value.toLowerCase()) // native toLowerCase():
2908    {
2909       case 'first': return 1;
2910       case 'second': return 2;
2911       default: return 3;
2912    }
2913 }
2914      * </code></pre>
2915      */
2916     sortType : null,
2917     /**
2918      * @cfg {String} sortDir
2919      * (Optional) Initial direction to sort (<code>"ASC"</code> or  <code>"DESC"</code>).  Defaults to
2920      * <code>"ASC"</code>.
2921      */
2922     sortDir : "ASC",
2923     /**
2924      * @cfg {Boolean} allowBlank
2925      * (Optional) Used for validating a {@link Ext.data.Record record}, defaults to <code>true</code>.
2926      * An empty value here will cause {@link Ext.data.Record}.{@link Ext.data.Record#isValid isValid}
2927      * to evaluate to <code>false</code>.
2928      */
2929     allowBlank : true
2930 });
2931 /**
2932  * @class Ext.data.DataReader
2933  * Abstract base class for reading structured data from a data source and converting
2934  * it into an object containing {@link Ext.data.Record} objects and metadata for use
2935  * by an {@link Ext.data.Store}.  This class is intended to be extended and should not
2936  * be created directly. For existing implementations, see {@link Ext.data.ArrayReader},
2937  * {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}.
2938  * @constructor Create a new DataReader
2939  * @param {Object} meta Metadata configuration options (implementation-specific).
2940  * @param {Array/Object} recordType
2941  * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
2942  * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
2943  * constructor created using {@link Ext.data.Record#create}.</p>
2944  */
2945 Ext.data.DataReader = function(meta, recordType){
2946     /**
2947      * This DataReader's configured metadata as passed to the constructor.
2948      * @type Mixed
2949      * @property meta
2950      */
2951     this.meta = meta;
2952     /**
2953      * @cfg {Array/Object} fields
2954      * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
2955      * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
2956      * constructor created from {@link Ext.data.Record#create}.</p>
2957      */
2958     this.recordType = Ext.isArray(recordType) ?
2959         Ext.data.Record.create(recordType) : recordType;
2960
2961     // if recordType defined make sure extraction functions are defined
2962     if (this.recordType){
2963         this.buildExtractors();
2964     }
2965 };
2966
2967 Ext.data.DataReader.prototype = {
2968     /**
2969      * @cfg {String} messageProperty [undefined] Optional name of a property within a server-response that represents a user-feedback message.
2970      */
2971     /**
2972      * Abstract method created in extension's buildExtractors impl.
2973      */
2974     getTotal: Ext.emptyFn,
2975     /**
2976      * Abstract method created in extension's buildExtractors impl.
2977      */
2978     getRoot: Ext.emptyFn,
2979     /**
2980      * Abstract method created in extension's buildExtractors impl.
2981      */
2982     getMessage: Ext.emptyFn,
2983     /**
2984      * Abstract method created in extension's buildExtractors impl.
2985      */
2986     getSuccess: Ext.emptyFn,
2987     /**
2988      * Abstract method created in extension's buildExtractors impl.
2989      */
2990     getId: Ext.emptyFn,
2991     /**
2992      * Abstract method, overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}
2993      */
2994     buildExtractors : Ext.emptyFn,
2995     /**
2996      * Abstract method overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}
2997      */
2998     extractValues : Ext.emptyFn,
2999
3000     /**
3001      * Used for un-phantoming a record after a successful database insert.  Sets the records pk along with new data from server.
3002      * You <b>must</b> return at least the database pk using the idProperty defined in your DataReader configuration.  The incoming
3003      * data from server will be merged with the data in the local record.
3004      * In addition, you <b>must</b> return record-data from the server in the same order received.
3005      * Will perform a commit as well, un-marking dirty-fields.  Store's "update" event will be suppressed.
3006      * @param {Record/Record[]} record The phantom record to be realized.
3007      * @param {Object/Object[]} data The new record data to apply.  Must include the primary-key from database defined in idProperty field.
3008      */
3009     realize: function(rs, data){
3010         if (Ext.isArray(rs)) {
3011             for (var i = rs.length - 1; i >= 0; i--) {
3012                 // recurse
3013                 if (Ext.isArray(data)) {
3014                     this.realize(rs.splice(i,1).shift(), data.splice(i,1).shift());
3015                 }
3016                 else {
3017                     // weird...rs is an array but data isn't??  recurse but just send in the whole invalid data object.
3018                     // the else clause below will detect !this.isData and throw exception.
3019                     this.realize(rs.splice(i,1).shift(), data);
3020                 }
3021             }
3022         }
3023         else {
3024             // If rs is NOT an array but data IS, see if data contains just 1 record.  If so extract it and carry on.
3025             if (Ext.isArray(data) && data.length == 1) {
3026                 data = data.shift();
3027             }
3028             if (!this.isData(data)) {
3029                 // TODO: Let exception-handler choose to commit or not rather than blindly rs.commit() here.
3030                 //rs.commit();
3031                 throw new Ext.data.DataReader.Error('realize', rs);
3032             }
3033             rs.phantom = false; // <-- That's what it's all about
3034             rs._phid = rs.id;  // <-- copy phantom-id -> _phid, so we can remap in Store#onCreateRecords
3035             rs.id = this.getId(data);
3036             rs.data = data;
3037
3038             rs.commit();
3039         }
3040     },
3041
3042     /**
3043      * Used for updating a non-phantom or "real" record's data with fresh data from server after remote-save.
3044      * If returning data from multiple-records after a batch-update, you <b>must</b> return record-data from the server in
3045      * the same order received.  Will perform a commit as well, un-marking dirty-fields.  Store's "update" event will be
3046      * suppressed as the record receives fresh new data-hash
3047      * @param {Record/Record[]} rs
3048      * @param {Object/Object[]} data
3049      */
3050     update : function(rs, data) {
3051         if (Ext.isArray(rs)) {
3052             for (var i=rs.length-1; i >= 0; i--) {
3053                 if (Ext.isArray(data)) {
3054                     this.update(rs.splice(i,1).shift(), data.splice(i,1).shift());
3055                 }
3056                 else {
3057                     // weird...rs is an array but data isn't??  recurse but just send in the whole data object.
3058                     // the else clause below will detect !this.isData and throw exception.
3059                     this.update(rs.splice(i,1).shift(), data);
3060                 }
3061             }
3062         }
3063         else {
3064             // If rs is NOT an array but data IS, see if data contains just 1 record.  If so extract it and carry on.
3065             if (Ext.isArray(data) && data.length == 1) {
3066                 data = data.shift();
3067             }
3068             if (this.isData(data)) {
3069                 rs.data = Ext.apply(rs.data, data);
3070             }
3071             rs.commit();
3072         }
3073     },
3074
3075     /**
3076      * returns extracted, type-cast rows of data.  Iterates to call #extractValues for each row
3077      * @param {Object[]/Object} data-root from server response
3078      * @param {Boolean} returnRecords [false] Set true to return instances of Ext.data.Record
3079      * @private
3080      */
3081     extractData : function(root, returnRecords) {
3082         // A bit ugly this, too bad the Record's raw data couldn't be saved in a common property named "raw" or something.
3083         var rawName = (this instanceof Ext.data.JsonReader) ? 'json' : 'node';
3084
3085         var rs = [];
3086
3087         // Had to add Check for XmlReader, #isData returns true if root is an Xml-object.  Want to check in order to re-factor
3088         // #extractData into DataReader base, since the implementations are almost identical for JsonReader, XmlReader
3089         if (this.isData(root) && !(this instanceof Ext.data.XmlReader)) {
3090             root = [root];
3091         }
3092         var f       = this.recordType.prototype.fields,
3093             fi      = f.items,
3094             fl      = f.length,
3095             rs      = [];
3096         if (returnRecords === true) {
3097             var Record = this.recordType;
3098             for (var i = 0; i < root.length; i++) {
3099                 var n = root[i];
3100                 var record = new Record(this.extractValues(n, fi, fl), this.getId(n));
3101                 record[rawName] = n;    // <-- There's implementation of ugly bit, setting the raw record-data.
3102                 rs.push(record);
3103             }
3104         }
3105         else {
3106             for (var i = 0; i < root.length; i++) {
3107                 var data = this.extractValues(root[i], fi, fl);
3108                 data[this.meta.idProperty] = this.getId(root[i]);
3109                 rs.push(data);
3110             }
3111         }
3112         return rs;
3113     },
3114
3115     /**
3116      * Returns true if the supplied data-hash <b>looks</b> and quacks like data.  Checks to see if it has a key
3117      * corresponding to idProperty defined in your DataReader config containing non-empty pk.
3118      * @param {Object} data
3119      * @return {Boolean}
3120      */
3121     isData : function(data) {
3122         return (data && Ext.isObject(data) && !Ext.isEmpty(this.getId(data))) ? true : false;
3123     },
3124
3125     // private function a store will createSequence upon
3126     onMetaChange : function(meta){
3127         delete this.ef;
3128         this.meta = meta;
3129         this.recordType = Ext.data.Record.create(meta.fields);
3130         this.buildExtractors();
3131     }
3132 };
3133
3134 /**
3135  * @class Ext.data.DataReader.Error
3136  * @extends Ext.Error
3137  * General error class for Ext.data.DataReader
3138  */
3139 Ext.data.DataReader.Error = Ext.extend(Ext.Error, {
3140     constructor : function(message, arg) {
3141         this.arg = arg;
3142         Ext.Error.call(this, message);
3143     },
3144     name: 'Ext.data.DataReader'
3145 });
3146 Ext.apply(Ext.data.DataReader.Error.prototype, {
3147     lang : {
3148         'update': "#update received invalid data from server.  Please see docs for DataReader#update and review your DataReader configuration.",
3149         'realize': "#realize was called with invalid remote-data.  Please see the docs for DataReader#realize and review your DataReader configuration.",
3150         'invalid-response': "#readResponse received an invalid response from the server."
3151     }
3152 });
3153 /**
3154  * @class Ext.data.DataWriter
3155  * <p>Ext.data.DataWriter facilitates create, update, and destroy actions between
3156  * an Ext.data.Store and a server-side framework. A Writer enabled Store will
3157  * automatically manage the Ajax requests to perform CRUD actions on a Store.</p>
3158  * <p>Ext.data.DataWriter is an abstract base class which is intended to be extended
3159  * and should not be created directly. For existing implementations, see
3160  * {@link Ext.data.JsonWriter}.</p>
3161  * <p>Creating a writer is simple:</p>
3162  * <pre><code>
3163 var writer = new Ext.data.JsonWriter({
3164     encode: false   // &lt;--- false causes data to be printed to jsonData config-property of Ext.Ajax#reqeust
3165 });
3166  * </code></pre>
3167  * * <p>Same old JsonReader as Ext-2.x:</p>
3168  * <pre><code>
3169 var reader = new Ext.data.JsonReader({idProperty: 'id'}, [{name: 'first'}, {name: 'last'}, {name: 'email'}]);
3170  * </code></pre>
3171  *
3172  * <p>The proxy for a writer enabled store can be configured with a simple <code>url</code>:</p>
3173  * <pre><code>
3174 // Create a standard HttpProxy instance.
3175 var proxy = new Ext.data.HttpProxy({
3176     url: 'app.php/users'    // &lt;--- Supports "provides"-type urls, such as '/users.json', '/products.xml' (Hello Rails/Merb)
3177 });
3178  * </code></pre>
3179  * <p>For finer grained control, the proxy may also be configured with an <code>API</code>:</p>
3180  * <pre><code>
3181 // Maximum flexibility with the API-configuration
3182 var proxy = new Ext.data.HttpProxy({
3183     api: {
3184         read    : 'app.php/users/read',
3185         create  : 'app.php/users/create',
3186         update  : 'app.php/users/update',
3187         destroy : {  // &lt;--- Supports object-syntax as well
3188             url: 'app.php/users/destroy',
3189             method: "DELETE"
3190         }
3191     }
3192 });
3193  * </code></pre>
3194  * <p>Pulling it all together into a Writer-enabled Store:</p>
3195  * <pre><code>
3196 var store = new Ext.data.Store({
3197     proxy: proxy,
3198     reader: reader,
3199     writer: writer,
3200     autoLoad: true,
3201     autoSave: true  // -- Cell-level updates.
3202 });
3203  * </code></pre>
3204  * <p>Initiating write-actions <b>automatically</b>, using the existing Ext2.0 Store/Record API:</p>
3205  * <pre><code>
3206 var rec = store.getAt(0);
3207 rec.set('email', 'foo@bar.com');  // &lt;--- Immediately initiates an UPDATE action through configured proxy.
3208
3209 store.remove(rec);  // &lt;---- Immediately initiates a DESTROY action through configured proxy.
3210  * </code></pre>
3211  * <p>For <b>record/batch</b> updates, use the Store-configuration {@link Ext.data.Store#autoSave autoSave:false}</p>
3212  * <pre><code>
3213 var store = new Ext.data.Store({
3214     proxy: proxy,
3215     reader: reader,
3216     writer: writer,
3217     autoLoad: true,
3218     autoSave: false  // -- disable cell-updates
3219 });
3220
3221 var urec = store.getAt(0);
3222 urec.set('email', 'foo@bar.com');
3223
3224 var drec = store.getAt(1);
3225 store.remove(drec);
3226
3227 // Push the button!
3228 store.save();
3229  * </code></pre>
3230  * @constructor Create a new DataWriter
3231  * @param {Object} meta Metadata configuration options (implementation-specific)
3232  * @param {Object} recordType Either an Array of field definition objects as specified
3233  * in {@link Ext.data.Record#create}, or an {@link Ext.data.Record} object created
3234  * using {@link Ext.data.Record#create}.
3235  */
3236 Ext.data.DataWriter = function(config){
3237     Ext.apply(this, config);
3238 };
3239 Ext.data.DataWriter.prototype = {
3240
3241     /**
3242      * @cfg {Boolean} writeAllFields
3243      * <tt>false</tt> by default.  Set <tt>true</tt> to have DataWriter return ALL fields of a modified
3244      * record -- not just those that changed.
3245      * <tt>false</tt> to have DataWriter only request modified fields from a record.
3246      */
3247     writeAllFields : false,
3248     /**
3249      * @cfg {Boolean} listful
3250      * <tt>false</tt> by default.  Set <tt>true</tt> to have the DataWriter <b>always</b> write HTTP params as a list,
3251      * even when acting upon a single record.
3252      */
3253     listful : false,    // <-- listful is actually not used internally here in DataWriter.  @see Ext.data.Store#execute.
3254
3255     /**
3256      * 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},
3257      * Where the first parameter is the <i>receiver</i> of paramaters and the second, baseParams, <i>the source</i>.
3258      * @param {Object} params The request-params receiver.
3259      * @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}.
3260      * @param {String} action [{@link Ext.data.Api#actions create|update|destroy}]
3261      * @param {Record/Record[]} rs The recordset to write, the subject(s) of the write action.
3262      */
3263     apply : function(params, baseParams, action, rs) {
3264         var data    = [],
3265         renderer    = action + 'Record';
3266         // TODO implement @cfg listful here
3267         if (Ext.isArray(rs)) {
3268             Ext.each(rs, function(rec){
3269                 data.push(this[renderer](rec));
3270             }, this);
3271         }
3272         else if (rs instanceof Ext.data.Record) {
3273             data = this[renderer](rs);
3274         }
3275         this.render(params, baseParams, data);
3276     },
3277
3278     /**
3279      * abstract method meant to be overridden by all DataWriter extensions.  It's the extension's job to apply the "data" to the "params".
3280      * The data-object provided to render is populated with data according to the meta-info defined in the user's DataReader config,
3281      * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
3282      * @param {Record[]} rs Store recordset
3283      * @param {Object} params Http params to be sent to server.
3284      * @param {Object} data object populated according to DataReader meta-data.
3285      */
3286     render : Ext.emptyFn,
3287
3288     /**
3289      * @cfg {Function} updateRecord Abstract method that should be implemented in all subclasses
3290      * (e.g.: {@link Ext.data.JsonWriter#updateRecord JsonWriter.updateRecord}
3291      */
3292     updateRecord : Ext.emptyFn,
3293
3294     /**
3295      * @cfg {Function} createRecord Abstract method that should be implemented in all subclasses
3296      * (e.g.: {@link Ext.data.JsonWriter#createRecord JsonWriter.createRecord})
3297      */
3298     createRecord : Ext.emptyFn,
3299
3300     /**
3301      * @cfg {Function} destroyRecord Abstract method that should be implemented in all subclasses
3302      * (e.g.: {@link Ext.data.JsonWriter#destroyRecord JsonWriter.destroyRecord})
3303      */
3304     destroyRecord : Ext.emptyFn,
3305
3306     /**
3307      * Converts a Record to a hash, taking into account the state of the Ext.data.Record along with configuration properties
3308      * related to its rendering, such as {@link #writeAllFields}, {@link Ext.data.Record#phantom phantom}, {@link Ext.data.Record#getChanges getChanges} and
3309      * {@link Ext.data.DataReader#idProperty idProperty}
3310      * @param {Ext.data.Record} rec The Record from which to create a hash.
3311      * @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.
3312      * @return {Object}
3313      * @protected
3314      * TODO Implement excludes/only configuration with 2nd param?
3315      */
3316     toHash : function(rec, config) {
3317         var map = rec.fields.map,
3318             data = {},
3319             raw = (this.writeAllFields === false && rec.phantom === false) ? rec.getChanges() : rec.data,
3320             m;
3321         Ext.iterate(raw, function(prop, value){
3322             if((m = map[prop])){
3323                 data[m.mapping ? m.mapping : m.name] = value;
3324             }
3325         });
3326         // 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.
3327         // We can tell its not auto-increment if the user defined a DataReader field for it *and* that field's value is non-empty.
3328         // we could also do a RegExp here for the Ext.data.Record AUTO_ID prefix.
3329         if (rec.phantom) {
3330             if (rec.fields.containsKey(this.meta.idProperty) && Ext.isEmpty(rec.data[this.meta.idProperty])) {
3331                 delete data[this.meta.idProperty];
3332             }
3333         } else {
3334             data[this.meta.idProperty] = rec.id
3335         }
3336         return data;
3337     },
3338
3339     /**
3340      * Converts a {@link Ext.data.DataWriter#toHash Hashed} {@link Ext.data.Record} to fields-array array suitable
3341      * for encoding to xml via XTemplate, eg:
3342 <code><pre>&lt;tpl for=".">&lt;{name}>{value}&lt;/{name}&lt;/tpl></pre></code>
3343      * eg, <b>non-phantom</b>:
3344 <code><pre>{id: 1, first: 'foo', last: 'bar'} --> [{name: 'id', value: 1}, {name: 'first', value: 'foo'}, {name: 'last', value: 'bar'}]</pre></code>
3345      * {@link Ext.data.Record#phantom Phantom} records will have had their idProperty omitted in {@link #toHash} if determined to be auto-generated.
3346      * Non AUTOINCREMENT pks should have been protected.
3347      * @param {Hash} data Hashed by Ext.data.DataWriter#toHash
3348      * @return {[Object]} Array of attribute-objects.
3349      * @protected
3350      */
3351     toArray : function(data) {
3352         var fields = [];
3353         Ext.iterate(data, function(k, v) {fields.push({name: k, value: v});},this);
3354         return fields;
3355     }
3356 };/**
3357  * @class Ext.data.DataProxy
3358  * @extends Ext.util.Observable
3359  * <p>Abstract base class for implementations which provide retrieval of unformatted data objects.
3360  * This class is intended to be extended and should not be created directly. For existing implementations,
3361  * see {@link Ext.data.DirectProxy}, {@link Ext.data.HttpProxy}, {@link Ext.data.ScriptTagProxy} and
3362  * {@link Ext.data.MemoryProxy}.</p>
3363  * <p>DataProxy implementations are usually used in conjunction with an implementation of {@link Ext.data.DataReader}
3364  * (of the appropriate type which knows how to parse the data object) to provide a block of
3365  * {@link Ext.data.Records} to an {@link Ext.data.Store}.</p>
3366  * <p>The parameter to a DataProxy constructor may be an {@link Ext.data.Connection} or can also be the
3367  * config object to an {@link Ext.data.Connection}.</p>
3368  * <p>Custom implementations must implement either the <code><b>doRequest</b></code> method (preferred) or the
3369  * <code>load</code> method (deprecated). See
3370  * {@link Ext.data.HttpProxy}.{@link Ext.data.HttpProxy#doRequest doRequest} or
3371  * {@link Ext.data.HttpProxy}.{@link Ext.data.HttpProxy#load load} for additional details.</p>
3372  * <p><b><u>Example 1</u></b></p>
3373  * <pre><code>
3374 proxy: new Ext.data.ScriptTagProxy({
3375     {@link Ext.data.Connection#url url}: 'http://extjs.com/forum/topics-remote.php'
3376 }),
3377  * </code></pre>
3378  * <p><b><u>Example 2</u></b></p>
3379  * <pre><code>
3380 proxy : new Ext.data.HttpProxy({
3381     {@link Ext.data.Connection#method method}: 'GET',
3382     {@link Ext.data.HttpProxy#prettyUrls prettyUrls}: false,
3383     {@link Ext.data.Connection#url url}: 'local/default.php', // see options parameter for {@link Ext.Ajax#request}
3384     {@link #api}: {
3385         // all actions except the following will use above url
3386         create  : 'local/new.php',
3387         update  : 'local/update.php'
3388     }
3389 }),
3390  * </code></pre>
3391  * <p>And <b>new in Ext version 3</b>, attach centralized event-listeners upon the DataProxy class itself!  This is a great place
3392  * to implement a <i>messaging system</i> to centralize your application's user-feedback and error-handling.</p>
3393  * <pre><code>
3394 // Listen to all "beforewrite" event fired by all proxies.
3395 Ext.data.DataProxy.on('beforewrite', function(proxy, action) {
3396     console.log('beforewrite: ', action);
3397 });
3398
3399 // Listen to "write" event fired by all proxies
3400 Ext.data.DataProxy.on('write', function(proxy, action, data, res, rs) {
3401     console.info('write: ', action);
3402 });
3403
3404 // Listen to "exception" event fired by all proxies
3405 Ext.data.DataProxy.on('exception', function(proxy, type, action) {
3406     console.error(type + action + ' exception);
3407 });
3408  * </code></pre>
3409  * <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}.
3410  */
3411 Ext.data.DataProxy = function(conn){
3412     // make sure we have a config object here to support ux proxies.
3413     // All proxies should now send config into superclass constructor.
3414     conn = conn || {};
3415
3416     // This line caused a bug when people use custom Connection object having its own request method.
3417     // http://extjs.com/forum/showthread.php?t=67194.  Have to set DataProxy config
3418     //Ext.applyIf(this, conn);
3419
3420     this.api     = conn.api;
3421     this.url     = conn.url;
3422     this.restful = conn.restful;
3423     this.listeners = conn.listeners;
3424
3425     // deprecated
3426     this.prettyUrls = conn.prettyUrls;
3427
3428     /**
3429      * @cfg {Object} api
3430      * Specific urls to call on CRUD action methods "read", "create", "update" and "destroy".
3431      * Defaults to:<pre><code>
3432 api: {
3433     read    : undefined,
3434     create  : undefined,
3435     update  : undefined,
3436     destroy : undefined
3437 }
3438      * </code></pre>
3439      * <p>The url is built based upon the action being executed <tt>[load|create|save|destroy]</tt>
3440      * using the commensurate <tt>{@link #api}</tt> property, or if undefined default to the
3441      * configured {@link Ext.data.Store}.{@link Ext.data.Store#url url}.</p><br>
3442      * <p>For example:</p>
3443      * <pre><code>
3444 api: {
3445     load :    '/controller/load',
3446     create :  '/controller/new',  // Server MUST return idProperty of new record
3447     save :    '/controller/update',
3448     destroy : '/controller/destroy_action'
3449 }
3450
3451 // Alternatively, one can use the object-form to specify each API-action
3452 api: {
3453     load: {url: 'read.php', method: 'GET'},
3454     create: 'create.php',
3455     destroy: 'destroy.php',
3456     save: 'update.php'
3457 }
3458      * </code></pre>
3459      * <p>If the specific URL for a given CRUD action is undefined, the CRUD action request
3460      * will be directed to the configured <tt>{@link Ext.data.Connection#url url}</tt>.</p>
3461      * <br><p><b>Note</b>: To modify the URL for an action dynamically the appropriate API
3462      * property should be modified before the action is requested using the corresponding before
3463      * action event.  For example to modify the URL associated with the load action:
3464      * <pre><code>
3465 // modify the url for the action
3466 myStore.on({
3467     beforeload: {
3468         fn: function (store, options) {
3469             // use <tt>{@link Ext.data.HttpProxy#setUrl setUrl}</tt> to change the URL for *just* this request.
3470             store.proxy.setUrl('changed1.php');
3471
3472             // set optional second parameter to true to make this URL change
3473             // permanent, applying this URL for all subsequent requests.
3474             store.proxy.setUrl('changed1.php', true);
3475
3476             // Altering the proxy API should be done using the public
3477             // method <tt>{@link Ext.data.DataProxy#setApi setApi}</tt>.
3478             store.proxy.setApi('read', 'changed2.php');
3479
3480             // Or set the entire API with a config-object.
3481             // When using the config-object option, you must redefine the <b>entire</b>
3482             // API -- not just a specific action of it.
3483             store.proxy.setApi({
3484                 read    : 'changed_read.php',
3485                 create  : 'changed_create.php',
3486                 update  : 'changed_update.php',
3487                 destroy : 'changed_destroy.php'
3488             });
3489         }
3490     }
3491 });
3492      * </code></pre>
3493      * </p>
3494      */
3495
3496     this.addEvents(
3497         /**
3498          * @event exception
3499          * <p>Fires if an exception occurs in the Proxy during a remote request. This event is relayed
3500          * through a corresponding {@link Ext.data.Store}.{@link Ext.data.Store#exception exception},
3501          * so any Store instance may observe this event.</p>
3502          * <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
3503          * through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of exception events from <b>all</b>
3504          * DataProxies by attaching a listener to the Ext.data.Proxy class itself.</p>
3505          * <p>This event can be fired for one of two reasons:</p>
3506          * <div class="mdetail-params"><ul>
3507          * <li>remote-request <b>failed</b> : <div class="sub-desc">
3508          * The server did not return status === 200.
3509          * </div></li>
3510          * <li>remote-request <b>succeeded</b> : <div class="sub-desc">
3511          * The remote-request succeeded but the reader could not read the response.
3512          * This means the server returned data, but the configured Reader threw an
3513          * error while reading the response.  In this case, this event will be
3514          * raised and the caught error will be passed along into this event.
3515          * </div></li>
3516          * </ul></div>
3517          * <br><p>This event fires with two different contexts based upon the 2nd
3518          * parameter <tt>type [remote|response]</tt>.  The first four parameters
3519          * are identical between the two contexts -- only the final two parameters
3520          * differ.</p>
3521          * @param {DataProxy} this The proxy that sent the request
3522          * @param {String} type
3523          * <p>The value of this parameter will be either <tt>'response'</tt> or <tt>'remote'</tt>.</p>
3524          * <div class="mdetail-params"><ul>
3525          * <li><b><tt>'response'</tt></b> : <div class="sub-desc">
3526          * <p>An <b>invalid</b> response from the server was returned: either 404,
3527          * 500 or the response meta-data does not match that defined in the DataReader
3528          * (e.g.: root, idProperty, successProperty).</p>
3529          * </div></li>
3530          * <li><b><tt>'remote'</tt></b> : <div class="sub-desc">
3531          * <p>A <b>valid</b> response was returned from the server having
3532          * successProperty === false.  This response might contain an error-message
3533          * sent from the server.  For example, the user may have failed
3534          * authentication/authorization or a database validation error occurred.</p>
3535          * </div></li>
3536          * </ul></div>
3537          * @param {String} action Name of the action (see {@link Ext.data.Api#actions}.
3538          * @param {Object} options The options for the action that were specified in the {@link #request}.
3539          * @param {Object} response
3540          * <p>The value of this parameter depends on the value of the <code>type</code> parameter:</p>
3541          * <div class="mdetail-params"><ul>
3542          * <li><b><tt>'response'</tt></b> : <div class="sub-desc">
3543          * <p>The raw browser response object (e.g.: XMLHttpRequest)</p>
3544          * </div></li>
3545          * <li><b><tt>'remote'</tt></b> : <div class="sub-desc">
3546          * <p>The decoded response object sent from the server.</p>
3547          * </div></li>
3548          * </ul></div>
3549          * @param {Mixed} arg
3550          * <p>The type and value of this parameter depends on the value of the <code>type</code> parameter:</p>
3551          * <div class="mdetail-params"><ul>
3552          * <li><b><tt>'response'</tt></b> : Error<div class="sub-desc">
3553          * <p>The JavaScript Error object caught if the configured Reader could not read the data.
3554          * If the remote request returns success===false, this parameter will be null.</p>
3555          * </div></li>
3556          * <li><b><tt>'remote'</tt></b> : Record/Record[]<div class="sub-desc">
3557          * <p>This parameter will only exist if the <tt>action</tt> was a <b>write</b> action
3558          * (Ext.data.Api.actions.create|update|destroy).</p>
3559          * </div></li>
3560          * </ul></div>
3561          */
3562         'exception',
3563         /**
3564          * @event beforeload
3565          * Fires before a request to retrieve a data object.
3566          * @param {DataProxy} this The proxy for the request
3567          * @param {Object} params The params object passed to the {@link #request} function
3568          */
3569         'beforeload',
3570         /**
3571          * @event load
3572          * Fires before the load method's callback is called.
3573          * @param {DataProxy} this The proxy for the request
3574          * @param {Object} o The request transaction object
3575          * @param {Object} options The callback's <tt>options</tt> property as passed to the {@link #request} function
3576          */
3577         'load',
3578         /**
3579          * @event loadexception
3580          * <p>This event is <b>deprecated</b>.  The signature of the loadexception event
3581          * varies depending on the proxy, use the catch-all {@link #exception} event instead.
3582          * This event will fire in addition to the {@link #exception} event.</p>
3583          * @param {misc} misc See {@link #exception}.
3584          * @deprecated
3585          */
3586         'loadexception',
3587         /**
3588          * @event beforewrite
3589          * <p>Fires before a request is generated for one of the actions Ext.data.Api.actions.create|update|destroy</p>
3590          * <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
3591          * through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of beforewrite events from <b>all</b>
3592          * DataProxies by attaching a listener to the Ext.data.Proxy class itself.</p>
3593          * @param {DataProxy} this The proxy for the request
3594          * @param {String} action [Ext.data.Api.actions.create|update|destroy]
3595          * @param {Record/Record[]} rs The Record(s) to create|update|destroy.
3596          * @param {Object} params The request <code>params</code> object.  Edit <code>params</code> to add parameters to the request.
3597          */
3598         'beforewrite',
3599         /**
3600          * @event write
3601          * <p>Fires before the request-callback is called</p>
3602          * <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
3603          * through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of write events from <b>all</b>
3604          * DataProxies by attaching a listener to the Ext.data.Proxy class itself.</p>
3605          * @param {DataProxy} this The proxy that sent the request
3606          * @param {String} action [Ext.data.Api.actions.create|upate|destroy]
3607          * @param {Object} data The data object extracted from the server-response
3608          * @param {Object} response The decoded response from server
3609          * @param {Record/Record[]} rs The Record(s) from Store
3610          * @param {Object} options The callback's <tt>options</tt> property as passed to the {@link #request} function
3611          */
3612         'write'
3613     );
3614     Ext.data.DataProxy.superclass.constructor.call(this);
3615
3616     // Prepare the proxy api.  Ensures all API-actions are defined with the Object-form.
3617     try {
3618         Ext.data.Api.prepare(this);
3619     } catch (e) {
3620         if (e instanceof Ext.data.Api.Error) {
3621             e.toConsole();
3622         }
3623     }
3624     // relay each proxy's events onto Ext.data.DataProxy class for centralized Proxy-listening
3625     Ext.data.DataProxy.relayEvents(this, ['beforewrite', 'write', 'exception']);
3626 };
3627
3628 Ext.extend(Ext.data.DataProxy, Ext.util.Observable, {
3629     /**
3630      * @cfg {Boolean} restful
3631      * <p>Defaults to <tt>false</tt>.  Set to <tt>true</tt> to operate in a RESTful manner.</p>
3632      * <br><p> Note: this parameter will automatically be set to <tt>true</tt> if the
3633      * {@link Ext.data.Store} it is plugged into is set to <code>restful: true</code>. If the
3634      * Store is RESTful, there is no need to set this option on the proxy.</p>
3635      * <br><p>RESTful implementations enable the serverside framework to automatically route
3636      * actions sent to one url based upon the HTTP method, for example:
3637      * <pre><code>
3638 store: new Ext.data.Store({
3639     restful: true,
3640     proxy: new Ext.data.HttpProxy({url:'/users'}); // all requests sent to /users
3641     ...
3642 )}
3643      * </code></pre>
3644      * If there is no <code>{@link #api}</code> specified in the configuration of the proxy,
3645      * all requests will be marshalled to a single RESTful url (/users) so the serverside
3646      * framework can inspect the HTTP Method and act accordingly:
3647      * <pre>
3648 <u>Method</u>   <u>url</u>        <u>action</u>
3649 POST     /users     create
3650 GET      /users     read
3651 PUT      /users/23  update
3652 DESTROY  /users/23  delete
3653      * </pre></p>
3654      * <p>If set to <tt>true</tt>, a {@link Ext.data.Record#phantom non-phantom} record's
3655      * {@link Ext.data.Record#id id} will be appended to the url. Some MVC (e.g., Ruby on Rails,
3656      * Merb and Django) support segment based urls where the segments in the URL follow the
3657      * Model-View-Controller approach:<pre><code>
3658      * someSite.com/controller/action/id
3659      * </code></pre>
3660      * Where the segments in the url are typically:<div class="mdetail-params"><ul>
3661      * <li>The first segment : represents the controller class that should be invoked.</li>
3662      * <li>The second segment : represents the class function, or method, that should be called.</li>
3663      * <li>The third segment : represents the ID (a variable typically passed to the method).</li>
3664      * </ul></div></p>
3665      * <br><p>Refer to <code>{@link Ext.data.DataProxy#api}</code> for additional information.</p>
3666      */
3667     restful: false,
3668
3669     /**
3670      * <p>Redefines the Proxy's API or a single action of an API. Can be called with two method signatures.</p>
3671      * <p>If called with an object as the only parameter, the object should redefine the <b>entire</b> API, e.g.:</p><pre><code>
3672 proxy.setApi({
3673     read    : '/users/read',
3674     create  : '/users/create',
3675     update  : '/users/update',
3676     destroy : '/users/destroy'
3677 });
3678 </code></pre>
3679      * <p>If called with two parameters, the first parameter should be a string specifying the API action to
3680      * redefine and the second parameter should be the URL (or function if using DirectProxy) to call for that action, e.g.:</p><pre><code>
3681 proxy.setApi(Ext.data.Api.actions.read, '/users/new_load_url');
3682 </code></pre>
3683      * @param {String/Object} api An API specification object, or the name of an action.
3684      * @param {String/Function} url The URL (or function if using DirectProxy) to call for the action.
3685      */
3686     setApi : function() {
3687         if (arguments.length == 1) {
3688             var valid = Ext.data.Api.isValid(arguments[0]);
3689             if (valid === true) {
3690                 this.api = arguments[0];
3691             }
3692             else {
3693                 throw new Ext.data.Api.Error('invalid', valid);
3694             }
3695         }
3696         else if (arguments.length == 2) {
3697             if (!Ext.data.Api.isAction(arguments[0])) {
3698                 throw new Ext.data.Api.Error('invalid', arguments[0]);
3699             }
3700             this.api[arguments[0]] = arguments[1];
3701         }
3702         Ext.data.Api.prepare(this);
3703     },
3704
3705     /**
3706      * Returns true if the specified action is defined as a unique action in the api-config.
3707      * request.  If all API-actions are routed to unique urls, the xaction parameter is unecessary.  However, if no api is defined
3708      * and all Proxy actions are routed to DataProxy#url, the server-side will require the xaction parameter to perform a switch to
3709      * the corresponding code for CRUD action.
3710      * @param {String [Ext.data.Api.CREATE|READ|UPDATE|DESTROY]} action
3711      * @return {Boolean}
3712      */
3713     isApiAction : function(action) {
3714         return (this.api[action]) ? true : false;
3715     },
3716
3717     /**
3718      * All proxy actions are executed through this method.  Automatically fires the "before" + action event
3719      * @param {String} action Name of the action
3720      * @param {Ext.data.Record/Ext.data.Record[]/null} rs Will be null when action is 'load'
3721      * @param {Object} params
3722      * @param {Ext.data.DataReader} reader
3723      * @param {Function} callback
3724      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the Proxy object.
3725      * @param {Object} options Any options specified for the action (e.g. see {@link Ext.data.Store#load}.
3726      */
3727     request : function(action, rs, params, reader, callback, scope, options) {
3728         if (!this.api[action] && !this.load) {
3729             throw new Ext.data.DataProxy.Error('action-undefined', action);
3730         }
3731         params = params || {};
3732         if ((action === Ext.data.Api.actions.read) ? this.fireEvent("beforeload", this, params) : this.fireEvent("beforewrite", this, action, rs, params) !== false) {
3733             this.doRequest.apply(this, arguments);
3734         }
3735         else {
3736             callback.call(scope || this, null, options, false);
3737         }
3738     },
3739
3740
3741     /**
3742      * <b>Deprecated</b> load method using old method signature. See {@doRequest} for preferred method.
3743      * @deprecated
3744      * @param {Object} params
3745      * @param {Object} reader
3746      * @param {Object} callback
3747      * @param {Object} scope
3748      * @param {Object} arg
3749      */
3750     load : null,
3751
3752     /**
3753      * @cfg {Function} doRequest Abstract method that should be implemented in all subclasses.  <b>Note:</b> Should only be used by custom-proxy developers.
3754      * (e.g.: {@link Ext.data.HttpProxy#doRequest HttpProxy.doRequest},
3755      * {@link Ext.data.DirectProxy#doRequest DirectProxy.doRequest}).
3756      */
3757     doRequest : function(action, rs, params, reader, callback, scope, options) {
3758         // default implementation of doRequest for backwards compatibility with 2.0 proxies.
3759         // If we're executing here, the action is probably "load".
3760         // Call with the pre-3.0 method signature.
3761         this.load(params, reader, callback, scope, options);
3762     },
3763
3764     /**
3765      * @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}.
3766      * @param {String} action Action name as per {@link Ext.data.Api.actions#read}.
3767      * @param {Object} o The request transaction object
3768      * @param {Object} res The server response
3769      * @fires loadexception (deprecated)
3770      * @fires exception
3771      * @fires load
3772      * @protected
3773      */
3774     onRead : Ext.emptyFn,
3775     /**
3776      * @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}.
3777      * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
3778      * @param {Object} trans The request transaction object
3779      * @param {Object} res The server response
3780      * @fires exception
3781      * @fires write
3782      * @protected
3783      */
3784     onWrite : Ext.emptyFn,
3785     /**
3786      * buildUrl
3787      * Sets the appropriate url based upon the action being executed.  If restful is true, and only a single record is being acted upon,
3788      * url will be built Rails-style, as in "/controller/action/32".  restful will aply iff the supplied record is an
3789      * instance of Ext.data.Record rather than an Array of them.
3790      * @param {String} action The api action being executed [read|create|update|destroy]
3791      * @param {Ext.data.Record/Ext.data.Record[]} record The record or Array of Records being acted upon.
3792      * @return {String} url
3793      * @private
3794      */
3795     buildUrl : function(action, record) {
3796         record = record || null;
3797
3798         // conn.url gets nullified after each request.  If it's NOT null here, that means the user must have intervened with a call
3799         // to DataProxy#setUrl or DataProxy#setApi and changed it before the request was executed.  If that's the case, use conn.url,
3800         // otherwise, build the url from the api or this.url.
3801         var url = (this.conn && this.conn.url) ? this.conn.url : (this.api[action]) ? this.api[action].url : this.url;
3802         if (!url) {
3803             throw new Ext.data.Api.Error('invalid-url', action);
3804         }
3805
3806         // look for urls having "provides" suffix used in some MVC frameworks like Rails/Merb and others.  The provides suffice informs
3807         // the server what data-format the client is dealing with and returns data in the same format (eg: application/json, application/xml, etc)
3808         // e.g.: /users.json, /users.xml, etc.
3809         // with restful routes, we need urls like:
3810         // PUT /users/1.json
3811         // DELETE /users/1.json
3812         var provides = null;
3813         var m = url.match(/(.*)(\.json|\.xml|\.html)$/);
3814         if (m) {
3815             provides = m[2];    // eg ".json"
3816             url      = m[1];    // eg: "/users"
3817         }
3818         // prettyUrls is deprectated in favor of restful-config
3819         if ((this.restful === true || this.prettyUrls === true) && record instanceof Ext.data.Record && !record.phantom) {
3820             url += '/' + record.id;
3821         }
3822         return (provides === null) ? url : url + provides;
3823     },
3824
3825     /**
3826      * Destroys the proxy by purging any event listeners and cancelling any active requests.
3827      */
3828     destroy: function(){
3829         this.purgeListeners();
3830     }
3831 });
3832
3833 // Apply the Observable prototype to the DataProxy class so that proxy instances can relay their
3834 // events to the class.  Allows for centralized listening of all proxy instances upon the DataProxy class.
3835 Ext.apply(Ext.data.DataProxy, Ext.util.Observable.prototype);
3836 Ext.util.Observable.call(Ext.data.DataProxy);
3837
3838 /**
3839  * @class Ext.data.DataProxy.Error
3840  * @extends Ext.Error
3841  * DataProxy Error extension.
3842  * constructor
3843  * @param {String} message Message describing the error.
3844  * @param {Record/Record[]} arg
3845  */
3846 Ext.data.DataProxy.Error = Ext.extend(Ext.Error, {
3847     constructor : function(message, arg) {
3848         this.arg = arg;
3849         Ext.Error.call(this, message);
3850     },
3851     name: 'Ext.data.DataProxy'
3852 });
3853 Ext.apply(Ext.data.DataProxy.Error.prototype, {
3854     lang: {
3855         'action-undefined': "DataProxy attempted to execute an API-action but found an undefined url / function.  Please review your Proxy url/api-configuration.",
3856         'api-invalid': 'Recieved an invalid API-configuration.  Please ensure your proxy API-configuration contains only the actions from Ext.data.Api.actions.'
3857     }
3858 });
3859
3860
3861 /**
3862  * @class Ext.data.Request
3863  * A simple Request class used internally to the data package to provide more generalized remote-requests
3864  * to a DataProxy.
3865  * TODO Not yet implemented.  Implement in Ext.data.Store#execute
3866  */
3867 Ext.data.Request = function(params) {
3868     Ext.apply(this, params);
3869 };
3870 Ext.data.Request.prototype = {
3871     /**
3872      * @cfg {String} action
3873      */
3874     action : undefined,
3875     /**
3876      * @cfg {Ext.data.Record[]/Ext.data.Record} rs The Store recordset associated with the request.
3877      */
3878     rs : undefined,
3879     /**
3880      * @cfg {Object} params HTTP request params
3881      */
3882     params: undefined,
3883     /**
3884      * @cfg {Function} callback The function to call when request is complete
3885      */
3886     callback : Ext.emptyFn,
3887     /**
3888      * @cfg {Object} scope The scope of the callback funtion
3889      */
3890     scope : undefined,
3891     /**
3892      * @cfg {Ext.data.DataReader} reader The DataReader instance which will parse the received response
3893      */
3894     reader : undefined
3895 };
3896 /**
3897  * @class Ext.data.Response
3898  * A generic response class to normalize response-handling internally to the framework.
3899  */
3900 Ext.data.Response = function(params) {
3901     Ext.apply(this, params);
3902 };
3903 Ext.data.Response.prototype = {
3904     /**
3905      * @cfg {String} action {@link Ext.data.Api#actions}
3906      */
3907     action: undefined,
3908     /**
3909      * @cfg {Boolean} success
3910      */
3911     success : undefined,
3912     /**
3913      * @cfg {String} message
3914      */
3915     message : undefined,
3916     /**
3917      * @cfg {Array/Object} data
3918      */
3919     data: undefined,
3920     /**
3921      * @cfg {Object} raw The raw response returned from server-code
3922      */
3923     raw: undefined,
3924     /**
3925      * @cfg {Ext.data.Record/Ext.data.Record[]} records related to the Request action
3926      */
3927     records: undefined
3928 };
3929 /**
3930  * @class Ext.data.ScriptTagProxy
3931  * @extends Ext.data.DataProxy
3932  * An implementation of Ext.data.DataProxy that reads a data object from a URL which may be in a domain
3933  * other than the originating domain of the running page.<br>
3934  * <p>
3935  * <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
3936  * of the running page, you must use this class, rather than HttpProxy.</b><br>
3937  * <p>
3938  * The content passed back from a server resource requested by a ScriptTagProxy <b>must</b> be executable JavaScript
3939  * source code because it is used as the source inside a &lt;script> tag.<br>
3940  * <p>
3941  * In order for the browser to process the returned data, the server must wrap the data object
3942  * with a call to a callback function, the name of which is passed as a parameter by the ScriptTagProxy.
3943  * Below is a Java example for a servlet which returns data for either a ScriptTagProxy, or an HttpProxy
3944  * depending on whether the callback name was passed:
3945  * <p>
3946  * <pre><code>
3947 boolean scriptTag = false;
3948 String cb = request.getParameter("callback");
3949 if (cb != null) {
3950     scriptTag = true;
3951     response.setContentType("text/javascript");
3952 } else {
3953     response.setContentType("application/x-json");
3954 }
3955 Writer out = response.getWriter();
3956 if (scriptTag) {
3957     out.write(cb + "(");
3958 }
3959 out.print(dataBlock.toJsonString());
3960 if (scriptTag) {
3961     out.write(");");
3962 }
3963 </code></pre>
3964  * <p>Below is a PHP example to do the same thing:</p><pre><code>
3965 $callback = $_REQUEST['callback'];
3966
3967 // Create the output object.
3968 $output = array('a' => 'Apple', 'b' => 'Banana');
3969
3970 //start output
3971 if ($callback) {
3972     header('Content-Type: text/javascript');
3973     echo $callback . '(' . json_encode($output) . ');';
3974 } else {
3975     header('Content-Type: application/x-json');
3976     echo json_encode($output);
3977 }
3978 </code></pre>
3979  * <p>Below is the ASP.Net code to do the same thing:</p><pre><code>
3980 String jsonString = "{success: true}";
3981 String cb = Request.Params.Get("callback");
3982 String responseString = "";
3983 if (!String.IsNullOrEmpty(cb)) {
3984     responseString = cb + "(" + jsonString + ")";
3985 } else {
3986     responseString = jsonString;
3987 }
3988 Response.Write(responseString);
3989 </code></pre>
3990  *
3991  * @constructor
3992  * @param {Object} config A configuration object.
3993  */
3994 Ext.data.ScriptTagProxy = function(config){
3995     Ext.apply(this, config);
3996
3997     Ext.data.ScriptTagProxy.superclass.constructor.call(this, config);
3998
3999     this.head = document.getElementsByTagName("head")[0];
4000
4001     /**
4002      * @event loadexception
4003      * <b>Deprecated</b> in favor of 'exception' event.
4004      * Fires if an exception occurs in the Proxy during data loading.  This event can be fired for one of two reasons:
4005      * <ul><li><b>The load call timed out.</b>  This means the load callback did not execute within the time limit
4006      * specified by {@link #timeout}.  In this case, this event will be raised and the
4007      * fourth parameter (read error) will be null.</li>
4008      * <li><b>The load succeeded but the reader could not read the response.</b>  This means the server returned
4009      * data, but the configured Reader threw an error while reading the data.  In this case, this event will be
4010      * raised and the caught error will be passed along as the fourth parameter of this event.</li></ul>
4011      * Note that this event is also relayed through {@link Ext.data.Store}, so you can listen for it directly
4012      * on any Store instance.
4013      * @param {Object} this
4014      * @param {Object} options The loading options that were specified (see {@link #load} for details).  If the load
4015      * call timed out, this parameter will be null.
4016      * @param {Object} arg The callback's arg object passed to the {@link #load} function
4017      * @param {Error} e The JavaScript Error object caught if the configured Reader could not read the data.
4018      * If the remote request returns success: false, this parameter will be null.
4019      */
4020 };
4021
4022 Ext.data.ScriptTagProxy.TRANS_ID = 1000;
4023
4024 Ext.extend(Ext.data.ScriptTagProxy, Ext.data.DataProxy, {
4025     /**
4026      * @cfg {String} url The URL from which to request the data object.
4027      */
4028     /**
4029      * @cfg {Number} timeout (optional) The number of milliseconds to wait for a response. Defaults to 30 seconds.
4030      */
4031     timeout : 30000,
4032     /**
4033      * @cfg {String} callbackParam (Optional) The name of the parameter to pass to the server which tells
4034      * the server the name of the callback function set up by the load call to process the returned data object.
4035      * Defaults to "callback".<p>The server-side processing must read this parameter value, and generate
4036      * javascript output which calls this named function passing the data object as its only parameter.
4037      */
4038     callbackParam : "callback",
4039     /**
4040      *  @cfg {Boolean} nocache (optional) Defaults to true. Disable caching by adding a unique parameter
4041      * name to the request.
4042      */
4043     nocache : true,
4044
4045     /**
4046      * HttpProxy implementation of DataProxy#doRequest
4047      * @param {String} action
4048      * @param {Ext.data.Record/Ext.data.Record[]} rs If action is <tt>read</tt>, rs will be null
4049      * @param {Object} params An object containing properties which are to be used as HTTP parameters
4050      * for the request to the remote server.
4051      * @param {Ext.data.DataReader} reader The Reader object which converts the data
4052      * object into a block of Ext.data.Records.
4053      * @param {Function} callback The function into which to pass the block of Ext.data.Records.
4054      * The function must be passed <ul>
4055      * <li>The Record block object</li>
4056      * <li>The "arg" argument from the load function</li>
4057      * <li>A boolean success indicator</li>
4058      * </ul>
4059      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
4060      * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
4061      */
4062     doRequest : function(action, rs, params, reader, callback, scope, arg) {
4063         var p = Ext.urlEncode(Ext.apply(params, this.extraParams));
4064
4065         var url = this.buildUrl(action, rs);
4066         if (!url) {
4067             throw new Ext.data.Api.Error('invalid-url', url);
4068         }
4069         url = Ext.urlAppend(url, p);
4070
4071         if(this.nocache){
4072             url = Ext.urlAppend(url, '_dc=' + (new Date().getTime()));
4073         }
4074         var transId = ++Ext.data.ScriptTagProxy.TRANS_ID;
4075         var trans = {
4076             id : transId,
4077             action: action,
4078             cb : "stcCallback"+transId,
4079             scriptId : "stcScript"+transId,
4080             params : params,
4081             arg : arg,
4082             url : url,
4083             callback : callback,
4084             scope : scope,
4085             reader : reader
4086         };
4087         window[trans.cb] = this.createCallback(action, rs, trans);
4088         url += String.format("&{0}={1}", this.callbackParam, trans.cb);
4089         if(this.autoAbort !== false){
4090             this.abort();
4091         }
4092
4093         trans.timeoutId = this.handleFailure.defer(this.timeout, this, [trans]);
4094
4095         var script = document.createElement("script");
4096         script.setAttribute("src", url);
4097         script.setAttribute("type", "text/javascript");
4098         script.setAttribute("id", trans.scriptId);
4099         this.head.appendChild(script);
4100
4101         this.trans = trans;
4102     },
4103
4104     // @private createCallback
4105     createCallback : function(action, rs, trans) {
4106         var self = this;
4107         return function(res) {
4108             self.trans = false;
4109             self.destroyTrans(trans, true);
4110             if (action === Ext.data.Api.actions.read) {
4111                 self.onRead.call(self, action, trans, res);
4112             } else {
4113                 self.onWrite.call(self, action, trans, res, rs);
4114             }
4115         };
4116     },
4117     /**
4118      * Callback for read actions
4119      * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
4120      * @param {Object} trans The request transaction object
4121      * @param {Object} res The server response
4122      * @protected
4123      */
4124     onRead : function(action, trans, res) {
4125         var result;
4126         try {
4127             result = trans.reader.readRecords(res);
4128         }catch(e){
4129             // @deprecated: fire loadexception
4130             this.fireEvent("loadexception", this, trans, res, e);
4131
4132             this.fireEvent('exception', this, 'response', action, trans, res, e);
4133             trans.callback.call(trans.scope||window, null, trans.arg, false);
4134             return;
4135         }
4136         if (result.success === false) {
4137             // @deprecated: fire old loadexception for backwards-compat.
4138             this.fireEvent('loadexception', this, trans, res);
4139
4140             this.fireEvent('exception', this, 'remote', action, trans, res, null);
4141         } else {
4142             this.fireEvent("load", this, res, trans.arg);
4143         }
4144         trans.callback.call(trans.scope||window, result, trans.arg, result.success);
4145     },
4146     /**
4147      * Callback for write actions
4148      * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
4149      * @param {Object} trans The request transaction object
4150      * @param {Object} res The server response
4151      * @protected
4152      */
4153     onWrite : function(action, trans, response, rs) {
4154         var reader = trans.reader;
4155         try {
4156             // though we already have a response object here in STP, run through readResponse to catch any meta-data exceptions.
4157             var res = reader.readResponse(action, response);
4158         } catch (e) {
4159             this.fireEvent('exception', this, 'response', action, trans, res, e);
4160             trans.callback.call(trans.scope||window, null, res, false);
4161             return;
4162         }
4163         if(!res.success === true){
4164             this.fireEvent('exception', this, 'remote', action, trans, res, rs);
4165             trans.callback.call(trans.scope||window, null, res, false);
4166             return;
4167         }
4168         this.fireEvent("write", this, action, res.data, res, rs, trans.arg );
4169         trans.callback.call(trans.scope||window, res.data, res, true);
4170     },
4171
4172     // private
4173     isLoading : function(){
4174         return this.trans ? true : false;
4175     },
4176
4177     /**
4178      * Abort the current server request.
4179      */
4180     abort : function(){
4181         if(this.isLoading()){
4182             this.destroyTrans(this.trans);
4183         }
4184     },
4185
4186     // private
4187     destroyTrans : function(trans, isLoaded){
4188         this.head.removeChild(document.getElementById(trans.scriptId));
4189         clearTimeout(trans.timeoutId);
4190         if(isLoaded){
4191             window[trans.cb] = undefined;
4192             try{
4193                 delete window[trans.cb];
4194             }catch(e){}
4195         }else{
4196             // if hasn't been loaded, wait for load to remove it to prevent script error
4197             window[trans.cb] = function(){
4198                 window[trans.cb] = undefined;
4199                 try{
4200                     delete window[trans.cb];
4201                 }catch(e){}
4202             };
4203         }
4204     },
4205
4206     // private
4207     handleFailure : function(trans){
4208         this.trans = false;
4209         this.destroyTrans(trans, false);
4210         if (trans.action === Ext.data.Api.actions.read) {
4211             // @deprecated firing loadexception
4212             this.fireEvent("loadexception", this, null, trans.arg);
4213         }
4214
4215         this.fireEvent('exception', this, 'response', trans.action, {
4216             response: null,
4217             options: trans.arg
4218         });
4219         trans.callback.call(trans.scope||window, null, trans.arg, false);
4220     },
4221
4222     // inherit docs
4223     destroy: function(){
4224         this.abort();
4225         Ext.data.ScriptTagProxy.superclass.destroy.call(this);
4226     }
4227 });/**
4228  * @class Ext.data.HttpProxy
4229  * @extends Ext.data.DataProxy
4230  * <p>An implementation of {@link Ext.data.DataProxy} that processes data requests within the same
4231  * domain of the originating page.</p>
4232  * <p><b>Note</b>: this class cannot be used to retrieve data from a domain other
4233  * than the domain from which the running page was served. For cross-domain requests, use a
4234  * {@link Ext.data.ScriptTagProxy ScriptTagProxy}.</p>
4235  * <p>Be aware that to enable the browser to parse an XML document, the server must set
4236  * the Content-Type header in the HTTP response to "<tt>text/xml</tt>".</p>
4237  * @constructor
4238  * @param {Object} conn
4239  * An {@link Ext.data.Connection} object, or options parameter to {@link Ext.Ajax#request}.
4240  * <p>Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the
4241  * Store's call to {@link #load} will override any specified <tt>callback</tt> and <tt>params</tt>
4242  * options. In this case, use the Store's {@link Ext.data.Store#events events} to modify parameters,
4243  * or react to loading events. The Store's {@link Ext.data.Store#baseParams baseParams} may also be
4244  * used to pass parameters known at instantiation time.</p>
4245  * <p>If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make
4246  * the request.</p>
4247  */
4248 Ext.data.HttpProxy = function(conn){
4249     Ext.data.HttpProxy.superclass.constructor.call(this, conn);
4250
4251     /**
4252      * The Connection object (Or options parameter to {@link Ext.Ajax#request}) which this HttpProxy
4253      * uses to make requests to the server. Properties of this object may be changed dynamically to
4254      * change the way data is requested.
4255      * @property
4256      */
4257     this.conn = conn;
4258
4259     // nullify the connection url.  The url param has been copied to 'this' above.  The connection
4260     // url will be set during each execution of doRequest when buildUrl is called.  This makes it easier for users to override the
4261     // connection url during beforeaction events (ie: beforeload, beforewrite, etc).
4262     // Url is always re-defined during doRequest.
4263     this.conn.url = null;
4264
4265     this.useAjax = !conn || !conn.events;
4266
4267     // A hash containing active requests, keyed on action [Ext.data.Api.actions.create|read|update|destroy]
4268     var actions = Ext.data.Api.actions;
4269     this.activeRequest = {};
4270     for (var verb in actions) {
4271         this.activeRequest[actions[verb]] = undefined;
4272     }
4273 };
4274
4275 Ext.extend(Ext.data.HttpProxy, Ext.data.DataProxy, {
4276     /**
4277      * Return the {@link Ext.data.Connection} object being used by this Proxy.
4278      * @return {Connection} The Connection object. This object may be used to subscribe to events on
4279      * a finer-grained basis than the DataProxy events.
4280      */
4281     getConnection : function() {
4282         return this.useAjax ? Ext.Ajax : this.conn;
4283     },
4284
4285     /**
4286      * Used for overriding the url used for a single request.  Designed to be called during a beforeaction event.  Calling setUrl
4287      * will override any urls set via the api configuration parameter.  Set the optional parameter makePermanent to set the url for
4288      * all subsequent requests.  If not set to makePermanent, the next request will use the same url or api configuration defined
4289      * in the initial proxy configuration.
4290      * @param {String} url
4291      * @param {Boolean} makePermanent (Optional) [false]
4292      *
4293      * (e.g.: beforeload, beforesave, etc).
4294      */
4295     setUrl : function(url, makePermanent) {
4296         this.conn.url = url;
4297         if (makePermanent === true) {
4298             this.url = url;
4299             this.api = null;
4300             Ext.data.Api.prepare(this);
4301         }
4302     },
4303
4304     /**
4305      * HttpProxy implementation of DataProxy#doRequest
4306      * @param {String} action The crud action type (create, read, update, destroy)
4307      * @param {Ext.data.Record/Ext.data.Record[]} rs If action is load, rs will be null
4308      * @param {Object} params An object containing properties which are to be used as HTTP parameters
4309      * for the request to the remote server.
4310      * @param {Ext.data.DataReader} reader The Reader object which converts the data
4311      * object into a block of Ext.data.Records.
4312      * @param {Function} callback
4313      * <div class="sub-desc"><p>A function to be called after the request.
4314      * The <tt>callback</tt> is passed the following arguments:<ul>
4315      * <li><tt>r</tt> : Ext.data.Record[] The block of Ext.data.Records.</li>
4316      * <li><tt>options</tt>: Options object from the action request</li>
4317      * <li><tt>success</tt>: Boolean success indicator</li></ul></p></div>
4318      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
4319      * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
4320      * @protected
4321      */
4322     doRequest : function(action, rs, params, reader, cb, scope, arg) {
4323         var  o = {
4324             method: (this.api[action]) ? this.api[action]['method'] : undefined,
4325             request: {
4326                 callback : cb,
4327                 scope : scope,
4328                 arg : arg
4329             },
4330             reader: reader,
4331             callback : this.createCallback(action, rs),
4332             scope: this
4333         };
4334
4335         // If possible, transmit data using jsonData || xmlData on Ext.Ajax.request (An installed DataWriter would have written it there.).
4336         // Use std HTTP params otherwise.
4337         if (params.jsonData) {
4338             o.jsonData = params.jsonData;
4339         } else if (params.xmlData) {
4340             o.xmlData = params.xmlData;
4341         } else {
4342             o.params = params || {};
4343         }
4344         // Set the connection url.  If this.conn.url is not null here,
4345         // the user must have overridden the url during a beforewrite/beforeload event-handler.
4346         // this.conn.url is nullified after each request.
4347         this.conn.url = this.buildUrl(action, rs);
4348
4349         if(this.useAjax){
4350
4351             Ext.applyIf(o, this.conn);
4352
4353             // If a currently running request is found for this action, abort it.
4354             if (this.activeRequest[action]) {
4355                 ////
4356                 // Disabled aborting activeRequest while implementing REST.  activeRequest[action] will have to become an array
4357                 // TODO ideas anyone?
4358                 //
4359                 //Ext.Ajax.abort(this.activeRequest[action]);
4360             }
4361             this.activeRequest[action] = Ext.Ajax.request(o);
4362         }else{
4363             this.conn.request(o);
4364         }
4365         // request is sent, nullify the connection url in preparation for the next request
4366         this.conn.url = null;
4367     },
4368
4369     /**
4370      * Returns a callback function for a request.  Note a special case is made for the
4371      * read action vs all the others.
4372      * @param {String} action [create|update|delete|load]
4373      * @param {Ext.data.Record[]} rs The Store-recordset being acted upon
4374      * @private
4375      */
4376     createCallback : function(action, rs) {
4377         return function(o, success, response) {
4378             this.activeRequest[action] = undefined;
4379             if (!success) {
4380                 if (action === Ext.data.Api.actions.read) {
4381                     // @deprecated: fire loadexception for backwards compat.
4382                     // TODO remove
4383                     this.fireEvent('loadexception', this, o, response);
4384                 }
4385                 this.fireEvent('exception', this, 'response', action, o, response);
4386                 o.request.callback.call(o.request.scope, null, o.request.arg, false);
4387                 return;
4388             }
4389             if (action === Ext.data.Api.actions.read) {
4390                 this.onRead(action, o, response);
4391             } else {
4392                 this.onWrite(action, o, response, rs);
4393             }
4394         };
4395     },
4396
4397     /**
4398      * Callback for read action
4399      * @param {String} action Action name as per {@link Ext.data.Api.actions#read}.
4400      * @param {Object} o The request transaction object
4401      * @param {Object} res The server response
4402      * @fires loadexception (deprecated)
4403      * @fires exception
4404      * @fires load
4405      * @protected
4406      */
4407     onRead : function(action, o, response) {
4408         var result;
4409         try {
4410             result = o.reader.read(response);
4411         }catch(e){
4412             // @deprecated: fire old loadexception for backwards-compat.
4413             // TODO remove
4414             this.fireEvent('loadexception', this, o, response, e);
4415
4416             this.fireEvent('exception', this, 'response', action, o, response, e);
4417             o.request.callback.call(o.request.scope, null, o.request.arg, false);
4418             return;
4419         }
4420         if (result.success === false) {
4421             // @deprecated: fire old loadexception for backwards-compat.
4422             // TODO remove
4423             this.fireEvent('loadexception', this, o, response);
4424
4425             // Get DataReader read-back a response-object to pass along to exception event
4426             var res = o.reader.readResponse(action, response);
4427             this.fireEvent('exception', this, 'remote', action, o, res, null);
4428         }
4429         else {
4430             this.fireEvent('load', this, o, o.request.arg);
4431         }
4432         // TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance
4433         // the calls to request.callback(...) in each will have to be made identical.
4434         // NOTE reader.readResponse does not currently return Ext.data.Response
4435         o.request.callback.call(o.request.scope, result, o.request.arg, result.success);
4436     },
4437     /**
4438      * Callback for write actions
4439      * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
4440      * @param {Object} trans The request transaction object
4441      * @param {Object} res The server response
4442      * @fires exception
4443      * @fires write
4444      * @protected
4445      */
4446     onWrite : function(action, o, response, rs) {
4447         var reader = o.reader;
4448         var res;
4449         try {
4450             res = reader.readResponse(action, response);
4451         } catch (e) {
4452             this.fireEvent('exception', this, 'response', action, o, response, e);
4453             o.request.callback.call(o.request.scope, null, o.request.arg, false);
4454             return;
4455         }
4456         if (res.success === true) {
4457             this.fireEvent('write', this, action, res.data, res, rs, o.request.arg);
4458         } else {
4459             this.fireEvent('exception', this, 'remote', action, o, res, rs);
4460         }
4461         // TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance
4462         // the calls to request.callback(...) in each will have to be made similar.
4463         // NOTE reader.readResponse does not currently return Ext.data.Response
4464         o.request.callback.call(o.request.scope, res.data, res, res.success);
4465     },
4466
4467     // inherit docs
4468     destroy: function(){
4469         if(!this.useAjax){
4470             this.conn.abort();
4471         }else if(this.activeRequest){
4472             var actions = Ext.data.Api.actions;
4473             for (var verb in actions) {
4474                 if(this.activeRequest[actions[verb]]){
4475                     Ext.Ajax.abort(this.activeRequest[actions[verb]]);
4476                 }
4477             }
4478         }
4479         Ext.data.HttpProxy.superclass.destroy.call(this);
4480     }
4481 });/**
4482  * @class Ext.data.MemoryProxy
4483  * @extends Ext.data.DataProxy
4484  * An implementation of Ext.data.DataProxy that simply passes the data specified in its constructor
4485  * to the Reader when its load method is called.
4486  * @constructor
4487  * @param {Object} data The data object which the Reader uses to construct a block of Ext.data.Records.
4488  */
4489 Ext.data.MemoryProxy = function(data){
4490     // Must define a dummy api with "read" action to satisfy DataProxy#doRequest and Ext.data.Api#prepare *before* calling super
4491     var api = {};
4492     api[Ext.data.Api.actions.read] = true;
4493     Ext.data.MemoryProxy.superclass.constructor.call(this, {
4494         api: api
4495     });
4496     this.data = data;
4497 };
4498
4499 Ext.extend(Ext.data.MemoryProxy, Ext.data.DataProxy, {
4500     /**
4501      * @event loadexception
4502      * Fires if an exception occurs in the Proxy during data loading. Note that this event is also relayed
4503      * through {@link Ext.data.Store}, so you can listen for it directly on any Store instance.
4504      * @param {Object} this
4505      * @param {Object} arg The callback's arg object passed to the {@link #load} function
4506      * @param {Object} null This parameter does not apply and will always be null for MemoryProxy
4507      * @param {Error} e The JavaScript Error object caught if the configured Reader could not read the data
4508      */
4509
4510        /**
4511      * MemoryProxy implementation of DataProxy#doRequest
4512      * @param {String} action
4513      * @param {Ext.data.Record/Ext.data.Record[]} rs If action is load, rs will be null
4514      * @param {Object} params An object containing properties which are to be used as HTTP parameters
4515      * for the request to the remote server.
4516      * @param {Ext.data.DataReader} reader The Reader object which converts the data
4517      * object into a block of Ext.data.Records.
4518      * @param {Function} callback The function into which to pass the block of Ext.data.Records.
4519      * The function must be passed <ul>
4520      * <li>The Record block object</li>
4521      * <li>The "arg" argument from the load function</li>
4522      * <li>A boolean success indicator</li>
4523      * </ul>
4524      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
4525      * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
4526      */
4527     doRequest : function(action, rs, params, reader, callback, scope, arg) {
4528         // No implementation for CRUD in MemoryProxy.  Assumes all actions are 'load'
4529         params = params || {};
4530         var result;
4531         try {
4532             result = reader.readRecords(this.data);
4533         }catch(e){
4534             // @deprecated loadexception
4535             this.fireEvent("loadexception", this, null, arg, e);
4536
4537             this.fireEvent('exception', this, 'response', action, arg, null, e);
4538             callback.call(scope, null, arg, false);
4539             return;
4540         }
4541         callback.call(scope, result, arg, true);
4542     }
4543 });/**
4544  * @class Ext.data.Types
4545  * <p>This is s static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
4546  * <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
4547  * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
4548  * of this class.</p>
4549  * <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
4550  * each type definition must contain three properties:</p>
4551  * <div class="mdetail-params"><ul>
4552  * <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
4553  * to be stored in the Field. The function is passed the collowing parameters:
4554  * <div class="mdetail-params"><ul>
4555  * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
4556  * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
4557  * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
4558  * Depending on the Reader type, this could be an Array ({@link Ext.data.ArrayReader ArrayReader}), an object
4559  * ({@link Ext.data.JsonReader JsonReader}), or an XML element ({@link Ext.data.XMLReader XMLReader}).</div></li>
4560  * </ul></div></div></li>
4561  * <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>
4562  * <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
4563  * </ul></div>
4564  * <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
4565  * which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
4566  *<pre><code>
4567 // Add a new Field data type which stores a VELatLong object in the Record.
4568 Ext.data.Types.VELATLONG = {
4569     convert: function(v, data) {
4570         return new VELatLong(data.lat, data.long);
4571     },
4572     sortType: function(v) {
4573         return v.Latitude;  // When sorting, order by latitude
4574     },
4575     type: 'VELatLong'
4576 };
4577 </code></pre>
4578  * <p>Then, when declaring a Record, use <pre><code>
4579 var types = Ext.data.Types; // allow shorthand type access
4580 UnitRecord = Ext.data.Record.create([
4581     { name: 'unitName', mapping: 'UnitName' },
4582     { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
4583     { name: 'latitude', mapping: 'lat', type: types.FLOAT },
4584     { name: 'latitude', mapping: 'lat', type: types.FLOAT },
4585     { name: 'position', type: types.VELATLONG }
4586 ]);
4587 </code></pre>
4588  * @singleton
4589  */
4590 Ext.data.Types = new function(){
4591     var st = Ext.data.SortTypes;
4592     Ext.apply(this, {
4593         /**
4594          * @type Regexp
4595          * @property stripRe
4596          * A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
4597          * This should be overridden for localization.
4598          */
4599         stripRe: /[\$,%]/g,
4600         
4601         /**
4602          * @type Object.
4603          * @property AUTO
4604          * This data type means that no conversion is applied to the raw data before it is placed into a Record.
4605          */
4606         AUTO: {
4607             convert: function(v){ return v; },
4608             sortType: st.none,
4609             type: 'auto'
4610         },
4611
4612         /**
4613          * @type Object.
4614          * @property STRING
4615          * This data type means that the raw data is converted into a String before it is placed into a Record.
4616          */
4617         STRING: {
4618             convert: function(v){ return (v === undefined || v === null) ? '' : String(v); },
4619             sortType: st.asUCString,
4620             type: 'string'
4621         },
4622
4623         /**
4624          * @type Object.
4625          * @property INT
4626          * This data type means that the raw data is converted into an integer before it is placed into a Record.
4627          * <p>The synonym <code>INTEGER</code> is equivalent.</p>
4628          */
4629         INT: {
4630             convert: function(v){
4631                 return v !== undefined && v !== null && v !== '' ?
4632                     parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : 0;
4633             },
4634             sortType: st.none,
4635             type: 'int'
4636         },
4637         
4638         /**
4639          * @type Object.
4640          * @property FLOAT
4641          * This data type means that the raw data is converted into a number before it is placed into a Record.
4642          * <p>The synonym <code>NUMBER</code> is equivalent.</p>
4643          */
4644         FLOAT: {
4645             convert: function(v){
4646                 return v !== undefined && v !== null && v !== '' ?
4647                     parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : 0;
4648             },
4649             sortType: st.none,
4650             type: 'float'
4651         },
4652         
4653         /**
4654          * @type Object.
4655          * @property BOOL
4656          * <p>This data type means that the raw data is converted into a boolean before it is placed into
4657          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
4658          * <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
4659          */
4660         BOOL: {
4661             convert: function(v){ return v === true || v === 'true' || v == 1; },
4662             sortType: st.none,
4663             type: 'bool'
4664         },
4665         
4666         /**
4667          * @type Object.
4668          * @property DATE
4669          * This data type means that the raw data is converted into a Date before it is placed into a Record.
4670          * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
4671          * being applied.
4672          */
4673         DATE: {
4674             convert: function(v){
4675                 var df = this.dateFormat;
4676                 if(!v){
4677                     return null;
4678                 }
4679                 if(Ext.isDate(v)){
4680                     return v;
4681                 }
4682                 if(df){
4683                     if(df == 'timestamp'){
4684                         return new Date(v*1000);
4685                     }
4686                     if(df == 'time'){
4687                         return new Date(parseInt(v, 10));
4688                     }
4689                     return Date.parseDate(v, df);
4690                 }
4691                 var parsed = Date.parse(v);
4692                 return parsed ? new Date(parsed) : null;
4693             },
4694             sortType: st.asDate,
4695             type: 'date'
4696         }
4697     });
4698     
4699     Ext.apply(this, {
4700         /**
4701          * @type Object.
4702          * @property BOOLEAN
4703          * <p>This data type means that the raw data is converted into a boolean before it is placed into
4704          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
4705          * <p>The synonym <code>BOOL</code> is equivalent.</p>
4706          */
4707         BOOLEAN: this.BOOL,
4708         /**
4709          * @type Object.
4710          * @property INTEGER
4711          * This data type means that the raw data is converted into an integer before it is placed into a Record.
4712          * <p>The synonym <code>INT</code> is equivalent.</p>
4713          */
4714         INTEGER: this.INT,
4715         /**
4716          * @type Object.
4717          * @property NUMBER
4718          * This data type means that the raw data is converted into a number before it is placed into a Record.
4719          * <p>The synonym <code>FLOAT</code> is equivalent.</p>
4720          */
4721         NUMBER: this.FLOAT    
4722     });
4723 };