/**
* This class is used to send requests to the server using {@link Ext.direct.Manager Ext Direct}.
* When a request is made, the transport mechanism is handed off to the appropriate
* {@link Ext.direct.RemotingProvider Provider} to complete the call.
*
* # Specifying the functions
*
* This proxy expects Direct remoting method to be passed in order to be able to complete requests,
* one Direct function per CRUD method. This is done via {@link #api} configuration:
*
* api: {
* read: 'MyApp.readRecords',
* create: 'MyApp.createRecords',
* update: 'MyApp.updateRecords',
* destroy: 'MyApp.destroyRecords'
* }
*
* You can also use a `prefix` config to avoid duplicating full namespaces for Direct functions:
*
* api: {
* prefix: 'MyApp',
* read: 'readRecords',
* create: 'createRecords',
* update: 'updateRecords',
* destroy: 'destroyRecords'
* }
*
* The preferred way is to specify function names to allow late resolution, however you can
* pass function references instead if desired:
*
* api: {
* read: MyApp.readRecords,
* create: MyApp.createRecords,
* update: MyApp.updateRecords,
* destroy: MyApp.destroyRecords
* }
*
* This method of configuring API is not recommended because this way the Direct functions
* need to be created very early in the application lifecycle, long before
* {@link Ext.app.Application} instance is initialized.
*
* You can also use the {@link #directFn} configuration instead of {@link #api}. This will use
* the same Direct function for all types of requests.
*
* # Server API
*
* The server side methods are expected to conform to the following calling conventions:
*
* ## `read`
*
* Accept one argument which is either named arguments in an object (default), or an array
* of values depending on the {@link #paramsAsHash} configuration. Return an array of records
* or an object with format recognizable by the configured {@link Ext.data.reader.Reader}
* instance.
*
* Example {@link Ext.direct.RemotingProvider#cfg-actions Direct API declaration}:
*
* actions: {
* MyApp: [{
* name: 'readRecords',
* params: [],
* strict: false
* }]
* }
*
* Example function invocation:
*
* MyApp.readRecords(
* {
* start: 0,
* limit: 10
* },
* // Results are passed to the callback function
* function(records) {
* console.log(records);
* // Logs: [{ id: 'r0', text: 'foo' }, { id: 'r1', text: 'bar' }]
* }
* );
*
* ## `create`
*
* Accept one ordered argument which is either an object with data for the new record,
* or an array of objects for multiple records. Return an array of identifiers for actually
* created records. See {@link Ext.data.Model#clientIdProperty} for more information.
*
* Example {@link Ext.direct.RemotingProvider#cfg-actions Direct API declaration}:
*
* actions: [
* MyApp: [{
* name: 'createRecords',
* len: 1
* }]
* }
*
* Example function invocation:
*
* MyApp.createRecords(
* [
* { id: 0, text: 'foo' },
* { id: 1, text: 'bar' }
* ],
* // Results are passed to the callback function
* function(records) {
* console.log(records);
* // Logs: [{ clientId: 0, id: 'r0' }, { clientId: 1, id: 'r1' }]
* }
* );
*
* ## `update`
*
* Accept one ordered argument which is either an object with updated data and valid
* record identifier, or an array of objects for multiple records. Return an array of
* objects with updated record data.
*
* Example {@link Ext.direct.RemotingProvider#cfg-actions Direct API declaration}:
*
* actions: [
* MyApp: [{
* name: 'updateRecords',
* len: 1
* }]
* }
*
* Example function invocation:
*
* MyApp.updateRecords(
* [
* { id: 'r0', text: 'blerg' },
* { id: 'r1', text: 'throbbe' }
* ],
* // Results are passed to the callback function
* function(records) {
* console.log(records);
* // Logs: [{ id: 'r0', text: 'blerg' }, { id: 'r1', text: 'throbbe }]
* }
* );
*
* ## `destroy`
*
* Accept one ordered argument which is an array of record identifiers to be deleted.
* Return an object with at least one {@link Ext.data.reader.Json#successProperty}
* property set to `true` or `false`, with more optional properties recognizable by configured
* {@link Ext.data.reader.Reader} instance.
*
* Example {@link Ext.direct.RemotingProvider#cfg-actions Direct API declaration}:
*
* actions: [
* MyApp: [{
* name: 'destroyRecords',
* len: 1
* }]
* }
*
* Example function invocation:
*
* MyApp.destroyRecords(
* [
* { id: 'r0' },
* { id: 'r1' }
* ],
* // Results are passed to the callback function
* function(result) {
* // Default successProperty is `success`
* if (!result.success) {
* // Handle the error
* }
* }
* );
*
* ## Read method parameters
*
* Direct proxy provides options to help configure which parameters will be sent to the server
* for Read operations. By setting the {@link #paramsAsHash} option to `true`, the proxy will
* send an object literal containing each of the passed parameters. This is the default. When
* {@link #paramsAsHash} is set to `false`, Proxy will pass the Read function an array of values
* instead of an object, with the order determined by {@link #paramOrder} value.
*
* Setting {@link #paramOrder} to any value other than `undefined` will automatically reset
* {@link #paramsAsHash} to `false`.
*
* # Example Usage
*
* Ext.define('User', {
* extend: 'Ext.data.Model',
* fields: ['firstName', 'lastName']
* });
*
* Ext.define('Users', {
* extend: 'Ext.data.Store',
* model: 'User',
* proxy: {
* type: 'direct',
* directFn: 'MyApp.getUsers',
* // Tells the proxy to pass `start` and `limit` as two by-position arguments:
* paramOrder: 'start,limit'
* }
* });
*
* var store = new Users();
* store.load();
*/
Ext.define('Ext.data.proxy.Direct', {
/* Begin Definitions */
extend: 'Ext.data.proxy.Server',
alternateClassName: 'Ext.data.DirectProxy',
alias: 'proxy.direct',
requires: ['Ext.direct.Manager'],
/* End Definitions */
/**
* @cfg url
* @hide
*/
config: {
/**
* @cfg {String/String[]} paramOrder
* A list of params to be passed to server side Read function. Specify the params
* in the order in which they must be executed on the server-side as either (1)
* an Array of String values, or (2) a String of params delimited by either
* whitespace, comma, or pipe. For example, any of the following would be
* acceptable:
*
* paramOrder: ['param1','param2','param3']
* paramOrder: 'param1 param2 param3'
* paramOrder: 'param1,param2,param3'
* paramOrder: 'param1|param2|param'
*/
paramOrder: undefined,
/**
* @cfg {Boolean} paramsAsHash
* Send Read function parameters as a collection of named arguments. Providing a
* {@link #paramOrder} nullifies this configuration.
*/
paramsAsHash: true,
/**
* @cfg {Function/String} directFn
* Function to call when executing a request. `directFn` is a simple alternative
* to defining the api configuration parameter for Stores which will not
* implement a full CRUD api. The `directFn` may also be a string reference to
* the fully qualified name of the function, for example:
* `'MyApp.company.GetProfile'`. This can be useful when using dynamic loading.
* The string will be resolved before calling the function for the first time.
*/
directFn: undefined,
/**
* @cfg {Object} api
* The same as {@link Ext.data.proxy.Server#api}, however instead of providing
* urls you should provide a Direct function name for each CRUD method.
*
* Instead of providing fully qualified names for each function, you can use
* `prefix` property to provide a common prefix for all functions:
*
* api: {
* prefix: 'MyApp',
* read: 'readRecords',
* create: 'createRecords',
* update: 'updateRecords',
* destroy: 'destroyRecords'
* }
*
* This way function names will be resolved to `'MyApp.readRecords'`,
* `'MyApp.createRecords'`, etc. Note that using `prefix` and fully qualified
* function names is **not** supported, and prefix will be used for every
* function name when configured.
*
* See also {@link #directFn}.
*/
api: undefined,
/**
* @cfg {Object/Array} metadata
* Optional set of fixed parameters to send with every Proxy request, similar to
* {@link #extraParams} but available with all CRUD requests. Also unlike
* {@link #extraParams}, metadata is not mixed with the ordinary data but sent
* separately in the data packet.
* You may need to update your server side Ext Direct stack to use this feature.
*/
metadata: undefined
},
/**
* @private
*/
paramOrderRe: /[\s,|]/,
constructor: function(config) {
this.callParent([config]);
this.canceledOperations = {};
},
applyParamOrder: function(paramOrder) {
if (Ext.isString(paramOrder)) {
paramOrder = paramOrder.split(this.paramOrderRe);
}
return paramOrder;
},
updateApi: function() {
this.methodsResolved = false;
},
updateDirectFn: function() {
this.methodsResolved = false;
},
resolveMethods: function() {
var me = this,
fn = me.getDirectFn(),
api = me.getApi(),
method;
if (fn) {
me.setDirectFn(method = Ext.direct.Manager.parseMethod(fn));
if (!Ext.isFunction(method)) {
Ext.raise('Cannot resolve directFn ' + fn);
}
}
if (api) {
api = Ext.direct.Manager.resolveApi(api, me);
me.setApi(api);
}
me.methodsResolved = true;
},
doRequest: function(operation) {
var me = this,
writer, request, action, params, args, api, fn;
if (!me.methodsResolved) {
me.resolveMethods();
}
request = me.buildRequest(operation);
action = request.getAction();
api = me.getApi();
if (api) {
fn = api[action];
}
fn = fn || me.getDirectFn();
//<debug>
if (!fn || !fn.directCfg) {
Ext.raise({
msg: 'No Ext Direct function specified for Direct proxy "' + action + '" operation',
proxy: me
});
}
// This might lead to exceptions so bail out early
if (!me.paramOrder && fn.directCfg.method.len > 1) {
Ext.raise({
msg: 'Incorrect parameters for Direct proxy "' + action + '" operation',
proxy: me
});
}
//</debug>
writer = me.getWriter();
if (writer && operation.allowWrite()) {
request = writer.write(request);
}
// The weird construct below is due to historical way of handling extraParams;
// they were mixed in with request data in ServerProxy.buildRequest() and were
// inseparable after that point. This does not work well with CUD operations
// so instead of using potentially poisoned request params we took the raw
// JSON data as Direct function argument payload (but only for CUD!). A side
// effect of that was that the request metadata (extraParams) was only available
// for read operations.
// We keep this craziness for backwards compatibility.
if (action === 'read') {
params = request.getParams();
}
else {
params = request.getJsonData();
}
args = fn.directCfg.method.getArgs({
params: params,
allowSingle: writer.getAllowSingle(),
paramOrder: me.getParamOrder(),
paramsAsHash: me.getParamsAsHash(),
paramsAsArray: true,
metadata: me.getMetadata(),
callback: me.createRequestCallback(request, operation),
scope: me
});
request.setConfig({
args: args,
directFn: fn
});
fn.apply(window, args);
// Store expects us to return something to indicate that the request
// is pending; not doing so will make a buffered Store repeat the
// requests over and over.
return request;
},
/**
* Aborts a running request by operation.
*
* @param {Ext.data.operation.Operation} operation The operation to abort. This parameter
* is mandatory.
*/
abort: function(operation) {
var id;
// Assume this can be called with request instead of operation, a la Ajax proxy
if (operation && operation.isDataRequest) {
operation = operation.getOperation();
}
// Check definedness again, the above could have returned null
if (operation && operation.isOperation) {
id = operation.id;
}
// We cannot abort a running request but we can ignore the data when it comes back.
if (id != null) {
this.canceledOperations[id] = true;
}
},
/**
* @method applyEncoding
* @inheritdoc
*/
applyEncoding: Ext.identityFn,
createRequestCallback: function(request, operation) {
var me = this;
return function(data, event) {
if (!me.canceledOperations[operation.id]) {
me.processResponse(event.status, operation, request, event);
}
delete me.canceledOperations[operation.id];
};
},
/**
* @method extractResponseData
* @inheritdoc
*/
extractResponseData: function(response) {
return Ext.isDefined(response.result) ? response.result : response.data;
},
/**
* @method setException
* @inheritdoc
*/
setException: function(operation, response) {
operation.setException(response.message);
},
/**
* @method buildUrl
* @inheritdoc
*/
buildUrl: function() {
return '';
}
});