/ext-4.1.0_b3/src/data/Operation.js
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});