/**
* Proxies are used by {@link Ext.data.Store Stores} to handle the loading and saving of
* {@link Ext.data.Model Model} data. Usually developers will not need to create or interact
* with proxies directly.
*
* # Types of Proxy
*
* There are two main types of Proxy - {@link Ext.data.proxy.Client Client} and
* {@link Ext.data.proxy.Server Server}. The Client proxies save their data locally and include
* the following subclasses:
*
* - {@link Ext.data.proxy.LocalStorage LocalStorageProxy} - saves its data to localStorage
* if the browser supports it
* - {@link Ext.data.proxy.SessionStorage SessionStorageProxy} - saves its data to sessionStorage
* if the browsers supports it
* - {@link Ext.data.proxy.Memory MemoryProxy} - holds data in memory only, any data is lost
* when the page is refreshed
*
* The Server proxies save their data by sending requests to some remote server. These proxies
* include:
*
* - {@link Ext.data.proxy.Ajax Ajax} - sends requests to a server on the same domain
* - {@link Ext.data.proxy.JsonP JsonP} - uses JSON-P to send requests to a server on a different
* domain
* - {@link Ext.data.proxy.Rest Rest} - uses RESTful HTTP methods (GET/PUT/POST/DELETE)
* to communicate with server
* - {@link Ext.data.proxy.Direct Direct} - uses {@link Ext.direct.Manager} to send requests
*
* Proxies operate on the principle that all operations performed are either Create, Read, Update
* or Delete. These four operations are mapped to the methods {@link #method!create},
* {@link #method!read}, {@link #method!update} and {@link #method!erase} respectively. Each Proxy
* subclass implements these functions.
*
* The CRUD methods each expect an {@link Ext.data.operation.Operation Operation} object as the only
* argument. The Operation encapsulates information about the action the Store wishes to perform,
* the {@link Ext.data.Model model} instances that are to be modified, etc. See the
* {@link Ext.data.operation.Operation Operation} documentation for more details. Each CRUD
* method also accepts a callback function to be called asynchronously on completion.
*
* Proxies also support batching of Operations via a {@link Ext.data.Batch batch} object, invoked
* by the {@link #batch}
* method.
*/
Ext.define('Ext.data.proxy.Proxy', {
mixins: [
'Ext.mixin.Factoryable',
'Ext.mixin.Observable'
],
$configPrefixed: false,
alias: 'proxy.proxy', // also configures Factoryable
alternateClassName: [
'Ext.data.DataProxy',
'Ext.data.Proxy'
],
requires: [
'Ext.data.schema.Schema',
'Ext.data.reader.Reader',
'Ext.data.writer.Writer'
],
uses: [
'Ext.data.Batch',
'Ext.data.operation.*',
'Ext.data.Model'
],
config: {
/**
* @cfg {String} batchOrder
* Comma-separated ordering 'create', 'update' and 'destroy' actions when batching.
* Override this to set a different order for the batched CRUD actions to be executed in.
* Defaults to 'create,update,destroy'.
*/
batchOrder: 'create,update,destroy',
/**
* @cfg {Boolean} batchActions
* True to batch actions of a particular type when synchronizing the store. Defaults to
* `true`.
*/
batchActions: true,
/**
* @cfg {String/Ext.data.Model} model
* The name of the Model to tie to this Proxy. Can be either the string name of the Model,
* or a reference to the Model constructor. Required.
*/
model: undefined,
// @cmd-auto-dependency {aliasPrefix : "reader.", defaultPropertyName : "defaultReaderType"}
/**
* @cfg {Object/String/Ext.data.reader.Reader} reader
* The Ext.data.reader.Reader to use to decode the server's response or data read
* from client. This can either be a Reader instance, a config object or just a
* valid Reader type name (e.g. 'json', 'xml').
*/
reader: {
type: 'json'
},
// @cmd-auto-dependency {aliasPrefix : "writer.", defaultPropertyName : "defaultWriterType"}
/**
* @cfg {Object/String/Ext.data.writer.Writer} writer
* The Ext.data.writer.Writer to use to encode any request sent to the server or
* saved to client. This can either be a Writer instance, a config object or just
* a valid Writer type name (e.g. 'json', 'xml').
*/
writer: {
type: 'json'
}
},
/**
* @property {Boolean} isProxy
* `true` in this class to identify an object as an instantiated Proxy, or subclass thereof.
*/
isProxy: true,
/**
* @property {Boolean} [isSynchronous=false]
* Identifies the proxy as (a)synchronous.
*/
isSynchronous: false,
/**
* @event metachange
* Fires when this proxy's reader provides new metadata. Metadata usually consists
* of new field definitions, but can include any configuration data required by an
* application, and can be processed as needed in the event handler.
* This event is currently only fired for JsonReaders. Note that this event is also
* propagated by {@link Ext.data.Store}, which is typically where it would be handled.
* @param {Ext.data.proxy.Proxy} this
* @param {Object} meta The JSON metadata
*/
/**
* Creates the Proxy
* @param {Object} [config] Config object.
*/
constructor: function(config) {
// Will call initConfig
this.mixins.observable.constructor.call(this, config);
// We need to abort all pending operations when destroying
this.pendingOperations = {};
},
applyModel: function(model) {
return Ext.data.schema.Schema.lookupEntity(model);
},
updateModel: function(model) {
var reader;
if (model) {
reader = this.getReader();
if (reader && !reader.getModel()) {
reader.setModel(model);
}
}
},
applyReader: function(reader) {
// Synchronous proxies need to force keepRawData to allow Grid features
// like Summary and Grouping access rawData after the Reader processed records.
// It doesn't do much harm since synchronous proxies are Client side ones,
// which will keep their datasets in memory or local storage anyway.
if (this.isSynchronous) {
reader = reader || {};
reader.keepRawData = true;
}
return Ext.Factory.reader(reader);
},
updateReader: function(reader) {
var me = this,
model;
if (reader) {
model = me.getModel();
if (!model) {
model = reader.getModel();
if (model) {
me.setModel(model);
}
}
else {
reader.setModel(model);
}
if (reader.responseType != null) {
me.responseType = reader.responseType;
}
}
},
applyWriter: function(writer) {
var reader = this.getReader();
writer = Ext.Factory.writer(writer);
// XML Writers may have a record config to define the node name of each record tag.
// If not set, but the Reader has a record config, use the Reader's record config.
if (writer.getRecord && !writer.getRecord() && reader && reader.getRecord) {
reader = reader.getRecord();
if (reader) {
writer.setRecord(reader);
}
}
return writer;
},
abort: Ext.emptyFn,
/**
* @private
* Called each time the reader's onMetaChange is called so that the proxy can fire the
* metachange event
*/
onMetaChange: function(meta) {
this.fireEvent('metachange', this, meta);
},
/**
* Performs the given create operation.
* @param {Ext.data.operation.Operation} operation The Operation to perform
* @method
*/
create: Ext.emptyFn,
/**
* Performs the given read operation.
* @param {Ext.data.operation.Operation} operation The Operation to perform
* @method
*/
read: Ext.emptyFn,
/**
* Performs the given update operation.
* @param {Ext.data.operation.Operation} operation The Operation to perform
* @method
*/
update: Ext.emptyFn,
/**
* Performs the given destroy operation.
* @param {Ext.data.operation.Operation} operation The Operation to perform
* @method
*/
erase: Ext.emptyFn,
/**
* Performs a batch of {@link Ext.data.operation.Operation Operations}, in the order specified
* by {@link #batchOrder}. Used internally by {@link Ext.data.Store}'s
* {@link Ext.data.Store#sync sync} method. Example usage:
*
* myProxy.batch({
* create : [myModel1, myModel2],
* update : [myModel3],
* destroy: [myModel4, myModel5]
* });
*
* Where the myModel* above are {@link Ext.data.Model Model} instances - in this case 1 and 2
* are new instances and have not been saved before, 3 has been saved previously but needs to be
* updated, and 4 and 5 have already been saved but should now be destroyed.
*
* Note that the previous version of this method took 2 arguments (operations and listeners).
* While this is still supported for now, the current signature is now a single `options`
* argument that can contain both operations and listeners, in addition to other options.
* The multi-argument signature will likely be deprecated in a future release.
*
* @param {Object} options Object containing one or more properties supported by the batch
* method:
*
* @param {Object} options.operations Object containing the Model instances to act upon, keyed
* by action name
*
* @param {Object} [options.listeners] Event listeners object passed straight through to the
* Batch - see {@link Ext.data.Batch} for details
*
* @param {Ext.data.Batch/Object} [options.batch] A {@link Ext.data.Batch} object (or batch
* config to apply to the created batch). If unspecified a default batch will be auto-created.
*
* @param {Function} [options.callback] The function to be called upon completion of processing
* the batch. The callback is called regardless of success or failure and is passed the
* following parameters:
* @param {Ext.data.Batch} options.callback.batch The {@link Ext.data.Batch batch} that was
* processed, containing all operations in their current state after processing
* @param {Object} options.callback.options The options argument that was originally passed
* into batch
*
* @param {Function} [options.success] The function to be called upon successful completion
* of the batch. The success function is called only if no exceptions were reported in any
* operations. If one or more exceptions occurred then the `failure` function will be called
* instead. The success function is called with the following parameters:
* @param {Ext.data.Batch} options.success.batch The {@link Ext.data.Batch batch} that was
* processed, containing all operations in their current state after processing
* @param {Object} options.success.options The options argument that was originally passed into
* batch
*
* @param {Function} [options.failure] The function to be called upon unsuccessful completion
* of the batch. The failure function is called when one or more operations returns an
* exception during processing (even if some operations were also successful). In this case you
* can check the batch's {@link Ext.data.Batch#exceptions exceptions} array to see exactly
* which operations had exceptions. The failure function is called with the following
* parameters:
* @param {Ext.data.Batch} options.failure.batch The {@link Ext.data.Batch batch} that was
* processed, containing all operations in their current state after processing
* @param {Object} options.failure.options The options argument that was originally passed into
* batch
*
* @param {Object} [options.scope] The scope in which to execute any callbacks (i.e. the `this`
* object inside the callback, success and/or failure functions). Defaults to the proxy.
*
* @param {Object} [listeners] (deprecated) If `options` is the `operations`, this
* parameter is the listeners. Instead of passing these two arguments, the proper form
* is to pass them as:
*
* batch({
* operations: ...
* listeners: ...
* });
*
* @return {Ext.data.Batch} The newly created Batch
*/
batch: function(options, listeners) {
var me = this,
useBatch = me.getBatchActions(),
batch, records, actions, aLen, action, a, r, rLen, record;
if (options.operations === undefined) {
// the old-style (operations, listeners) signature was called
// so convert to the single options argument syntax
options = {
operations: options,
listeners: listeners
};
}
if (options.batch) {
if (Ext.isDefined(options.batch.runOperation)) {
batch = Ext.applyIf(options.batch, {
proxy: me,
listeners: {}
});
}
}
else {
options.batch = {
proxy: me,
listeners: options.listeners || {}
};
}
if (!batch) {
batch = new Ext.data.Batch(options.batch);
}
// Use single so that the listener gets removed upon completion.
batch.on('complete', Ext.bind(me.onBatchComplete, me, [options], 0), null, {
single: true,
priority: 1000
});
batch.$destroyOwner = options.$destroyOwner;
actions = me.getBatchOrder().split(',');
aLen = actions.length;
for (a = 0; a < aLen; a++) {
action = actions[a];
records = options.operations[action];
if (records) {
if (useBatch) {
batch.add(me.createOperation(action, {
records: records,
// Relay any additional params through to the Operation (and Request).
params: options.params
}));
}
else {
rLen = records.length;
for (r = 0; r < rLen; r++) {
record = records[r];
batch.add(me.createOperation(action, {
records: [record],
// Relay any additional params through to the Operation (and Request).
params: options.params
}));
}
}
}
}
batch.start();
return batch;
},
/**
* @private
* The internal callback that the proxy uses to call any specified user callbacks after
* completion of a batch
*/
onBatchComplete: function(batchOptions, batch) {
var scope = batchOptions.scope || this;
if (batch.hasException()) {
if (Ext.isFunction(batchOptions.failure)) {
Ext.callback(batchOptions.failure, scope, [batch, batchOptions]);
}
}
else if (Ext.isFunction(batchOptions.success)) {
Ext.callback(batchOptions.success, scope, [batch, batchOptions]);
}
if (Ext.isFunction(batchOptions.callback)) {
Ext.callback(batchOptions.callback, scope, [batch, batchOptions]);
}
// In certain cases when the batch was created by a ProxyStore we need to
// defer destruction until the store can process the batch results.
// The store will then destroy the batch.
if (!batch.$destroyOwner) {
batch.destroy();
}
},
createOperation: function(action, config) {
var operation = Ext.createByAlias('data.operation.' + action, config);
operation.setProxy(this);
this.pendingOperations[operation._internalId] = operation;
return operation;
},
completeOperation: function(operation) {
delete this.pendingOperations[operation._internalId];
},
clone: function() {
return new this.self(this.getInitialConfig());
},
destroy: function() {
var ops = this.pendingOperations,
opId, op;
for (opId in ops) {
op = ops[opId];
if (op && op.isRunning()) {
op.abort();
}
op.destroy();
}
this.pendingOperations = null;
this.callParent();
}
});