PageRenderTime 29ms CodeModel.GetById 14ms app.highlight 11ms RepoModel.GetById 1ms app.codeStats 0ms

/ext-4.1.0_b3/src/data/Operation.js

https://bitbucket.org/srogerf/javascript
JavaScript | 354 lines | 124 code | 42 blank | 188 comment | 22 complexity | d43dfe82b132bffe12af51ad52a05de4 MD5 | raw file
  1/**
  2 * @author Ed Spencer
  3 *
  4 * Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}. Operation objects are
  5 * used to enable communication between Stores and Proxies. Application developers should rarely need to interact with
  6 * Operation objects directly.
  7 *
  8 * Several Operations can be batched together in a {@link Ext.data.Batch batch}.
  9 */
 10Ext.define('Ext.data.Operation', {
 11    /**
 12     * @cfg {Boolean} synchronous
 13     * True if this Operation is to be executed synchronously. This property is inspected by a
 14     * {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in parallel or not.
 15     */
 16    synchronous: true,
 17
 18    /**
 19     * @cfg {String} action
 20     * The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'.
 21     */
 22    action: undefined,
 23
 24    /**
 25     * @cfg {Ext.util.Filter[]} filters
 26     * Optional array of filter objects. Only applies to 'read' actions.
 27     */
 28    filters: undefined,
 29
 30    /**
 31     * @cfg {Ext.util.Sorter[]} sorters
 32     * Optional array of sorter objects. Only applies to 'read' actions.
 33     */
 34    sorters: undefined,
 35
 36    /**
 37     * @cfg {Ext.util.Grouper[]} groupers
 38     * Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
 39     */
 40    groupers: undefined,
 41
 42    /**
 43     * @cfg {Number} start
 44     * The start index (offset), used in paging when running a 'read' action.
 45     */
 46    start: undefined,
 47
 48    /**
 49     * @cfg {Number} limit
 50     * The number of records to load. Used on 'read' actions when paging is being used.
 51     */
 52    limit: undefined,
 53
 54    /**
 55     * @cfg {Ext.data.Batch} batch
 56     * The batch that this Operation is a part of.
 57     */
 58    batch: undefined,
 59
 60    /**
 61     * @cfg {Function} callback
 62     * Function to execute when operation completed.
 63     * @cfg {Ext.data.Model[]} callback.records Array of records.
 64     * @cfg {Ext.data.Operation} callback.operation The Operation itself.
 65     * @cfg {Boolean} callback.success True when operation completed successfully.
 66     */
 67    callback: undefined,
 68
 69    /**
 70     * @cfg {Object} scope
 71     * Scope for the {@link #callback} function.
 72     */
 73    scope: undefined,
 74
 75    /**
 76     * @property {Boolean} started
 77     * The start status of this Operation. Use {@link #isStarted}.
 78     * @readonly
 79     * @private
 80     */
 81    started: false,
 82
 83    /**
 84     * @property {Boolean} running
 85     * The run status of this Operation. Use {@link #isRunning}.
 86     * @readonly
 87     * @private
 88     */
 89    running: false,
 90
 91    /**
 92     * @property {Boolean} complete
 93     * The completion status of this Operation. Use {@link #isComplete}.
 94     * @readonly
 95     * @private
 96     */
 97    complete: false,
 98
 99    /**
100     * @property {Boolean} success
101     * Whether the Operation was successful or not. This starts as undefined and is set to true
102     * or false by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
103     * {@link #wasSuccessful} to query success status.
104     * @readonly
105     * @private
106     */
107    success: undefined,
108
109    /**
110     * @property {Boolean} exception
111     * The exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
112     * @readonly
113     * @private
114     */
115    exception: false,
116
117    /**
118     * @property {String/Object} error
119     * The error object passed when {@link #setException} was called. This could be any object or primitive.
120     * @private
121     */
122    error: undefined,
123
124    /**
125     * @property {RegExp} actionCommitRecordsRe
126     * The RegExp used to categorize actions that require record commits.
127     */
128    actionCommitRecordsRe: /^(?:create|update)$/i,
129
130    /**
131     * @property {RegExp} actionSkipSyncRe
132     * The RegExp used to categorize actions that skip local record synchronization. This defaults
133     * to match 'destroy'.
134     */
135    actionSkipSyncRe: /^destroy$/i,
136
137    /**
138     * Creates new Operation object.
139     * @param {Object} config (optional) Config object.
140     */
141    constructor: function(config) {
142        Ext.apply(this, config || {});
143    },
144
145    /**
146     * This method is called to commit data to this instance's records given the records in
147     * the server response. This is followed by calling {@link Ext.data.Model#commit} on all
148     * those records (for 'create' and 'update' actions).
149     *
150     * If this {@link #action} is 'destroy', any server records are ignored and the
151     * {@link Ext.data.Model#commit} method is not called.
152     *
153     * @param {Ext.data.Model[]} serverRecords An array of {@link Ext.data.Model} objects returned by
154     * the server.
155     * @markdown
156     */
157    commitRecords: function (serverRecords) {
158        var me = this,
159            mc, index, clientRecords, serverRec, clientRec;
160
161        if (!me.actionSkipSyncRe.test(me.action)) {
162            clientRecords = me.records;
163
164            if (clientRecords && clientRecords.length) {
165                if(clientRecords.length > 1) {
166                    // if this operation has multiple records, client records need to be matched up with server records
167                    // so that any data returned from the server can be updated in the client records.
168                    mc = new Ext.util.MixedCollection();
169                    mc.addAll(serverRecords);
170
171                    for (index = clientRecords.length; index--; ) {
172                        clientRec = clientRecords[index];
173                        serverRec = mc.findBy(function(record) {
174                            var clientRecordId = clientRec.getId();
175                            if(clientRecordId && record.getId() === clientRecordId) {
176                                return true;
177                            }
178                            // if the server record cannot be found by id, find by internalId.
179                            // this allows client records that did not previously exist on the server
180                            // to be updated with the correct server id and data.
181                            return record.internalId === clientRec.internalId;
182                        });
183
184                        // replace client record data with server record data
185                        me.updateClientRecord(clientRec, serverRec);
186                    }
187                } else {
188                    // operation only has one record, so just match the first client record up with the first server record
189                    clientRec = clientRecords[0];
190                    serverRec = serverRecords[0];
191                    // if the client record is not a phantom, make sure the ids match before replacing the client data with server data.
192                    if(serverRec && (clientRec.phantom || clientRec.getId() === serverRec.getId())) {
193                        me.updateClientRecord(clientRec, serverRec);
194                    }
195                }
196
197                if (me.actionCommitRecordsRe.test(me.action)) {
198                    for (index = clientRecords.length; index--; ) {
199                        clientRecords[index].commit();
200                    }
201                }
202            }
203        }
204    },
205
206    /**
207     * Replaces the data in a client record with the data from a server record. If either record is undefined, does nothing.
208     * Since non-persistent fields will have default values in the server record, this method only replaces data for persistent
209     * fields to avoid overwriting the client record's data with default values from the server record.
210     * @private
211     * @param {Ext.data.Model} [clientRecord]
212     * @param {Ext.data.Model} [serverRecord]
213     */
214    updateClientRecord: function(clientRecord, serverRecord) {
215        if (clientRecord && serverRecord) {
216            clientRecord.beginEdit();
217
218            var fields = clientRecord.fields.items,
219                fLen   = fields.length,
220                field, f;
221
222            for (f = 0; f < fLen; f++) {
223                field = fields[f];
224
225                if (field.persist) {
226                    clientRecord.set(field.name, serverRecord.get(field.name));
227                }
228            }
229            if(clientRecord.phantom) {
230                clientRecord.setId(serverRecord.getId());
231            }
232            clientRecord.endEdit(true);
233        }
234    },
235
236    /**
237     * Marks the Operation as started.
238     */
239    setStarted: function() {
240        this.started = true;
241        this.running = true;
242    },
243
244    /**
245     * Marks the Operation as completed.
246     */
247    setCompleted: function() {
248        this.complete = true;
249        this.running  = false;
250    },
251
252    /**
253     * Marks the Operation as successful.
254     */
255    setSuccessful: function() {
256        this.success = true;
257    },
258
259    /**
260     * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
261     * @param {String/Object} error (optional) error string/object
262     */
263    setException: function(error) {
264        this.exception = true;
265        this.success = false;
266        this.running = false;
267        this.error = error;
268    },
269
270    /**
271     * Returns true if this Operation encountered an exception (see also {@link #getError})
272     * @return {Boolean} True if there was an exception
273     */
274    hasException: function() {
275        return this.exception === true;
276    },
277
278    /**
279     * Returns the error string or object that was set using {@link #setException}
280     * @return {String/Object} The error object
281     */
282    getError: function() {
283        return this.error;
284    },
285
286    /**
287     * 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).
288     * 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.
289     * @return {Ext.data.Model[]}
290     */
291    getRecords: function() {
292        var resultSet = this.getResultSet();
293        return this.records || (resultSet ? resultSet.records : null);
294    },
295
296    /**
297     * Returns the ResultSet object (if set by the Proxy). This object will contain the {@link Ext.data.Model model}
298     * instances as well as meta data such as number of instances fetched, number available etc
299     * @return {Ext.data.ResultSet} The ResultSet object
300     */
301    getResultSet: function() {
302        return this.resultSet;
303    },
304
305    /**
306     * Returns true if the Operation has been started. Note that the Operation may have started AND completed, see
307     * {@link #isRunning} to test if the Operation is currently running.
308     * @return {Boolean} True if the Operation has started
309     */
310    isStarted: function() {
311        return this.started === true;
312    },
313
314    /**
315     * Returns true if the Operation has been started but has not yet completed.
316     * @return {Boolean} True if the Operation is currently running
317     */
318    isRunning: function() {
319        return this.running === true;
320    },
321
322    /**
323     * Returns true if the Operation has been completed
324     * @return {Boolean} True if the Operation is complete
325     */
326    isComplete: function() {
327        return this.complete === true;
328    },
329
330    /**
331     * Returns true if the Operation has completed and was successful
332     * @return {Boolean} True if successful
333     */
334    wasSuccessful: function() {
335        return this.isComplete() && this.success === true;
336    },
337
338    /**
339     * @private
340     * Associates this Operation with a Batch
341     * @param {Ext.data.Batch} batch The batch
342     */
343    setBatch: function(batch) {
344        this.batch = batch;
345    },
346
347    /**
348     * Checks whether this operation should cause writing to occur.
349     * @return {Boolean} Whether the operation should cause a write to occur.
350     */
351    allowWrite: function() {
352        return this.action != 'read';
353    }
354});