    /** * 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());        }    },     /**     * @private     * 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;    }});