X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/c930e9176a5a85509c5b0230e2bff5c22a591432..2e847cf21b8ab9d15fa167b315ca5b2fa92638fc:/pkgs/data-foundation-debug.js diff --git a/pkgs/data-foundation-debug.js b/pkgs/data-foundation-debug.js index 5076cfc8..72b63530 100644 --- a/pkgs/data-foundation-debug.js +++ b/pkgs/data-foundation-debug.js @@ -1,9 +1,10 @@ /*! - * Ext JS Library 3.0.0 - * Copyright(c) 2006-2009 Ext JS, LLC + * Ext JS Library 3.1.1 + * Copyright(c) 2006-2010 Ext JS, LLC * licensing@extjs.com * http://www.extjs.com/license */ + /** * @class Ext.data.Api * @extends Object @@ -168,7 +169,8 @@ new Ext.data.HttpProxy({ proxy.api[action] = proxy.api[action] || proxy.url || proxy.directFn; if (typeof(proxy.api[action]) == 'string') { proxy.api[action] = { - url: proxy.api[action] + url: proxy.api[action], + method: (proxy.restful === true) ? Ext.data.Api.restActions[action] : undefined }; } } @@ -182,12 +184,84 @@ new Ext.data.HttpProxy({ restify : function(proxy) { proxy.restful = true; for (var verb in this.restActions) { - proxy.api[this.actions[verb]].method = this.restActions[verb]; + proxy.api[this.actions[verb]].method || + (proxy.api[this.actions[verb]].method = this.restActions[verb]); } + // TODO: perhaps move this interceptor elsewhere? like into DataProxy, perhaps? Placed here + // to satisfy initial 3.0 final release of REST features. + proxy.onWrite = proxy.onWrite.createInterceptor(function(action, o, response, rs) { + var reader = o.reader; + var res = new Ext.data.Response({ + action: action, + raw: response + }); + + switch (response.status) { + case 200: // standard 200 response, send control back to HttpProxy#onWrite by returning true from this intercepted #onWrite + return true; + break; + case 201: // entity created but no response returned + if (Ext.isEmpty(res.raw.responseText)) { + res.success = true; + } else { + //if the response contains data, treat it like a 200 + return true; + } + break; + case 204: // no-content. Create a fake response. + res.success = true; + res.data = null; + break; + default: + return true; + break; + } + if (res.success === true) { + this.fireEvent("write", this, action, res.data, res, rs, o.request.arg); + } else { + this.fireEvent('exception', this, 'remote', action, o, res, rs); + } + o.request.callback.call(o.request.scope, res.data, res, res.success); + + return false; // <-- false to prevent intercepted function from running. + }, proxy); } }; })(); +/** + * Ext.data.Response + * Experimental. Do not use directly. + */ +Ext.data.Response = function(params, response) { + Ext.apply(this, params, { + raw: response + }); +}; +Ext.data.Response.prototype = { + message : null, + success : false, + status : null, + root : null, + raw : null, + + getMessage : function() { + return this.message; + }, + getSuccess : function() { + return this.success; + }, + getStatus : function() { + return this.status; + }, + getRoot : function() { + return this.root; + }, + getRawResponse : function() { + return this.raw; + } +}; + /** * @class Ext.data.Api.Error * @extends Ext.Error @@ -208,6 +282,8 @@ Ext.apply(Ext.data.Api.Error.prototype, { 'execute': 'Attempted to execute an unknown action. Valid API actions are defined in Ext.data.Api.actions"' } }); + + /** * @class Ext.data.SortTypes @@ -309,15 +385,17 @@ Ext.data.SortTypes = { * {@link #data} and {@link #id} properties.

*

Record objects generated by this constructor inherit all the methods of Ext.data.Record listed below.

* @constructor - * This constructor should not be used to create Record objects. Instead, use {@link #create} to - * generate a subclass of Ext.data.Record configured with information about its constituent fields. + *

This constructor should not be used to create Record objects. Instead, use {@link #create} to + * generate a subclass of Ext.data.Record configured with information about its constituent fields.

+ *

The generated constructor has the same signature as this constructor.

* @param {Object} data (Optional) An object, the properties of which provide values for the new Record's * fields. If not specified the {@link Ext.data.Field#defaultValue defaultValue} * for each field will be assigned. - * @param {Object} id (Optional) The id of the Record. This id should be unique, and is used by the - * {@link Ext.data.Store} object which owns the Record to index its collection of Records. If - * an id is not specified a {@link #phantom} Record will be created - * with an {@link #Record.id automatically generated id}. + * @param {Object} id (Optional) The id of the Record. The id is used by the + * {@link Ext.data.Store} object which owns the Record to index its collection + * of Records (therefore this id should be unique within each store). If an + * id is not specified a {@link #phantom} + * Record will be created with an {@link #Record.id automatically generated id}. */ Ext.data.Record = function(data, id){ // if no id, call the auto id method @@ -361,7 +439,7 @@ myStore.{@link Ext.data.Store#add add}(myNewRecord); * @method create * @return {function} A constructor which is used to create new Records according - * to the definition. The constructor has the same signature as {@link #Ext.data.Record}. + * to the definition. The constructor has the same signature as {@link #Record}. * @static */ Ext.data.Record.create = function(o){ @@ -423,22 +501,34 @@ Ext.data.Record.prototype = { * @property id * @type {Object} */ + /** + *

Only present if this Record was created by an {@link Ext.data.XmlReader XmlReader}.

+ *

The XML element which was the source of the data for this Record.

+ * @property node + * @type {XMLElement} + */ + /** + *

Only present if this Record was created by an {@link Ext.data.ArrayReader ArrayReader} or a {@link Ext.data.JsonReader JsonReader}.

+ *

The Array or object which was the source of the data for this Record.

+ * @property json + * @type {Array|Object} + */ /** * Readonly flag - true if this Record has been modified. * @type Boolean */ dirty : false, editing : false, - error: null, + error : null, /** * This object contains a key and value storing the original values of all modified * fields or is null if no fields have been modified. * @property modified * @type {Object} */ - modified: null, + modified : null, /** - * false when the record does not yet exist in a server-side database (see + * true when the record does not yet exist in a server-side database (see * {@link #markDirty}). Any record which has a real database pk set as its id property * is NOT a phantom -- it's real. * @property phantom @@ -476,7 +566,7 @@ rec.{@link #commit}(); // update the record in the store, bypass setting dirty flag, // and do not store the change in the {@link Ext.data.Store#getModifiedRecords modified records} -rec.{@link #data}['firstname'] = 'Wilma'); // updates record, but not the view +rec.{@link #data}['firstname'] = 'Wilma'; // updates record, but not the view rec.{@link #commit}(); // updates the view * * Notes:
* @param {String} name The {@link Ext.data.Field#name name of the field} to set. - * @param {Object} value The value to set the field to. + * @param {String/Object/Array} value The value to set the field to. */ set : function(name, value){ - var isObj = (typeof value === 'object'); - if(!isObj && String(this.data[name]) === String(value)){ + var encode = Ext.isPrimitive(value) ? String : Ext.encode; + if(encode(this.data[name]) == encode(value)) { return; - } else if (isObj && Ext.encode(this.data[name]) === Ext.encode(value)) { - return; - } + } this.dirty = true; if(!this.modified){ this.modified = {}; } - if(typeof this.modified[name] == 'undefined'){ + if(this.modified[name] === undefined){ this.modified[name] = this.data[name]; } this.data[name] = value; @@ -511,21 +599,21 @@ rec.{@link #commit}(); // updates the view }, // private - afterEdit: function(){ + afterEdit : function(){ if(this.store){ this.store.afterEdit(this); } }, // private - afterReject: function(){ + afterReject : function(){ if(this.store){ this.store.afterReject(this); } }, // private - afterCommit: function(){ + afterCommit : function(){ if(this.store){ this.store.afterCommit(this); } @@ -635,10 +723,13 @@ rec.{@link #commit}(); // updates the view }, /** - * Creates a copy of this Record. - * @param {String} id (optional) A new Record id, defaults to {@link #Record.id autogenerating an id}. - * Note: if an id is not specified the copy created will be a - * {@link #phantom} Record. + * Creates a copy (clone) of this Record. + * @param {String} id (optional) A new Record id, defaults to the id + * of the record being copied. See {@link #id}. + * To generate a phantom record with a new id use:

+var rec = record.copy(); // clone the record
+Ext.data.Record.id(rec); // automatically generate a unique sequential id
+     * 
* @return {Record} */ copy : function(newId) { @@ -685,7 +776,8 @@ rec.{@link #commit}(); // updates the view this.modified[f.name] = this.data[f.name]; },this); } -};/** +}; +/** * @class Ext.StoreMgr * @extends Ext.util.MixedCollection * The default global group of stores. @@ -813,286 +905,16 @@ var recId = 100; // provide unique id for the record var r = new myStore.recordType(defaultData, ++recId); // create new record myStore.{@link #insert}(0, r); // insert a new record into the store (also see {@link #add}) * + *

Writing Data

+ *

And new in Ext version 3, use the new {@link Ext.data.DataWriter DataWriter} to create an automated, Writable Store + * along with RESTful features. * @constructor * Creates a new Store. * @param {Object} config A config object containing the objects needed for the Store to access data, * and read the data into Records. * @xtype store */ -Ext.data.Store = function(config){ - this.data = new Ext.util.MixedCollection(false); - this.data.getKey = function(o){ - return o.id; - }; - /** - * See the {@link #baseParams corresponding configuration option} - * for a description of this property. - * To modify this property see {@link #setBaseParam}. - * @property - */ - this.baseParams = {}; - - // temporary removed-records cache - this.removed = []; - - if(config && config.data){ - this.inlineData = config.data; - delete config.data; - } - - Ext.apply(this, config); - - this.paramNames = Ext.applyIf(this.paramNames || {}, this.defaultParamNames); - - if(this.url && !this.proxy){ - this.proxy = new Ext.data.HttpProxy({url: this.url}); - } - // If Store is RESTful, so too is the DataProxy - if (this.restful === true && this.proxy) { - // When operating RESTfully, a unique transaction is generated for each record. - this.batch = false; - Ext.data.Api.restify(this.proxy); - } - - if(this.reader){ // reader passed - if(!this.recordType){ - this.recordType = this.reader.recordType; - } - if(this.reader.onMetaChange){ - this.reader.onMetaChange = this.onMetaChange.createDelegate(this); - } - if (this.writer) { // writer passed - this.writer.meta = this.reader.meta; - this.pruneModifiedRecords = true; - } - } - - /** - * The {@link Ext.data.Record Record} constructor as supplied to (or created by) the - * {@link Ext.data.DataReader Reader}. Read-only. - *

If the Reader was constructed by passing in an Array of {@link Ext.data.Field} definition objects, - * instead of a Record constructor, it will implicitly create a Record constructor from that Array (see - * {@link Ext.data.Record}.{@link Ext.data.Record#create create} for additional details).

- *

This property may be used to create new Records of the type held in this Store, for example:


-// create the data store
-var store = new Ext.data.ArrayStore({
-    autoDestroy: true,
-    fields: [
-       {name: 'company'},
-       {name: 'price', type: 'float'},
-       {name: 'change', type: 'float'},
-       {name: 'pctChange', type: 'float'},
-       {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
-    ]
-});
-store.loadData(myData);
-
-// create the Grid
-var grid = new Ext.grid.EditorGridPanel({
-    store: store,
-    colModel: new Ext.grid.ColumnModel({
-        columns: [
-            {id:'company', header: 'Company', width: 160, dataIndex: 'company'},
-            {header: 'Price', renderer: 'usMoney', dataIndex: 'price'},
-            {header: 'Change', renderer: change, dataIndex: 'change'},
-            {header: '% Change', renderer: pctChange, dataIndex: 'pctChange'},
-            {header: 'Last Updated', width: 85,
-                renderer: Ext.util.Format.dateRenderer('m/d/Y'),
-                dataIndex: 'lastChange'}
-        ],
-        defaults: {
-            sortable: true,
-            width: 75
-        }
-    }),
-    autoExpandColumn: 'company', // match the id specified in the column model
-    height:350,
-    width:600,
-    title:'Array Grid',
-    tbar: [{
-        text: 'Add Record',
-        handler : function(){
-            var defaultData = {
-                change: 0,
-                company: 'New Company',
-                lastChange: (new Date()).clearTime(),
-                pctChange: 0,
-                price: 10
-            };
-            var recId = 3; // provide unique id
-            var p = new store.recordType(defaultData, recId); // create new record
-            grid.stopEditing();
-            store.{@link #insert}(0, p); // insert a new record into the store (also see {@link #add})
-            grid.startEditing(0, 0);
-        }
-    }]
-});
-     * 
- * @property recordType - * @type Function - */ - - if(this.recordType){ - /** - * A {@link Ext.util.MixedCollection MixedCollection} containing the defined {@link Ext.data.Field Field}s - * for the {@link Ext.data.Record Records} stored in this Store. Read-only. - * @property fields - * @type Ext.util.MixedCollection - */ - this.fields = this.recordType.prototype.fields; - } - this.modified = []; - - this.addEvents( - /** - * @event datachanged - * Fires when the data cache has changed in a bulk manner (e.g., it has been sorted, filtered, etc.) and a - * widget that is using this Store as a Record cache should refresh its view. - * @param {Store} this - */ - 'datachanged', - /** - * @event metachange - * Fires when this store's reader provides new metadata (fields). This is currently only supported for JsonReaders. - * @param {Store} this - * @param {Object} meta The JSON metadata - */ - 'metachange', - /** - * @event add - * Fires when Records have been {@link #add}ed to the Store - * @param {Store} this - * @param {Ext.data.Record[]} records The array of Records added - * @param {Number} index The index at which the record(s) were added - */ - 'add', - /** - * @event remove - * Fires when a Record has been {@link #remove}d from the Store - * @param {Store} this - * @param {Ext.data.Record} record The Record that was removed - * @param {Number} index The index at which the record was removed - */ - 'remove', - /** - * @event update - * Fires when a Record has been updated - * @param {Store} this - * @param {Ext.data.Record} record The Record that was updated - * @param {String} operation The update operation being performed. Value may be one of: - *

- Ext.data.Record.EDIT
- Ext.data.Record.REJECT
- Ext.data.Record.COMMIT
-         * 
- */ - 'update', - /** - * @event clear - * Fires when the data cache has been cleared. - * @param {Store} this - */ - 'clear', - /** - * @event exception - *

Fires if an exception occurs in the Proxy during a remote request. - * This event is relayed through the corresponding {@link Ext.data.DataProxy}. - * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception} - * for additional details. - * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception} - * for description. - */ - 'exception', - /** - * @event beforeload - * Fires before a request is made for a new data object. If the beforeload handler returns - * false the {@link #load} action will be canceled. - * @param {Store} this - * @param {Object} options The loading options that were specified (see {@link #load} for details) - */ - 'beforeload', - /** - * @event load - * Fires after a new set of Records has been loaded. - * @param {Store} this - * @param {Ext.data.Record[]} records The Records that were loaded - * @param {Object} options The loading options that were specified (see {@link #load} for details) - */ - 'load', - /** - * @event loadexception - *

This event is deprecated in favor of the catch-all {@link #exception} - * event instead.

- *

This event is relayed through the corresponding {@link Ext.data.DataProxy}. - * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception} - * for additional details. - * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception} - * for description. - */ - 'loadexception', - /** - * @event beforewrite - * @param {DataProxy} this - * @param {String} action [Ext.data.Api.actions.create|update|destroy] - * @param {Record/Array[Record]} rs - * @param {Object} options The loading options that were specified. Edit options.params to add Http parameters to the request. (see {@link #save} for details) - * @param {Object} arg The callback's arg object passed to the {@link #request} function - */ - 'beforewrite', - /** - * @event write - * Fires if the server returns 200 after an Ext.data.Api.actions CRUD action. - * Success or failure of the action is available in the result['successProperty'] property. - * The server-code might set the successProperty to false if a database validation - * failed, for example. - * @param {Ext.data.Store} store - * @param {String} action [Ext.data.Api.actions.create|update|destroy] - * @param {Object} result The 'data' picked-out out of the response for convenience. - * @param {Ext.Direct.Transaction} res - * @param {Record/Record[]} rs Store's records, the subject(s) of the write-action - */ - 'write' - ); - - if(this.proxy){ - this.relayEvents(this.proxy, ['loadexception', 'exception']); - } - // With a writer set for the Store, we want to listen to add/remove events to remotely create/destroy records. - if (this.writer) { - this.on({ - scope: this, - add: this.createRecords, - remove: this.destroyRecord, - update: this.updateRecord - }); - } - - this.sortToggle = {}; - if(this.sortField){ - this.setDefaultSort(this.sortField, this.sortDir); - }else if(this.sortInfo){ - this.setDefaultSort(this.sortInfo.field, this.sortInfo.direction); - } - - Ext.data.Store.superclass.constructor.call(this); - - if(this.id){ - this.storeId = this.id; - delete this.id; - } - if(this.storeId){ - Ext.StoreMgr.register(this); - } - if(this.inlineData){ - this.loadData(this.inlineData); - delete this.inlineData; - }else if(this.autoLoad){ - this.load.defer(10, this, [ - typeof this.autoLoad == 'object' ? - this.autoLoad : undefined]); - } -}; -Ext.extend(Ext.data.Store, Ext.util.Observable, { +Ext.data.Store = Ext.extend(Ext.util.Observable, { /** * @cfg {String} storeId If passed, the id to use to register with the {@link Ext.StoreMgr StoreMgr}. *

Note: if a (deprecated) {@link #id} is specified it will supersede the storeId @@ -1241,7 +1063,7 @@ sortInfo: { * internally be set to false.

*/ restful: false, - + /** * @cfg {Object} paramNames *

An object containing properties which specify the names of the paging and @@ -1261,7 +1083,7 @@ sortInfo: { * the parameter names to use in its {@link #load requests}. */ paramNames : undefined, - + /** * @cfg {Object} defaultParamNames * Provides the default values for the {@link #paramNames} property. To globally modify the parameters @@ -1274,17 +1096,349 @@ sortInfo: { dir : 'dir' }, + // private + batchKey : '_ext_batch_', + + constructor : function(config){ + this.data = new Ext.util.MixedCollection(false); + this.data.getKey = function(o){ + return o.id; + }; + + + // temporary removed-records cache + this.removed = []; + + if(config && config.data){ + this.inlineData = config.data; + delete config.data; + } + + Ext.apply(this, config); + + /** + * See the {@link #baseParams corresponding configuration option} + * for a description of this property. + * To modify this property see {@link #setBaseParam}. + * @property + */ + this.baseParams = Ext.isObject(this.baseParams) ? this.baseParams : {}; + + this.paramNames = Ext.applyIf(this.paramNames || {}, this.defaultParamNames); + + if((this.url || this.api) && !this.proxy){ + this.proxy = new Ext.data.HttpProxy({url: this.url, api: this.api}); + } + // If Store is RESTful, so too is the DataProxy + if (this.restful === true && this.proxy) { + // When operating RESTfully, a unique transaction is generated for each record. + // TODO might want to allow implemention of faux REST where batch is possible using RESTful routes only. + this.batch = false; + Ext.data.Api.restify(this.proxy); + } + + if(this.reader){ // reader passed + if(!this.recordType){ + this.recordType = this.reader.recordType; + } + if(this.reader.onMetaChange){ + this.reader.onMetaChange = this.reader.onMetaChange.createSequence(this.onMetaChange, this); + } + if (this.writer) { // writer passed + if (this.writer instanceof(Ext.data.DataWriter) === false) { // <-- config-object instead of instance. + this.writer = this.buildWriter(this.writer); + } + this.writer.meta = this.reader.meta; + this.pruneModifiedRecords = true; + } + } + + /** + * The {@link Ext.data.Record Record} constructor as supplied to (or created by) the + * {@link Ext.data.DataReader Reader}. Read-only. + *

If the Reader was constructed by passing in an Array of {@link Ext.data.Field} definition objects, + * instead of a Record constructor, it will implicitly create a Record constructor from that Array (see + * {@link Ext.data.Record}.{@link Ext.data.Record#create create} for additional details).

+ *

This property may be used to create new Records of the type held in this Store, for example:


+    // create the data store
+    var store = new Ext.data.ArrayStore({
+        autoDestroy: true,
+        fields: [
+           {name: 'company'},
+           {name: 'price', type: 'float'},
+           {name: 'change', type: 'float'},
+           {name: 'pctChange', type: 'float'},
+           {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
+        ]
+    });
+    store.loadData(myData);
+
+    // create the Grid
+    var grid = new Ext.grid.EditorGridPanel({
+        store: store,
+        colModel: new Ext.grid.ColumnModel({
+            columns: [
+                {id:'company', header: 'Company', width: 160, dataIndex: 'company'},
+                {header: 'Price', renderer: 'usMoney', dataIndex: 'price'},
+                {header: 'Change', renderer: change, dataIndex: 'change'},
+                {header: '% Change', renderer: pctChange, dataIndex: 'pctChange'},
+                {header: 'Last Updated', width: 85,
+                    renderer: Ext.util.Format.dateRenderer('m/d/Y'),
+                    dataIndex: 'lastChange'}
+            ],
+            defaults: {
+                sortable: true,
+                width: 75
+            }
+        }),
+        autoExpandColumn: 'company', // match the id specified in the column model
+        height:350,
+        width:600,
+        title:'Array Grid',
+        tbar: [{
+            text: 'Add Record',
+            handler : function(){
+                var defaultData = {
+                    change: 0,
+                    company: 'New Company',
+                    lastChange: (new Date()).clearTime(),
+                    pctChange: 0,
+                    price: 10
+                };
+                var recId = 3; // provide unique id
+                var p = new store.recordType(defaultData, recId); // create new record
+                grid.stopEditing();
+                store.{@link #insert}(0, p); // insert a new record into the store (also see {@link #add})
+                grid.startEditing(0, 0);
+            }
+        }]
+    });
+         * 
+ * @property recordType + * @type Function + */ + + if(this.recordType){ + /** + * A {@link Ext.util.MixedCollection MixedCollection} containing the defined {@link Ext.data.Field Field}s + * for the {@link Ext.data.Record Records} stored in this Store. Read-only. + * @property fields + * @type Ext.util.MixedCollection + */ + this.fields = this.recordType.prototype.fields; + } + this.modified = []; + + this.addEvents( + /** + * @event datachanged + * Fires when the data cache has changed in a bulk manner (e.g., it has been sorted, filtered, etc.) and a + * widget that is using this Store as a Record cache should refresh its view. + * @param {Store} this + */ + 'datachanged', + /** + * @event metachange + * Fires when this store's reader provides new metadata (fields). This is currently only supported for JsonReaders. + * @param {Store} this + * @param {Object} meta The JSON metadata + */ + 'metachange', + /** + * @event add + * Fires when Records have been {@link #add}ed to the Store + * @param {Store} this + * @param {Ext.data.Record[]} records The array of Records added + * @param {Number} index The index at which the record(s) were added + */ + 'add', + /** + * @event remove + * Fires when a Record has been {@link #remove}d from the Store + * @param {Store} this + * @param {Ext.data.Record} record The Record that was removed + * @param {Number} index The index at which the record was removed + */ + 'remove', + /** + * @event update + * Fires when a Record has been updated + * @param {Store} this + * @param {Ext.data.Record} record The Record that was updated + * @param {String} operation The update operation being performed. Value may be one of: + *

+     Ext.data.Record.EDIT
+     Ext.data.Record.REJECT
+     Ext.data.Record.COMMIT
+             * 
+ */ + 'update', + /** + * @event clear + * Fires when the data cache has been cleared. + * @param {Store} this + * @param {Record[]} The records that were cleared. + */ + 'clear', + /** + * @event exception + *

Fires if an exception occurs in the Proxy during a remote request. + * This event is relayed through the corresponding {@link Ext.data.DataProxy}. + * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception} + * for additional details. + * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception} + * for description. + */ + 'exception', + /** + * @event beforeload + * Fires before a request is made for a new data object. If the beforeload handler returns + * false the {@link #load} action will be canceled. + * @param {Store} this + * @param {Object} options The loading options that were specified (see {@link #load} for details) + */ + 'beforeload', + /** + * @event load + * Fires after a new set of Records has been loaded. + * @param {Store} this + * @param {Ext.data.Record[]} records The Records that were loaded + * @param {Object} options The loading options that were specified (see {@link #load} for details) + */ + 'load', + /** + * @event loadexception + *

This event is deprecated in favor of the catch-all {@link #exception} + * event instead.

+ *

This event is relayed through the corresponding {@link Ext.data.DataProxy}. + * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception} + * for additional details. + * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception} + * for description. + */ + 'loadexception', + /** + * @event beforewrite + * @param {Ext.data.Store} store + * @param {String} action [Ext.data.Api.actions.create|update|destroy] + * @param {Record/Array[Record]} rs + * @param {Object} options The loading options that were specified. Edit options.params to add Http parameters to the request. (see {@link #save} for details) + * @param {Object} arg The callback's arg object passed to the {@link #request} function + */ + 'beforewrite', + /** + * @event write + * Fires if the server returns 200 after an Ext.data.Api.actions CRUD action. + * Success of the action is determined in the result['successProperty']property (NOTE for RESTful stores, + * a simple 20x response is sufficient for the actions "destroy" and "update". The "create" action should should return 200 along with a database pk). + * @param {Ext.data.Store} store + * @param {String} action [Ext.data.Api.actions.create|update|destroy] + * @param {Object} result The 'data' picked-out out of the response for convenience. + * @param {Ext.Direct.Transaction} res + * @param {Record/Record[]} rs Store's records, the subject(s) of the write-action + */ + 'write', + /** + * @event beforesave + * Fires before a save action is called. A save encompasses destroying records, updating records and creating records. + * @param {Ext.data.Store} store + * @param {Object} data An object containing the data that is to be saved. The object will contain a key for each appropriate action, + * with an array of records for each action. + */ + 'beforesave', + /** + * @event save + * Fires after a save is completed. A save encompasses destroying records, updating records and creating records. + * @param {Ext.data.Store} store + * @param {Number} batch The identifier for the batch that was saved. + * @param {Object} data An object containing the data that is to be saved. The object will contain a key for each appropriate action, + * with an array of records for each action. + */ + 'save' + + ); + + if(this.proxy){ + // TODO remove deprecated loadexception with ext-3.0.1 + this.relayEvents(this.proxy, ['loadexception', 'exception']); + } + // With a writer set for the Store, we want to listen to add/remove events to remotely create/destroy records. + if (this.writer) { + this.on({ + scope: this, + add: this.createRecords, + remove: this.destroyRecord, + update: this.updateRecord, + clear: this.onClear + }); + } + + this.sortToggle = {}; + if(this.sortField){ + this.setDefaultSort(this.sortField, this.sortDir); + }else if(this.sortInfo){ + this.setDefaultSort(this.sortInfo.field, this.sortInfo.direction); + } + + Ext.data.Store.superclass.constructor.call(this); + + if(this.id){ + this.storeId = this.id; + delete this.id; + } + if(this.storeId){ + Ext.StoreMgr.register(this); + } + if(this.inlineData){ + this.loadData(this.inlineData); + delete this.inlineData; + }else if(this.autoLoad){ + this.load.defer(10, this, [ + typeof this.autoLoad == 'object' ? + this.autoLoad : undefined]); + } + // used internally to uniquely identify a batch + this.batchCounter = 0; + this.batches = {}; + }, + + /** + * builds a DataWriter instance when Store constructor is provided with a writer config-object instead of an instace. + * @param {Object} config Writer configuration + * @return {Ext.data.DataWriter} + * @private + */ + buildWriter : function(config) { + var klass = undefined, + type = (config.format || 'json').toLowerCase(); + switch (type) { + case 'json': + klass = Ext.data.JsonWriter; + break; + case 'xml': + klass = Ext.data.XmlWriter; + break; + default: + klass = Ext.data.JsonWriter; + } + return new klass(config); + }, + /** * Destroys the store. */ destroy : function(){ - if(this.storeId){ - Ext.StoreMgr.unregister(this); + if(!this.isDestroyed){ + if(this.storeId){ + Ext.StoreMgr.unregister(this); + } + this.clearData(); + this.data = null; + Ext.destroy(this.proxy); + this.reader = this.writer = null; + this.purgeListeners(); + this.isDestroyed = true; } - this.data = null; - Ext.destroy(this.proxy); - this.reader = this.writer = null; - this.purgeListeners(); }, /** @@ -1321,19 +1475,27 @@ sortInfo: { }, /** - * Remove a Record from the Store and fires the {@link #remove} event. - * @param {Ext.data.Record} record The Ext.data.Record object to remove from the cache. + * Remove Records from the Store and fires the {@link #remove} event. + * @param {Ext.data.Record/Ext.data.Record[]} record The record object or array of records to remove from the cache. */ remove : function(record){ + if(Ext.isArray(record)){ + Ext.each(record, function(r){ + this.remove(r); + }, this); + } var index = this.data.indexOf(record); if(index > -1){ + record.join(null); this.data.removeAt(index); - if(this.pruneModifiedRecords){ - this.modified.remove(record); - } - if(this.snapshot){ - this.snapshot.remove(record); - } + } + if(this.pruneModifiedRecords){ + this.modified.remove(record); + } + if(this.snapshot){ + this.snapshot.remove(record); + } + if(index > -1){ this.fireEvent('remove', this, record, index); } }, @@ -1348,16 +1510,30 @@ sortInfo: { /** * Remove all Records from the Store and fires the {@link #clear} event. + * @param {Boolean} silent [false] Defaults to false. Set true to not fire clear event. */ - removeAll : function(){ - this.data.clear(); + removeAll : function(silent){ + var items = []; + this.each(function(rec){ + items.push(rec); + }); + this.clearData(); if(this.snapshot){ this.snapshot.clear(); } if(this.pruneModifiedRecords){ this.modified = []; } - this.fireEvent('clear', this); + if (silent !== true) { // <-- prevents write-actions when we just want to clear a store. + this.fireEvent('clear', this, items); + } + }, + + // private + onClear: function(store, records){ + Ext.each(records, function(rec, index){ + this.destroyRecord(this, rec, index); + }, this); }, /** @@ -1372,6 +1548,9 @@ sortInfo: { this.data.insert(index, records[i]); records[i].join(this); } + if(this.snapshot){ + this.snapshot.addAll(records); + } this.fireEvent('add', this, records, index); }, @@ -1399,7 +1578,7 @@ sortInfo: { * @return {Ext.data.Record} The Record with the passed id. Returns undefined if not found. */ getById : function(id){ - return this.data.key(id); + return (this.snapshot || this.data).key(id); }, /** @@ -1429,6 +1608,14 @@ sortInfo: { this.lastOptions = o; }, + // private + clearData: function(){ + this.data.each(function(rec) { + rec.join(null); + }); + this.data.clear(); + }, + /** *

Loads the Record cache from the configured {@link #proxy} using the configured {@link #reader}.

*

Notes:

- *
  • callback : Function

    A function to be called after the Records - * have been loaded. The callback is called after the load event and is passed the following arguments:

  • - *
  • scope : Object

    Scope with which to call the callback (defaults + *

  • callback : Function

    A function to be called after the Records + * have been loaded. The callback is called after the load event is fired, and is passed the following arguments:

  • + *
  • scope : Object

    Scope with which to call the callback (defaults * to the Store object)

  • - *
  • add : Boolean

    Indicator to append loaded records rather than + *

  • add : Boolean

    Indicator to append loaded records rather than * replace the current cache. Note: see note for {@link #loadData}

  • * * @return {Boolean} If the developer provided {@link #beforeload} event handler returns @@ -1486,7 +1673,7 @@ sortInfo: { * @private */ updateRecord : function(store, record, action) { - if (action == Ext.data.Record.EDIT && this.autoSave === true && (!record.phantom || (record.phantom && record.isValid))) { + if (action == Ext.data.Record.EDIT && this.autoSave === true && (!record.phantom || (record.phantom && record.isValid()))) { this.save(); } }, @@ -1545,25 +1732,29 @@ sortInfo: { * @throws Error * @private */ - execute : function(action, rs, options) { + execute : function(action, rs, options, /* private */ batch) { // blow up if action not Ext.data.CREATE, READ, UPDATE, DESTROY if (!Ext.data.Api.isAction(action)) { throw new Ext.data.Api.Error('execute', action); } - // make sure options has a params key + // make sure options has a fresh, new params hash options = Ext.applyIf(options||{}, { params: {} }); - + if(batch !== undefined){ + this.addToBatch(batch); + } // have to separate before-events since load has a different signature than create,destroy and save events since load does not // include the rs (record resultset) parameter. Capture return values from the beforeaction into doRequest flag. var doRequest = true; if (action === 'read') { doRequest = this.fireEvent('beforeload', this, options); + Ext.applyIf(options.params, this.baseParams); } else { - // if Writer is configured as listful, force single-recoord rs to be [{}} instead of {} + // if Writer is configured as listful, force single-record rs to be [{}] instead of {} + // TODO Move listful rendering into DataWriter where the @cfg is defined. Should be easy now. if (this.writer.listful === true && this.restful !== true) { rs = (Ext.isArray(rs)) ? rs : [rs]; } @@ -1573,18 +1764,20 @@ sortInfo: { } // Write the action to options.params if ((doRequest = this.fireEvent('beforewrite', this, action, rs, options)) !== false) { - this.writer.write(action, options.params, rs); + this.writer.apply(options.params, this.baseParams, action, rs); } } if (doRequest !== false) { // Send request to proxy. - var params = Ext.apply({}, options.params, this.baseParams); if (this.writer && this.proxy.url && !this.proxy.restful && !Ext.data.Api.hasUniqueUrl(this.proxy, action)) { - params.xaction = action; + options.params.xaction = action; // <-- really old, probaby unecessary. } - // Note: Up until this point we've been dealing with 'action' as a key from Ext.data.Api.actions. We'll flip it now - // and send the value into DataProxy#request, since it's the value which maps to the DataProxy#api - this.proxy.request(Ext.data.Api.actions[action], rs, params, this.reader, this.createCallback(action, rs), this, options); + // Note: Up until this point we've been dealing with 'action' as a key from Ext.data.Api.actions. + // We'll flip it now and send the value into DataProxy#request, since it's the value which maps to + // the user's configured DataProxy#api + // TODO Refactor all Proxies to accept an instance of Ext.data.Request (not yet defined) instead of this looooooong list + // of params. This method is an artifact from Ext2. + this.proxy.request(Ext.data.Api.actions[action], rs, options.params, this.reader, this.createCallback(action, rs, batch), this, options); } return doRequest; }, @@ -1601,81 +1794,140 @@ sortInfo: { * * @TODO: Create extensions of Error class and send associated Record with thrown exceptions. * e.g.: Ext.data.DataReader.Error or Ext.data.Error or Ext.data.DataProxy.Error, etc. + * @return {Number} batch Returns a number to uniquely identify the "batch" of saves occurring. -1 will be returned + * if there are no items to save or the save was cancelled. */ save : function() { if (!this.writer) { throw new Ext.data.Store.Error('writer-undefined'); } + var queue = [], + len, + trans, + batch, + data = {}; // DESTROY: First check for removed records. Records in this.removed are guaranteed non-phantoms. @see Store#remove - if (this.removed.length) { - this.doTransaction('destroy', this.removed); + if(this.removed.length){ + queue.push(['destroy', this.removed]); } // Check for modified records. Use a copy so Store#rejectChanges will work if server returns error. var rs = [].concat(this.getModifiedRecords()); - if (!rs.length) { // Bail-out if empty... - return true; - } - - // CREATE: Next check for phantoms within rs. splice-off and execute create. - var phantoms = []; - for (var i = rs.length-1; i >= 0; i--) { - if (rs[i].phantom === true) { - var rec = rs.splice(i, 1).shift(); - if (rec.isValid()) { - phantoms.push(rec); + if(rs.length){ + // CREATE: Next check for phantoms within rs. splice-off and execute create. + var phantoms = []; + for(var i = rs.length-1; i >= 0; i--){ + if(rs[i].phantom === true){ + var rec = rs.splice(i, 1).shift(); + if(rec.isValid()){ + phantoms.push(rec); + } + }else if(!rs[i].isValid()){ // <-- while we're here, splice-off any !isValid real records + rs.splice(i,1); } - } else if (!rs[i].isValid()) { // <-- while we're here, splice-off any !isValid real records - rs.splice(i,1); } - } - // If we have valid phantoms, create them... - if (phantoms.length) { - this.doTransaction('create', phantoms); - } + // If we have valid phantoms, create them... + if(phantoms.length){ + queue.push(['create', phantoms]); + } - // UPDATE: And finally, if we're still here after splicing-off phantoms and !isValid real records, update the rest... - if (rs.length) { - this.doTransaction('update', rs); + // UPDATE: And finally, if we're still here after splicing-off phantoms and !isValid real records, update the rest... + if(rs.length){ + queue.push(['update', rs]); + } } - return true; + len = queue.length; + if(len){ + batch = ++this.batchCounter; + for(var i = 0; i < len; ++i){ + trans = queue[i]; + data[trans[0]] = trans[1]; + } + if(this.fireEvent('beforesave', this, data) !== false){ + for(var i = 0; i < len; ++i){ + trans = queue[i]; + this.doTransaction(trans[0], trans[1], batch); + } + return batch; + } + } + return -1; }, // private. Simply wraps call to Store#execute in try/catch. Defers to Store#handleException on error. Loops if batch: false - doTransaction : function(action, rs) { + doTransaction : function(action, rs, batch) { function transaction(records) { - try { - this.execute(action, records); - } catch (e) { + try{ + this.execute(action, records, undefined, batch); + }catch (e){ this.handleException(e); } } - if (this.batch === false) { - for (var i = 0, len = rs.length; i < len; i++) { + if(this.batch === false){ + for(var i = 0, len = rs.length; i < len; i++){ transaction.call(this, rs[i]); } - } else { + }else{ transaction.call(this, rs); } }, + // private + addToBatch : function(batch){ + var b = this.batches, + key = this.batchKey + batch, + o = b[key]; + + if(!o){ + b[key] = o = { + id: batch, + count: 0, + data: {} + } + } + ++o.count; + }, + + removeFromBatch : function(batch, action, data){ + var b = this.batches, + key = this.batchKey + batch, + o = b[key], + data, + arr; + + + if(o){ + arr = o.data[action] || []; + o.data[action] = arr.concat(data); + if(o.count === 1){ + data = o.data; + delete b[key]; + this.fireEvent('save', this, batch, data); + }else{ + --o.count; + } + } + }, + // @private callback-handler for remote CRUD actions // Do not override -- override loadRecords, onCreateRecords, onDestroyRecords and onUpdateRecords instead. - createCallback : function(action, rs) { + createCallback : function(action, rs, batch) { var actions = Ext.data.Api.actions; return (action == 'read') ? this.loadRecords : function(data, response, success) { // calls: onCreateRecords | onUpdateRecords | onDestroyRecords - this['on' + Ext.util.Format.capitalize(action) + 'Records'](success, rs, data); + this['on' + Ext.util.Format.capitalize(action) + 'Records'](success, rs, [].concat(data)); // If success === false here, exception will have been called in DataProxy if (success === true) { this.fireEvent('write', this, action, data, response, rs); } + this.removeFromBatch(batch, action, data); }; }, // Clears records from modified array after an exception event. // NOTE: records are left marked dirty. Do we want to commit them even though they were not updated/realized? + // TODO remove this method? clearModified : function(rs) { if (Ext.isArray(rs)) { for (var n=rs.length-1;n>=0;n--) { @@ -1736,7 +1988,7 @@ sortInfo: { // @protected onDestroyRecords proxy callback for destroy action onDestroyRecords : function(success, rs, data) { // splice each rec out of this.removed - rs = (rs instanceof Ext.data.Record) ? [rs] : rs; + rs = (rs instanceof Ext.data.Record) ? [rs] : [].concat(rs); for (var i=0,len=rs.length;iReloads the Record cache from the configured Proxy using the configured {@link Ext.data.Reader Reader} and - * the options from the last load operation performed.

    + *

    Reloads the Record cache from the configured Proxy using the configured + * {@link Ext.data.Reader Reader} and the options from the last load operation + * performed.

    *

    Note: see the Important note in {@link #load}.

    - * @param {Object} options (optional) An Object containing {@link #load loading options} which may - * override the options used in the last {@link #load} operation. See {@link #load} for details (defaults to - * null, in which case the {@link #lastOptions} are used). + * @param {Object} options

    (optional) An Object containing + * {@link #load loading options} which may override the {@link #lastOptions options} + * used in the last {@link #load} operation. See {@link #load} for details + * (defaults to null, in which case the {@link #lastOptions} are + * used).

    + *

    To add new params to the existing params:

    
    +lastOptions = myStore.lastOptions;
    +Ext.apply(lastOptions.params, {
    +    myNewParam: true
    +});
    +myStore.reload(lastOptions);
    +     * 
    */ reload : function(options){ this.load(Ext.applyIf(options||{}, this.lastOptions)); @@ -1770,6 +2032,9 @@ sortInfo: { // private // Called as a callback by the Reader during a load operation. loadRecords : function(o, options, success){ + if (this.isDestroyed === true) { + return; + } if(!o || success === false){ if(success !== false){ this.fireEvent('load', this, [], options); @@ -1791,7 +2056,7 @@ sortInfo: { this.data = this.snapshot; delete this.snapshot; } - this.data.clear(); + this.clearData(); this.data.addAll(r); this.totalLength = t; this.applySort(); @@ -1937,7 +2202,8 @@ sortInfo: { * Calls the specified function for each of the {@link Ext.data.Record Records} in the cache. * @param {Function} fn The function to call. The {@link Ext.data.Record Record} is passed as the first parameter. * Returning false aborts and exits the iteration. - * @param {Object} scope (optional) The scope in which to call the function (defaults to the {@link Ext.data.Record Record}). + * @param {Object} scope (optional) The scope (this reference) in which the function is executed. + * Defaults to the current {@link Ext.data.Record Record} in the iteration. */ each : function(fn, scope){ this.data.each(fn, scope); @@ -2008,7 +2274,7 @@ sortInfo: { * to test for filtering. Access field values using {@link Ext.data.Record#get}.

    *
  • id : Object

    The ID of the Record passed.

  • * - * @param {Object} scope (optional) The scope of the function (defaults to this) + * @param {Object} scope (optional) The scope (this reference) in which the function is executed. Defaults to this Store. */ filterBy : function(fn, scope){ this.snapshot = this.snapshot || this.data; @@ -2039,7 +2305,7 @@ sortInfo: { * to test for filtering. Access field values using {@link Ext.data.Record#get}.

    *
  • id : Object

    The ID of the Record passed.

  • * - * @param {Object} scope (optional) The scope of the function (defaults to this) + * @param {Object} scope (optional) The scope (this reference) in which the function is executed. Defaults to this Store. * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records **/ queryBy : function(fn, scope){ @@ -2048,10 +2314,10 @@ sortInfo: { }, /** - * Finds the index of the first matching record in this store by a specific property/value. - * @param {String} property A property on your objects - * @param {String/RegExp} value Either a string that the property value - * should begin with, or a RegExp to test against the property. + * Finds the index of the first matching Record in this store by a specific field value. + * @param {String} fieldName The name of the Record field to test. + * @param {String/RegExp} value Either a string that the field value + * should begin with, or a RegExp to test against the field. * @param {Number} startIndex (optional) The index to start searching at * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning * @param {Boolean} caseSensitive (optional) True for case sensitive comparison @@ -2063,9 +2329,9 @@ sortInfo: { }, /** - * Finds the index of the first matching record in this store by a specific property/value. - * @param {String} property A property on your objects - * @param {String/RegExp} value The value to match against + * Finds the index of the first matching Record in this store by a specific field value. + * @param {String} fieldName The name of the Record field to test. + * @param {Mixed} value The value to match the field against. * @param {Number} startIndex (optional) The index to start searching at * @return {Number} The matched index or -1 */ @@ -2083,7 +2349,7 @@ sortInfo: { * to test for filtering. Access field values using {@link Ext.data.Record#get}.

    *
  • id : Object

    The ID of the Record passed.

  • * - * @param {Object} scope (optional) The scope of the function (defaults to this) + * @param {Object} scope (optional) The scope (this reference) in which the function is executed. Defaults to this Store. * @param {Number} startIndex (optional) The index to start searching at * @return {Number} The matched index or -1 */ @@ -2178,18 +2444,27 @@ sortInfo: { for(var i = 0, len = m.length; i < len; i++){ m[i].reject(); } + var m = this.removed.slice(0).reverse(); + this.removed = []; + for(var i = 0, len = m.length; i < len; i++){ + this.insert(m[i].lastIndex||0, m[i]); + m[i].reject(); + } }, // private - onMetaChange : function(meta, rtype, o){ - this.recordType = rtype; - this.fields = rtype.prototype.fields; + onMetaChange : function(meta){ + this.recordType = this.reader.recordType; + this.fields = this.recordType.prototype.fields; delete this.snapshot; - if(meta.sortInfo){ - this.sortInfo = meta.sortInfo; + if(this.reader.meta.sortInfo){ + this.sortInfo = this.reader.meta.sortInfo; }else if(this.sortInfo && !this.fields.get(this.sortInfo.field)){ delete this.sortInfo; } + if(this.writer){ + this.writer.meta = this.reader.meta; + } this.modified = []; this.fireEvent('metachange', this, this.reader.meta); }, @@ -2235,7 +2510,6 @@ Ext.apply(Ext.data.Store.Error.prototype, { 'writer-undefined' : 'Attempted to execute a write-action without a DataWriter installed.' } }); - /** * @class Ext.data.Field *

    This class encapsulates the field definition information specified in the field definition objects @@ -2292,17 +2566,16 @@ Ext.data.Field = function(config){ case "int": cv = function(v){ return v !== undefined && v !== null && v !== '' ? - parseInt(String(v).replace(stripRe, ""), 10) : ''; + parseInt(String(v).replace(stripRe, ""), 10) : ''; }; break; case "float": cv = function(v){ return v !== undefined && v !== null && v !== '' ? - parseFloat(String(v).replace(stripRe, ""), 10) : ''; + parseFloat(String(v).replace(stripRe, ""), 10) : ''; }; break; case "bool": - case "boolean": cv = function(v){ return v === true || v === "true" || v == 1; }; break; case "date": @@ -2325,7 +2598,10 @@ Ext.data.Field = function(config){ var parsed = Date.parse(v); return parsed ? new Date(parsed) : null; }; - break; + break; + default: + cv = function(v){ return v; }; + break; } this.convert = cv; @@ -2386,7 +2662,7 @@ var store = new Ext.data.Store({ reader: new Ext.data.JsonReader( { idProperty: 'key', - root: 'daRoot', + root: 'daRoot', totalProperty: 'total' }, Dude // recordType @@ -2471,13 +2747,13 @@ sortType: function(value) { * "ASC". */ sortDir : "ASC", - /** - * @cfg {Boolean} allowBlank - * (Optional) Used for validating a {@link Ext.data.Record record}, defaults to true. - * An empty value here will cause {@link Ext.data.Record}.{@link Ext.data.Record#isValid isValid} - * to evaluate to false. - */ - allowBlank : true + /** + * @cfg {Boolean} allowBlank + * (Optional) Used for validating a {@link Ext.data.Record record}, defaults to true. + * An empty value here will cause {@link Ext.data.Record}.{@link Ext.data.Record#isValid isValid} + * to evaluate to false. + */ + allowBlank : true };/** * @class Ext.data.DataReader * Abstract base class for reading structured data from a data source and converting @@ -2507,14 +2783,49 @@ Ext.data.DataReader = function(meta, recordType){ */ this.recordType = Ext.isArray(recordType) ? Ext.data.Record.create(recordType) : recordType; + + // if recordType defined make sure extraction functions are defined + if (this.recordType){ + this.buildExtractors(); + } }; Ext.data.DataReader.prototype = { - /** - * Abstract method, overridden in {@link Ext.data.JsonReader} + * @cfg {String} messageProperty [undefined] Optional name of a property within a server-response that represents a user-feedback message. + */ + /** + * Abstract method created in extension's buildExtractors impl. + */ + getTotal: Ext.emptyFn, + /** + * Abstract method created in extension's buildExtractors impl. + */ + getRoot: Ext.emptyFn, + /** + * Abstract method created in extension's buildExtractors impl. + */ + getMessage: Ext.emptyFn, + /** + * Abstract method created in extension's buildExtractors impl. + */ + getSuccess: Ext.emptyFn, + /** + * Abstract method created in extension's buildExtractors impl. + */ + getId: Ext.emptyFn, + /** + * Abstract method, overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader} */ buildExtractors : Ext.emptyFn, + /** + * Abstract method overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader} + */ + extractData : Ext.emptyFn, + /** + * Abstract method overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader} + */ + extractValues : Ext.emptyFn, /** * Used for un-phantoming a record after a successful database insert. Sets the records pk along with new data from server. @@ -2549,24 +2860,24 @@ Ext.data.DataReader.prototype = { //rs.commit(); throw new Ext.data.DataReader.Error('realize', rs); } - this.buildExtractors(); - var values = this.extractValues(data, rs.fields.items, rs.fields.items.length); rs.phantom = false; // <-- That's what it's all about rs._phid = rs.id; // <-- copy phantom-id -> _phid, so we can remap in Store#onCreateRecords - rs.id = data[this.meta.idProperty]; - rs.data = values; + rs.id = this.getId(data); + + rs.fields.each(function(f) { + if (data[f.name] !== f.defaultValue) { + rs.data[f.name] = data[f.name]; + } + }); rs.commit(); } }, /** * Used for updating a non-phantom or "real" record's data with fresh data from server after remote-save. - * You must return a complete new record from the server. If you don't, your local record's missing fields - * will be populated with the default values specified in your Ext.data.Record.create specification. Without a defaultValue, - * local fields will be populated with empty string "". So return your entire record's data after both remote create and update. - * In addition, you must return record-data from the server in the same order received. - * Will perform a commit as well, un-marking dirty-fields. Store's "update" event will be suppressed as the record receives - * a fresh new data-hash. + * If returning data from multiple-records after a batch-update, you must return record-data from the server in + * the same order received. Will perform a commit as well, un-marking dirty-fields. Store's "update" event will be + * suppressed as the record receives fresh new data-hash * @param {Record/Record[]} rs * @param {Object/Object[]} data */ @@ -2584,22 +2895,61 @@ Ext.data.DataReader.prototype = { } } else { - // If rs is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on. + // If rs is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on. if (Ext.isArray(data) && data.length == 1) { data = data.shift(); } - if (!this.isData(data)) { - // TODO: create custom Exception class to return record in thrown exception. Allow exception-handler the choice - // to commit or not rather than blindly rs.commit() here. - rs.commit(); - throw new Ext.data.DataReader.Error('update', rs); + if (this.isData(data)) { + rs.fields.each(function(f) { + if (data[f.name] !== f.defaultValue) { + rs.data[f.name] = data[f.name]; + } + }); } - this.buildExtractors(); - rs.data = this.extractValues(Ext.apply(rs.data, data), rs.fields.items, rs.fields.items.length); rs.commit(); } }, + /** + * returns extracted, type-cast rows of data. Iterates to call #extractValues for each row + * @param {Object[]/Object} data-root from server response + * @param {Boolean} returnRecords [false] Set true to return instances of Ext.data.Record + * @private + */ + extractData : function(root, returnRecords) { + // A bit ugly this, too bad the Record's raw data couldn't be saved in a common property named "raw" or something. + var rawName = (this instanceof Ext.data.JsonReader) ? 'json' : 'node'; + + var rs = []; + + // Had to add Check for XmlReader, #isData returns true if root is an Xml-object. Want to check in order to re-factor + // #extractData into DataReader base, since the implementations are almost identical for JsonReader, XmlReader + if (this.isData(root) && !(this instanceof Ext.data.XmlReader)) { + root = [root]; + } + var f = this.recordType.prototype.fields, + fi = f.items, + fl = f.length, + rs = []; + if (returnRecords === true) { + var Record = this.recordType; + for (var i = 0; i < root.length; i++) { + var n = root[i]; + var record = new Record(this.extractValues(n, fi, fl), this.getId(n)); + record[rawName] = n; // <-- There's implementation of ugly bit, setting the raw record-data. + rs.push(record); + } + } + else { + for (var i = 0; i < root.length; i++) { + var data = this.extractValues(root[i], fi, fl); + data[this.meta.idProperty] = this.getId(root[i]); + rs.push(data); + } + } + return rs; + }, + /** * Returns true if the supplied data-hash looks and quacks like data. Checks to see if it has a key * corresponding to idProperty defined in your DataReader config containing non-empty pk. @@ -2607,7 +2957,15 @@ Ext.data.DataReader.prototype = { * @return {Boolean} */ isData : function(data) { - return (data && Ext.isObject(data) && !Ext.isEmpty(data[this.meta.idProperty])) ? true : false; + return (data && Ext.isObject(data) && !Ext.isEmpty(this.getId(data))) ? true : false; + }, + + // private function a store will createSequence upon + onMetaChange : function(meta){ + delete this.ef; + this.meta = meta; + this.recordType = Ext.data.Record.create(meta.fields); + this.buildExtractors(); } }; @@ -2630,8 +2988,6 @@ Ext.apply(Ext.data.DataReader.Error.prototype, { 'invalid-response': "#readResponse received an invalid response from the server." } }); - - /** * @class Ext.data.DataWriter *

    Ext.data.DataWriter facilitates create, update, and destroy actions between @@ -2642,34 +2998,72 @@ Ext.apply(Ext.data.DataReader.Error.prototype, { * {@link Ext.data.JsonWriter}.

    *

    Creating a writer is simple:

    *
    
    -var writer = new Ext.data.JsonWriter();
    +var writer = new Ext.data.JsonWriter({
    +    encode: false   // <--- false causes data to be printed to jsonData config-property of Ext.Ajax#reqeust
    +});
    + * 
    + * *

    Same old JsonReader as Ext-2.x:

    + *
    
    +var reader = new Ext.data.JsonReader({idProperty: 'id'}, [{name: 'first'}, {name: 'last'}, {name: 'email'}]);
      * 
    + * *

    The proxy for a writer enabled store can be configured with a simple url:

    *
    
     // Create a standard HttpProxy instance.
     var proxy = new Ext.data.HttpProxy({
    -    url: 'app.php/users'
    +    url: 'app.php/users'    // <--- Supports "provides"-type urls, such as '/users.json', '/products.xml' (Hello Rails/Merb)
     });
      * 
    - *

    For finer grained control, the proxy may also be configured with an api:

    + *

    For finer grained control, the proxy may also be configured with an API:

    *
    
    -// Use the api specification
    +// Maximum flexibility with the API-configuration
     var proxy = new Ext.data.HttpProxy({
         api: {
             read    : 'app.php/users/read',
             create  : 'app.php/users/create',
             update  : 'app.php/users/update',
    -        destroy : 'app.php/users/destroy'
    +        destroy : {  // <--- Supports object-syntax as well
    +            url: 'app.php/users/destroy',
    +            method: "DELETE"
    +        }
         }
     });
      * 
    - *

    Creating a Writer enabled store:

    + *

    Pulling it all together into a Writer-enabled Store:

    *
    
     var store = new Ext.data.Store({
         proxy: proxy,
         reader: reader,
    -    writer: writer
    +    writer: writer,
    +    autoLoad: true,
    +    autoSave: true  // -- Cell-level updates.
     });
    + * 
    + *

    Initiating write-actions automatically, using the existing Ext2.0 Store/Record API:

    + *
    
    +var rec = store.getAt(0);
    +rec.set('email', 'foo@bar.com');  // <--- Immediately initiates an UPDATE action through configured proxy.
    +
    +store.remove(rec);  // <---- Immediately initiates a DESTROY action through configured proxy.
    + * 
    + *

    For record/batch updates, use the Store-configuration {@link Ext.data.Store#autoSave autoSave:false}

    + *
    
    +var store = new Ext.data.Store({
    +    proxy: proxy,
    +    reader: reader,
    +    writer: writer,
    +    autoLoad: true,
    +    autoSave: false  // -- disable cell-updates
    +});
    +
    +var urec = store.getAt(0);
    +urec.set('email', 'foo@bar.com');
    +
    +var drec = store.getAt(1);
    +store.remove(drec);
    +
    +// Push the button!
    +store.save();
      * 
    * @constructor Create a new DataWriter * @param {Object} meta Metadata configuration options (implementation-specific) @@ -2678,14 +3072,8 @@ var store = new Ext.data.Store({ * using {@link Ext.data.Record#create}. */ Ext.data.DataWriter = function(config){ - /** - * This DataWriter's configured metadata as passed to the constructor. - * @type Mixed - * @property meta - */ Ext.apply(this, config); }; - Ext.data.DataWriter.prototype = { /** @@ -2703,14 +3091,26 @@ Ext.data.DataWriter.prototype = { listful : false, // <-- listful is actually not used internally here in DataWriter. @see Ext.data.Store#execute. /** - * Writes data in preparation for server-write action. Simply proxies to DataWriter#update, DataWriter#create - * DataWriter#destroy. - * @param {String} action [CREATE|UPDATE|DESTROY] - * @param {Object} params The params-hash to write-to - * @param {Record/Record[]} rs The recordset write. + * 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}, + * Where the first parameter is the receiver of paramaters and the second, baseParams, the source. + * @param {Object} params The request-params receiver. + * @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}. + * @param {String} action [{@link Ext.data.Api#actions create|update|destroy}] + * @param {Record/Record[]} rs The recordset to write, the subject(s) of the write action. */ - write : function(action, params, rs) { - this.render(action, rs, params, this[action](rs)); + apply : function(params, baseParams, action, rs) { + var data = [], + renderer = action + 'Record'; + // TODO implement @cfg listful here + if (Ext.isArray(rs)) { + Ext.each(rs, function(rec){ + data.push(this[renderer](rec)); + }, this); + } + else if (rs instanceof Ext.data.Record) { + data = this[renderer](rs); + } + this.render(params, baseParams, data); }, /** @@ -2724,84 +3124,17 @@ Ext.data.DataWriter.prototype = { render : Ext.emptyFn, /** - * update - * @param {Object} p Params-hash to apply result to. - * @param {Record/Record[]} rs Record(s) to write - * @private - */ - update : function(rs) { - var params = {}; - if (Ext.isArray(rs)) { - var data = [], - ids = []; - Ext.each(rs, function(val){ - ids.push(val.id); - data.push(this.updateRecord(val)); - }, this); - params[this.meta.idProperty] = ids; - params[this.meta.root] = data; - } - else if (rs instanceof Ext.data.Record) { - params[this.meta.idProperty] = rs.id; - params[this.meta.root] = this.updateRecord(rs); - } - return params; - }, - - /** - * @cfg {Function} saveRecord Abstract method that should be implemented in all subclasses - * (e.g.: {@link Ext.data.JsonWriter#saveRecord JsonWriter.saveRecord} + * @cfg {Function} updateRecord Abstract method that should be implemented in all subclasses + * (e.g.: {@link Ext.data.JsonWriter#updateRecord JsonWriter.updateRecord} */ updateRecord : Ext.emptyFn, - /** - * create - * @param {Object} p Params-hash to apply result to. - * @param {Record/Record[]} rs Record(s) to write - * @private - */ - create : function(rs) { - var params = {}; - if (Ext.isArray(rs)) { - var data = []; - Ext.each(rs, function(val){ - data.push(this.createRecord(val)); - }, this); - params[this.meta.root] = data; - } - else if (rs instanceof Ext.data.Record) { - params[this.meta.root] = this.createRecord(rs); - } - return params; - }, - /** * @cfg {Function} createRecord Abstract method that should be implemented in all subclasses * (e.g.: {@link Ext.data.JsonWriter#createRecord JsonWriter.createRecord}) */ createRecord : Ext.emptyFn, - /** - * destroy - * @param {Object} p Params-hash to apply result to. - * @param {Record/Record[]} rs Record(s) to write - * @private - */ - destroy : function(rs) { - var params = {}; - if (Ext.isArray(rs)) { - var data = [], - ids = []; - Ext.each(rs, function(val){ - data.push(this.destroyRecord(val)); - }, this); - params[this.meta.root] = data; - } else if (rs instanceof Ext.data.Record) { - params[this.meta.root] = this.destroyRecord(rs); - } - return params; - }, - /** * @cfg {Function} destroyRecord Abstract method that should be implemented in all subclasses * (e.g.: {@link Ext.data.JsonWriter#destroyRecord JsonWriter.destroyRecord}) @@ -2809,11 +3142,16 @@ Ext.data.DataWriter.prototype = { destroyRecord : Ext.emptyFn, /** - * Converts a Record to a hash - * @param {Record} - * @private + * Converts a Record to a hash, taking into account the state of the Ext.data.Record along with configuration properties + * related to its rendering, such as {@link #writeAllFields}, {@link Ext.data.Record#phantom phantom}, {@link Ext.data.Record#getChanges getChanges} and + * {@link Ext.data.DataReader#idProperty idProperty} + * @param {Ext.data.Record} + * @param {Object} config NOT YET IMPLEMENTED. Will implement an exlude/only configuration for fine-control over which fields do/don't get rendered. + * @return {Object} + * @protected + * TODO Implement excludes/only configuration with 2nd param? */ - toHash : function(rec) { + toHash : function(rec, config) { var map = rec.fields.map, data = {}, raw = (this.writeAllFields === false && rec.phantom === false) ? rec.getChanges() : rec.data, @@ -2823,8 +3161,35 @@ Ext.data.DataWriter.prototype = { data[m.mapping ? m.mapping : m.name] = value; } }); - data[this.meta.idProperty] = rec.id; + // 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. + // We can tell its not auto-increment if the user defined a DataReader field for it *and* that field's value is non-empty. + // we could also do a RegExp here for the Ext.data.Record AUTO_ID prefix. + if (rec.phantom) { + if (rec.fields.containsKey(this.meta.idProperty) && Ext.isEmpty(rec.data[this.meta.idProperty])) { + delete data[this.meta.idProperty]; + } + } else { + data[this.meta.idProperty] = rec.id + } return data; + }, + + /** + * Converts a {@link Ext.data.DataWriter#toHash Hashed} {@link Ext.data.Record} to fields-array array suitable + * for encoding to xml via XTemplate, eg: +
    <tpl for="."><{name}>{value}</{name}</tpl>
    + * eg, non-phantom: +
    {id: 1, first: 'foo', last: 'bar'} --> [{name: 'id', value: 1}, {name: 'first', value: 'foo'}, {name: 'last', value: 'bar'}]
    + * {@link Ext.data.Record#phantom Phantom} records will have had their idProperty omitted in {@link #toHash} if determined to be auto-generated. + * Non AUTOINCREMENT pks should have been protected. + * @param {Hash} data Hashed by Ext.data.DataWriter#toHash + * @return {[Object]} Array of attribute-objects. + * @protected + */ + toArray : function(data) { + var fields = []; + Ext.iterate(data, function(k, v) {fields.push({name: k, value: v});},this); + return fields; } };/** * @class Ext.data.DataProxy @@ -2861,6 +3226,25 @@ proxy : new Ext.data.HttpProxy({ } }), * + *

    And new in Ext version 3, attach centralized event-listeners upon the DataProxy class itself! This is a great place + * to implement a messaging system to centralize your application's user-feedback and error-handling.

    + *
    
    +// Listen to all "beforewrite" event fired by all proxies.
    +Ext.data.DataProxy.on('beforewrite', function(proxy, action) {
    +    console.log('beforewrite: ', action);
    +});
    +
    +// Listen to "write" event fired by all proxies
    +Ext.data.DataProxy.on('write', function(proxy, action, data, res, rs) {
    +    console.info('write: ', action);
    +});
    +
    +// Listen to "exception" event fired by all proxies
    +Ext.data.DataProxy.on('exception', function(proxy, type, action) {
    +    console.error(type + action + ' exception);
    +});
    + * 
    + * Note: These three events are all fired with the signature of the corresponding DataProxy instance event {@link #beforewrite beforewrite}, {@link #write write} and {@link #exception exception}. */ Ext.data.DataProxy = function(conn){ // make sure we have a config object here to support ux proxies. @@ -2889,7 +3273,27 @@ api: { update : undefined, destroy : undefined } - + * + *

    The url is built based upon the action being executed [load|create|save|destroy] + * using the commensurate {@link #api} property, or if undefined default to the + * configured {@link Ext.data.Store}.{@link Ext.data.Store#url url}.


    + *

    For example:

    + *
    
    +api: {
    +    load :    '/controller/load',
    +    create :  '/controller/new',  // Server MUST return idProperty of new record
    +    save :    '/controller/update',
    +    destroy : '/controller/destroy_action'
    +}
    +
    +// Alternatively, one can use the object-form to specify each API-action
    +api: {
    +    load: {url: 'read.php', method: 'GET'},
    +    create: 'create.php',
    +    destroy: 'destroy.php',
    +    save: 'update.php'
    +}
    +     * 
    *

    If the specific URL for a given CRUD action is undefined, the CRUD action request * will be directed to the configured {@link Ext.data.Connection#url url}.

    *

    Note: To modify the URL for an action dynamically the appropriate API @@ -2907,22 +3311,9 @@ myStore.on({ // permanent, applying this URL for all subsequent requests. store.proxy.setUrl('changed1.php', true); - // manually set the private connection URL. - // Warning: Accessing the private URL property should be avoided. - // Use the public method {@link Ext.data.HttpProxy#setUrl setUrl} instead, shown above. - // It should be noted that changing the URL like this will affect - // the URL for just this request. Subsequent requests will use the - // API or URL defined in your initial proxy configuration. - store.proxy.conn.url = 'changed1.php'; - - // proxy URL will be superseded by API (only if proxy created to use ajax): - // It should be noted that proxy API changes are permanent and will - // be used for all subsequent requests. - store.proxy.api.load = 'changed2.php'; - - // However, altering the proxy API should be done using the public - // method {@link Ext.data.DataProxy#setApi setApi} instead. - store.proxy.setApi('load', 'changed2.php'); + // Altering the proxy API should be done using the public + // method {@link Ext.data.DataProxy#setApi setApi}. + store.proxy.setApi('read', 'changed2.php'); // Or set the entire API with a config-object. // When using the config-object option, you must redefine the entire @@ -2939,23 +3330,17 @@ myStore.on({ * *

    */ - // Prepare the proxy api. Ensures all API-actions are defined with the Object-form. - try { - Ext.data.Api.prepare(this); - } catch (e) { - if (e instanceof Ext.data.Api.Error) { - e.toConsole(); - } - } this.addEvents( /** * @event exception - *

    Fires if an exception occurs in the Proxy during a remote request. - * This event is relayed through a corresponding - * {@link Ext.data.Store}.{@link Ext.data.Store#exception exception}, - * so any Store instance may observe this event. - * This event can be fired for one of two reasons:

    + *

    Fires if an exception occurs in the Proxy during a remote request. This event is relayed + * through a corresponding {@link Ext.data.Store}.{@link Ext.data.Store#exception exception}, + * so any Store instance may observe this event.

    + *

    In addition to being fired through the DataProxy instance that raised the event, this event is also fired + * through the Ext.data.DataProxy class to allow for centralized processing of exception events from all + * DataProxies by attaching a listener to the Ext.data.Proxy class itself.

    + *

    This event can be fired for one of two reasons:

    *
    - * @param {Object} scope The scope in which to call the callback + * @param {Object} scope The scope (this reference) in which the callback function is executed. Defaults to the browser window. * @param {Object} arg An optional argument which is passed to the callback as its second parameter. */ doRequest : function(action, rs, params, reader, callback, scope, arg) { @@ -3414,7 +3957,7 @@ Ext.extend(Ext.data.ScriptTagProxy, Ext.data.DataProxy, { * @param {String} action [Ext.data.Api.actions.create|read|update|destroy] * @param {Object} trans The request transaction object * @param {Object} res The server response - * @private + * @protected */ onRead : function(action, trans, res) { var result; @@ -3443,25 +3986,25 @@ Ext.extend(Ext.data.ScriptTagProxy, Ext.data.DataProxy, { * @param {String} action [Ext.data.Api.actions.create|read|update|destroy] * @param {Object} trans The request transaction object * @param {Object} res The server response - * @private + * @protected */ - onWrite : function(action, trans, res, rs) { + onWrite : function(action, trans, response, rs) { var reader = trans.reader; try { // though we already have a response object here in STP, run through readResponse to catch any meta-data exceptions. - reader.readResponse(action, res); + var res = reader.readResponse(action, response); } catch (e) { this.fireEvent('exception', this, 'response', action, trans, res, e); trans.callback.call(trans.scope||window, null, res, false); return; } - if(!res[reader.meta.successProperty] === true){ + if(!res.success === true){ this.fireEvent('exception', this, 'remote', action, trans, res, rs); trans.callback.call(trans.scope||window, null, res, false); return; } - this.fireEvent("write", this, action, res[reader.meta.root], res, rs, trans.arg ); - trans.callback.call(trans.scope||window, res[reader.meta.root], res, true); + this.fireEvent("write", this, action, res.data, res, rs, trans.arg ); + trans.callback.call(trans.scope||window, res.data, res, true); }, // private @@ -3532,7 +4075,7 @@ Ext.extend(Ext.data.ScriptTagProxy, Ext.data.DataProxy, { * @constructor * @param {Object} conn * An {@link Ext.data.Connection} object, or options parameter to {@link Ext.Ajax#request}. - *

    Note that if this HttpProxy is being used by a (@link Ext.data.Store Store}, then the + *

    Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the * Store's call to {@link #load} will override any specified callback and params * options. In this case, use the Store's {@link Ext.data.Store#events events} to modify parameters, * or react to loading events. The Store's {@link Ext.data.Store#baseParams baseParams} may also be @@ -3553,13 +4096,13 @@ Ext.data.HttpProxy = function(conn){ // nullify the connection url. The url param has been copied to 'this' above. The connection // url will be set during each execution of doRequest when buildUrl is called. This makes it easier for users to override the - // connection url during beforeaction events (ie: beforeload, beforesave, etc). The connection url will be nullified - // after each request as well. Url is always re-defined during doRequest. + // connection url during beforeaction events (ie: beforeload, beforewrite, etc). + // Url is always re-defined during doRequest. this.conn.url = null; this.useAjax = !conn || !conn.events; - //private. A hash containing active requests, keyed on action [Ext.data.Api.actions.create|read|update|destroy] + // A hash containing active requests, keyed on action [Ext.data.Api.actions.create|read|update|destroy] var actions = Ext.data.Api.actions; this.activeRequest = {}; for (var verb in actions) { @@ -3568,40 +4111,6 @@ Ext.data.HttpProxy = function(conn){ }; Ext.extend(Ext.data.HttpProxy, Ext.data.DataProxy, { - /** - * @cfg {Boolean} restful - *

    If set to true, a {@link Ext.data.Record#phantom non-phantom} record's - * {@link Ext.data.Record#id id} will be appended to the url (defaults to false).


    - *

    The url is built based upon the action being executed [load|create|save|destroy] - * using the commensurate {@link #api} property, or if undefined default to the - * configured {@link Ext.data.Store}.{@link Ext.data.Store#url url}.


    - *

    Some MVC (e.g., Ruby on Rails, Merb and Django) support this style of segment based urls - * where the segments in the URL follow the Model-View-Controller approach.

    
    -     * someSite.com/controller/action/id
    -     * 
    - * Where the segments in the url are typically:
      - *
    • The first segment : represents the controller class that should be invoked.
    • - *
    • The second segment : represents the class function, or method, that should be called.
    • - *
    • The third segment : represents the ID (a variable typically passed to the method).
    • - *

    - *

    For example:

    - *
    
    -api: {
    -    load :    '/controller/load',
    -    create :  '/controller/new',  // Server MUST return idProperty of new record
    -    save :    '/controller/update',
    -    destroy : '/controller/destroy_action'
    -}
    -
    -// Alternatively, one can use the object-form to specify each API-action
    -api: {
    -    load: {url: 'read.php', method: 'GET'},
    -    create: 'create.php',
    -    destroy: 'destroy.php',
    -    save: 'update.php'
    -}
    -     */
    -
         /**
          * Return the {@link Ext.data.Connection} object being used by this Proxy.
          * @return {Connection} The Connection object. This object may be used to subscribe to events on
    @@ -3625,6 +4134,7 @@ api: {
             this.conn.url = url;
             if (makePermanent === true) {
                 this.url = url;
    +            this.api = null;
                 Ext.data.Api.prepare(this);
             }
         },
    @@ -3643,8 +4153,9 @@ api: {
          * 
  • r : Ext.data.Record[] The block of Ext.data.Records.
  • *
  • options: Options object from the action request
  • *
  • success: Boolean success indicator
  • - * @param {Object} scope The scope in which to call the callback + * @param {Object} scope The scope (this reference) in which the callback function is executed. Defaults to the browser window. * @param {Object} arg An optional argument which is passed to the callback as its second parameter. + * @protected */ doRequest : function(action, rs, params, reader, cb, scope, arg) { var o = { @@ -3658,29 +4169,31 @@ api: { callback : this.createCallback(action, rs), scope: this }; - // Sample the request data: If it's an object, then it hasn't been json-encoded yet. - // Transmit data using jsonData config of Ext.Ajax.request - if (typeof(params[reader.meta.root]) === 'object') { - o.jsonData = params; + + // If possible, transmit data using jsonData || xmlData on Ext.Ajax.request (An installed DataWriter would have written it there.). + // Use std HTTP params otherwise. + if (params.jsonData) { + o.jsonData = params.jsonData; + } else if (params.xmlData) { + o.xmlData = params.xmlData; } else { o.params = params || {}; } // Set the connection url. If this.conn.url is not null here, - // the user may have overridden the url during a beforeaction event-handler. + // the user must have overridden the url during a beforewrite/beforeload event-handler. // this.conn.url is nullified after each request. - if (this.conn.url === null) { - this.conn.url = this.buildUrl(action, rs); - } - else if (this.restful === true && rs instanceof Ext.data.Record && !rs.phantom) { - this.conn.url += '/' + rs.id; - } + this.conn.url = this.buildUrl(action, rs); + if(this.useAjax){ Ext.applyIf(o, this.conn); // If a currently running request is found for this action, abort it. if (this.activeRequest[action]) { + //// // Disabled aborting activeRequest while implementing REST. activeRequest[action] will have to become an array + // TODO ideas anyone? + // //Ext.Ajax.abort(this.activeRequest[action]); } this.activeRequest[action] = Ext.Ajax.request(o); @@ -3704,6 +4217,7 @@ api: { if (!success) { if (action === Ext.data.Api.actions.read) { // @deprecated: fire loadexception for backwards compat. + // TODO remove in 3.1 this.fireEvent('loadexception', this, o, response); } this.fireEvent('exception', this, 'response', action, o, response); @@ -3715,7 +4229,7 @@ api: { } else { this.onWrite(action, o, response, rs); } - } + }; }, /** @@ -3723,7 +4237,10 @@ api: { * @param {String} action Action name as per {@link Ext.data.Api.actions#read}. * @param {Object} o The request transaction object * @param {Object} res The server response - * @private + * @fires loadexception (deprecated) + * @fires exception + * @fires load + * @protected */ onRead : function(action, o, response) { var result; @@ -3731,13 +4248,16 @@ api: { result = o.reader.read(response); }catch(e){ // @deprecated: fire old loadexception for backwards-compat. + // TODO remove in 3.1 this.fireEvent('loadexception', this, o, response, e); + this.fireEvent('exception', this, 'response', action, o, response, e); o.request.callback.call(o.request.scope, null, o.request.arg, false); return; } if (result.success === false) { // @deprecated: fire old loadexception for backwards-compat. + // TODO remove in 3.1 this.fireEvent('loadexception', this, o, response); // Get DataReader read-back a response-object to pass along to exception event @@ -3747,6 +4267,9 @@ api: { else { this.fireEvent('load', this, o, o.request.arg); } + // TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance + // the calls to request.callback(...) in each will have to be made identical. + // NOTE reader.readResponse does not currently return Ext.data.Response o.request.callback.call(o.request.scope, result, o.request.arg, result.success); }, /** @@ -3754,7 +4277,9 @@ api: { * @param {String} action [Ext.data.Api.actions.create|read|update|destroy] * @param {Object} trans The request transaction object * @param {Object} res The server response - * @private + * @fires exception + * @fires write + * @protected */ onWrite : function(action, o, response, rs) { var reader = o.reader; @@ -3766,12 +4291,15 @@ api: { o.request.callback.call(o.request.scope, null, o.request.arg, false); return; } - if (res[reader.meta.successProperty] === false) { - this.fireEvent('exception', this, 'remote', action, o, res, rs); + if (res.success === true) { + this.fireEvent('write', this, action, res.data, res, rs, o.request.arg); } else { - this.fireEvent('write', this, action, res[reader.meta.root], res, rs, o.request.arg); + this.fireEvent('exception', this, 'remote', action, o, res, rs); } - o.request.callback.call(o.request.scope, res[reader.meta.root], res, res[reader.meta.successProperty]); + // TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance + // the calls to request.callback(...) in each will have to be made similar. + // NOTE reader.readResponse does not currently return Ext.data.Response + o.request.callback.call(o.request.scope, res.data, res, res.success); }, // inherit docs @@ -3831,7 +4359,7 @@ Ext.extend(Ext.data.MemoryProxy, Ext.data.DataProxy, { *
  • The "arg" argument from the load function
  • *
  • A boolean success indicator
  • * - * @param {Object} scope The scope in which to call the callback + * @param {Object} scope The scope (this reference) in which the callback function is executed. Defaults to the browser window. * @param {Object} arg An optional argument which is passed to the callback as its second parameter. */ doRequest : function(action, rs, params, reader, callback, scope, arg) {