Bienvenue sur PostGIS.fr

Bienvenue sur PostGIS.fr , le site de la communauté des utilisateurs francophones de PostGIS.

PostGIS ajoute le support d'objets géographique à la base de données PostgreSQL. En effet, PostGIS "spatialise" le serverur PostgreSQL, ce qui permet de l'utiliser comme une base de données SIG.

Maintenu à jour, en fonction de nos disponibilités et des diverses sorties des outils que nous testons, nous vous proposons l'ensemble de nos travaux publiés en langue française.

source: trunk/workshop-routing-foss4g/web/ext/pkgs/data-foundation-debug.js @ 77

Revision 76, 194.7 KB checked in by djay, 13 years ago (diff)

Ajout du répertoire web

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