/*!
- * Ext JS Library 3.0.0
+ * Ext JS Library 3.0.3
* Copyright(c) 2006-2009 Ext JS, LLC
* licensing@extjs.com
* http://www.extjs.com/license
*/
+
/**
* @class Ext.data.Api
* @extends Object
for (var verb in this.restActions) {
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
+ return true;
+ break;
+ case 201: // entity created but no response returned
+ //res[reader.meta.successProperty] = true;
+ res.success = true;
+ break;
+ case 204: // no-content. Create a fake response.
+ //res[reader.meta.successProperty] = true;
+ //res[reader.meta.root] = null;
+ res.success = true;
+ res.data = null;
+ break;
+ default:
+ return true;
+ break;
+ }
+ /*
+ if (res[reader.meta.successProperty] === true) {
+ this.fireEvent("write", this, action, res[reader.meta.root], res, rs, o.request.arg);
+ } else {
+ 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('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
'execute': 'Attempted to execute an unknown action. Valid API actions are defined in Ext.data.Api.actions"'
}
});
+
+
\r
/**\r
* @class Ext.data.SortTypes\r
* <code>{@link #data}</code> and <code>{@link #id}</code> properties.</p>
* <p>Record objects generated by this constructor inherit all the methods of Ext.data.Record listed below.</p>
* @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.
+ * <p>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.<p>
+ * <p><b>The generated constructor has the same signature as this constructor.</b></p>
* @param {Object} data (Optional) An object, the properties of which provide values for the new Record's
* fields. If not specified the <code>{@link Ext.data.Field#defaultValue defaultValue}</code>
* for each field will be assigned.
</code></pre>
* @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){
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})
* </code></pre>
+ * <p><u>Writing Data</u></p>
+ * <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>
+ * along with <a href="http://extjs.com/deploy/dev/examples/restful/restful.html">RESTful features.</a>
* @constructor
* Creates a new Store.
* @param {Object} config A config object containing the objects needed for the Store to access data,
}
Ext.apply(this, config);
-
+
this.paramNames = Ext.applyIf(this.paramNames || {}, this.defaultParamNames);
if(this.url && !this.proxy){
this.recordType = this.reader.recordType;
}
if(this.reader.onMetaChange){
- this.reader.onMetaChange = this.onMetaChange.createDelegate(this);
+ //this.reader.onMetaChange = this.onMetaChange.createDelegate(this);
+ this.reader.onMetaChange = this.reader.onMetaChange.createSequence(this.onMetaChange, this);
}
if (this.writer) { // writer passed
this.writer.meta = this.reader.meta;
* @event clear
* Fires when the data cache has been cleared.
* @param {Store} this
+ * @param {Record[]} The records that were cleared.
*/
'clear',
/**
'loadexception',
/**
* @event beforewrite
- * @param {DataProxy} this
+ * @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 <code>options.params</code> to add Http parameters to the request. (see {@link #save} for details)
/**
* @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 <code>result['successProperty']</code> property.
- * The server-code might set the <code>successProperty</code> to <tt>false</tt> if a database validation
- * failed, for example.
+ * Success of the action is determined in the <code>result['successProperty']</code>property (<b>NOTE</b> 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.
scope: this,
add: this.createRecords,
remove: this.destroyRecord,
- update: this.updateRecord
+ update: this.updateRecord,
+ clear: this.onClear
});
}
* internally be set to <tt>false</tt>.</p>
*/
restful: false,
-
+
/**
* @cfg {Object} paramNames
* <p>An object containing properties which specify the names of the paging and
* 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
* 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();
},
/**
remove : function(record){
var index = this.data.indexOf(record);
if(index > -1){
+ record.join(null);
this.data.removeAt(index);
if(this.pruneModifiedRecords){
this.modified.remove(record);
* Remove all Records from the Store and fires the {@link #clear} event.
*/
removeAll : function(){
- this.data.clear();
+ 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);
+ this.fireEvent('clear', this, items);
+ },
+
+ // private
+ onClear: function(store, records){
+ Ext.each(records, function(rec, index){
+ this.destroyRecord(this, rec, index);
+ }, this);
},
/**
this.lastOptions = o;
},
+ // private
+ clearData: function(){
+ this.data.each(function(rec) {
+ rec.join(null);
+ });
+ this.data.clear();
+ },
+
/**
* <p>Loads the Record cache from the configured <tt>{@link #proxy}</tt> using the configured <tt>{@link #reader}</tt>.</p>
* <br><p>Notes:</p><div class="mdetail-params"><ul>
var doRequest = true;
if (action === 'read') {
+ Ext.applyIf(options.params, this.baseParams);
doRequest = this.fireEvent('beforeload', this, options);
}
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];
}
// 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;
+ 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
+ // 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
this.proxy.request(Ext.data.Api.actions[action], rs, params, this.reader, this.createCallback(action, rs), this, options);
}
return doRequest;
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);
// 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--) {
// @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;i<len;i++) {
this.removed.splice(this.removed.indexOf(rs[i]), 1);
}
this.data = this.snapshot;
delete this.snapshot;
}
- this.data.clear();
+ this.clearData();
this.data.addAll(r);
this.totalLength = t;
this.applySort();
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);
},
*/\r
this.recordType = Ext.isArray(recordType) ?\r
Ext.data.Record.create(recordType) : recordType;\r
+
+ // if recordType defined make sure extraction functions are defined\r
+ if (this.recordType){\r
+ this.buildExtractors();\r
+ }
};\r
\r
Ext.data.DataReader.prototype = {\r
-\r
/**\r
- * Abstract method, overridden in {@link Ext.data.JsonReader}\r
+ * @cfg {String} messageProperty [undefined] Optional name of a property within a server-response that represents a user-feedback message.\r
+ */\r
+ /**\r
+ * Abstract method created in extension's buildExtractors impl.\r
+ */\r
+ getTotal: Ext.emptyFn,\r
+ /**\r
+ * Abstract method created in extension's buildExtractors impl.\r
+ */\r
+ getRoot: Ext.emptyFn,\r
+ /**\r
+ * Abstract method created in extension's buildExtractors impl.\r
+ */\r
+ getMessage: Ext.emptyFn,\r
+ /**\r
+ * Abstract method created in extension's buildExtractors impl.\r
+ */\r
+ getSuccess: Ext.emptyFn,\r
+ /**\r
+ * Abstract method created in extension's buildExtractors impl.\r
+ */\r
+ getId: Ext.emptyFn,\r
+ /**\r
+ * Abstract method, overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}\r
*/\r
buildExtractors : Ext.emptyFn,\r
+ /**\r
+ * Abstract method overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}\r
+ */\r
+ extractData : Ext.emptyFn,\r
+ /**\r
+ * Abstract method overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}\r
+ */\r
+ extractValues : Ext.emptyFn,\r
\r
/**\r
* Used for un-phantoming a record after a successful database insert. Sets the records pk along with new data from server.\r
//rs.commit();\r
throw new Ext.data.DataReader.Error('realize', rs);\r
}\r
- this.buildExtractors();\r
- var values = this.extractValues(data, rs.fields.items, rs.fields.items.length);\r
rs.phantom = false; // <-- That's what it's all about\r
rs._phid = rs.id; // <-- copy phantom-id -> _phid, so we can remap in Store#onCreateRecords\r
- rs.id = data[this.meta.idProperty];\r
- rs.data = values;\r
+ rs.id = this.getId(data);\r
+ rs.data = data;\r
rs.commit();\r
}\r
},\r
\r
/**\r
* Used for updating a non-phantom or "real" record's data with fresh data from server after remote-save.\r
- * You <b>must</b> return a complete new record from the server. If you don't, your local record's missing fields\r
- * will be populated with the default values specified in your Ext.data.Record.create specification. Without a defaultValue,\r
- * local fields will be populated with empty string "". So return your entire record's data after both remote create and update.\r
- * In addition, you <b>must</b> return record-data from the server in the same order received.\r
- * Will perform a commit as well, un-marking dirty-fields. Store's "update" event will be suppressed as the record receives\r
- * a fresh new data-hash.\r
+ * If returning data from multiple-records after a batch-update, you <b>must</b> return record-data from the server in\r
+ * the same order received. Will perform a commit as well, un-marking dirty-fields. Store's "update" event will be\r
+ * suppressed as the record receives fresh new data-hash\r
* @param {Record/Record[]} rs\r
* @param {Object/Object[]} data\r
*/\r
}\r
}\r
else {\r
- // If rs is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on.\r
+ // If rs is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on.\r
if (Ext.isArray(data) && data.length == 1) {\r
data = data.shift();\r
}\r
- if (!this.isData(data)) {\r
- // TODO: create custom Exception class to return record in thrown exception. Allow exception-handler the choice\r
- // to commit or not rather than blindly rs.commit() here.\r
- rs.commit();\r
- throw new Ext.data.DataReader.Error('update', rs);\r
+ if (this.isData(data)) {\r
+ rs.data = Ext.apply(rs.data, data);\r
}\r
- this.buildExtractors();\r
- rs.data = this.extractValues(Ext.apply(rs.data, data), rs.fields.items, rs.fields.items.length);\r
rs.commit();\r
}\r
},\r
* @return {Boolean}\r
*/\r
isData : function(data) {\r
- return (data && Ext.isObject(data) && !Ext.isEmpty(data[this.meta.idProperty])) ? true : false;\r
+ return (data && Ext.isObject(data) && !Ext.isEmpty(this.getId(data))) ? true : false;\r
+ },\r
+\r
+ // private function a store will createSequence upon\r
+ onMetaChange : function(meta){\r
+ delete this.ef;\r
+ this.meta = meta;\r
+ this.recordType = Ext.data.Record.create(meta.fields);\r
+ this.buildExtractors();\r
}\r
};\r
\r
});\r
\r
\r
+/**\r
+ * Ext.data.Response\r
+ * A generic response class to normalize response-handling internally to the framework.\r
+ * TODO move to own file, add to jsb.\r
+ */\r
+Ext.data.Response = function(params) {\r
+ Ext.apply(this, params);\r
+};\r
+Ext.data.Response.prototype = {\r
+ /**\r
+ * @property {String} action {@link Ext.data.Api#actions}\r
+ */\r
+ action: undefined,\r
+ /**\r
+ * @property {Boolean} success\r
+ */\r
+ success : undefined,\r
+ /**\r
+ * @property {String} message\r
+ */\r
+ message : undefined,\r
+ /**\r
+ * @property {Array/Object} data\r
+ */\r
+ data: undefined,\r
+ /**\r
+ * @property {Object} raw The raw response returned from server-code\r
+ */\r
+ raw: undefined,\r
+ /**\r
+ * @property {Ext.data.Record/Ext.data.Record[]} record(s) related to the Request action\r
+ */\r
+ records: undefined\r
+}\r
/**
* @class Ext.data.DataWriter
* <p>Ext.data.DataWriter facilitates create, update, and destroy actions between
* 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 = {
/**
* @param {Record/Record[]} rs The recordset write.
*/
write : function(action, params, rs) {
- this.render(action, rs, params, this[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(action, rs, params, data);
},
/**
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})
destroyRecord : Ext.emptyFn,
/**
- * Converts a Record to a hash
+ * Converts a Record to a hash.
* @param {Record}
* @private
*/
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;
}
};/**\r
update : undefined,\r
destroy : undefined\r
}\r
-</code></pre>\r
+ * </code></pre>\r
+ * <p>The url is built based upon the action being executed <tt>[load|create|save|destroy]</tt>\r
+ * using the commensurate <tt>{@link #api}</tt> property, or if undefined default to the\r
+ * configured {@link Ext.data.Store}.{@link Ext.data.Store#url url}.</p><br>\r
+ * <p>For example:</p>\r
+ * <pre><code>\r
+api: {\r
+ load : '/controller/load',\r
+ create : '/controller/new', // Server MUST return idProperty of new record\r
+ save : '/controller/update',\r
+ destroy : '/controller/destroy_action'\r
+}\r
+\r
+// Alternatively, one can use the object-form to specify each API-action\r
+api: {\r
+ load: {url: 'read.php', method: 'GET'},\r
+ create: 'create.php',\r
+ destroy: 'destroy.php',\r
+ save: 'update.php'\r
+}\r
+ * </code></pre>\r
* <p>If the specific URL for a given CRUD action is undefined, the CRUD action request\r
* will be directed to the configured <tt>{@link Ext.data.Connection#url url}</tt>.</p>\r
* <br><p><b>Note</b>: To modify the URL for an action dynamically the appropriate API\r
// permanent, applying this URL for all subsequent requests.\r
store.proxy.setUrl('changed1.php', true);\r
\r
- // manually set the <b>private</b> connection URL.\r
- // <b>Warning:</b> Accessing the private URL property should be avoided.\r
- // Use the public method <tt>{@link Ext.data.HttpProxy#setUrl setUrl}</tt> instead, shown above.\r
- // It should be noted that changing the URL like this will affect\r
- // the URL for just this request. Subsequent requests will use the\r
- // API or URL defined in your initial proxy configuration.\r
- store.proxy.conn.url = 'changed1.php';\r
-\r
- // proxy URL will be superseded by API (only if proxy created to use ajax):\r
- // It should be noted that proxy API changes are permanent and will\r
- // be used for all subsequent requests.\r
- store.proxy.api.load = 'changed2.php';\r
-\r
- // However, altering the proxy API should be done using the public\r
- // method <tt>{@link Ext.data.DataProxy#setApi setApi}</tt> instead.\r
- store.proxy.setApi('load', 'changed2.php');\r
+ // Altering the proxy API should be done using the public\r
+ // method <tt>{@link Ext.data.DataProxy#setApi setApi}</tt>.\r
+ store.proxy.setApi('read', 'changed2.php');\r
\r
// Or set the entire API with a config-object.\r
// When using the config-object option, you must redefine the <b>entire</b>\r
* </code></pre>\r
* </p>\r
*/\r
- // Prepare the proxy api. Ensures all API-actions are defined with the Object-form.\r
- try {\r
- Ext.data.Api.prepare(this);\r
- } catch (e) {\r
- if (e instanceof Ext.data.Api.Error) {\r
- e.toConsole();\r
- }\r
- }\r
\r
this.addEvents(\r
/**\r
* @event exception\r
- * <p>Fires if an exception occurs in the Proxy during a remote request.\r
- * This event is relayed through a corresponding\r
- * {@link Ext.data.Store}.{@link Ext.data.Store#exception exception},\r
- * so any Store instance may observe this event.\r
- * This event can be fired for one of two reasons:</p>\r
+ * <p>Fires if an exception occurs in the Proxy during a remote request. This event is relayed\r
+ * through a corresponding {@link Ext.data.Store}.{@link Ext.data.Store#exception exception},\r
+ * so any Store instance may observe this event.</p>\r
+ * <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired\r
+ * through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of exception events from <b>all</b>\r
+ * DataProxies by attaching a listener to the Ext.data.Proxy class itself.</p>\r
+ * <p>This event can be fired for one of two reasons:</p>\r
* <div class="mdetail-params"><ul>\r
* <li>remote-request <b>failed</b> : <div class="sub-desc">\r
* The server did not return status === 200.\r
'loadexception',\r
/**\r
* @event beforewrite\r
- * Fires before a request is generated for one of the actions Ext.data.Api.actions.create|update|destroy\r
+ * <p>Fires before a request is generated for one of the actions Ext.data.Api.actions.create|update|destroy</p>\r
+ * <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired\r
+ * through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of beforewrite events from <b>all</b>\r
+ * DataProxies by attaching a listener to the Ext.data.Proxy class itself.</p>\r
* @param {DataProxy} this The proxy for the request\r
* @param {String} action [Ext.data.Api.actions.create|update|destroy]\r
* @param {Record/Array[Record]} rs The Record(s) to create|update|destroy.\r
'beforewrite',\r
/**\r
* @event write\r
- * Fires before the request-callback is called\r
+ * <p>Fires before the request-callback is called</p>\r
+ * <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired\r
+ * through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of write events from <b>all</b>\r
+ * DataProxies by attaching a listener to the Ext.data.Proxy class itself.</p>\r
* @param {DataProxy} this The proxy that sent the request\r
* @param {String} action [Ext.data.Api.actions.create|upate|destroy]\r
* @param {Object} data The data object extracted from the server-response\r
'write'\r
);\r
Ext.data.DataProxy.superclass.constructor.call(this);\r
+\r
+ // Prepare the proxy api. Ensures all API-actions are defined with the Object-form.\r
+ try {\r
+ Ext.data.Api.prepare(this);\r
+ } catch (e) {\r
+ if (e instanceof Ext.data.Api.Error) {\r
+ e.toConsole();\r
+ }\r
+ }\r
+ // relay each proxy's events onto Ext.data.DataProxy class for centralized Proxy-listening\r
+ Ext.data.DataProxy.relayEvents(this, ['beforewrite', 'write', 'exception']);\r
};\r
\r
Ext.extend(Ext.data.DataProxy, Ext.util.Observable, {\r
...\r
)}\r
* </code></pre>\r
- * There is no <code>{@link #api}</code> specified in the configuration of the proxy,\r
+ * If there is no <code>{@link #api}</code> specified in the configuration of the proxy,\r
* all requests will be marshalled to a single RESTful url (/users) so the serverside\r
* framework can inspect the HTTP Method and act accordingly:\r
* <pre>\r
PUT /users/23 update\r
DESTROY /users/23 delete\r
* </pre></p>\r
+ * <p>If set to <tt>true</tt>, a {@link Ext.data.Record#phantom non-phantom} record's\r
+ * {@link Ext.data.Record#id id} will be appended to the url. Some MVC (e.g., Ruby on Rails,\r
+ * Merb and Django) support segment based urls where the segments in the URL follow the\r
+ * Model-View-Controller approach:<pre><code>\r
+ * someSite.com/controller/action/id\r
+ * </code></pre>\r
+ * Where the segments in the url are typically:<div class="mdetail-params"><ul>\r
+ * <li>The first segment : represents the controller class that should be invoked.</li>\r
+ * <li>The second segment : represents the class function, or method, that should be called.</li>\r
+ * <li>The third segment : represents the ID (a variable typically passed to the method).</li>\r
+ * </ul></div></p>\r
+ * <br><p>Refer to <code>{@link Ext.data.DataProxy#api}</code> for additional information.</p>\r
*/\r
restful: false,\r
\r
throw new Ext.data.Api.Error('invalid-url', action);\r
}\r
\r
+ // look for urls having "provides" suffix (from Rails/Merb and others...),\r
+ // e.g.: /users.json, /users.xml, etc\r
+ // with restful routes, we need urls like:\r
+ // PUT /users/1.json\r
+ // DELETE /users/1.json\r
var format = null;\r
- var m = url.match(/(.*)(\.\w+)$/); // <-- look for urls with "provides" suffix, e.g.: /users.json, /users.xml, etc\r
+ var m = url.match(/(.*)(\.json|\.xml|\.html)$/);\r
if (m) {\r
- format = m[2];\r
- url = m[1];\r
+ format = m[2]; // eg ".json"\r
+ url = m[1]; // eg: "/users"\r
}\r
// prettyUrls is deprectated in favor of restful-config\r
if ((this.prettyUrls === true || this.restful === true) && record instanceof Ext.data.Record && !record.phantom) {\r
}\r
});\r
\r
+// Apply the Observable prototype to the DataProxy class so that proxy instances can relay their\r
+// events to the class. Allows for centralized listening of all proxy instances upon the DataProxy class.\r
+Ext.apply(Ext.data.DataProxy, Ext.util.Observable.prototype);\r
+Ext.util.Observable.call(Ext.data.DataProxy);\r
+\r
/**\r
* @class Ext.data.DataProxy.Error\r
* @extends Ext.Error\r
'api-invalid': 'Recieved an invalid API-configuration. Please ensure your proxy API-configuration contains only the actions from Ext.data.Api.actions.'\r
}\r
});\r
+\r
+\r
/**\r
* @class Ext.data.ScriptTagProxy\r
* @extends Ext.data.DataProxy\r
* @param {Object} res The server response\r
* @private\r
*/\r
- onWrite : function(action, trans, res, rs) {\r
+ onWrite : function(action, trans, response, rs) {\r
var reader = trans.reader;\r
try {\r
// though we already have a response object here in STP, run through readResponse to catch any meta-data exceptions.\r
- reader.readResponse(action, res);\r
+ var res = reader.readResponse(action, response);\r
} catch (e) {\r
this.fireEvent('exception', this, 'response', action, trans, res, e);\r
trans.callback.call(trans.scope||window, null, res, false);\r
return;\r
}\r
- if(!res[reader.meta.successProperty] === true){\r
+ if(!res.success === true){\r
this.fireEvent('exception', this, 'remote', action, trans, res, rs);\r
trans.callback.call(trans.scope||window, null, res, false);\r
return;\r
}\r
- this.fireEvent("write", this, action, res[reader.meta.root], res, rs, trans.arg );\r
- trans.callback.call(trans.scope||window, res[reader.meta.root], res, true);\r
+ this.fireEvent("write", this, action, res.data, res, rs, trans.arg );\r
+ trans.callback.call(trans.scope||window, res.data, res, true);\r
},\r
\r
// private\r
\r
// nullify the connection url. The url param has been copied to 'this' above. The connection\r
// url will be set during each execution of doRequest when buildUrl is called. This makes it easier for users to override the\r
- // connection url during beforeaction events (ie: beforeload, beforesave, etc). The connection url will be nullified\r
- // after each request as well. Url is always re-defined during doRequest.\r
+ // connection url during beforeaction events (ie: beforeload, beforewrite, etc).\r
+ // Url is always re-defined during doRequest.\r
this.conn.url = null;\r
\r
this.useAjax = !conn || !conn.events;\r
\r
- //private. A hash containing active requests, keyed on action [Ext.data.Api.actions.create|read|update|destroy]\r
+ // A hash containing active requests, keyed on action [Ext.data.Api.actions.create|read|update|destroy]\r
var actions = Ext.data.Api.actions;\r
this.activeRequest = {};\r
for (var verb in actions) {\r
};\r
\r
Ext.extend(Ext.data.HttpProxy, Ext.data.DataProxy, {\r
- /**\r
- * @cfg {Boolean} restful\r
- * <p>If set to <tt>true</tt>, a {@link Ext.data.Record#phantom non-phantom} record's\r
- * {@link Ext.data.Record#id id} will be appended to the url (defaults to <tt>false</tt>).</p><br>\r
- * <p>The url is built based upon the action being executed <tt>[load|create|save|destroy]</tt>\r
- * using the commensurate <tt>{@link #api}</tt> property, or if undefined default to the\r
- * configured {@link Ext.data.Store}.{@link Ext.data.Store#url url}.</p><br>\r
- * <p>Some MVC (e.g., Ruby on Rails, Merb and Django) support this style of segment based urls\r
- * where the segments in the URL follow the Model-View-Controller approach.</p><pre><code>\r
- * someSite.com/controller/action/id\r
- * </code></pre>\r
- * Where the segments in the url are typically:<div class="mdetail-params"><ul>\r
- * <li>The first segment : represents the controller class that should be invoked.</li>\r
- * <li>The second segment : represents the class function, or method, that should be called.</li>\r
- * <li>The third segment : represents the ID (a variable typically passed to the method).</li>\r
- * </ul></div></p>\r
- * <p>For example:</p>\r
- * <pre><code>\r
-api: {\r
- load : '/controller/load',\r
- create : '/controller/new', // Server MUST return idProperty of new record\r
- save : '/controller/update',\r
- destroy : '/controller/destroy_action'\r
-}\r
-\r
-// Alternatively, one can use the object-form to specify each API-action\r
-api: {\r
- load: {url: 'read.php', method: 'GET'},\r
- create: 'create.php',\r
- destroy: 'destroy.php',\r
- save: 'update.php'\r
-}\r
- */\r
-\r
/**\r
* Return the {@link Ext.data.Connection} object being used by this Proxy.\r
* @return {Connection} The Connection object. This object may be used to subscribe to events on\r
this.conn.url = url;\r
if (makePermanent === true) {\r
this.url = url;\r
+ this.api = null;\r
Ext.data.Api.prepare(this);\r
}\r
},\r
callback : this.createCallback(action, rs),\r
scope: this\r
};\r
- // Sample the request data: If it's an object, then it hasn't been json-encoded yet.\r
- // Transmit data using jsonData config of Ext.Ajax.request\r
- if (typeof(params[reader.meta.root]) === 'object') {\r
- o.jsonData = params;\r
+\r
+ // If possible, transmit data using jsonData || xmlData on Ext.Ajax.request (An installed DataWriter would have written it there.).\r
+ // Use std HTTP params otherwise.\r
+ // TODO wrap into 1 Ext.apply now?\r
+ if (params.jsonData) {\r
+ o.jsonData = params.jsonData;\r
+ } else if (params.xmlData) {\r
+ o.xmlData = params.xmlData;\r
} else {\r
o.params = params || {};\r
}\r
if (this.conn.url === null) {\r
this.conn.url = this.buildUrl(action, rs);\r
}\r
- else if (this.restful === true && rs instanceof Ext.data.Record && !rs.phantom) {\r
+ else if (this.restful === true && rs instanceof Ext.data.Record && !rs.phantom) { // <-- user must have intervened with #setApi or #setUrl\r
this.conn.url += '/' + rs.id;\r
}\r
if(this.useAjax){\r
\r
// If a currently running request is found for this action, abort it.\r
if (this.activeRequest[action]) {\r
+ ////\r
// Disabled aborting activeRequest while implementing REST. activeRequest[action] will have to become an array\r
+ // TODO ideas anyone?\r
+ //\r
//Ext.Ajax.abort(this.activeRequest[action]);\r
}\r
this.activeRequest[action] = Ext.Ajax.request(o);\r
if (!success) {\r
if (action === Ext.data.Api.actions.read) {\r
// @deprecated: fire loadexception for backwards compat.\r
+ // TODO remove in 3.1\r
this.fireEvent('loadexception', this, o, response);\r
}\r
this.fireEvent('exception', this, 'response', action, o, response);\r
* @param {String} action Action name as per {@link Ext.data.Api.actions#read}.\r
* @param {Object} o The request transaction object\r
* @param {Object} res The server response\r
+ * @fires loadexception (deprecated)\r
+ * @fires exception\r
+ * @fires load\r
* @private\r
*/\r
onRead : function(action, o, response) {\r
result = o.reader.read(response);\r
}catch(e){\r
// @deprecated: fire old loadexception for backwards-compat.\r
+ // TODO remove in 3.1\r
this.fireEvent('loadexception', this, o, response, e);\r
+\r
this.fireEvent('exception', this, 'response', action, o, response, e);\r
o.request.callback.call(o.request.scope, null, o.request.arg, false);\r
return;\r
}\r
if (result.success === false) {\r
// @deprecated: fire old loadexception for backwards-compat.\r
+ // TODO remove in 3.1\r
this.fireEvent('loadexception', this, o, response);\r
\r
// Get DataReader read-back a response-object to pass along to exception event\r
else {\r
this.fireEvent('load', this, o, o.request.arg);\r
}\r
+ // TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance\r
+ // the calls to request.callback(...) in each will have to be made identical.\r
+ // NOTE reader.readResponse does not currently return Ext.data.Response\r
o.request.callback.call(o.request.scope, result, o.request.arg, result.success);\r
},\r
/**\r
* @param {String} action [Ext.data.Api.actions.create|read|update|destroy]\r
* @param {Object} trans The request transaction object\r
* @param {Object} res The server response\r
+ * @fires exception\r
+ * @fires write\r
* @private\r
*/\r
onWrite : function(action, o, response, rs) {\r
o.request.callback.call(o.request.scope, null, o.request.arg, false);\r
return;\r
}\r
- if (res[reader.meta.successProperty] === false) {\r
+ if (res.success === false) {\r
this.fireEvent('exception', this, 'remote', action, o, res, rs);\r
} else {\r
- this.fireEvent('write', this, action, res[reader.meta.root], res, rs, o.request.arg);\r
+ this.fireEvent('write', this, action, res.data, res, rs, o.request.arg);\r
}\r
- o.request.callback.call(o.request.scope, res[reader.meta.root], res, res[reader.meta.successProperty]);\r
+ // TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance\r
+ // the calls to request.callback(...) in each will have to be made similar.\r
+ // NOTE reader.readResponse does not currently return Ext.data.Response\r
+ o.request.callback.call(o.request.scope, res.data, res, res.success);\r
},\r
\r
// inherit docs\r