/** * Represents a read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}. * Operation objects are used to enable communication between Stores and Proxies. * Application developers should rarely need to interact with Operation objects directly. * * Several Operations can be batched together in a {@link Ext.data.Batch batch}. */Ext.define('Ext.data.operation.Operation', { alternateClassName: 'Ext.data.Operation', isOperation: true, config: { /** * @cfg {Boolean} synchronous * True if this Operation is to be executed synchronously. This property is inspected by a * {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in parallel or not. */ synchronous: false, /** * @cfg {String} url * The url for this operation. Typically this will be provided by a proxy and not configured here. */ url: '', /** * @cfg {Object} params * Parameters to pass along with the request when performing the operation. */ params: undefined, /** * @cfg {Function} callback * Function to execute when operation completed. * @cfg {Ext.data.Model[]} callback.records Array of records. * @cfg {Ext.data.operation.Operation} callback.operation The Operation itself. * @cfg {Boolean} callback.success True when operation completed successfully. */ callback: undefined, /** * @cfg {Object} scope * Scope for the {@link #callback} function. */ scope: undefined, /** * @cfg {Ext.data.ResultSet} resultSet * The ResultSet for this operation. * @accessor */ resultSet: null, /** * @private * @cfg {Object} response * The response for this operation. */ response: null, /** * @cfg {Ext.data.Request} request * The request for this operation. */ request: null, /** * @cfg {Ext.data.Model[]} records * The records associated with this operation. If this is a `read` operation, this will be * `null` until data is returned from the {@link Ext.data.proxy.Proxy}. */ records: null, /** * @cfg {Object} id * The id of the operation. */ id: undefined, /** * @cfg {Ext.data.proxy.Proxy} proxy * The proxy for this operation */ proxy: null, /** * @cfg {Ext.data.Batch} * The batch for this operation, if applicable */ batch: null, /** * @cfg {Function} recordCreator * Passed to the reader, see {@link Ext.data.reader.Reader#read} * @private */ recordCreator: null, //We use this because in a lot of cases the developer can indirectly pass // a callback/scope and that will get pushed on to the operation. As such, // create our own hook for the callback that will fire first /** * @cfg {Function} internalCallback * A callback to run before the {@link #callback}. * @private */ internalCallback: null, /** * @cfg {Object} internalScope * Scope to run the {@link #internalCallback} * @private */ internalScope: null }, /** * @property {Number} order * This number is used by `{@link Ext.data.Batch#sort}` to order operations. Order of * operations of the same type is determined by foreign-key dependencies. The array of * operations is sorted by ascending (increasing) values of `order`. * @private * @readonly * @since 5.0.0 */ order: 0, /** * @property {Number} foreignKeyDirection * This number is used by `{@link Ext.data.Batch#sort}` to order operations of the * same type. This value is multipled by the "entity rank" (which is determined by * foreign-key dependencies) to invert the direction of the sort based on the type of * operation. Only `Ext.data.operation.Destroy` overrides this value. * @private * @readonly * @since 5.0.0 */ foreignKeyDirection: 1, /** * @property {Boolean} started * The start status of this Operation. Use {@link #isStarted}. * @readonly * @private */ started: false, /** * @property {Boolean} running * The run status of this Operation. Use {@link #isRunning}. * @readonly * @private */ running: false, /** * @property {Boolean} complete * The completion status of this Operation. Use {@link #isComplete}. * @readonly * @private */ complete: false, /** * @property {Boolean} success * Whether the Operation was successful or not. This starts as undefined and is set to true * or false by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use * {@link #wasSuccessful} to query success status. * @readonly * @private */ success: undefined, /** * @property {Boolean} exception * The exception status of this Operation. Use {@link #hasException} and see {@link #getError}. * @readonly * @private */ exception: false, /** * @property {String/Object} error * The error object passed when {@link #setException} was called. This could be any object or primitive. * @private */ error: undefined, idPrefix: 'ext-operation-', /** * Creates new Operation object. * @param {Object} config (optional) Config object. */ constructor: function(config) { // This ugliness is here to prevent an issue when specifying scope // as an object literal. The object will be pulled in as part of // the merge() during initConfig which will change the object // reference. As such, we attempt to fudge it here until we come // up with a better solution for describing how specific config // objects should behave during init() time. var scope = config && config.scope; this.initConfig(config); if (config) { config.scope = scope; } if (scope) { this.setScope(scope); this.initialConfig.scope = scope; } // We need an internal id to track operations in Proxy this._internalId = Ext.id(this, this.idPrefix); }, getAction: function() { return this.action; }, /** * @private * Executes the operation on the configured {@link #proxy}. * @return {Ext.data.Request} The request object */ execute: function() { var me = this, request; delete me.error; delete me.success; me.complete = me.exception = false; me.setStarted(); me.request = request = me.doExecute(); if (request) { request.setOperation(me); } return request; }, doExecute: Ext.emptyFn, /** * Aborts the processing of this operation on the {@link #proxy}. * This is only valid for proxies that make asynchronous requests. */ abort: function() { var me = this, request = me.request; if (me.running && request) { me.getProxy().abort(request); me.request = null; } }, process: function(resultSet, request, response, autoComplete) { var me = this; autoComplete = autoComplete !== false; me.setResponse(response); me.setResultSet(resultSet); if (resultSet.getSuccess()) { me.doProcess(resultSet, request, response); me.setSuccessful(autoComplete); } else if (autoComplete) { me.setException(resultSet.getMessage()); } }, // This private object is used to save on memory allocation. This instance is used to // apply server record updates as part of a record commit performed by calling the // set() method on the record. See doProcess. _commitSetOptions: { convert: true, commit: true }, /** * Process records in the operation after the response is successful and the result * set is parsed correctly. The base class implementation of this method is used by * "create" and "update" operations to allow the server response to update the client * side records. * * @param {Ext.data.ResultSet} resultSet The result set * @param {Ext.data.Request} request The request * @param {Object} response The response * @protected */ doProcess: function(resultSet, request, response) { var me = this, commitSetOptions = me._commitSetOptions, clientRecords = me.getRecords(), clientLen = clientRecords.length, clientIdProperty = clientRecords[0].clientIdProperty, serverRecords = resultSet.getRecords(), // a data array, not records yet serverLen = serverRecords ? serverRecords.length : 0, clientMap, serverRecord, clientRecord, i; if (serverLen && clientIdProperty) { // Linear pass over clientRecords to map them by their idProperty clientMap = Ext.Array.toValueMap(clientRecords, 'id'); // Linear pass over serverRecords to match them by clientIdProperty to the // corresponding clientRecord (if one exists). for (i = 0; i < serverLen; ++i) { serverRecord = serverRecords[i]; clientRecord = clientMap[serverRecord[clientIdProperty]]; if (clientRecord) { // Remove this one so we don't commit() on it next delete clientMap[clientRecord.id]; // Remove the clientIdProperty value since we don't want to store it delete serverRecord[clientIdProperty]; clientRecord.set(serverRecord, commitSetOptions); // set & commit } //<debug> else { Ext.log.warn('Ignoring server record: ' + Ext.encode(serverRecord)); } //</debug> } // Linear pass over any remaining client records. for (i in clientMap) { clientMap[i].commit(); } } else { // Either no serverRecords or no clientIdProperty, so index correspondence is // all we have to go on. If there is no serverRecord at a given index we just // commit() the record. for (i = 0; i < clientLen; ++i) { clientRecord = clientRecords[i]; if (serverLen === 0 || !(serverRecord = serverRecords[i])) { // once i > serverLen then serverRecords[i] will be undefined... clientRecord.commit(); } else { clientRecord.set(serverRecord, commitSetOptions); } } } }, /** * Marks the Operation as started. */ setStarted: function() { this.started = this.running = true; }, /** * Marks the Operation as completed. */ setCompleted: function() { var me = this, proxy = me.getProxy(); me.complete = true; me.running = false; me.triggerCallbacks(); if (proxy) { proxy.completeOperation(me); } }, /** * Marks the Operation as successful. * @param {Boolean} [complete] `true` to also mark this operation * as being complete See {@link #setCompleted}. */ setSuccessful: function(complete) { this.success = true; if (complete) { this.setCompleted(); } }, /** * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object. * @param {String/Object} error (optional) error string/object */ setException: function(error) { var me = this; me.exception = true; me.success = me.running = false; me.error = error; me.setCompleted(); }, triggerCallbacks: function() { var me = this, callback = me.getInternalCallback(); // Call internal callback first (usually the Store's onProxyLoad method) if (callback) { callback.call(me.getInternalScope() || me, me); me.setInternalCallback(null); me.setInternalScope(null); } // Call the user's callback as passed to Store's read/write if (callback = me.getCallback()) { // Maintain the public API for callback callback.call(me.getScope() || me, me.getRecords(), me, me.wasSuccessful()); me.setCallback(null); me.setScope(null); } }, /** * Returns true if this Operation encountered an exception (see also {@link #getError}) * @return {Boolean} True if there was an exception */ hasException: function() { return this.exception; }, /** * Returns the error string or object that was set using {@link #setException} * @return {String/Object} The error object */ getError: function() { return this.error; }, /** * Returns the {@link Ext.data.Model record}s associated with this operation. For read * operations the records as set by the {@link Ext.data.proxy.Proxy Proxy} will be * returned (returns `null` if the proxy has not yet set the records). * * For create, update, and destroy operations the operation's initially configured * records will be returned, although the proxy may modify these records' data at some * point after the operation is initialized. * * @return {Ext.data.Model[]} */ getRecords: function() { var resultSet; return this._records || ((resultSet = this.getResultSet()) ? resultSet.getRecords() : null); }, /** * Returns true if the Operation has been started. Note that the Operation may have started AND completed, see * {@link #isRunning} to test if the Operation is currently running. * @return {Boolean} True if the Operation has started */ isStarted: function() { return this.started; }, /** * Returns true if the Operation has been started but has not yet completed. * @return {Boolean} True if the Operation is currently running */ isRunning: function() { return this.running; }, /** * Returns true if the Operation has been completed * @return {Boolean} True if the Operation is complete */ isComplete: function() { return this.complete; }, /** * Returns true if the Operation has completed and was successful * @return {Boolean} True if successful */ wasSuccessful: function() { return this.isComplete() && this.success === true; // success can be undefined }, /** * Checks whether this operation should cause writing to occur. * @return {Boolean} Whether the operation should cause a write to occur. */ allowWrite: function() { return true; }});