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