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