]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * A Model or Entity represents some object that your application manages. For example, one\r | |
3 | * might define a Model for Users, Products, Cars, or other real-world object that we want\r | |
4 | * to model in the system. Models are used by {@link Ext.data.Store stores}, which are in\r | |
5 | * turn used by many of the data-bound components in Ext.\r | |
6 | *\r | |
7 | * # Fields\r | |
8 | *\r | |
9 | * Models are defined as a set of fields and any arbitrary methods and properties relevant\r | |
10 | * to the model. For example:\r | |
11 | *\r | |
12 | * Ext.define('User', {\r | |
13 | * extend: 'Ext.data.Model',\r | |
14 | * fields: [\r | |
15 | * {name: 'name', type: 'string'},\r | |
16 | * {name: 'age', type: 'int', convert: null},\r | |
17 | * {name: 'phone', type: 'string'},\r | |
18 | * {name: 'alive', type: 'boolean', defaultValue: true, convert: null}\r | |
19 | * ],\r | |
20 | *\r | |
21 | * changeName: function() {\r | |
22 | * var oldName = this.get('name'),\r | |
23 | * newName = oldName + " The Barbarian";\r | |
24 | *\r | |
25 | * this.set('name', newName);\r | |
26 | * }\r | |
27 | * });\r | |
28 | *\r | |
29 | * Now we can create instances of our User model and call any model logic we defined:\r | |
30 | *\r | |
31 | * var user = Ext.create('User', {\r | |
32 | * id : 'ABCD12345',\r | |
33 | * name : 'Conan',\r | |
34 | * age : 24,\r | |
35 | * phone: '555-555-5555'\r | |
36 | * });\r | |
37 | *\r | |
38 | * user.changeName();\r | |
39 | * user.get('name'); //returns "Conan The Barbarian"\r | |
40 | *\r | |
41 | * By default, the built in field types such as number and boolean coerce string values\r | |
42 | * in the raw data by virtue of their {@link Ext.data.field.Field#method-convert} method.\r | |
43 | * When the server can be relied upon to send data in a format that does not need to be\r | |
44 | * converted, disabling this can improve performance. The {@link Ext.data.reader.Json Json}\r | |
45 | * and {@link Ext.data.reader.Array Array} readers are likely candidates for this\r | |
46 | * optimization. To disable field conversions you simply specify `null` for the field's\r | |
47 | * {@link Ext.data.field.Field#cfg-convert convert config}.\r | |
48 | *\r | |
49 | * ## The "id" Field and `idProperty`\r | |
50 | *\r | |
51 | * A Model definition always has an *identifying field* which should yield a unique key\r | |
52 | * for each instance. By default, a field named "id" will be created with a\r | |
53 | * {@link Ext.data.Field#mapping mapping} of "id". This happens because of the default\r | |
54 | * {@link #idProperty} provided in Model definitions.\r | |
55 | *\r | |
56 | * To alter which field is the identifying field, use the {@link #idProperty} config.\r | |
57 | *\r | |
58 | * # Validators\r | |
59 | *\r | |
60 | * Models have built-in support for field validators. Validators are added to models as in\r | |
61 | * the follow example:\r | |
62 | *\r | |
63 | * Ext.define('User', {\r | |
64 | * extend: 'Ext.data.Model',\r | |
65 | * fields: [\r | |
66 | * { name: 'name', type: 'string' },\r | |
67 | * { name: 'age', type: 'int' },\r | |
68 | * { name: 'phone', type: 'string' },\r | |
69 | * { name: 'gender', type: 'string' },\r | |
70 | * { name: 'username', type: 'string' },\r | |
71 | * { name: 'alive', type: 'boolean', defaultValue: true }\r | |
72 | * ],\r | |
73 | *\r | |
74 | * validators: {\r | |
75 | * age: 'presence',\r | |
76 | * name: { type: 'length', min: 2 },\r | |
77 | * gender: { type: 'inclusion', list: ['Male', 'Female'] },\r | |
78 | * username: [\r | |
79 | * { type: 'exclusion', list: ['Admin', 'Operator'] },\r | |
80 | * { type: 'format', matcher: /([a-z]+)[0-9]{2,3}/i }\r | |
81 | * ]\r | |
82 | * }\r | |
83 | * });\r | |
84 | *\r | |
85 | * The derived type of `Ext.data.field.Field` can also provide validation. If `validators`\r | |
86 | * need to be duplicated on multiple fields, instead consider creating a custom field type.\r | |
87 | *\r | |
88 | * ## Validation\r | |
89 | *\r | |
90 | * The results of the validators can be retrieved via the "associated" validation record:\r | |
91 | *\r | |
92 | * var instance = Ext.create('User', {\r | |
93 | * name: 'Ed',\r | |
94 | * gender: 'Male',\r | |
95 | * username: 'edspencer'\r | |
96 | * });\r | |
97 | *\r | |
98 | * var validation = instance.getValidation();\r | |
99 | *\r | |
100 | * The returned object is an instance of `Ext.data.Validation` and has as its fields the\r | |
101 | * result of the field `validators`. The validation object is "dirty" if there are one or\r | |
102 | * more validation errors present.\r | |
103 | *\r | |
104 | * This record is also available when using data binding as a "pseudo-association" called\r | |
105 | * "validation". This pseudo-association can be hidden by an explicitly declared\r | |
106 | * association by the same name (for compatibility reasons), but doing so is not\r | |
107 | * recommended.\r | |
108 | *\r | |
109 | * The `{@link Ext.Component#modelValidation}` config can be used to enable automatic\r | |
110 | * binding from the "validation" of a record to the form fields that may be bound to its\r | |
111 | * values.\r | |
112 | *\r | |
113 | * # Associations\r | |
114 | *\r | |
115 | * Models often have associations with other Models. These associations can be defined by\r | |
116 | * fields (often called "foreign keys") or by other data such as a many-to-many (or "matrix").\r | |
117 | *\r | |
118 | * ## Foreign-Key Associations - One-to-Many\r | |
119 | *\r | |
120 | * The simplest way to define an association from one Model to another is to add a\r | |
121 | * {@link Ext.data.field.Field#cfg-reference reference config} to the appropriate field.\r | |
122 | *\r | |
123 | * Ext.define('Post', {\r | |
124 | * extend: 'Ext.data.Model',\r | |
125 | *\r | |
126 | * fields: [\r | |
127 | * { name: 'user_id', reference: 'User' }\r | |
128 | * ]\r | |
129 | * });\r | |
130 | *\r | |
131 | * Ext.define('Comment', {\r | |
132 | * extend: 'Ext.data.Model',\r | |
133 | *\r | |
134 | * fields: [\r | |
135 | * { name: 'user_id', reference: 'User' },\r | |
136 | * { name: 'post_id', reference: 'Post' }\r | |
137 | * ]\r | |
138 | * });\r | |
139 | *\r | |
140 | * Ext.define('User', {\r | |
141 | * extend: 'Ext.data.Model',\r | |
142 | *\r | |
143 | * fields: [\r | |
144 | * 'name'\r | |
145 | * ]\r | |
146 | * });\r | |
147 | *\r | |
148 | * The placement of `reference` on the appropriate fields tells the Model which field has\r | |
149 | * the foreign-key and the type of Model it identifies. That is, the value of these fields\r | |
150 | * is set to value of the `idProperty` field of the target Model.\r | |
151 | *\r | |
152 | * ### One-to-Many Without Foreign-Keys\r | |
153 | *\r | |
154 | * To define an association without a foreign-key field, you will need to use either the\r | |
155 | * `{@link #cfg-hasMany}` or `{@link #cfg-belongsTo}`.\r | |
156 | *\r | |
157 | * Ext.define('Post', {\r | |
158 | * extend: 'Ext.data.Model',\r | |
159 | *\r | |
160 | * belongsTo: 'User'\r | |
161 | * });\r | |
162 | *\r | |
163 | * Ext.define('Comment', {\r | |
164 | * extend: 'Ext.data.Model',\r | |
165 | *\r | |
166 | * belongsTo: [ 'Post', 'User' ]\r | |
167 | * });\r | |
168 | *\r | |
169 | * // User is as above\r | |
170 | *\r | |
171 | * These declarations have changed slightly from previous releases. In previous releases\r | |
172 | * both "sides" of an association had to declare their particular roles. This is now only\r | |
173 | * required if the defaults assumed for names are not satisfactory.\r | |
174 | *\r | |
175 | * ## Foreign-Key Associations - One-to-One\r | |
176 | *\r | |
177 | * A special case of one-to-many associations is the one-to-one case. This is defined as\r | |
178 | * a `{@link Ext.data.field.Field#reference unique reference}`.\r | |
179 | *\r | |
180 | * Ext.define('Address', {\r | |
181 | * extend: 'Ext.data.Model',\r | |
182 | *\r | |
183 | * fields: [\r | |
184 | * 'address',\r | |
185 | * 'city',\r | |
186 | * 'state'\r | |
187 | * ]\r | |
188 | * });\r | |
189 | *\r | |
190 | * Ext.define('User', {\r | |
191 | * extend: 'Ext.data.Model',\r | |
192 | *\r | |
193 | * fields: [{\r | |
194 | * name: 'addressId',\r | |
195 | * reference: 'Address',\r | |
196 | * unique: true\r | |
197 | * }]\r | |
198 | * });\r | |
199 | *\r | |
200 | * ## Many-to-Many\r | |
201 | *\r | |
202 | * The classic use case for many-to-many is a User and Group. Users can belong to many\r | |
203 | * Groups and Groups can contain many Users. This association is declared using the\r | |
204 | * `{@link #cfg-manyToMany}` config like so:\r | |
205 | *\r | |
206 | *\r | |
207 | * Ext.define('User', {\r | |
208 | * extend: 'Ext.data.Model',\r | |
209 | *\r | |
210 | * fields: [\r | |
211 | * 'name'\r | |
212 | * ],\r | |
213 | *\r | |
214 | * manyToMany: 'Group'\r | |
215 | * });\r | |
216 | *\r | |
217 | * Ext.define('Group', {\r | |
218 | * extend: 'Ext.data.Model',\r | |
219 | *\r | |
220 | * fields: [\r | |
221 | * 'name'\r | |
222 | * ],\r | |
223 | *\r | |
224 | * manyToMany: 'User'\r | |
225 | * });\r | |
226 | *\r | |
227 | * As with other associations, only one "side" needs to be declared.\r | |
228 | *\r | |
229 | * To manage the relationship between a `manyToMany` relationship, a {@link Ext.data.Session}\r | |
230 | * must be used.\r | |
231 | *\r | |
232 | * # Using a Proxy\r | |
233 | *\r | |
234 | * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or\r | |
235 | * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which\r | |
236 | * can be set directly on the Model:\r | |
237 | *\r | |
238 | * Ext.define('User', {\r | |
239 | * extend: 'Ext.data.Model',\r | |
240 | * fields: ['id', 'name', 'email'],\r | |
241 | *\r | |
242 | * proxy: {\r | |
243 | * type: 'rest',\r | |
244 | * url : '/users'\r | |
245 | * }\r | |
246 | * });\r | |
247 | *\r | |
248 | * Here we've set up a {@link Ext.data.proxy.Rest Rest Proxy}, which knows how to load and save data to and from a\r | |
249 | * RESTful backend. Let's see how this works:\r | |
250 | *\r | |
251 | * var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});\r | |
252 | *\r | |
253 | * user.save(); //POST /users\r | |
254 | *\r | |
255 | * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's\r | |
256 | * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,\r | |
257 | * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We\r | |
258 | * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.\r | |
259 | *\r | |
260 | * Loading data via the Proxy is accomplished with the static `load` method:\r | |
261 | *\r | |
262 | * //Uses the configured RestProxy to make a GET request to /users/123\r | |
263 | * User.load(123, {\r | |
264 | * success: function(user) {\r | |
265 | * console.log(user.getId()); //logs 123\r | |
266 | * }\r | |
267 | * });\r | |
268 | *\r | |
269 | * Models can also be updated and destroyed easily:\r | |
270 | *\r | |
271 | * //the user Model we loaded in the last snippet:\r | |
272 | * user.set('name', 'Edward Spencer');\r | |
273 | *\r | |
274 | * //tells the Proxy to save the Model. In this case it will perform a PUT request to /users/123 as this Model already has an id\r | |
275 | * user.save({\r | |
276 | * success: function() {\r | |
277 | * console.log('The User was updated');\r | |
278 | * }\r | |
279 | * });\r | |
280 | *\r | |
281 | * //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123\r | |
282 | * user.erase({\r | |
283 | * success: function() {\r | |
284 | * console.log('The User was destroyed!');\r | |
285 | * }\r | |
286 | * });\r | |
287 | * \r | |
288 | * # HTTP Parameter names when using a {@link Ext.data.proxy.Ajax Ajax proxy}\r | |
289 | *\r | |
290 | * By default, the model ID is specified in an HTTP parameter named `id`. To change the\r | |
291 | * name of this parameter use the Proxy's {@link Ext.data.proxy.Ajax#idParam idParam}\r | |
292 | * configuration.\r | |
293 | *\r | |
294 | * Parameters for other commonly passed values such as\r | |
295 | * {@link Ext.data.proxy.Ajax#pageParam page number} or\r | |
296 | * {@link Ext.data.proxy.Ajax#startParam start row} may also be configured.\r | |
297 | *\r | |
298 | * # Usage in Stores\r | |
299 | *\r | |
300 | * It is very common to want to load a set of Model instances to be displayed and manipulated in the UI. We do this by\r | |
301 | * creating a {@link Ext.data.Store Store}:\r | |
302 | *\r | |
303 | * var store = Ext.create('Ext.data.Store', {\r | |
304 | * model: 'User'\r | |
305 | * });\r | |
306 | *\r | |
307 | * //uses the Proxy we set up on Model to load the Store data\r | |
308 | * store.load();\r | |
309 | *\r | |
310 | * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a\r | |
311 | * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link\r | |
312 | * Ext.data.Store Store docs} for more information on Stores.\r | |
313 | */\r | |
314 | Ext.define('Ext.data.Model', {\r | |
315 | alternateClassName: 'Ext.data.Record',\r | |
316 | \r | |
317 | requires: [\r | |
318 | 'Ext.data.ErrorCollection',\r | |
319 | 'Ext.data.operation.*',\r | |
320 | 'Ext.data.field.*',\r | |
321 | 'Ext.data.validator.Validator',\r | |
322 | 'Ext.data.schema.Schema',\r | |
323 | 'Ext.data.identifier.Generator',\r | |
324 | 'Ext.data.identifier.Sequential'\r | |
325 | ],\r | |
326 | uses: [\r | |
327 | 'Ext.data.Validation'\r | |
328 | ],\r | |
329 | \r | |
330 | /**\r | |
331 | * @property {Boolean} isEntity\r | |
332 | * The value `true` to identify this class and its subclasses.\r | |
333 | * @readonly\r | |
334 | */\r | |
335 | isEntity: true,\r | |
336 | \r | |
337 | /**\r | |
338 | * @property {Boolean} isModel\r | |
339 | * The value `true` to identify this class and its subclasses.\r | |
340 | * @readonly\r | |
341 | */\r | |
342 | isModel: true,\r | |
343 | \r | |
344 | // Record ids are more flexible.\r | |
345 | validIdRe: null,\r | |
346 | \r | |
347 | erasing: false,\r | |
348 | \r | |
349 | observableType: 'record',\r | |
350 | \r | |
351 | constructor: function (data, session) {\r | |
352 | var me = this,\r | |
353 | cls = me.self,\r | |
354 | identifier = cls.identifier,\r | |
355 | Model = Ext.data.Model,\r | |
356 | modelIdentifier = Model.identifier,\r | |
357 | idProperty = me.idField.name,\r | |
358 | array, id, initializeFn, internalId, len, i, fields;\r | |
359 | \r | |
360 | // Yes, this is here on purpose. See EXTJS-16494. The second\r | |
361 | // assignment seems to work around a strange JIT issue that prevents\r | |
362 | // this.data being assigned in random scenarios, even though the data\r | |
363 | // is passed into the constructor. The issue occurs on 4th gen iPads and\r | |
364 | // lower, possibly other older iOS devices.\r | |
365 | me.data = me.data = data || (data = {});\r | |
366 | me.session = session || null;\r | |
367 | me.internalId = internalId = modelIdentifier.generate();\r | |
368 | \r | |
369 | //<debug>\r | |
370 | var dataId = data[idProperty];\r | |
371 | if (session && !session.isSession) {\r | |
372 | Ext.raise('Bad Model constructor argument 2 - "session" is not a Session');\r | |
373 | }\r | |
374 | //</debug>\r | |
375 | \r | |
376 | if ((array = data) instanceof Array) {\r | |
377 | me.data = data = {};\r | |
378 | fields = me.getFields();\r | |
379 | len = Math.min(fields.length, array.length);\r | |
380 | for (i = 0; i < len; ++i) {\r | |
381 | data[fields[i].name] = array[i];\r | |
382 | }\r | |
383 | }\r | |
384 | \r | |
385 | if (!(initializeFn = cls.initializeFn)) {\r | |
386 | cls.initializeFn = initializeFn = Model.makeInitializeFn(cls);\r | |
387 | }\r | |
388 | if (!initializeFn.$nullFn) {\r | |
389 | cls.initializeFn(me);\r | |
390 | }\r | |
391 | \r | |
392 | // Must do this after running the initializeFn due to converters on idField\r | |
393 | if (!(me.id = id = data[idProperty]) && id !== 0) {\r | |
394 | //<debug>\r | |
395 | if (dataId) {\r | |
396 | Ext.raise('The model ID configured in data ("' + dataId + '") has been rejected by the ' + me.fieldsMap[idProperty].type + ' field converter for the ' + idProperty + ' field');\r | |
397 | }\r | |
398 | //</debug>\r | |
399 | if (session) {\r | |
400 | identifier = session.getIdentifier(cls);\r | |
401 | id = identifier.generate();\r | |
402 | } else if (modelIdentifier === identifier) {\r | |
403 | id = internalId;\r | |
404 | } else {\r | |
405 | id = identifier.generate();\r | |
406 | }\r | |
407 | \r | |
408 | data[idProperty] = me.id = id;\r | |
409 | me.phantom = true;\r | |
410 | }\r | |
411 | \r | |
412 | if (session) {\r | |
413 | session.add(me);\r | |
414 | }\r | |
415 | \r | |
416 | if (me.init && Ext.isFunction(me.init)) {\r | |
417 | me.init();\r | |
418 | }\r | |
419 | },\r | |
420 | \r | |
421 | /**\r | |
422 | * @property {String} entityName\r | |
423 | * The short name of this entity class. This name is derived from the `namespace` of\r | |
424 | * the associated `schema` and this class name. By default, a class is not given a\r | |
425 | * shortened name.\r | |
426 | *\r | |
427 | * All entities in a given `schema` must have a unique `entityName`.\r | |
428 | * \r | |
429 | * For more details see "Relative Naming" in {@link Ext.data.schema.Schema}.\r | |
430 | */\r | |
431 | \r | |
432 | /**\r | |
433 | * @property {Boolean} editing\r | |
434 | * Internal flag used to track whether or not the model instance is currently being edited.\r | |
435 | * @readonly\r | |
436 | */\r | |
437 | editing: false,\r | |
438 | \r | |
439 | /**\r | |
440 | * @property {Boolean} dirty\r | |
441 | * True if this record has been modified.\r | |
442 | * @readonly\r | |
443 | */\r | |
444 | dirty: false,\r | |
445 | \r | |
446 | /**\r | |
447 | * @property {Ext.data.Session} session\r | |
448 | * The {@link Ext.data.Session} for this record.\r | |
449 | * @readonly\r | |
450 | */\r | |
451 | session: null,\r | |
452 | \r | |
453 | /**\r | |
454 | * @property {Boolean} dropped\r | |
455 | * True if this record is pending delete on the server. This is set by the `drop`\r | |
456 | * method and transmitted to the server by the `save` method.\r | |
457 | * @readonly\r | |
458 | */\r | |
459 | dropped: false,\r | |
460 | \r | |
461 | /**\r | |
462 | * @property {Boolean} erased\r | |
463 | * True if this record has been erased on the server. This flag is set of the `erase`\r | |
464 | * method.\r | |
465 | * @readonly\r | |
466 | */\r | |
467 | erased: false,\r | |
468 | \r | |
469 | /**\r | |
470 | * @cfg {String} [clientIdProperty]\r | |
471 | * The name of the property a server will use to send back a client-generated id in a\r | |
472 | * `create` or `update` `{@link Ext.data.operation.Operation operation}`.\r | |
473 | *\r | |
474 | * If specified, this property cannot have the same name as any other field.\r | |
475 | *\r | |
476 | * For example:\r | |
477 | *\r | |
478 | * Ext.define('Person', {\r | |
479 | * idProperty: 'id', // this is the default value (for clarity)\r | |
480 | *\r | |
481 | * clientIdProperty: 'clientId',\r | |
482 | *\r | |
483 | * identifier: 'negative', // to generate -1, -2 etc on the client\r | |
484 | *\r | |
485 | * fields: [ 'name' ]\r | |
486 | * });\r | |
487 | *\r | |
488 | * var person = new Person({\r | |
489 | * // no id provided, so -1 is generated\r | |
490 | * name: 'Clark Kent'\r | |
491 | * });\r | |
492 | *\r | |
493 | * The server is given this data during the `create`:\r | |
494 | *\r | |
495 | * {\r | |
496 | * id: -1,\r | |
497 | * name: 'Clark Kent'\r | |
498 | * }\r | |
499 | *\r | |
500 | * The server allocates a real id and responds like so:\r | |
501 | *\r | |
502 | * {\r | |
503 | * id: 427,\r | |
504 | * clientId: -1\r | |
505 | * }\r | |
506 | *\r | |
507 | * This property is most useful when creating multiple entities in a single call to\r | |
508 | * the server in a `{@link Ext.data.operation.Create create operation}`. Alternatively,\r | |
509 | * the server could respond with records that correspond one-to-one to those sent in\r | |
510 | * the `operation`.\r | |
511 | *\r | |
512 | * For example the client could send a `create` with this data:\r | |
513 | *\r | |
514 | * [ { id: -1, name: 'Clark Kent' },\r | |
515 | * { id: -2, name: 'Peter Parker' },\r | |
516 | * { id: -3, name: 'Bruce Banner' } ]\r | |
517 | *\r | |
518 | * And the server could respond in the same order:\r | |
519 | *\r | |
520 | * [ { id: 427 }, // updates id = -1\r | |
521 | * { id: 428 }, // updates id = -2\r | |
522 | * { id: 429 } ] // updates id = -3\r | |
523 | *\r | |
524 | * Or using `clientIdProperty` the server could respond in arbitrary order:\r | |
525 | *\r | |
526 | * [ { id: 427, clientId: -3 },\r | |
527 | * { id: 428, clientId: -1 },\r | |
528 | * { id: 429, clientId: -2 } ]\r | |
529 | *\r | |
530 | * **IMPORTANT:** When upgrading from previous versions be aware that this property\r | |
531 | * used to perform the role of `{@link Ext.data.writer.Writer#clientIdProperty}` as\r | |
532 | * well as that described above. To continue send a client-generated id as other than\r | |
533 | * the `idProperty`, set `clientIdProperty` on the `writer`. A better solution, however,\r | |
534 | * is most likely a properly configured `identifier` as that would work better with\r | |
535 | * associations.\r | |
536 | */\r | |
537 | clientIdProperty: null,\r | |
538 | \r | |
539 | evented: false,\r | |
540 | \r | |
541 | /**\r | |
542 | * @property {Boolean} phantom\r | |
543 | * True when the record does not yet exist in a server-side database. Any record which\r | |
544 | * has a real database identity set as its `idProperty` is NOT a phantom -- it's real.\r | |
545 | */\r | |
546 | phantom: false,\r | |
547 | \r | |
548 | /**\r | |
549 | * @cfg {String} [idProperty='id']\r | |
550 | * The name of the field treated as this Model's unique id.\r | |
551 | *\r | |
552 | * If changing the idProperty in a subclass, the generated id field will replace the one\r | |
553 | * generated by the superclass, for example;\r | |
554 | *\r | |
555 | * Ext.define('Super', {\r | |
556 | * extend: 'Ext.data.Model',\r | |
557 | * fields: ['name']\r | |
558 | * });\r | |
559 | *\r | |
560 | * Ext.define('Sub', {\r | |
561 | * extend: 'Super',\r | |
562 | * idProperty: 'customId'\r | |
563 | * });\r | |
564 | *\r | |
565 | * var fields = Super.getFields();\r | |
566 | * // Has 2 fields, "name" & "id"\r | |
567 | * console.log(fields[0].name, fields[1].name, fields.length);\r | |
568 | *\r | |
569 | * fields = Sub.getFields();\r | |
570 | * // Has 2 fields, "name" & "customId", "id" is replaced\r | |
571 | * console.log(fields[0].name, fields[1].name, fields.length);\r | |
572 | *\r | |
573 | * The data values for this field must be unique or there will be id value collisions\r | |
574 | * in the {@link Ext.data.Store Store}.\r | |
575 | */\r | |
576 | idProperty: 'id',\r | |
577 | \r | |
578 | /**\r | |
579 | * @cfg {Object} manyToMany\r | |
580 | * A config object for a {@link Ext.data.schema.ManyToMany ManyToMany} association.\r | |
581 | * See the class description for {@link Ext.data.schema.ManyToMany ManyToMany} for\r | |
582 | * configuration examples.\r | |
583 | */\r | |
584 | manyToMany: null,\r | |
585 | \r | |
586 | /**\r | |
587 | * @cfg {String/Object} identifier\r | |
588 | * The id generator to use for this model. The `identifier` generates values for the\r | |
589 | * {@link #idProperty} when no value is given. Records with client-side generated\r | |
590 | * values for {@link #idProperty} are called {@link #phantom} records since they are\r | |
591 | * not yet known to the server.\r | |
592 | *\r | |
593 | * This can be overridden at the model level to provide a custom generator for a model.\r | |
594 | * The simplest form of this would be:\r | |
595 | *\r | |
596 | * Ext.define('MyApp.data.MyModel', {\r | |
597 | * extend: 'Ext.data.Model',\r | |
598 | * requires: ['Ext.data.identifier.Sequential'],\r | |
599 | * identifier: 'sequential',\r | |
600 | * ...\r | |
601 | * });\r | |
602 | *\r | |
603 | * The above would generate {@link Ext.data.identifier.Sequential sequential} id's such\r | |
604 | * as 1, 2, 3 etc..\r | |
605 | *\r | |
606 | * Another useful id generator is {@link Ext.data.identifier.Uuid}:\r | |
607 | *\r | |
608 | * Ext.define('MyApp.data.MyModel', {\r | |
609 | * extend: 'Ext.data.Model',\r | |
610 | * requires: ['Ext.data.identifier.Uuid'],\r | |
611 | * identifier: 'uuid',\r | |
612 | * ...\r | |
613 | * });\r | |
614 | *\r | |
615 | * An id generator can also be further configured:\r | |
616 | *\r | |
617 | * Ext.define('MyApp.data.MyModel', {\r | |
618 | * extend: 'Ext.data.Model',\r | |
619 | * identifier: {\r | |
620 | * type: 'sequential',\r | |
621 | * seed: 1000,\r | |
622 | * prefix: 'ID_'\r | |
623 | * }\r | |
624 | * });\r | |
625 | *\r | |
626 | * The above would generate id's such as ID_1000, ID_1001, ID_1002 etc..\r | |
627 | *\r | |
628 | * If multiple models share an id space, a single generator can be shared:\r | |
629 | *\r | |
630 | * Ext.define('MyApp.data.MyModelX', {\r | |
631 | * extend: 'Ext.data.Model',\r | |
632 | * identifier: {\r | |
633 | * type: 'sequential',\r | |
634 | * id: 'xy'\r | |
635 | * }\r | |
636 | * });\r | |
637 | *\r | |
638 | * Ext.define('MyApp.data.MyModelY', {\r | |
639 | * extend: 'Ext.data.Model',\r | |
640 | * identifier: {\r | |
641 | * type: 'sequential',\r | |
642 | * id: 'xy'\r | |
643 | * }\r | |
644 | * });\r | |
645 | *\r | |
646 | * For more complex, shared id generators, a custom generator is the best approach.\r | |
647 | * See {@link Ext.data.identifier.Generator} for details on creating custom id generators.\r | |
648 | */\r | |
649 | identifier: null,\r | |
650 | \r | |
651 | // Fields config and property\r | |
652 | // @cmd-auto-dependency {aliasPrefix: "data.field."}\r | |
653 | /**\r | |
654 | * @cfg {Object[]/String[]} fields\r | |
655 | * An Array of `Ext.data.field.Field` config objects, simply the field \r | |
656 | * {@link Ext.data.field.Field#name name}, or a mix of config objects and strings. \r | |
657 | * If just a name is given, the field type defaults to `auto`.\r | |
658 | * \r | |
659 | * In a {@link Ext.data.field.Field Field} config object you may pass the alias of \r | |
660 | * the `Ext.data.field.*` type using the `type` config option.\r | |
661 | * \r | |
662 | * // two fields are set:\r | |
663 | * // - an 'auto' field with a name of 'firstName'\r | |
664 | * // - and an Ext.data.field.Integer field with a name of 'age'\r | |
665 | * fields: ['firstName', {\r | |
666 | * type: 'int',\r | |
667 | * name: 'age'\r | |
668 | * }]\r | |
669 | * \r | |
670 | * Fields will automatically be created at read time for any for any keys in the \r | |
671 | * data passed to the Model's {@link #proxy proxy's} \r | |
672 | * {@link Ext.data.reader.Reader reader} whose name is not explicitly configured in \r | |
673 | * the `fields` config.\r | |
674 | * \r | |
675 | * Extending a Model class will inherit all the `fields` from the superclass / \r | |
676 | * ancestor classes.\r | |
677 | */\r | |
678 | /**\r | |
679 | * @property {Ext.data.field.Field[]} fields\r | |
680 | * An array fields defined for this Model (including fields defined in superclasses)\r | |
681 | * in ordinal order; that is in declaration order.\r | |
682 | * @private\r | |
683 | * @readonly\r | |
684 | */\r | |
685 | \r | |
686 | /**\r | |
687 | * @property {Object} fieldOrdinals\r | |
688 | * This property is indexed by field name and contains the ordinal of that field. The\r | |
689 | * ordinal often has meaning to servers and is derived based on the position in the\r | |
690 | * `fields` array.\r | |
691 | * \r | |
692 | * This can be used like so:\r | |
693 | * \r | |
694 | * Ext.define('MyApp.models.User', {\r | |
695 | * extend: 'Ext.data.Model',\r | |
696 | *\r | |
697 | * fields: [\r | |
698 | * { name: 'name' }\r | |
699 | * ]\r | |
700 | * });\r | |
701 | * \r | |
702 | * var nameOrdinal = MyApp.models.User.fieldOrdinals.name;\r | |
703 | * \r | |
704 | * // or, if you have an instance:\r | |
705 | *\r | |
706 | * var user = new MyApp.models.User();\r | |
707 | * var nameOrdinal = user.fieldOrdinals.name;\r | |
708 | *\r | |
709 | * @private\r | |
710 | * @readonly\r | |
711 | */\r | |
712 | \r | |
713 | /**\r | |
714 | * @property {Object} modified\r | |
715 | * A hash of field values which holds the initial values of fields before a set of edits\r | |
716 | * are {@link #commit committed}.\r | |
717 | */\r | |
718 | \r | |
719 | /**\r | |
720 | * @property {Object} previousValues\r | |
721 | * This object is similar to the `modified` object except it holds the data values as\r | |
722 | * they were prior to the most recent change.\r | |
723 | * @readonly\r | |
724 | * @private\r | |
725 | */\r | |
726 | previousValues: undefined, // Not "null" so getPrevious returns undefined first time\r | |
727 | \r | |
728 | // @cmd-auto-dependency { aliasPrefix : "proxy.", defaultPropertyName : "defaultProxyType"}\r | |
729 | /**\r | |
730 | * @cfg {String/Object/Ext.data.proxy.Proxy} proxy\r | |
731 | * The {@link Ext.data.proxy.Proxy proxy} to use for this class.\r | |
732 | */\r | |
733 | proxy: undefined,\r | |
734 | \r | |
735 | /**\r | |
736 | * @cfg {String/Object} [schema='default']\r | |
737 | * The name of the {@link Ext.data.schema.Schema schema} to which this entity and its\r | |
738 | * associations belong. For details on custom schemas see `Ext.data.schema.Schema`.\r | |
739 | */\r | |
740 | /**\r | |
741 | * @property {Ext.data.schema.Schema} schema\r | |
742 | * The `Ext.data.schema.Schema` to which this entity and its associations belong.\r | |
743 | * @readonly\r | |
744 | */\r | |
745 | schema: 'default',\r | |
746 | \r | |
747 | /**\r | |
748 | * @cfg {String} [versionProperty]\r | |
749 | * If specified, this is the name of the property that contains the entity "version".\r | |
750 | * The version property is used to manage a long-running transaction and allows the\r | |
751 | * detection of simultaneous modification.\r | |
752 | * \r | |
753 | * The way a version property is used is that the client receives the version as it\r | |
754 | * would any other entity property. When saving an entity, this property is always\r | |
755 | * included in the request and the server uses the value in a "conditional update".\r | |
756 | * If the current version of the entity on the server matches the version property\r | |
757 | * sent by the client, the update is allowed. Otherwise, the update fails.\r | |
758 | * \r | |
759 | * On successful update, both the client and server increment the version. This is\r | |
760 | * done on the server in the conditional update and on the client when it receives a\r | |
761 | * success on its update request.\r | |
762 | */\r | |
763 | versionProperty: null,\r | |
764 | \r | |
765 | /**\r | |
766 | * @property {Number} generation\r | |
767 | * This property is incremented on each modification of a record.\r | |
768 | * @readonly\r | |
769 | * @since 5.0.0\r | |
770 | */\r | |
771 | generation: 1,\r | |
772 | \r | |
773 | /**\r | |
774 | * @cfg {Object[]} validators\r | |
775 | * An array of {@link Ext.data.validator.Validator validators} for this model.\r | |
776 | */\r | |
777 | \r | |
778 | /**\r | |
779 | * @cfg {String} [validationSeparator=null]\r | |
780 | * If specified this property is used to concatenate multiple errors for each field\r | |
781 | * as reported by the `validators`.\r | |
782 | */\r | |
783 | validationSeparator: null,\r | |
784 | \r | |
785 | /**\r | |
786 | * @cfg {Boolean} [convertOnSet=true]\r | |
787 | * Set to `false` to prevent any converters from being called on fields specified in \r | |
788 | * a {@link Ext.data.Model#set set} operation.\r | |
789 | * \r | |
790 | * **Note:** Setting the config to `false` will only prevent the convert / calculate \r | |
791 | * call when the set `fieldName` param matches the field's `{@link #name}`. In the \r | |
792 | * following example the calls to set `salary` will not execute the convert method \r | |
793 | * on `set` while the calls to set `vested` will execute the convert method on the \r | |
794 | * initial read as well as on `set`.\r | |
795 | * \r | |
796 | * Example model definition:\r | |
797 | * \r | |
798 | * Ext.define('MyApp.model.Employee', {\r | |
799 | * extend: 'Ext.data.Model',\r | |
800 | * fields: ['yearsOfService', {\r | |
801 | * name: 'salary',\r | |
802 | * convert: function (val) {\r | |
803 | * var startingBonus = val * .1;\r | |
804 | * return val + startingBonus;\r | |
805 | * }\r | |
806 | * }, {\r | |
807 | * name: 'vested',\r | |
808 | * convert: function (val, record) {\r | |
809 | * return record.get('yearsOfService') >= 4;\r | |
810 | * },\r | |
811 | * depends: 'yearsOfService'\r | |
812 | * }],\r | |
813 | * convertOnSet: false\r | |
814 | * });\r | |
815 | * \r | |
816 | * var tina = Ext.create('MyApp.model.Employee', {\r | |
817 | * salary: 50000,\r | |
818 | * yearsOfService: 3\r | |
819 | * });\r | |
820 | * \r | |
821 | * console.log(tina.get('salary')); // logs 55000\r | |
822 | * console.log(tina.get('vested')); // logs false\r | |
823 | * \r | |
824 | * tina.set({\r | |
825 | * salary: 60000,\r | |
826 | * yearsOfService: 4\r | |
827 | * });\r | |
828 | * console.log(tina.get('salary')); // logs 60000\r | |
829 | * console.log(tina.get('vested')); // logs true\r | |
830 | */\r | |
831 | convertOnSet: true,\r | |
832 | \r | |
833 | // Associations configs and properties\r | |
834 | /**\r | |
835 | * @cfg {Object[]} associations\r | |
836 | * An array of {@link Ext.data.schema.Association associations} for this model.\r | |
837 | */\r | |
838 | /**\r | |
839 | * @cfg {String/Object/String[]/Object[]} hasMany\r | |
840 | * One or more {@link #hasMany HasMany associations} for this model.\r | |
841 | */\r | |
842 | /**\r | |
843 | * @cfg {String/Object/String[]/Object[]} belongsTo\r | |
844 | * One or more {@link #belongsTo BelongsTo associations} for this model.\r | |
845 | */\r | |
846 | \r | |
847 | /**\r | |
848 | * Begins an edit. While in edit mode, no events (e.g.. the `update` event) are\r | |
849 | * relayed to the containing store. When an edit has begun, it must be followed by\r | |
850 | * either `endEdit` or `cancelEdit`.\r | |
851 | */\r | |
852 | beginEdit: function () {\r | |
853 | var me = this,\r | |
854 | modified = me.modified,\r | |
855 | previousValues = me.previousValues;\r | |
856 | \r | |
857 | if (!me.editing) {\r | |
858 | me.editing = true;\r | |
859 | \r | |
860 | me.editMemento = {\r | |
861 | dirty: me.dirty,\r | |
862 | data: Ext.apply({}, me.data),\r | |
863 | generation: me.generation,\r | |
864 | modified: modified && Ext.apply({}, modified),\r | |
865 | previousValues: previousValues && Ext.apply({}, previousValues)\r | |
866 | };\r | |
867 | }\r | |
868 | },\r | |
869 | \r | |
870 | /**\r | |
871 | * Cancels all changes made in the current edit operation.\r | |
872 | */\r | |
873 | cancelEdit: function () {\r | |
874 | var me = this,\r | |
875 | editMemento = me.editMemento;\r | |
876 | \r | |
877 | if (editMemento) {\r | |
878 | me.editing = false;\r | |
879 | \r | |
880 | // reset the modified state, nothing changed since the edit began\r | |
881 | Ext.apply(me, editMemento);\r | |
882 | me.editMemento = null;\r | |
883 | }\r | |
884 | },\r | |
885 | \r | |
886 | /**\r | |
887 | * Ends an edit. If any data was modified, the containing store is notified\r | |
888 | * (ie, the store's `update` event will fire).\r | |
889 | * @param {Boolean} [silent] True to not notify any stores of the change.\r | |
890 | * @param {String[]} [modifiedFieldNames] Array of field names changed during edit.\r | |
891 | */\r | |
892 | endEdit: function (silent, modifiedFieldNames) {\r | |
893 | var me = this,\r | |
894 | editMemento = me.editMemento;\r | |
895 | \r | |
896 | if (editMemento) {\r | |
897 | me.editing = false;\r | |
898 | me.editMemento = null;\r | |
899 | \r | |
900 | // Since these reflect changes we never notified others about, the real set\r | |
901 | // of "previousValues" is what we captured in the memento:\r | |
902 | me.previousValues = editMemento.previousValues;\r | |
903 | \r | |
904 | if (!silent) {\r | |
905 | if (!modifiedFieldNames) {\r | |
906 | modifiedFieldNames = me.getModifiedFieldNames(editMemento.data);\r | |
907 | }\r | |
908 | \r | |
909 | if (me.dirty || (modifiedFieldNames && modifiedFieldNames.length)) {\r | |
910 | me.callJoined('afterEdit', [modifiedFieldNames]);\r | |
911 | }\r | |
912 | }\r | |
913 | }\r | |
914 | },\r | |
915 | \r | |
916 | getField: function (name) {\r | |
917 | return this.self.getField(name);\r | |
918 | },\r | |
919 | \r | |
920 | /**\r | |
921 | * Get the fields array for this model.\r | |
922 | * @return {Ext.data.field.Field[]} The fields array\r | |
923 | */\r | |
924 | getFields: function () {\r | |
925 | return this.self.getFields();\r | |
926 | },\r | |
927 | \r | |
928 | getFieldsMap: function () {\r | |
929 | return this.fieldsMap;\r | |
930 | },\r | |
931 | \r | |
932 | /**\r | |
933 | * Get the idProperty for this model.\r | |
934 | * @return {String} The idProperty\r | |
935 | */\r | |
936 | getIdProperty: function () {\r | |
937 | return this.idProperty;\r | |
938 | },\r | |
939 | \r | |
940 | /**\r | |
941 | * Returns the unique ID allocated to this model instance as defined by `idProperty`.\r | |
942 | * @return {Number/String} The id\r | |
943 | */\r | |
944 | getId: function () {\r | |
945 | return this.id;\r | |
946 | },\r | |
947 | \r | |
948 | /**\r | |
949 | * Return a unique observable ID. Model is not observable but tree nodes (`Ext.data.NodeInterface`) are, so\r | |
950 | * they must be globally unique within the {@link #observableType}.\r | |
951 | * @protected\r | |
952 | */\r | |
953 | getObservableId: function() {\r | |
954 | return this.internalId;\r | |
955 | },\r | |
956 | \r | |
957 | /**\r | |
958 | * Sets the model instance's id field to the given id.\r | |
959 | * @param {Number/String} id The new id.\r | |
960 | * @param {Object} [options] See {@link #set}.\r | |
961 | */\r | |
962 | setId: function (id, options) {\r | |
963 | this.set(this.idProperty, id, options);\r | |
964 | },\r | |
965 | \r | |
966 | /**\r | |
967 | * This method returns the value of a field given its name prior to its most recent\r | |
968 | * change.\r | |
969 | * @param {String} fieldName The field's {@link Ext.data.field.Field#name name}.\r | |
970 | * @return {Object} The value of the given field prior to its current value. `undefined`\r | |
971 | * if there is no previous value;\r | |
972 | */\r | |
973 | getPrevious: function (fieldName) {\r | |
974 | var previousValues = this.previousValues;\r | |
975 | return previousValues && previousValues[fieldName];\r | |
976 | },\r | |
977 | \r | |
978 | /**\r | |
979 | * Returns true if the passed field name has been `{@link #modified}` since the load or last commit.\r | |
980 | * @param {String} fieldName The field's {@link Ext.data.field.Field#name name}.\r | |
981 | * @return {Boolean}\r | |
982 | */\r | |
983 | isModified: function (fieldName) {\r | |
984 | var modified = this.modified;\r | |
985 | return !!(modified && modified.hasOwnProperty(fieldName));\r | |
986 | },\r | |
987 | \r | |
988 | /**\r | |
989 | * Returns the original value of a modified field. If there is no modified value,\r | |
990 | * `undefined` will be return. Also see {@link #isModified}.\r | |
991 | * @param {String} fieldName The name of the field for which to return the original value.\r | |
992 | * @return {Object} modified\r | |
993 | */\r | |
994 | getModified: function (fieldName) {\r | |
995 | var out;\r | |
996 | if (this.isModified(fieldName)) {\r | |
997 | out = this.modified[fieldName];\r | |
998 | }\r | |
999 | return out;\r | |
1000 | },\r | |
1001 | \r | |
1002 | /**\r | |
1003 | * Returns the value of the given field.\r | |
1004 | * @param {String} fieldName The name of the field.\r | |
1005 | * @return {Object} The value of the specified field.\r | |
1006 | */\r | |
1007 | get: function (fieldName) {\r | |
1008 | return this.data[fieldName];\r | |
1009 | },\r | |
1010 | \r | |
1011 | // This object is used whenever the set() method is called and given a string as the\r | |
1012 | // first argument. This approach saves memory (and GC costs) since we could be called\r | |
1013 | // a lot.\r | |
1014 | _singleProp: {},\r | |
1015 | \r | |
1016 | _rejectOptions: {\r | |
1017 | convert: false,\r | |
1018 | silent: true\r | |
1019 | },\r | |
1020 | \r | |
1021 | /**\r | |
1022 | * Sets the given field to the given value. For example:\r | |
1023 | * \r | |
1024 | * record.set('name', 'value');\r | |
1025 | * \r | |
1026 | * This method can also be passed an object containing multiple values to set at once.\r | |
1027 | * For example:\r | |
1028 | * \r | |
1029 | * record.set({\r | |
1030 | * name: 'value',\r | |
1031 | * age: 42\r | |
1032 | * });\r | |
1033 | * \r | |
1034 | * The following store events are fired when the modified record belongs to a store:\r | |
1035 | *\r | |
1036 | * - {@link Ext.data.Store#event-beginupdate beginupdate}\r | |
1037 | * - {@link Ext.data.Store#event-update update}\r | |
1038 | * - {@link Ext.data.Store#event-endupdate endupdate}\r | |
1039 | * \r | |
1040 | * @param {String/Object} fieldName The field to set, or an object containing key/value \r | |
1041 | * pairs.\r | |
1042 | * @param {Object} newValue The value for the field (if `fieldName` is a string).\r | |
1043 | * @param {Object} [options] Options for governing this update.\r | |
1044 | * @param {Boolean} [options.convert=true] Set to `false` to prevent any converters from \r | |
1045 | * being called during the set operation. This may be useful when setting a large bunch of \r | |
1046 | * raw values.\r | |
1047 | * @param {Boolean} [options.dirty=true] Pass `false` if the field values are to be\r | |
1048 | * understood as non-dirty (fresh from the server). When `true`, this change will be\r | |
1049 | * reflected in the `modified` collection.\r | |
1050 | * @param {Boolean} [options.commit=false] Pass `true` to call the {@link #commit} method \r | |
1051 | * after setting fields. If this option is passed, the usual after change processing will \r | |
1052 | * be bypassed. {@link #commit Commit} will be called even if there are no field changes.\r | |
1053 | * @param {Boolean} [options.silent=false] Pass `true` to suppress notification of any\r | |
1054 | * changes made by this call. Use with caution.\r | |
1055 | * @return {String[]} The array of modified field names or null if nothing was modified.\r | |
1056 | */\r | |
1057 | set: function (fieldName, newValue, options) {\r | |
1058 | var me = this,\r | |
1059 | cls = me.self,\r | |
1060 | data = me.data,\r | |
1061 | modified = me.modified,\r | |
1062 | prevVals = me.previousValues,\r | |
1063 | session = me.session,\r | |
1064 | single = Ext.isString(fieldName),\r | |
1065 | opt = (single ? options : newValue),\r | |
1066 | convertOnSet = opt ? opt.convert !== false : me.convertOnSet,\r | |
1067 | fieldsMap = me.fieldsMap,\r | |
1068 | silent = opt && opt.silent,\r | |
1069 | commit = opt && opt.commit,\r | |
1070 | updateRefs = !(opt && opt.refs === false) && session,\r | |
1071 | // Don't need to do dirty processing with commit, since we'll always\r | |
1072 | // end up with nothing modified and not dirty\r | |
1073 | dirty = !(opt && opt.dirty === false && !commit),\r | |
1074 | modifiedFieldNames = null,\r | |
1075 | currentValue, field, idChanged, key, name, oldId, comparator, dep, dependents,\r | |
1076 | i, dirtyRank=0, numFields, newId, rankedFields, reference, value, values;\r | |
1077 | \r | |
1078 | if (single) {\r | |
1079 | values = me._singleProp;\r | |
1080 | values[fieldName] = newValue;\r | |
1081 | } else {\r | |
1082 | values = fieldName;\r | |
1083 | }\r | |
1084 | \r | |
1085 | if (!(rankedFields = cls.rankedFields)) {\r | |
1086 | // On the first edit of a record of this type we need to ensure we have the\r | |
1087 | // topo-sort done:\r | |
1088 | rankedFields = cls.rankFields();\r | |
1089 | }\r | |
1090 | numFields = rankedFields.length;\r | |
1091 | \r | |
1092 | do {\r | |
1093 | for (name in values) {\r | |
1094 | value = values[name];\r | |
1095 | currentValue = data[name];\r | |
1096 | comparator = me;\r | |
1097 | field = fieldsMap[name];\r | |
1098 | \r | |
1099 | if (field) {\r | |
1100 | if (convertOnSet && field.convert) {\r | |
1101 | value = field.convert(value, me);\r | |
1102 | }\r | |
1103 | comparator = field;\r | |
1104 | reference = field.reference;\r | |
1105 | } else {\r | |
1106 | reference = null;\r | |
1107 | }\r | |
1108 | \r | |
1109 | if (comparator.isEqual(currentValue, value)) {\r | |
1110 | continue; // new value is the same, so no change...\r | |
1111 | }\r | |
1112 | \r | |
1113 | data[name] = value;\r | |
1114 | (modifiedFieldNames || (modifiedFieldNames = [])).push(name);\r | |
1115 | (prevVals || (me.previousValues = prevVals = {}))[name] = currentValue;\r | |
1116 | \r | |
1117 | // We need the cls to be present because it means the association class is loaded,\r | |
1118 | // otherwise it could be pending.\r | |
1119 | if (reference && reference.cls) {\r | |
1120 | if (updateRefs) {\r | |
1121 | session.updateReference(me, field, value, currentValue);\r | |
1122 | }\r | |
1123 | reference.onValueChange(me, session, value, currentValue);\r | |
1124 | }\r | |
1125 | \r | |
1126 | i = (dependents = field && field.dependents) && dependents.length;\r | |
1127 | while (i-- > 0) {\r | |
1128 | // we use the field instance to hold the dirty bit to avoid any\r | |
1129 | // extra allocations... we'll clear this before we depart. We do\r | |
1130 | // this so we can perform the fewest recalculations possible as\r | |
1131 | // each dependent field only needs to be recalculated once.\r | |
1132 | (dep = dependents[i]).dirty = true;\r | |
1133 | dirtyRank = dirtyRank ? Math.min(dirtyRank, dep.rank) : dep.rank;\r | |
1134 | }\r | |
1135 | \r | |
1136 | if (!field || field.persist) {\r | |
1137 | if (modified && modified.hasOwnProperty(name)) {\r | |
1138 | if (!dirty || comparator.isEqual(modified[name], value)) {\r | |
1139 | // The original value in me.modified equals the new value, so\r | |
1140 | // the field is no longer modified:\r | |
1141 | delete modified[name];\r | |
1142 | me.dirty = -1; // fix me.dirty later (still truthy)\r | |
1143 | }\r | |
1144 | } else if (dirty) {\r | |
1145 | if (!modified) {\r | |
1146 | me.modified = modified = {}; // create only when needed\r | |
1147 | }\r | |
1148 | me.dirty = true;\r | |
1149 | modified[name] = currentValue;\r | |
1150 | }\r | |
1151 | }\r | |
1152 | \r | |
1153 | if (name === me.idField.name) {\r | |
1154 | idChanged = true;\r | |
1155 | oldId = currentValue;\r | |
1156 | newId = value;\r | |
1157 | }\r | |
1158 | }\r | |
1159 | \r | |
1160 | if (!dirtyRank) {\r | |
1161 | // Unless there are dependent fields to process we can break now. This is\r | |
1162 | // what will happen for all code pre-dating the depends or simply not\r | |
1163 | // using it, so it will add very little overhead when not used.\r | |
1164 | break;\r | |
1165 | }\r | |
1166 | \r | |
1167 | // dirtyRank has the minimum rank (a 1-based value) of any dependent field\r | |
1168 | // that needs recalculating due to changes above. The way we go about this\r | |
1169 | // is to use our helper object for processing single argument invocations\r | |
1170 | // to process just this one field. This is because the act of setting it\r | |
1171 | // may cause another field to be invalidated, so while we cannot know at\r | |
1172 | // this moment all the fields we need to recalculate, we know that only\r | |
1173 | // those following this field in rankedFields can possibly be among them.\r | |
1174 | \r | |
1175 | field = rankedFields[dirtyRank - 1]; // dirtyRank is 1-based\r | |
1176 | field.dirty = false; // clear just this field's dirty state\r | |
1177 | \r | |
1178 | if (single) {\r | |
1179 | delete values[fieldName]; // cleanup last value\r | |
1180 | } else {\r | |
1181 | values = me._singleProp; // switch over\r | |
1182 | single = true;\r | |
1183 | }\r | |
1184 | \r | |
1185 | fieldName = field.name;\r | |
1186 | values[fieldName] = data[fieldName];\r | |
1187 | // We are now processing a dependent field, so we want to force a\r | |
1188 | // convert to occur because it's the only way it will get a value\r | |
1189 | convertOnSet = true;\r | |
1190 | \r | |
1191 | // Since dirtyRank is 1-based and refers to the field we need to handle\r | |
1192 | // on this pass, we can treat it like an index for a minute and look at\r | |
1193 | // the next field on towards the end to find the index of the next dirty\r | |
1194 | // field.\r | |
1195 | for ( ; dirtyRank < numFields; ++dirtyRank) {\r | |
1196 | if (rankedFields[dirtyRank].dirty) {\r | |
1197 | break;\r | |
1198 | }\r | |
1199 | }\r | |
1200 | \r | |
1201 | if (dirtyRank < numFields) {\r | |
1202 | // We found a field after this one marked as dirty so make the index\r | |
1203 | // a proper 1-based rank:\r | |
1204 | ++dirtyRank;\r | |
1205 | } else {\r | |
1206 | // We did not find any more dirty fields after this one, so clear the\r | |
1207 | // dirtyRank and we will perhaps fall out after the next update\r | |
1208 | dirtyRank = 0;\r | |
1209 | }\r | |
1210 | } while (1);\r | |
1211 | \r | |
1212 | if (me.dirty < 0) {\r | |
1213 | // We might have removed the last modified field, so check to see if there\r | |
1214 | // are any modified fields remaining and correct me.dirty:\r | |
1215 | me.dirty = false;\r | |
1216 | for (key in modified) {\r | |
1217 | if (modified.hasOwnProperty(key)) {\r | |
1218 | me.dirty = true;\r | |
1219 | break;\r | |
1220 | }\r | |
1221 | }\r | |
1222 | }\r | |
1223 | \r | |
1224 | if (single) {\r | |
1225 | // cleanup our reused object for next time... important to do this before\r | |
1226 | // we fire any events or call anyone else (like afterEdit)!\r | |
1227 | delete values[fieldName];\r | |
1228 | }\r | |
1229 | \r | |
1230 | ++me.generation;\r | |
1231 | \r | |
1232 | if (idChanged) {\r | |
1233 | me.id = newId;\r | |
1234 | me.callJoined('onIdChanged', [oldId, newId]);\r | |
1235 | }\r | |
1236 | \r | |
1237 | if (commit) {\r | |
1238 | me.commit(silent, modifiedFieldNames);\r | |
1239 | } else if (!silent && !me.editing && modifiedFieldNames) {\r | |
1240 | me.callJoined('afterEdit', [modifiedFieldNames]);\r | |
1241 | }\r | |
1242 | \r | |
1243 | return modifiedFieldNames;\r | |
1244 | },\r | |
1245 | \r | |
1246 | /**\r | |
1247 | * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects\r | |
1248 | * all changes made to the model instance since either creation, or the last commit operation. Modified fields are\r | |
1249 | * reverted to their original values.\r | |
1250 | *\r | |
1251 | * Developers should subscribe to the {@link Ext.data.Store#event-update} event to have their code notified of reject\r | |
1252 | * operations.\r | |
1253 | *\r | |
1254 | * @param {Boolean} [silent=false] `true` to skip notification of the owning store of the change.\r | |
1255 | */\r | |
1256 | reject: function (silent) {\r | |
1257 | var me = this,\r | |
1258 | modified = me.modified;\r | |
1259 | \r | |
1260 | //<debug>\r | |
1261 | if (me.erased) {\r | |
1262 | Ext.raise('Cannot reject once a record has been erased.');\r | |
1263 | }\r | |
1264 | //</debug>\r | |
1265 | \r | |
1266 | if (modified) {\r | |
1267 | me.set(modified, me._rejectOptions);\r | |
1268 | }\r | |
1269 | \r | |
1270 | me.dropped = false;\r | |
1271 | me.clearState();\r | |
1272 | \r | |
1273 | if (!silent) {\r | |
1274 | me.callJoined('afterReject');\r | |
1275 | }\r | |
1276 | },\r | |
1277 | \r | |
1278 | /**\r | |
1279 | * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the\r | |
1280 | * instance since either creation or the last commit operation.\r | |
1281 | *\r | |
1282 | * Developers should subscribe to the {@link Ext.data.Store#event-update} event to have their code notified of commit\r | |
1283 | * operations.\r | |
1284 | *\r | |
1285 | * @param {Boolean} [silent=false] Pass `true` to skip notification of the owning store of the change.\r | |
1286 | * @param {String[]} [modifiedFieldNames] Array of field names changed during sync with server if known.\r | |
1287 | * Omit or pass `null` if unknown. An empty array means that it is known that no fields were modified\r | |
1288 | * by the server's response.\r | |
1289 | * Defaults to false.\r | |
1290 | */\r | |
1291 | commit: function (silent, modifiedFieldNames) {\r | |
1292 | var me = this,\r | |
1293 | versionProperty = me.versionProperty,\r | |
1294 | data = me.data,\r | |
1295 | erased;\r | |
1296 | \r | |
1297 | me.clearState();\r | |
1298 | if (versionProperty && !me.phantom && !isNaN(data[versionProperty])) {\r | |
1299 | ++data[versionProperty];\r | |
1300 | }\r | |
1301 | me.phantom = false;\r | |
1302 | \r | |
1303 | if (me.dropped) {\r | |
1304 | me.erased = erased = true;\r | |
1305 | }\r | |
1306 | \r | |
1307 | if (!silent) {\r | |
1308 | if (erased) {\r | |
1309 | me.callJoined('afterErase');\r | |
1310 | } else {\r | |
1311 | me.callJoined('afterCommit', [modifiedFieldNames]);\r | |
1312 | }\r | |
1313 | }\r | |
1314 | },\r | |
1315 | \r | |
1316 | clearState: function() {\r | |
1317 | var me = this;\r | |
1318 | \r | |
1319 | me.dirty = me.editing = false;\r | |
1320 | me.editMemento = me.modified = null;\r | |
1321 | },\r | |
1322 | \r | |
1323 | /**\r | |
1324 | * Marks this record as `dropped` and waiting to be deleted on the server. When a\r | |
1325 | * record is dropped, it is automatically removed from all association stores and\r | |
1326 | * any child records associated to this record are also dropped (a "cascade delete")\r | |
1327 | * depending on the `cascade` parameter.\r | |
1328 | *\r | |
1329 | * @param {Boolean} [cascade=true] Pass `false` to disable the cascade to drop child\r | |
1330 | * records.\r | |
1331 | * @since 5.0.0\r | |
1332 | */\r | |
1333 | drop: function (cascade) {\r | |
1334 | var me = this,\r | |
1335 | associations = me.associations,\r | |
1336 | session = me.session,\r | |
1337 | roleName;\r | |
1338 | \r | |
1339 | if (me.erased || me.dropped) {\r | |
1340 | return;\r | |
1341 | }\r | |
1342 | \r | |
1343 | me.dropped = true;\r | |
1344 | if (associations && cascade !== false) {\r | |
1345 | for (roleName in associations) {\r | |
1346 | associations[roleName].onDrop(me, session);\r | |
1347 | }\r | |
1348 | }\r | |
1349 | me.callJoined('afterDrop');\r | |
1350 | if (me.phantom) {\r | |
1351 | me.setErased();\r | |
1352 | }\r | |
1353 | },\r | |
1354 | \r | |
1355 | /**\r | |
1356 | * Tells this model instance that an observer is looking at it.\r | |
1357 | * @param {Ext.data.Store} item The store to which this model has been added.\r | |
1358 | */\r | |
1359 | join: function (item) {\r | |
1360 | var me = this,\r | |
1361 | joined = me.joined;\r | |
1362 | \r | |
1363 | // Optimize this, gets called a lot\r | |
1364 | if (!joined) {\r | |
1365 | joined = me.joined = [item];\r | |
1366 | } else if (!joined.length) {\r | |
1367 | joined[0] = item;\r | |
1368 | } else {\r | |
1369 | // TODO: do we need joined here? Perhaps push will do.\r | |
1370 | Ext.Array.include(joined, item);\r | |
1371 | }\r | |
1372 | \r | |
1373 | if (item.isStore && !me.store) {\r | |
1374 | /**\r | |
1375 | * @property {Ext.data.Store} store\r | |
1376 | * The {@link Ext.data.Store Store} to which this instance belongs.\r | |
1377 | *\r | |
1378 | * **Note:** If this instance is bound to multiple stores, this property\r | |
1379 | * will reference only the first.\r | |
1380 | */\r | |
1381 | me.store = item;\r | |
1382 | }\r | |
1383 | },\r | |
1384 | \r | |
1385 | /**\r | |
1386 | * Tells this model instance that it has been removed from the store.\r | |
1387 | * @param {Ext.data.Store} store The store from which this model has been removed.\r | |
1388 | */\r | |
1389 | unjoin: function (item) {\r | |
1390 | var me = this,\r | |
1391 | joined = me.joined,\r | |
1392 | \r | |
1393 | // TreeModels are never joined to their TreeStore.\r | |
1394 | // But unjoin is called by the base class's onCollectionRemove, so joined may be undefined.\r | |
1395 | len = joined && joined.length,\r | |
1396 | store = me.store,\r | |
1397 | i;\r | |
1398 | \r | |
1399 | if (len === 1 && joined[0] === item) {\r | |
1400 | joined.length = 0;\r | |
1401 | } else if (len) {\r | |
1402 | Ext.Array.remove(joined, item);\r | |
1403 | }\r | |
1404 | \r | |
1405 | if (store === item) {\r | |
1406 | store = null;\r | |
1407 | if (joined) {\r | |
1408 | for (i = 0, len = joined.length; i < len; ++i) {\r | |
1409 | item = joined[i];\r | |
1410 | if (item.isStore) {\r | |
1411 | store = item;\r | |
1412 | break;\r | |
1413 | }\r | |
1414 | }\r | |
1415 | }\r | |
1416 | me.store = store;\r | |
1417 | }\r | |
1418 | },\r | |
1419 | \r | |
1420 | /**\r | |
1421 | * Creates a clone of this record. States like `dropped`, `phantom` and `dirty` are\r | |
1422 | * all preserved in the cloned record.\r | |
1423 | *\r | |
1424 | * @param {Ext.data.Session} [session] The session to which the new record\r | |
1425 | * belongs.\r | |
1426 | * @return {Ext.data.Model} The cloned record.\r | |
1427 | */\r | |
1428 | clone: function (session) {\r | |
1429 | var me = this,\r | |
1430 | modified = me.modified,\r | |
1431 | ret = me.copy(me.id, session);\r | |
1432 | \r | |
1433 | if (modified) {\r | |
1434 | // Restore the modified fields state\r | |
1435 | ret.modified = Ext.apply({}, modified);\r | |
1436 | }\r | |
1437 | \r | |
1438 | ret.dirty = me.dirty;\r | |
1439 | ret.dropped = me.dropped;\r | |
1440 | ret.phantom = me.phantom;\r | |
1441 | \r | |
1442 | return ret;\r | |
1443 | },\r | |
1444 | \r | |
1445 | /**\r | |
1446 | * Creates a clean copy of this record. The returned record will not consider any its\r | |
1447 | * fields as modified.\r | |
1448 | *\r | |
1449 | * To generate a phantom instance with a new id pass `null`:\r | |
1450 | *\r | |
1451 | * var rec = record.copy(null); // clone the record but no id (one is generated)\r | |
1452 | *\r | |
1453 | * @param {String} [newId] A new id, defaults to the id of the instance being copied.\r | |
1454 | * See `{@link Ext.data.Model#idProperty idProperty}`.\r | |
1455 | * @param {Ext.data.Session} [session] The session to which the new record\r | |
1456 | * belongs.\r | |
1457 | *\r | |
1458 | * @return {Ext.data.Model}\r | |
1459 | */\r | |
1460 | copy: function (newId, session) {\r | |
1461 | var me = this,\r | |
1462 | data = Ext.apply({}, me.data),\r | |
1463 | idProperty = me.idProperty,\r | |
1464 | T = me.self;\r | |
1465 | \r | |
1466 | if (newId || newId === 0) {\r | |
1467 | data[idProperty] = newId;\r | |
1468 | } else if (newId === null) {\r | |
1469 | delete data[idProperty];\r | |
1470 | }\r | |
1471 | \r | |
1472 | return new T(data, session);\r | |
1473 | },\r | |
1474 | \r | |
1475 | /**\r | |
1476 | * Returns the configured Proxy for this Model.\r | |
1477 | * @return {Ext.data.proxy.Proxy} The proxy\r | |
1478 | */\r | |
1479 | getProxy: function() {\r | |
1480 | return this.self.getProxy();\r | |
1481 | },\r | |
1482 | \r | |
1483 | /**\r | |
1484 | * Returns the `Ext.data.Validation` record holding the results of this record's\r | |
1485 | * `validators`. This record is lazily created on first request and is then kept on\r | |
1486 | * this record to be updated later.\r | |
1487 | *\r | |
1488 | * See the class description for more about `validators`.\r | |
1489 | *\r | |
1490 | * @param {Boolean} [refresh] Pass `false` to not call the `refresh` method on the\r | |
1491 | * validation instance prior to returning it. Pass `true` to force a `refresh` of the\r | |
1492 | * validation instance. By default the returned record is only refreshed if changes\r | |
1493 | * have been made to this record.\r | |
1494 | * @return {Ext.data.Validation} The `Validation` record for this record.\r | |
1495 | * @since 5.0.0\r | |
1496 | */\r | |
1497 | getValidation: function (refresh) {\r | |
1498 | var me = this,\r | |
1499 | ret = me.validation;\r | |
1500 | \r | |
1501 | if (!ret) {\r | |
1502 | me.validation = ret = new Ext.data.Validation();\r | |
1503 | ret.attach(me);\r | |
1504 | }\r | |
1505 | \r | |
1506 | if (refresh === true || (refresh !== false && ret.syncGeneration !== me.generation)) {\r | |
1507 | ret.refresh(refresh);\r | |
1508 | }\r | |
1509 | \r | |
1510 | return ret;\r | |
1511 | },\r | |
1512 | \r | |
1513 | /**\r | |
1514 | * Validates the current data against all of its configured {@link #validators}. The\r | |
1515 | * returned collection holds an object for each reported problem from a `validator`.\r | |
1516 | *\r | |
1517 | * @return {Ext.data.ErrorCollection} The errors collection.\r | |
1518 | * @deprecated 5.0 Use `getValidation` instead.\r | |
1519 | */\r | |
1520 | validate: function() {\r | |
1521 | return new Ext.data.ErrorCollection().init(this);\r | |
1522 | },\r | |
1523 | \r | |
1524 | /**\r | |
1525 | * Checks if the model is valid. See {@link #getValidation}.\r | |
1526 | * @return {Boolean} True if the model is valid.\r | |
1527 | */\r | |
1528 | isValid: function () {\r | |
1529 | return this.getValidation().isValid();\r | |
1530 | },\r | |
1531 | \r | |
1532 | /**\r | |
1533 | * Returns a url-suitable string for this model instance. By default this just returns the name of the Model class\r | |
1534 | * followed by the instance ID - for example an instance of MyApp.model.User with ID 123 will return 'user/123'.\r | |
1535 | * @return {String} The url string for this model instance.\r | |
1536 | */\r | |
1537 | toUrl: function() {\r | |
1538 | var pieces = this.$className.split('.'),\r | |
1539 | name = pieces[pieces.length - 1].toLowerCase();\r | |
1540 | \r | |
1541 | return name + '/' + this.getId();\r | |
1542 | },\r | |
1543 | \r | |
1544 | /**\r | |
1545 | * @localdoc Destroys the model using the configured proxy. The erase action is\r | |
1546 | * asynchronous. Any processing of the erased record should be done in a callback.\r | |
1547 | *\r | |
1548 | * Ext.define('MyApp.model.User', {\r | |
1549 | * extend: 'Ext.data.Model',\r | |
1550 | * fields: [\r | |
1551 | * {name: 'id', type: 'int'},\r | |
1552 | * {name: 'name', type: 'string'}\r | |
1553 | * ],\r | |
1554 | * proxy: {\r | |
1555 | * type: 'ajax',\r | |
1556 | * url: 'server.url'\r | |
1557 | * }\r | |
1558 | * });\r | |
1559 | *\r | |
1560 | * var user = new MyApp.model.User({\r | |
1561 | * name: 'Foo'\r | |
1562 | * });\r | |
1563 | *\r | |
1564 | * // pass the phantom record data to the server to be saved\r | |
1565 | * user.save({\r | |
1566 | * success: function(record, operation) {\r | |
1567 | * // do something if the save succeeded\r | |
1568 | * // erase the created record\r | |
1569 | * record.erase({\r | |
1570 | * failure: function(record, operation) {\r | |
1571 | * // do something if the erase failed\r | |
1572 | * },\r | |
1573 | * success: function(record, operation) {\r | |
1574 | * // do something if the erase succeeded\r | |
1575 | * },\r | |
1576 | * callback: function(record, operation, success) {\r | |
1577 | * // do something if the erase succeeded or failed\r | |
1578 | * }\r | |
1579 | * });\r | |
1580 | * }\r | |
1581 | * });\r | |
1582 | *\r | |
1583 | * **NOTE:** If a {@link #phantom} record is erased it will not be processed via the\r | |
1584 | * proxy. However, any passed `success` or `callback` functions will be called.\r | |
1585 | *\r | |
1586 | * The options param is an {@link Ext.data.operation.Destroy} config object\r | |
1587 | * containing success, failure and callback functions, plus optional scope.\r | |
1588 | *\r | |
1589 | * @inheritdoc #method-load\r | |
1590 | * @return {Ext.data.operation.Destroy} The destroy operation\r | |
1591 | */\r | |
1592 | erase: function(options) {\r | |
1593 | var me = this;\r | |
1594 | \r | |
1595 | me.erasing = true;\r | |
1596 | \r | |
1597 | // Drop causes a removal from the backing Collection.\r | |
1598 | // The store's onCollectionRemove will respond to this by adding the record to its "to remove" stack and setting its needsSync\r | |
1599 | // flag unless the above "erasing" flag is set.\r | |
1600 | me.drop();\r | |
1601 | \r | |
1602 | me.erasing = false;\r | |
1603 | return me.save(options);\r | |
1604 | },\r | |
1605 | \r | |
1606 | setErased: function() {\r | |
1607 | this.erased = true;\r | |
1608 | this.callJoined('afterErase');\r | |
1609 | },\r | |
1610 | \r | |
1611 | /**\r | |
1612 | * Gets an object of only the fields that have been modified since this record was\r | |
1613 | * created or committed. Only persistent fields are tracked in the `modified` set so\r | |
1614 | * this method will only return changes to persistent fields.\r | |
1615 | *\r | |
1616 | * For more control over the returned data, see `{@link #getData}`.\r | |
1617 | * @return {Object}\r | |
1618 | */\r | |
1619 | getChanges: function () {\r | |
1620 | return this.getData(this._getChangesOptions);\r | |
1621 | },\r | |
1622 | \r | |
1623 | /**\r | |
1624 | * Returns the array of fields that are declared as critical (must always send).\r | |
1625 | * @return {Ext.data.field.Field[]}\r | |
1626 | */\r | |
1627 | getCriticalFields: function () {\r | |
1628 | var cls = this.self,\r | |
1629 | ret = cls.criticalFields;\r | |
1630 | \r | |
1631 | if (!ret) {\r | |
1632 | cls.rankFields();\r | |
1633 | ret = cls.criticalFields;\r | |
1634 | }\r | |
1635 | \r | |
1636 | return ret;\r | |
1637 | },\r | |
1638 | \r | |
1639 | /**\r | |
1640 | * This method is called by the {@link Ext.data.reader.Reader} after loading a model from\r | |
1641 | * the server. This is after processing any inline associations that are available.\r | |
1642 | * \r | |
1643 | * @method onLoad\r | |
1644 | *\r | |
1645 | * @protected\r | |
1646 | * @template\r | |
1647 | */\r | |
1648 | \r | |
1649 | /**\r | |
1650 | * Gets all of the data from this Models *loaded* associations. It does this\r | |
1651 | * recursively. For example if we have a User which hasMany Orders, and each Order\r | |
1652 | * hasMany OrderItems, it will return an object like this:\r | |
1653 | *\r | |
1654 | * {\r | |
1655 | * orders: [\r | |
1656 | * {\r | |
1657 | * id: 123,\r | |
1658 | * status: 'shipped',\r | |
1659 | * orderItems: [\r | |
1660 | * ...\r | |
1661 | * ]\r | |
1662 | * }\r | |
1663 | * ]\r | |
1664 | * }\r | |
1665 | *\r | |
1666 | * @param {Object} [result] The object on to which the associations will be added. If\r | |
1667 | * no object is passed one is created. This object is then returned.\r | |
1668 | * @param {Boolean/Object} [options] An object containing options describing the data\r | |
1669 | * desired.\r | |
1670 | * @param {Boolean} [options.associated=true] Pass `true` to include associated data from\r | |
1671 | * other associated records.\r | |
1672 | * @param {Boolean} [options.changes=false] Pass `true` to only include fields that\r | |
1673 | * have been modified. Note that field modifications are only tracked for fields that\r | |
1674 | * are not declared with `persist` set to `false`. In other words, only persistent\r | |
1675 | * fields have changes tracked so passing `true` for this means `options.persist` is\r | |
1676 | * redundant.\r | |
1677 | * @param {Boolean} [options.critical] Pass `true` to include fields set as `critical`.\r | |
1678 | * This is only meaningful when `options.changes` is `true` since critical fields may\r | |
1679 | * not have been modified.\r | |
1680 | * @param {Boolean} [options.persist] Pass `true` to only return persistent fields.\r | |
1681 | * This is implied when `options.changes` is set to `true`.\r | |
1682 | * @param {Boolean} [options.serialize=false] Pass `true` to invoke the `serialize`\r | |
1683 | * method on the returned fields.\r | |
1684 | * @return {Object} The nested data set for the Model's loaded associations.\r | |
1685 | */\r | |
1686 | getAssociatedData: function (result, options) {\r | |
1687 | var me = this,\r | |
1688 | associations = me.associations,\r | |
1689 | deep, i, item, items, itemData, length, \r | |
1690 | record, role, roleName, opts, clear, associated;\r | |
1691 | \r | |
1692 | result = result || {};\r | |
1693 | \r | |
1694 | me.$gathering = 1;\r | |
1695 | \r | |
1696 | if (options) {\r | |
1697 | options = Ext.Object.chain(options);\r | |
1698 | }\r | |
1699 | \r | |
1700 | for (roleName in associations) {\r | |
1701 | role = associations[roleName];\r | |
1702 | item = role.getAssociatedItem(me);\r | |
1703 | if (!item || item.$gathering) {\r | |
1704 | continue;\r | |
1705 | }\r | |
1706 | \r | |
1707 | if (item.isStore) {\r | |
1708 | item.$gathering = 1;\r | |
1709 | \r | |
1710 | items = item.getData().items; // get the records for the store\r | |
1711 | length = items.length;\r | |
1712 | itemData = [];\r | |
1713 | \r | |
1714 | for (i = 0; i < length; ++i) {\r | |
1715 | // NOTE - we don't check whether the record is gathering here because\r | |
1716 | // we cannot remove it from the store (it would invalidate the index\r | |
1717 | // values and misrepresent the content). Instead we tell getData to\r | |
1718 | // only get the fields vs descend further.\r | |
1719 | record = items[i];\r | |
1720 | deep = !record.$gathering;\r | |
1721 | record.$gathering = 1;\r | |
1722 | if (options) {\r | |
1723 | associated = options.associated;\r | |
1724 | if (associated === undefined) {\r | |
1725 | options.associated = deep;\r | |
1726 | clear = true;\r | |
1727 | } else if (!deep) {\r | |
1728 | options.associated = false;\r | |
1729 | clear = true;\r | |
1730 | }\r | |
1731 | opts = options;\r | |
1732 | } else {\r | |
1733 | opts = deep ? me._getAssociatedOptions: me._getNotAssociatedOptions;\r | |
1734 | }\r | |
1735 | itemData.push(record.getData(opts));\r | |
1736 | if (clear) {\r | |
1737 | options.associated = associated;\r | |
1738 | clear = false;\r | |
1739 | }\r | |
1740 | delete record.$gathering;\r | |
1741 | }\r | |
1742 | \r | |
1743 | delete item.$gathering;\r | |
1744 | } else {\r | |
1745 | opts = options || me._getAssociatedOptions;\r | |
1746 | if (options && options.associated === undefined) {\r | |
1747 | opts.associated = true;\r | |
1748 | }\r | |
1749 | itemData = item.getData(opts);\r | |
1750 | }\r | |
1751 | \r | |
1752 | result[roleName] = itemData;\r | |
1753 | }\r | |
1754 | \r | |
1755 | delete me.$gathering;\r | |
1756 | \r | |
1757 | return result;\r | |
1758 | },\r | |
1759 | \r | |
1760 | /**\r | |
1761 | * Gets all values for each field in this model and returns an object containing the\r | |
1762 | * current data. This can be tuned by passing an `options` object with various\r | |
1763 | * properties describing the desired result. Passing `true` simply returns all fields\r | |
1764 | * *and* all associated record data.\r | |
1765 | *\r | |
1766 | * @param {Boolean/Object} [options] An object containing options describing the data\r | |
1767 | * desired. If `true` is passed it is treated as an object with `associated` set to\r | |
1768 | * `true`.\r | |
1769 | * @param {Boolean} [options.associated=false] Pass `true` to include associated data.\r | |
1770 | * This is equivalent to pass `true` as the only argument. See `getAssociatedData`.\r | |
1771 | * @param {Boolean} [options.changes=false] Pass `true` to only include fields that\r | |
1772 | * have been modified. Note that field modifications are only tracked for fields that\r | |
1773 | * are not declared with `persist` set to `false`. In other words, only persistent\r | |
1774 | * fields have changes tracked so passing `true` for this means `options.persist` is\r | |
1775 | * redundant.\r | |
1776 | * @param {Boolean} [options.critical] Pass `true` to include fields set as `critical`.\r | |
1777 | * This is only meaningful when `options.changes` is `true` since critical fields may\r | |
1778 | * not have been modified.\r | |
1779 | * @param {Boolean} [options.persist] Pass `true` to only return persistent fields.\r | |
1780 | * This is implied when `options.changes` is set to `true`.\r | |
1781 | * @param {Boolean} [options.serialize=false] Pass `true` to invoke the `serialize`\r | |
1782 | * method on the returned fields.\r | |
1783 | * @return {Object} An object containing all the values in this model.\r | |
1784 | */\r | |
1785 | getData: function (options) {\r | |
1786 | var me = this,\r | |
1787 | ret = {},\r | |
1788 | opts = (options === true) ? me._getAssociatedOptions : (options || ret), //cheat\r | |
1789 | data = me.data,\r | |
1790 | associated = opts.associated,\r | |
1791 | changes = opts.changes,\r | |
1792 | critical = changes && opts.critical,\r | |
1793 | content = changes ? me.modified : data,\r | |
1794 | fieldsMap = me.fieldsMap,\r | |
1795 | persist = opts.persist,\r | |
1796 | serialize = opts.serialize,\r | |
1797 | criticalFields, field, n, name, value;\r | |
1798 | \r | |
1799 | // DON'T use "opts" from here on...\r | |
1800 | \r | |
1801 | // Keep in mind the two legacy use cases:\r | |
1802 | // - getData() ==> Ext.apply({}, me.data)\r | |
1803 | // - getData(true) ==> Ext.apply(Ext.apply({}, me.data), me.getAssociatedData())\r | |
1804 | \r | |
1805 | if (content) { // when processing only changes, me.modified could be null\r | |
1806 | for (name in content) {\r | |
1807 | value = data[name];\r | |
1808 | \r | |
1809 | field = fieldsMap[name];\r | |
1810 | if (field) {\r | |
1811 | if (persist && !field.persist) {\r | |
1812 | continue;\r | |
1813 | }\r | |
1814 | if (serialize && field.serialize) {\r | |
1815 | value = field.serialize(value, me);\r | |
1816 | }\r | |
1817 | }\r | |
1818 | \r | |
1819 | ret[name] = value;\r | |
1820 | }\r | |
1821 | }\r | |
1822 | \r | |
1823 | if (critical) {\r | |
1824 | criticalFields = me.self.criticalFields || me.getCriticalFields();\r | |
1825 | for (n = criticalFields.length; n-- > 0; ) {\r | |
1826 | name = (field = criticalFields[n]).name;\r | |
1827 | \r | |
1828 | if (!(name in ret)) {\r | |
1829 | value = data[name];\r | |
1830 | if (serialize && field.serialize) {\r | |
1831 | value = field.serialize(value, me);\r | |
1832 | }\r | |
1833 | ret[name] = value;\r | |
1834 | }\r | |
1835 | }\r | |
1836 | }\r | |
1837 | \r | |
1838 | if (associated) {\r | |
1839 | me.getAssociatedData(ret, opts); // pass ret so new data is added to our object\r | |
1840 | }\r | |
1841 | \r | |
1842 | return ret;\r | |
1843 | },\r | |
1844 | \r | |
1845 | /**\r | |
1846 | * Returns the array of fields that are declared as non-persist or "transient".\r | |
1847 | * @return {Ext.data.field.Field[]}\r | |
1848 | * @since 5.0.0\r | |
1849 | */\r | |
1850 | getTransientFields: function () {\r | |
1851 | var cls = this.self,\r | |
1852 | ret = cls.transientFields;\r | |
1853 | \r | |
1854 | if (!ret) {\r | |
1855 | cls.rankFields(); // populates transientFields as well as rank\r | |
1856 | ret = cls.transientFields;\r | |
1857 | }\r | |
1858 | \r | |
1859 | return ret;\r | |
1860 | },\r | |
1861 | \r | |
1862 | /**\r | |
1863 | * Checks whether this model is loading data from the {@link #proxy}.\r | |
1864 | * @return {Boolean} `true` if in a loading state.\r | |
1865 | */\r | |
1866 | isLoading: function() {\r | |
1867 | return !!this.loadOperation;\r | |
1868 | },\r | |
1869 | \r | |
1870 | /**\r | |
1871 | * Aborts a pending {@link #load} operation. If the record is not loading, this does nothing.\r | |
1872 | */\r | |
1873 | abort: function() {\r | |
1874 | var operation = this.loadOperation;\r | |
1875 | if (operation) {\r | |
1876 | operation.abort();\r | |
1877 | }\r | |
1878 | },\r | |
1879 | \r | |
1880 | /**\r | |
1881 | * @localdoc Loads the model instance using the configured proxy. The load action\r | |
1882 | * is asynchronous. Any processing of the loaded record should be done in a\r | |
1883 | * callback.\r | |
1884 | *\r | |
1885 | * Ext.define('MyApp.model.User', {\r | |
1886 | * extend: 'Ext.data.Model',\r | |
1887 | * fields: [\r | |
1888 | * {name: 'id', type: 'int'},\r | |
1889 | * {name: 'name', type: 'string'}\r | |
1890 | * ],\r | |
1891 | * proxy: {\r | |
1892 | * type: 'ajax',\r | |
1893 | * url: 'server.url'\r | |
1894 | * }\r | |
1895 | * });\r | |
1896 | *\r | |
1897 | * var user = new MyApp.model.User();\r | |
1898 | * user.load({\r | |
1899 | * scope: this,\r | |
1900 | * failure: function(record, operation) {\r | |
1901 | * // do something if the load failed\r | |
1902 | * },\r | |
1903 | * success: function(record, operation) {\r | |
1904 | * // do something if the load succeeded\r | |
1905 | * },\r | |
1906 | * callback: function(record, operation, success) {\r | |
1907 | * // do something whether the load succeeded or failed\r | |
1908 | * }\r | |
1909 | * });\r | |
1910 | *\r | |
1911 | * The options param is an {@link Ext.data.operation.Read} config object containing\r | |
1912 | * success, failure and callback functions, plus optional scope.\r | |
1913 | *\r | |
1914 | * @param {Object} [options] Options to pass to the proxy.\r | |
1915 | * @param {Function} options.success A function to be called when the\r | |
1916 | * model is processed by the proxy successfully.\r | |
1917 | * The callback is passed the following parameters:\r | |
1918 | * @param {Ext.data.Model} options.success.record The record.\r | |
1919 | * @param {Ext.data.operation.Operation} options.success.operation The operation.\r | |
1920 | * \r | |
1921 | * @param {Function} options.failure A function to be called when the\r | |
1922 | * model is unable to be processed by the server.\r | |
1923 | * The callback is passed the following parameters:\r | |
1924 | * @param {Ext.data.Model} options.failure.record The record.\r | |
1925 | * @param {Ext.data.operation.Operation} options.failure.operation The operation.\r | |
1926 | * \r | |
1927 | * @param {Function} options.callback A function to be called whether the proxy\r | |
1928 | * transaction was successful or not.\r | |
1929 | * The callback is passed the following parameters:\r | |
1930 | * @param {Ext.data.Model} options.callback.record The record.\r | |
1931 | * @param {Ext.data.operation.Operation} options.callback.operation The operation.\r | |
1932 | * @param {Boolean} options.callback.success `true` if the operation was successful.\r | |
1933 | * \r | |
1934 | * @param {Object} options.scope The scope in which to execute the callback\r | |
1935 | * functions. Defaults to the model instance.\r | |
1936 | *\r | |
1937 | * @return {Ext.data.operation.Read} The read operation.\r | |
1938 | */\r | |
1939 | load: function(options) {\r | |
1940 | options = Ext.apply({}, options);\r | |
1941 | \r | |
1942 | var me = this,\r | |
1943 | scope = options.scope || me,\r | |
1944 | proxy = me.getProxy(),\r | |
1945 | callback = options.callback,\r | |
1946 | operation = me.loadOperation,\r | |
1947 | id = me.getId(),\r | |
1948 | extras;\r | |
1949 | \r | |
1950 | if (operation) {\r | |
1951 | // Already loading, push any callbacks on and jump out\r | |
1952 | extras = operation.extraCalls;\r | |
1953 | if (!extras) {\r | |
1954 | extras = operation.extraCalls = [];\r | |
1955 | }\r | |
1956 | extras.push(options);\r | |
1957 | return operation;\r | |
1958 | }\r | |
1959 | \r | |
1960 | //<debug>\r | |
1961 | var doIdCheck = true;\r | |
1962 | if (me.phantom) {\r | |
1963 | doIdCheck = false;\r | |
1964 | }\r | |
1965 | //</debug>\r | |
1966 | \r | |
1967 | options.id = id;\r | |
1968 | \r | |
1969 | // Always set the recordCreator. If we have a session, we're already\r | |
1970 | // part of said session, so we don't need to handle that.\r | |
1971 | options.recordCreator = function(data, type, readOptions) {\r | |
1972 | // Important to change this here, because we might be loading associations,\r | |
1973 | // so we do not want this to propagate down. If we have a session, use that\r | |
1974 | // so that we end up getting the same record. Otherwise, just remove it.\r | |
1975 | var session = me.session;\r | |
1976 | if (readOptions) {\r | |
1977 | readOptions.recordCreator = session ? session.recordCreator : null;\r | |
1978 | }\r | |
1979 | me.set(data, me._commitOptions); \r | |
1980 | //<debug>\r | |
1981 | // Do the id check after set since converters may have run\r | |
1982 | if (doIdCheck && me.getId() !== id) {\r | |
1983 | Ext.raise('Invalid record id returned for ' + id + '@' + me.entityName);\r | |
1984 | }\r | |
1985 | //</debug>\r | |
1986 | return me;\r | |
1987 | };\r | |
1988 | \r | |
1989 | options.internalCallback = function(operation) {\r | |
1990 | var success = operation.wasSuccessful() && operation.getRecords().length > 0,\r | |
1991 | op = me.loadOperation,\r | |
1992 | extras = op.extraCalls,\r | |
1993 | successFailArgs = [me, operation],\r | |
1994 | callbackArgs = [me, operation, success],\r | |
1995 | i, len;\r | |
1996 | \r | |
1997 | me.loadOperation = null;\r | |
1998 | \r | |
1999 | if (success) {\r | |
2000 | Ext.callback(options.success, scope, successFailArgs);\r | |
2001 | } else {\r | |
2002 | Ext.callback(options.failure, scope, successFailArgs);\r | |
2003 | }\r | |
2004 | Ext.callback(callback, scope, callbackArgs);\r | |
2005 | \r | |
2006 | // Some code repetition here, however in a vast majority of cases\r | |
2007 | // we'll only have a single callback, so optimize for that case rather\r | |
2008 | // than setup arrays for all the callback options\r | |
2009 | if (extras) {\r | |
2010 | for (i = 0, len = extras.length; i < len; ++i) {\r | |
2011 | options = extras[i];\r | |
2012 | if (success) {\r | |
2013 | Ext.callback(options.success, scope, successFailArgs);\r | |
2014 | } else {\r | |
2015 | Ext.callback(options.failure, scope, successFailArgs);\r | |
2016 | }\r | |
2017 | Ext.callback(options.callback, scope, callbackArgs);\r | |
2018 | }\r | |
2019 | }\r | |
2020 | me.callJoined('afterLoad');\r | |
2021 | };\r | |
2022 | delete options.callback;\r | |
2023 | \r | |
2024 | me.loadOperation = operation = proxy.createOperation('read', options);\r | |
2025 | operation.execute();\r | |
2026 | \r | |
2027 | return operation;\r | |
2028 | },\r | |
2029 | \r | |
2030 | /**\r | |
2031 | * @localdoc Saves the model instance using the configured proxy. The save action\r | |
2032 | * is asynchronous. Any processing of the saved record should be done in a callback.\r | |
2033 | *\r | |
2034 | * Create example:\r | |
2035 | *\r | |
2036 | * Ext.define('MyApp.model.User', {\r | |
2037 | * extend: 'Ext.data.Model',\r | |
2038 | * fields: [\r | |
2039 | * {name: 'id', type: 'int'},\r | |
2040 | * {name: 'name', type: 'string'}\r | |
2041 | * ],\r | |
2042 | * proxy: {\r | |
2043 | * type: 'ajax',\r | |
2044 | * url: 'server.url'\r | |
2045 | * }\r | |
2046 | * });\r | |
2047 | *\r | |
2048 | * var user = new MyApp.model.User({\r | |
2049 | * name: 'Foo'\r | |
2050 | * });\r | |
2051 | *\r | |
2052 | * // pass the phantom record data to the server to be saved\r | |
2053 | * user.save({\r | |
2054 | * failure: function(record, operation) {\r | |
2055 | * // do something if the save failed\r | |
2056 | * },\r | |
2057 | * success: function(record, operation) {\r | |
2058 | * // do something if the save succeeded\r | |
2059 | * },\r | |
2060 | * callback: function(record, operation, success) {\r | |
2061 | * // do something whether the save succeeded or failed\r | |
2062 | * }\r | |
2063 | * });\r | |
2064 | *\r | |
2065 | * The response from a create operation should include the ID for the newly created\r | |
2066 | * record:\r | |
2067 | *\r | |
2068 | * // sample response\r | |
2069 | * {\r | |
2070 | * success: true,\r | |
2071 | * id: 1\r | |
2072 | * }\r | |
2073 | *\r | |
2074 | * // the id may be nested if the proxy's reader has a rootProperty config\r | |
2075 | * Ext.define('MyApp.model.User', {\r | |
2076 | * extend: 'Ext.data.Model',\r | |
2077 | * proxy: {\r | |
2078 | * type: 'ajax',\r | |
2079 | * url: 'server.url',\r | |
2080 | * reader: {\r | |
2081 | * type: 'ajax',\r | |
2082 | * rootProperty: 'data'\r | |
2083 | * }\r | |
2084 | * }\r | |
2085 | * });\r | |
2086 | *\r | |
2087 | * // sample nested response\r | |
2088 | * {\r | |
2089 | * success: true,\r | |
2090 | * data: {\r | |
2091 | * id: 1\r | |
2092 | * }\r | |
2093 | * }\r | |
2094 | *\r | |
2095 | * (Create + ) Update example:\r | |
2096 | *\r | |
2097 | * Ext.define('MyApp.model.User', {\r | |
2098 | * extend: 'Ext.data.Model',\r | |
2099 | * fields: [\r | |
2100 | * {name: 'id', type: 'int'},\r | |
2101 | * {name: 'name', type: 'string'}\r | |
2102 | * ],\r | |
2103 | * proxy: {\r | |
2104 | * type: 'ajax',\r | |
2105 | * url: 'server.url'\r | |
2106 | * }\r | |
2107 | * });\r | |
2108 | *\r | |
2109 | * var user = new MyApp.model.User({\r | |
2110 | * name: 'Foo'\r | |
2111 | * });\r | |
2112 | * user.save({\r | |
2113 | * success: function(record, operation) {\r | |
2114 | * record.set('name', 'Bar');\r | |
2115 | * // updates the remote record via the proxy\r | |
2116 | * record.save();\r | |
2117 | * }\r | |
2118 | * });\r | |
2119 | *\r | |
2120 | * (Create + ) Destroy example - see also {@link #erase}:\r | |
2121 | *\r | |
2122 | * Ext.define('MyApp.model.User', {\r | |
2123 | * extend: 'Ext.data.Model',\r | |
2124 | * fields: [\r | |
2125 | * {name: 'id', type: 'int'},\r | |
2126 | * {name: 'name', type: 'string'}\r | |
2127 | * ],\r | |
2128 | * proxy: {\r | |
2129 | * type: 'ajax',\r | |
2130 | * url: 'server.url'\r | |
2131 | * }\r | |
2132 | * });\r | |
2133 | *\r | |
2134 | * var user = new MyApp.model.User({\r | |
2135 | * name: 'Foo'\r | |
2136 | * });\r | |
2137 | * user.save({\r | |
2138 | * success: function(record, operation) {\r | |
2139 | * record.drop();\r | |
2140 | * // destroys the remote record via the proxy\r | |
2141 | * record.save();\r | |
2142 | * }\r | |
2143 | * });\r | |
2144 | *\r | |
2145 | * **NOTE:** If a {@link #phantom} record is {@link #drop dropped} and subsequently\r | |
2146 | * saved it will not be processed via the proxy. However, any passed `success`\r | |
2147 | * or `callback` functions will be called.\r | |
2148 | *\r | |
2149 | * The options param is an Operation config object containing success, failure and\r | |
2150 | * callback functions, plus optional scope. The type of Operation depends on the\r | |
2151 | * state of the model being saved.\r | |
2152 | *\r | |
2153 | * - {@link #phantom} model - {@link Ext.data.operation.Create}\r | |
2154 | * - {@link #isModified modified} model - {@link Ext.data.operation.Update}\r | |
2155 | * - {@link #dropped} model - {@link Ext.data.operation.Destroy}\r | |
2156 | *\r | |
2157 | * @inheritdoc #method-load\r | |
2158 | * @return {Ext.data.operation.Create/Ext.data.operation.Update/Ext.data.operation.Destroy}\r | |
2159 | * The operation instance for saving this model. The type of operation returned\r | |
2160 | * depends on the model state at the time of the action.\r | |
2161 | *\r | |
2162 | * - {@link #phantom} model - {@link Ext.data.operation.Create}\r | |
2163 | * - {@link #isModified modified} model - {@link Ext.data.operation.Update}\r | |
2164 | * - {@link #dropped} model - {@link Ext.data.operation.Destroy}\r | |
2165 | */\r | |
2166 | save: function(options) {\r | |
2167 | options = Ext.apply({}, options);\r | |
2168 | \r | |
2169 | var me = this,\r | |
2170 | phantom = me.phantom,\r | |
2171 | dropped = me.dropped,\r | |
2172 | action = dropped ? 'destroy' : (phantom ? 'create' : 'update'),\r | |
2173 | scope = options.scope || me,\r | |
2174 | callback = options.callback,\r | |
2175 | proxy = me.getProxy(),\r | |
2176 | operation;\r | |
2177 | \r | |
2178 | options.records = [me];\r | |
2179 | options.internalCallback = function(operation) {\r | |
2180 | var args = [me, operation],\r | |
2181 | success = operation.wasSuccessful();\r | |
2182 | if (success) {\r | |
2183 | Ext.callback(options.success, scope, args);\r | |
2184 | } else {\r | |
2185 | Ext.callback(options.failure, scope, args);\r | |
2186 | }\r | |
2187 | args.push(success);\r | |
2188 | Ext.callback(callback, scope, args);\r | |
2189 | };\r | |
2190 | delete options.callback;\r | |
2191 | \r | |
2192 | operation = proxy.createOperation(action, options);\r | |
2193 | \r | |
2194 | // Not a phantom, then we must perform this operation on the remote datasource.\r | |
2195 | // Record will be removed from the store in the callback upon a success response\r | |
2196 | if (dropped && phantom) {\r | |
2197 | // If it's a phantom, then call the callback directly with a dummy successful ResultSet\r | |
2198 | operation.setResultSet(Ext.data.reader.Reader.prototype.nullResultSet);\r | |
2199 | me.setErased();\r | |
2200 | operation.setSuccessful(true);\r | |
2201 | } else {\r | |
2202 | operation.execute();\r | |
2203 | }\r | |
2204 | return operation;\r | |
2205 | },\r | |
2206 | \r | |
2207 | //-------------------------------------------------------------------------\r | |
2208 | // Statics\r | |
2209 | \r | |
2210 | inheritableStatics: {\r | |
2211 | /**\r | |
2212 | * This method adds the given set of fields to this model class.\r | |
2213 | *\r | |
2214 | * @param {String[]/Object[]} newFields The new fields to add. Based on the `name`\r | |
2215 | * of a field this may replace a previous field definition.\r | |
2216 | *\r | |
2217 | * @protected\r | |
2218 | * @static\r | |
2219 | * @inheritable\r | |
2220 | * @since 5.0.0\r | |
2221 | */\r | |
2222 | addFields: function (newFields) {\r | |
2223 | this.replaceFields(newFields);\r | |
2224 | },\r | |
2225 | \r | |
2226 | /**\r | |
2227 | * This method replaces the specified set of fields with a given set of new fields.\r | |
2228 | * Fields should normally be considered immutable, but if the timing is right (that\r | |
2229 | * is, before derived classes are declared), it is permissible to change the fields\r | |
2230 | * collection.\r | |
2231 | *\r | |
2232 | * @param {String[]/Object[]} newFields The new fields to add. Based on the `name`\r | |
2233 | * of a field this may replace a previous field definition.\r | |
2234 | * @param {Boolean/String[]} removeFields The names of fields to remove or `true`\r | |
2235 | * to remove all existing fields. Removes are processed first followed by adds so\r | |
2236 | * if a field name appears in `newFields` as well that field will effectively be\r | |
2237 | * added (however, in that case there is no need to include the field in this\r | |
2238 | * array).\r | |
2239 | *\r | |
2240 | * @protected\r | |
2241 | * @static\r | |
2242 | * @inheritable\r | |
2243 | * @since 5.0.0\r | |
2244 | */\r | |
2245 | replaceFields: function (newFields, removeFields) {\r | |
2246 | var me = this,\r | |
2247 | proto = me.prototype,\r | |
2248 | Field = Ext.data.field.Field,\r | |
2249 | fields = me.fields,\r | |
2250 | fieldsMap = me.fieldsMap,\r | |
2251 | ordinals = me.fieldOrdinals,\r | |
2252 | field, i, idField, len, name, ordinal;\r | |
2253 | \r | |
2254 | if (removeFields === true) {\r | |
2255 | fields.length = 0;\r | |
2256 | me.fieldsMap = fieldsMap = {};\r | |
2257 | me.fieldOrdinals = ordinals = {};\r | |
2258 | } else if (removeFields) {\r | |
2259 | for (i = removeFields.length; i-- > 0; ) {\r | |
2260 | name = removeFields[i];\r | |
2261 | if (name in ordinals) {\r | |
2262 | delete ordinals[name];\r | |
2263 | delete fieldsMap[name];\r | |
2264 | }\r | |
2265 | }\r | |
2266 | \r | |
2267 | for (i = 0, len = fields.length; i < len; ++i) {\r | |
2268 | name = (field = fields[i]).name;\r | |
2269 | \r | |
2270 | if (name in ordinals) {\r | |
2271 | ordinals[name] = i;\r | |
2272 | } else {\r | |
2273 | // This field is being removed (it is no longer in ordinals).\r | |
2274 | fields.splice(i, 1);\r | |
2275 | --i;\r | |
2276 | --len;\r | |
2277 | // we need to do this forwards so that ordinals don't become\r | |
2278 | // invalid due to a splice\r | |
2279 | }\r | |
2280 | }\r | |
2281 | }\r | |
2282 | \r | |
2283 | for (i = 0, len = newFields ? newFields.length : 0; i < len; i++) {\r | |
2284 | name = (field = newFields[i]).name;\r | |
2285 | \r | |
2286 | if (!(name in ordinals)) {\r | |
2287 | ordinals[name] = ordinal = fields.length; // 0-based\r | |
2288 | fields.push(field = Field.create(field));\r | |
2289 | \r | |
2290 | fieldsMap[name] = field;\r | |
2291 | field.ordinal = ordinal;\r | |
2292 | field.definedBy = field.owner = this; // Ext.data.NodeInterface\r | |
2293 | }\r | |
2294 | }\r | |
2295 | \r | |
2296 | // The idField could have been replaced, so reacquire it.\r | |
2297 | me.idField = proto.idField = idField = fieldsMap[proto.idProperty];\r | |
2298 | idField.allowNull = idField.critical = idField.identifier = true;\r | |
2299 | idField.defaultValue = null;\r | |
2300 | \r | |
2301 | // In case we've created the initializer we need to zap it so we recreate it\r | |
2302 | // next time. Likewise with field ranking.\r | |
2303 | me.initializeFn = me.rankedFields = me.transientFields = me.criticalFields = null;\r | |
2304 | },\r | |
2305 | \r | |
2306 | /**\r | |
2307 | * Removes the given set of fields from this model.\r | |
2308 | *\r | |
2309 | * @param {Boolean/String[]} removeFields The names of fields to remove or `true`\r | |
2310 | * to remove all existing fields. Removes are processed first followed by adds so\r | |
2311 | * if a field name appears in `newFields` as well that field will effectively be\r | |
2312 | * added (however, in that case there is no need to include the field in this\r | |
2313 | * array).\r | |
2314 | *\r | |
2315 | * @protected\r | |
2316 | * @static\r | |
2317 | * @inheritable\r | |
2318 | * @since 5.0.0\r | |
2319 | */\r | |
2320 | removeFields: function (removeFields) {\r | |
2321 | this.replaceFields(null, removeFields);\r | |
2322 | },\r | |
2323 | \r | |
2324 | /**\r | |
2325 | * @private\r | |
2326 | * @static\r | |
2327 | * @inheritable\r | |
2328 | */\r | |
2329 | getIdFromData: function(data) {\r | |
2330 | var T = this,\r | |
2331 | idField = T.idField,\r | |
2332 | id = idField.calculated ? (new T(data)).id : data[idField.name];\r | |
2333 | \r | |
2334 | return id;\r | |
2335 | },\r | |
2336 | \r | |
2337 | /**\r | |
2338 | * @private\r | |
2339 | * @static\r | |
2340 | * @inheritable\r | |
2341 | */\r | |
2342 | createWithId: function (id, data, session) {\r | |
2343 | var d = data,\r | |
2344 | T = this;\r | |
2345 | \r | |
2346 | if (id || id === 0) {\r | |
2347 | d = {};\r | |
2348 | if (data) {\r | |
2349 | Ext.apply(d, data);\r | |
2350 | }\r | |
2351 | \r | |
2352 | d[T.idField.name] = id;\r | |
2353 | }\r | |
2354 | \r | |
2355 | return new T(d, session);\r | |
2356 | },\r | |
2357 | \r | |
2358 | /**\r | |
2359 | * @private\r | |
2360 | * @static\r | |
2361 | * @inheritable\r | |
2362 | */\r | |
2363 | getFields: function() {\r | |
2364 | return this.fields; \r | |
2365 | },\r | |
2366 | \r | |
2367 | /**\r | |
2368 | * @private\r | |
2369 | * @static\r | |
2370 | * @inheritable\r | |
2371 | */\r | |
2372 | getFieldsMap: function() {\r | |
2373 | return this.fieldsMap;\r | |
2374 | },\r | |
2375 | \r | |
2376 | /**\r | |
2377 | * @private\r | |
2378 | * @static\r | |
2379 | * @inheritable\r | |
2380 | */\r | |
2381 | getField: function (name) {\r | |
2382 | return this.fieldsMap[name] || null;\r | |
2383 | },\r | |
2384 | \r | |
2385 | /**\r | |
2386 | * Returns the configured Proxy for this Model.\r | |
2387 | * @return {Ext.data.proxy.Proxy} The proxy\r | |
2388 | * @static\r | |
2389 | * @inheritable\r | |
2390 | */\r | |
2391 | getProxy: function() {\r | |
2392 | var me = this,\r | |
2393 | proxy = me.proxy,\r | |
2394 | defaultProxy = me.defaultProxy,\r | |
2395 | defaults;\r | |
2396 | \r | |
2397 | if (!proxy) {\r | |
2398 | // Check what was defined by the class (via onClassExtended):\r | |
2399 | proxy = me.proxyConfig;\r | |
2400 | \r | |
2401 | if (!proxy && defaultProxy) {\r | |
2402 | proxy = defaultProxy;\r | |
2403 | }\r | |
2404 | \r | |
2405 | if (!proxy || !proxy.isProxy) {\r | |
2406 | if (typeof proxy === 'string') {\r | |
2407 | proxy = {\r | |
2408 | type: proxy\r | |
2409 | };\r | |
2410 | }\r | |
2411 | // We have nothing or a config for the proxy. Get some defaults from\r | |
2412 | // the Schema and smash anything we've provided over the top.\r | |
2413 | defaults = me.schema.constructProxy(me);\r | |
2414 | proxy = proxy ? Ext.merge(defaults, proxy) : defaults;\r | |
2415 | }\r | |
2416 | \r | |
2417 | proxy = me.setProxy(proxy);\r | |
2418 | }\r | |
2419 | \r | |
2420 | return proxy;\r | |
2421 | },\r | |
2422 | \r | |
2423 | /**\r | |
2424 | * Sets the Proxy to use for this model. Accepts any options that can be accepted by\r | |
2425 | * {@link Ext#createByAlias Ext.createByAlias}.\r | |
2426 | * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy\r | |
2427 | * @return {Ext.data.proxy.Proxy}\r | |
2428 | * @static\r | |
2429 | * @inheritable\r | |
2430 | */\r | |
2431 | setProxy: function (proxy) {\r | |
2432 | var me = this,\r | |
2433 | model;\r | |
2434 | \r | |
2435 | if (proxy) {\r | |
2436 | if (!proxy.isProxy) {\r | |
2437 | proxy = Ext.Factory.proxy(proxy);\r | |
2438 | } else {\r | |
2439 | model = proxy.getModel();\r | |
2440 | if (model && model !== me) {\r | |
2441 | proxy = proxy.clone();\r | |
2442 | }\r | |
2443 | }\r | |
2444 | \r | |
2445 | proxy.setModel(me);\r | |
2446 | }\r | |
2447 | \r | |
2448 | return (me.prototype.proxy = me.proxy = proxy);\r | |
2449 | },\r | |
2450 | \r | |
2451 | /**\r | |
2452 | * Asynchronously loads a model instance by id. Any processing of the loaded\r | |
2453 | * record should be done in a callback.\r | |
2454 | *\r | |
2455 | * Sample usage:\r | |
2456 | *\r | |
2457 | * Ext.define('MyApp.User', {\r | |
2458 | * extend: 'Ext.data.Model',\r | |
2459 | * fields: [\r | |
2460 | * {name: 'id', type: 'int'},\r | |
2461 | * {name: 'name', type: 'string'}\r | |
2462 | * ]\r | |
2463 | * });\r | |
2464 | *\r | |
2465 | * MyApp.User.load(10, {\r | |
2466 | * scope: this,\r | |
2467 | * failure: function(record, operation) {\r | |
2468 | * //do something if the load failed\r | |
2469 | * },\r | |
2470 | * success: function(record, operation) {\r | |
2471 | * //do something if the load succeeded\r | |
2472 | * },\r | |
2473 | * callback: function(record, operation, success) {\r | |
2474 | * //do something whether the load succeeded or failed\r | |
2475 | * }\r | |
2476 | * });\r | |
2477 | *\r | |
2478 | * @param {Number/String} id The ID of the model to load.\r | |
2479 | * **NOTE:** The model returned must have an ID matching the param in the load\r | |
2480 | * request.\r | |
2481 | *\r | |
2482 | * @param {Object} [options] The options param is an\r | |
2483 | * {@link Ext.data.operation.Read} config object containing success, failure and\r | |
2484 | * callback functions, plus optional scope.\r | |
2485 | *\r | |
2486 | * @param {Function} options.success A function to be called when the\r | |
2487 | * model is processed by the proxy successfully.\r | |
2488 | * The callback is passed the following parameters:\r | |
2489 | * @param {Ext.data.Model} options.success.record The record.\r | |
2490 | * @param {Ext.data.operation.Operation} options.success.operation The operation.\r | |
2491 | * \r | |
2492 | * @param {Function} options.failure A function to be called when the\r | |
2493 | * model is unable to be processed by the server.\r | |
2494 | * The callback is passed the following parameters:\r | |
2495 | * @param {Ext.data.Model} options.failure.record The record.\r | |
2496 | * @param {Ext.data.operation.Operation} options.failure.operation The operation.\r | |
2497 | * \r | |
2498 | * @param {Function} options.callback A function to be called whether the proxy\r | |
2499 | * transaction was successful or not.\r | |
2500 | * The callback is passed the following parameters:\r | |
2501 | * @param {Ext.data.Model} options.callback.record The record.\r | |
2502 | * @param {Ext.data.operation.Operation} options.callback.operation The\r | |
2503 | * operation.\r | |
2504 | * @param {Boolean} options.callback.success `true` if the operation was\r | |
2505 | * successful.\r | |
2506 | * \r | |
2507 | * @param {Object} options.scope The scope in which to execute the callback\r | |
2508 | * functions. Defaults to the model instance.\r | |
2509 | *\r | |
2510 | * @param {Ext.data.Session} [session] The session for this record.\r | |
2511 | *\r | |
2512 | * @return {Ext.data.Model} The newly created model. Note that the model will\r | |
2513 | * (probably) still be loading once it is returned from this method. To do any\r | |
2514 | * post-processing on the data, the appropriate place to do see is in the\r | |
2515 | * callback.\r | |
2516 | * \r | |
2517 | * @static\r | |
2518 | * @inheritable\r | |
2519 | */\r | |
2520 | load: function(id, options, session) {\r | |
2521 | var data = {},\r | |
2522 | rec;\r | |
2523 | \r | |
2524 | data[this.prototype.idProperty] = id;\r | |
2525 | rec = new this(data, session);\r | |
2526 | \r | |
2527 | rec.load(options);\r | |
2528 | return rec;\r | |
2529 | }\r | |
2530 | },\r | |
2531 | \r | |
2532 | deprecated: {\r | |
2533 | 5: {\r | |
2534 | methods: {\r | |
2535 | hasId: null,\r | |
2536 | markDirty: null,\r | |
2537 | setDirty: null,\r | |
2538 | eachStore: function (callback, scope) {\r | |
2539 | var me = this,\r | |
2540 | stores = me.stores,\r | |
2541 | len = stores.length,\r | |
2542 | i;\r | |
2543 | \r | |
2544 | for (i = 0; i < len; ++i) {\r | |
2545 | callback.call(scope, stores[i]);\r | |
2546 | }\r | |
2547 | },\r | |
2548 | \r | |
2549 | join: function(item) {\r | |
2550 | var me = this,\r | |
2551 | stores = me.stores,\r | |
2552 | joined = me.joined;\r | |
2553 | \r | |
2554 | if (!joined) {\r | |
2555 | joined = me.joined = [item];\r | |
2556 | } else {\r | |
2557 | joined.push(item);\r | |
2558 | }\r | |
2559 | \r | |
2560 | if (item.isStore) {\r | |
2561 | me.store = me.store || item;\r | |
2562 | if (!stores) {\r | |
2563 | stores = me.stores = [];\r | |
2564 | }\r | |
2565 | stores.push(item);\r | |
2566 | }\r | |
2567 | },\r | |
2568 | \r | |
2569 | unjoin: function(item) {\r | |
2570 | var me = this,\r | |
2571 | stores = me.stores,\r | |
2572 | joined = me.joined;\r | |
2573 | \r | |
2574 | if (joined.length === 1) {\r | |
2575 | joined.length = 0;\r | |
2576 | } else {\r | |
2577 | Ext.Array.remove(joined, item);\r | |
2578 | }\r | |
2579 | \r | |
2580 | if (item.isStore) {\r | |
2581 | Ext.Array.remove(stores, item);\r | |
2582 | me.store = stores[0] || null;\r | |
2583 | }\r | |
2584 | }\r | |
2585 | },\r | |
2586 | properties: {\r | |
2587 | persistenceProperty: null\r | |
2588 | },\r | |
2589 | inheritableStatics: {\r | |
2590 | methods: {\r | |
2591 | setFields: null\r | |
2592 | }\r | |
2593 | }\r | |
2594 | }\r | |
2595 | },\r | |
2596 | \r | |
2597 | //-------------------------------------------------------------------------\r | |
2598 | privates: {\r | |
2599 | _commitOptions: {\r | |
2600 | commit: true\r | |
2601 | },\r | |
2602 | _getChangesOptions: {\r | |
2603 | changes: true\r | |
2604 | },\r | |
2605 | _getAssociatedOptions: {\r | |
2606 | associated: true\r | |
2607 | },\r | |
2608 | _getNotAssociatedOptions: {\r | |
2609 | associated: false\r | |
2610 | },\r | |
2611 | \r | |
2612 | /**\r | |
2613 | * Copies data from the passed record into this record. If the passed record is undefined, does nothing.\r | |
2614 | *\r | |
2615 | * If this is a phantom record (represented only in the client, with no corresponding database entry), and\r | |
2616 | * the source record is not a phantom, then this record acquires the id of the source record.\r | |
2617 | *\r | |
2618 | * @param {Ext.data.Model} sourceRecord The record to copy data from.\r | |
2619 | * @return {String[]} The names of the fields which changed value.\r | |
2620 | * @private\r | |
2621 | */\r | |
2622 | copyFrom: function (sourceRecord) {\r | |
2623 | var me = this,\r | |
2624 | fields = me.fields,\r | |
2625 | fieldCount = fields.length,\r | |
2626 | modifiedFieldNames = [],\r | |
2627 | field, i = 0,\r | |
2628 | myData,\r | |
2629 | sourceData,\r | |
2630 | idProperty = me.idProperty,\r | |
2631 | name,\r | |
2632 | value;\r | |
2633 | \r | |
2634 | if (sourceRecord) {\r | |
2635 | myData = me.data;\r | |
2636 | sourceData = sourceRecord.data;\r | |
2637 | for (; i < fieldCount; i++) {\r | |
2638 | field = fields[i];\r | |
2639 | name = field.name;\r | |
2640 | \r | |
2641 | // Do not use setters.\r | |
2642 | // Copy returned values in directly from the data object.\r | |
2643 | // Converters have already been called because new Records\r | |
2644 | // have been created to copy from.\r | |
2645 | // This is a direct record-to-record value copy operation.\r | |
2646 | // don't copy the id, we'll do it at the end\r | |
2647 | if (name !== idProperty) {\r | |
2648 | value = sourceData[name];\r | |
2649 | \r | |
2650 | // If source property is specified, and value is different\r | |
2651 | // copy field value in and build updatedFields\r | |
2652 | if (value !== undefined && !me.isEqual(myData[name], value)) {\r | |
2653 | myData[name] = value;\r | |
2654 | modifiedFieldNames.push(name);\r | |
2655 | }\r | |
2656 | }\r | |
2657 | }\r | |
2658 | \r | |
2659 | // If this is a phantom record being updated from a concrete record, copy the ID in.\r | |
2660 | if (me.phantom && !sourceRecord.phantom) {\r | |
2661 | // beginEdit to prevent events firing\r | |
2662 | // commit at the end to prevent dirty being set\r | |
2663 | me.beginEdit();\r | |
2664 | me.setId(sourceRecord.getId());\r | |
2665 | me.endEdit(true);\r | |
2666 | me.commit(true);\r | |
2667 | }\r | |
2668 | }\r | |
2669 | return modifiedFieldNames;\r | |
2670 | },\r | |
2671 | \r | |
2672 | /**\r | |
2673 | * Helper function used by afterEdit, afterReject and afterCommit. Calls the given\r | |
2674 | * method on the `Ext.data.Store` that this instance has {@link #join joined}, if any.\r | |
2675 | * The store function will always be called with the model instance as its single\r | |
2676 | * argument. If this model is joined to a Ext.data.NodeStore, then this method calls\r | |
2677 | * the given method on the NodeStore and the associated Ext.data.TreeStore.\r | |
2678 | * @param {String} funcName The name function to call on each store.\r | |
2679 | * @param {Array} [args] The arguments to pass to the method. This instance is\r | |
2680 | * always inserted as the first argument.\r | |
2681 | * @private\r | |
2682 | */\r | |
2683 | callJoined: function (funcName, args) {\r | |
2684 | var me = this,\r | |
2685 | joined = me.joined,\r | |
2686 | session = me.session,\r | |
2687 | i, len, fn, item;\r | |
2688 | \r | |
2689 | if (!joined && !session) {\r | |
2690 | return;\r | |
2691 | }\r | |
2692 | \r | |
2693 | if (args) {\r | |
2694 | args.unshift(me);\r | |
2695 | } else {\r | |
2696 | args = [me];\r | |
2697 | } \r | |
2698 | \r | |
2699 | if (joined) {\r | |
2700 | for (i = 0, len = joined.length; i < len; ++i) {\r | |
2701 | item = joined[i];\r | |
2702 | if (item && (fn = item[funcName])) {\r | |
2703 | fn.apply(item, args);\r | |
2704 | }\r | |
2705 | }\r | |
2706 | }\r | |
2707 | \r | |
2708 | fn = session && session[funcName];\r | |
2709 | if (fn) {\r | |
2710 | fn.apply(session, args);\r | |
2711 | }\r | |
2712 | },\r | |
2713 | \r | |
2714 | /**\r | |
2715 | * Set the session for this record.\r | |
2716 | * @param {Ext.data.Session} session The session\r | |
2717 | */\r | |
2718 | setSession: function(session) {\r | |
2719 | //<debug>\r | |
2720 | if (session) {\r | |
2721 | if (this.session) {\r | |
2722 | Ext.raise('This model already belongs to a session.');\r | |
2723 | }\r | |
2724 | if (!this.id) {\r | |
2725 | Ext.raise('The model must have an id to participate in a session.');\r | |
2726 | }\r | |
2727 | }\r | |
2728 | //</debug>\r | |
2729 | this.session = session;\r | |
2730 | if (session) {\r | |
2731 | session.add(this);\r | |
2732 | }\r | |
2733 | },\r | |
2734 | \r | |
2735 | /**\r | |
2736 | * Gets the names of all the fields that were modified during an edit.\r | |
2737 | * @param {Object} [old] The saved data from `beginEdit`.\r | |
2738 | * @return {String[]} The array of modified field names.\r | |
2739 | * @private\r | |
2740 | */\r | |
2741 | getModifiedFieldNames: function (old) {\r | |
2742 | var me = this,\r | |
2743 | data = me.data,\r | |
2744 | modified = [],\r | |
2745 | oldData = old || me.editMemento.data,\r | |
2746 | key;\r | |
2747 | \r | |
2748 | for (key in data) {\r | |
2749 | if (data.hasOwnProperty(key)) {\r | |
2750 | if (!me.isEqual(data[key], oldData[key], key)) {\r | |
2751 | modified.push(key);\r | |
2752 | }\r | |
2753 | }\r | |
2754 | }\r | |
2755 | \r | |
2756 | return modified;\r | |
2757 | },\r | |
2758 | \r | |
2759 | /**\r | |
2760 | * Checks if two values are equal, taking into account certain special factors, for\r | |
2761 | * example dates.\r | |
2762 | * @param {Object} lhs The first value.\r | |
2763 | * @param {Object} rhs The second value.\r | |
2764 | * @return {Boolean} True if the values are equal.\r | |
2765 | * @private\r | |
2766 | */\r | |
2767 | isEqual: function (lhs, rhs, field) {\r | |
2768 | var f;\r | |
2769 | \r | |
2770 | if (field) {\r | |
2771 | f = field.isField ? field : this.fieldsMap[field];\r | |
2772 | if (f) {\r | |
2773 | return f.isEqual(lhs, rhs);\r | |
2774 | }\r | |
2775 | }\r | |
2776 | \r | |
2777 | // instanceof is ~10 times faster then Ext.isDate. Values here will not be\r | |
2778 | // cross-document objects\r | |
2779 | if (lhs instanceof Date && rhs instanceof Date) {\r | |
2780 | return lhs.getTime() === rhs.getTime();\r | |
2781 | }\r | |
2782 | return lhs === rhs;\r | |
2783 | },\r | |
2784 | \r | |
2785 | statics: {\r | |
2786 | /**\r | |
2787 | * @property\r | |
2788 | * @static\r | |
2789 | * @private\r | |
2790 | * @readonly\r | |
2791 | * @deprecated\r | |
2792 | * The update operation of type 'edit'. Used by {@link Ext.data.Store#event-update Store.update} event.\r | |
2793 | */\r | |
2794 | EDIT : 'edit',\r | |
2795 | /**\r | |
2796 | * @property\r | |
2797 | * @static\r | |
2798 | * @private\r | |
2799 | * @readonly\r | |
2800 | * @deprecated\r | |
2801 | * The update operation of type 'reject'. Used by {@link Ext.data.Store#event-update Store.update} event.\r | |
2802 | */\r | |
2803 | REJECT : 'reject',\r | |
2804 | /**\r | |
2805 | * @property\r | |
2806 | * @static\r | |
2807 | * @private\r | |
2808 | * @readonly\r | |
2809 | * @deprecated\r | |
2810 | * The update operation of type 'commit'. Used by {@link Ext.data.Store#event-update Store.update} event.\r | |
2811 | */\r | |
2812 | COMMIT : 'commit',\r | |
2813 | \r | |
2814 | /**\r | |
2815 | * @property {String/Object}\r | |
2816 | * @static\r | |
2817 | * @protected\r | |
2818 | * The default proxy to use for instances of this Model when no proxy is configured\r | |
2819 | * on the instance. When specified, the model will use this proxy instead of\r | |
2820 | * requesting one from the {@link Ext.data.Session Session}.\r | |
2821 | *\r | |
2822 | * Can be a string "type", or a {@link Ext.data.proxy.Proxy Proxy} config object.\r | |
2823 | *\r | |
2824 | * This proxy is not inherited by subclasses.\r | |
2825 | */\r | |
2826 | defaultProxy: 'memory',\r | |
2827 | \r | |
2828 | rankFields: function () {\r | |
2829 | var cls = this,\r | |
2830 | prototype = cls.prototype,\r | |
2831 | fields = cls.fields,\r | |
2832 | length = fields.length,\r | |
2833 | rankedFields = [],\r | |
2834 | criticalFields = [],\r | |
2835 | transientFields = [],\r | |
2836 | evilFields, field, i;\r | |
2837 | \r | |
2838 | cls.rankedFields = prototype.rankedFields = rankedFields;\r | |
2839 | cls.criticalFields = prototype.criticalFields = criticalFields;\r | |
2840 | cls.transientFields = prototype.transientFields = transientFields;\r | |
2841 | \r | |
2842 | // This first pass brings over any fields that have no dependencies at all\r | |
2843 | // and gathers the evil fields to the side (the fields that could depend on\r | |
2844 | // anything). This avoids the call to topoAdd that we must perform on all of\r | |
2845 | // the fields that do have depends (which is good since most fields will be\r | |
2846 | // handled here).\r | |
2847 | for (i = 0; i < length; ++i) {\r | |
2848 | field = fields[i];\r | |
2849 | if (field.critical) {\r | |
2850 | criticalFields.push(field);\r | |
2851 | }\r | |
2852 | if (!field.persist) {\r | |
2853 | transientFields.push(field);\r | |
2854 | }\r | |
2855 | if (field.evil) {\r | |
2856 | (evilFields || (evilFields = [])).push(field);\r | |
2857 | } else if (!field.depends) {\r | |
2858 | rankedFields.push(field);\r | |
2859 | field.rank = rankedFields.length; // 1-based\r | |
2860 | }\r | |
2861 | }\r | |
2862 | \r | |
2863 | for (i = 0; i < length; ++i) {\r | |
2864 | if (!(field = fields[i]).rank && !field.evil) {\r | |
2865 | cls.topoAdd(field);\r | |
2866 | }\r | |
2867 | }\r | |
2868 | \r | |
2869 | if (evilFields) {\r | |
2870 | for (i = 0, length = evilFields.length; i < length; ++i) {\r | |
2871 | rankedFields.push(field = evilFields[i]);\r | |
2872 | field.rank = rankedFields.length; // 1-based\r | |
2873 | }\r | |
2874 | }\r | |
2875 | \r | |
2876 | //<debug>\r | |
2877 | cls.topoStack = null; // cleanup diagnostic stack\r | |
2878 | //</debug>\r | |
2879 | \r | |
2880 | return rankedFields;\r | |
2881 | },\r | |
2882 | \r | |
2883 | topoAdd: function (field) {\r | |
2884 | var cls = this,\r | |
2885 | dep = field.depends,\r | |
2886 | dependsLength = dep ? dep.length : 0,\r | |
2887 | rankedFields = cls.rankedFields,\r | |
2888 | i, targetField;\r | |
2889 | \r | |
2890 | //<debug>\r | |
2891 | var topoStack = cls.topoStack || (cls.topoStack = []);\r | |
2892 | topoStack.push(field.name);\r | |
2893 | \r | |
2894 | if (field.rank === 0) { // if (adding)\r | |
2895 | Ext.raise(cls.$className + " has circular field dependencies: " +\r | |
2896 | topoStack.join(" --> "));\r | |
2897 | }\r | |
2898 | \r | |
2899 | if (topoStack.length && field.evil) {\r | |
2900 | Ext.raise(cls.$className + ": Field " +\r | |
2901 | topoStack[topoStack.length - 1] +\r | |
2902 | " cannot depend on depends-less field " + field.name);\r | |
2903 | }\r | |
2904 | \r | |
2905 | field.rank = 0; // adding (falsey but we can still detect cycles)\r | |
2906 | //</debug>\r | |
2907 | \r | |
2908 | for (i = 0; i < dependsLength; ++i) {\r | |
2909 | // Get the targetField on which we depend and add this field to the\r | |
2910 | // targetField.dependents[]\r | |
2911 | targetField = cls.fieldsMap[dep[i]];\r | |
2912 | //<debug>\r | |
2913 | if (!targetField) {\r | |
2914 | Ext.raise(cls.$className + ": Field " + field.name + " depends on undefined field " + dep[i]);\r | |
2915 | }\r | |
2916 | //</debug>\r | |
2917 | (targetField.dependents || (targetField.dependents = [])).push(field);\r | |
2918 | \r | |
2919 | if (!targetField.rank) { // if (!added)\r | |
2920 | cls.topoAdd(targetField);\r | |
2921 | }\r | |
2922 | }\r | |
2923 | \r | |
2924 | rankedFields.push(field);\r | |
2925 | field.rank = rankedFields.length; // 1-based (truthy to track "added" state)\r | |
2926 | \r | |
2927 | //<debug>\r | |
2928 | topoStack.pop();\r | |
2929 | //</debug>\r | |
2930 | },\r | |
2931 | \r | |
2932 | initFields: function(data, cls, proto) {\r | |
2933 | var Field = Ext.data.field.Field,\r | |
2934 | fieldDefs = data.fields,\r | |
2935 | // allocate fields [] and ordinals {} for the new class:\r | |
2936 | fields = [],\r | |
2937 | fieldOrdinals = {},\r | |
2938 | fieldsMap = {},\r | |
2939 | references = [],\r | |
2940 | superFields = proto.fields,\r | |
2941 | versionProperty = data.versionProperty || proto.versionProperty,\r | |
2942 | idProperty = cls.idProperty,\r | |
2943 | idField, field, i, length, name, ordinal, \r | |
2944 | reference, superIdField, superIdFieldName, idDeclared;\r | |
2945 | \r | |
2946 | // Process any inherited fields to produce a fields [] and ordinals {} for\r | |
2947 | // this class:\r | |
2948 | cls.fields = proto.fields = fields;\r | |
2949 | cls.fieldOrdinals = proto.fieldOrdinals = fieldOrdinals;\r | |
2950 | cls.fieldsMap = proto.fieldsMap = fieldsMap;\r | |
2951 | cls.references = proto.references = references;\r | |
2952 | \r | |
2953 | if (superFields) {\r | |
2954 | // We chain the super field so we can write to it\r | |
2955 | for (i = 0, length = superFields.length; i < length; ++i) {\r | |
2956 | fields[i] = field = Ext.Object.chain(superFields[i]);\r | |
2957 | \r | |
2958 | field.dependents = null; // we need to recalculate these\r | |
2959 | field.owner = cls;\r | |
2960 | fieldOrdinals[name = field.name] = i;\r | |
2961 | fieldsMap[name] = field;\r | |
2962 | // Clear the rank because it needs to be set on the first pass through\r | |
2963 | // the fields in the subclass, don't inherit it from the parent\r | |
2964 | field.rank = null;\r | |
2965 | \r | |
2966 | if (field.generated) {\r | |
2967 | superIdField = field;\r | |
2968 | superIdFieldName = field.name;\r | |
2969 | }\r | |
2970 | }\r | |
2971 | }\r | |
2972 | \r | |
2973 | // Merge in any fields from this class:\r | |
2974 | if (fieldDefs) {\r | |
2975 | delete data.fields;\r | |
2976 | for (i = 0, length = fieldDefs.length; i < length; ++i) {\r | |
2977 | field = fieldDefs[i];\r | |
2978 | reference = field.reference;\r | |
2979 | // Create a copy of the reference since we'll modify\r | |
2980 | // the reference on the field. Needed for subclasses\r | |
2981 | if (reference && typeof reference !== 'string') {\r | |
2982 | // Can have child objects, so merge it deeply\r | |
2983 | reference = Ext.merge({}, reference);\r | |
2984 | }\r | |
2985 | field.$reference = reference;\r | |
2986 | field = Field.create(fieldDefs[i]);\r | |
2987 | name = field.name;\r | |
2988 | ordinal = fieldOrdinals[name];\r | |
2989 | if (ordinal === undefined) {\r | |
2990 | // If the field is new, add it to the end of the fields[]\r | |
2991 | fieldOrdinals[name] = ordinal = fields.length;\r | |
2992 | }\r | |
2993 | // else, overwrite the field at the established ordinal\r | |
2994 | \r | |
2995 | fieldsMap[name] = field;\r | |
2996 | fields[ordinal] = field;\r | |
2997 | field.definedBy = field.owner = cls;\r | |
2998 | field.ordinal = ordinal;\r | |
2999 | if (name === idProperty) {\r | |
3000 | idDeclared = field;\r | |
3001 | }\r | |
3002 | }\r | |
3003 | }\r | |
3004 | \r | |
3005 | // Lookup the idProperty in the ordinals map and create a synthetic field if\r | |
3006 | // we don't have one.\r | |
3007 | idField = fieldsMap[idProperty];\r | |
3008 | if (!idField) {\r | |
3009 | if (superIdField && superIdField.generated) {\r | |
3010 | ordinal = superIdField.ordinal;\r | |
3011 | } else {\r | |
3012 | ordinal = fields.length;\r | |
3013 | }\r | |
3014 | delete fieldsMap[superIdFieldName];\r | |
3015 | delete fieldOrdinals[superIdFieldName]\r | |
3016 | idField = new Field(idProperty);\r | |
3017 | fields[ordinal] = idField;\r | |
3018 | fieldOrdinals[idProperty] = ordinal;\r | |
3019 | fieldsMap[idProperty] = idField;\r | |
3020 | idField.definedBy = cls;\r | |
3021 | idField.ordinal = ordinal;\r | |
3022 | idField.generated = true;\r | |
3023 | } else if (idDeclared && superIdField && superIdField.generated) {\r | |
3024 | // If we're declaring the id as a field in our fields array and it's different to\r | |
3025 | // the super id field that has been generated, pull it out and fix up the ordinals. This\r | |
3026 | // likely won't happen often, to do it earlier we would need to know the contents of the fields\r | |
3027 | // which would mean iterating over them twice.\r | |
3028 | Ext.Array.remove(fields, superIdField);\r | |
3029 | delete fieldsMap[superIdFieldName];\r | |
3030 | delete fieldOrdinals[superIdFieldName];\r | |
3031 | fieldsMap[idProperty] = idDeclared;\r | |
3032 | for (i = 0, length = fields.length; i < length; ++i) {\r | |
3033 | field = fields[i];\r | |
3034 | fields.ordinal = i;\r | |
3035 | fieldOrdinals[field.name] = i;\r | |
3036 | }\r | |
3037 | }\r | |
3038 | \r | |
3039 | idField.allowNull = idField.critical = idField.identifier = true;\r | |
3040 | idField.defaultValue = null;\r | |
3041 | \r | |
3042 | cls.idField = proto.idField = idField;\r | |
3043 | \r | |
3044 | if (versionProperty) {\r | |
3045 | field = fieldsMap[versionProperty];\r | |
3046 | if (!field) {\r | |
3047 | ordinal = fields.length;\r | |
3048 | field = new Field({\r | |
3049 | name: versionProperty,\r | |
3050 | type: 'int'\r | |
3051 | });\r | |
3052 | fields[ordinal] = field;\r | |
3053 | fieldOrdinals[versionProperty] = ordinal;\r | |
3054 | fieldsMap[versionProperty] = field;\r | |
3055 | field.definedBy = cls;\r | |
3056 | field.ordinal = ordinal;\r | |
3057 | field.generated = true;\r | |
3058 | }\r | |
3059 | field.defaultValue = 1;\r | |
3060 | field.critical = true;\r | |
3061 | }\r | |
3062 | \r | |
3063 | // NOTE: Be aware that the one fellow that manipulates these after this\r | |
3064 | // point is Ext.data.NodeInterface.\r | |
3065 | },\r | |
3066 | \r | |
3067 | initValidators: function(data, cls, proto) {\r | |
3068 | var superValidators = proto.validators,\r | |
3069 | validators, field, copy, validatorDefs,\r | |
3070 | i, length, fieldValidator, name, validator, item;\r | |
3071 | \r | |
3072 | if (superValidators) {\r | |
3073 | validators = {};\r | |
3074 | for (field in superValidators) {\r | |
3075 | validators[field] = Ext.Array.clone(superValidators[field]);\r | |
3076 | }\r | |
3077 | }\r | |
3078 | \r | |
3079 | validatorDefs = data.validators || data.validations;\r | |
3080 | //<debug>\r | |
3081 | if (data.validations) {\r | |
3082 | delete data.validations;\r | |
3083 | Ext.log.warn((cls.$className || 'Ext.data.Model' ) +\r | |
3084 | ': validations has been deprecated. Please use validators instead.');\r | |
3085 | }\r | |
3086 | //</debug>\r | |
3087 | if (validatorDefs) {\r | |
3088 | delete data.validators;\r | |
3089 | \r | |
3090 | validators = validators || {};\r | |
3091 | \r | |
3092 | // Support older array syntax\r | |
3093 | if (Ext.isArray(validatorDefs)) {\r | |
3094 | copy = {};\r | |
3095 | for (i = 0, length = validatorDefs.length; i < length; ++i) {\r | |
3096 | item = validatorDefs[i];\r | |
3097 | name = item.field;\r | |
3098 | if (!copy[name]) {\r | |
3099 | copy[name] = [];\r | |
3100 | }\r | |
3101 | // Check for function form\r | |
3102 | item = item.fn || item;\r | |
3103 | copy[name].push(item);\r | |
3104 | }\r | |
3105 | validatorDefs = copy;\r | |
3106 | }\r | |
3107 | \r | |
3108 | for (name in validatorDefs) {\r | |
3109 | fieldValidator = validatorDefs[name];\r | |
3110 | if (!Ext.isArray(fieldValidator)) {\r | |
3111 | fieldValidator = [fieldValidator];\r | |
3112 | }\r | |
3113 | \r | |
3114 | validator = validators[name];\r | |
3115 | if (validators[name]) {\r | |
3116 | // Declared in super\r | |
3117 | Ext.Array.push(validator, fieldValidator);\r | |
3118 | } else {\r | |
3119 | validators[name] = fieldValidator;\r | |
3120 | }\r | |
3121 | }\r | |
3122 | }\r | |
3123 | if (validators) {\r | |
3124 | for (name in validators) {\r | |
3125 | field = cls.getField(name);\r | |
3126 | if (field) {\r | |
3127 | field.setModelValidators(validators[name]);\r | |
3128 | }\r | |
3129 | }\r | |
3130 | }\r | |
3131 | cls.validators = proto.validators = validators;\r | |
3132 | },\r | |
3133 | \r | |
3134 | initAssociations: function (schema, data, cls) {\r | |
3135 | // The 4 legacy associations:\r | |
3136 | var associations = data.associations,\r | |
3137 | belongsTo = data.belongsTo,\r | |
3138 | hasMany = data.hasMany,\r | |
3139 | hasOne = data.hasOne,\r | |
3140 | // The new one:\r | |
3141 | matrices = data.manyToMany,\r | |
3142 | i, length, assoc;\r | |
3143 | \r | |
3144 | //<debug>\r | |
3145 | if (data.belongsTo) {\r | |
3146 | Ext.log.warn('Use of "belongsTo" is obsolete' +\r | |
3147 | (cls.$className ? ' in ' + cls.$className : ''));\r | |
3148 | delete data.belongsTo;\r | |
3149 | }\r | |
3150 | //</debug>\r | |
3151 | \r | |
3152 | delete data.manyToMany;\r | |
3153 | if (matrices) {\r | |
3154 | schema.addMatrices(cls, matrices);\r | |
3155 | }\r | |
3156 | \r | |
3157 | // Legacy:\r | |
3158 | delete data.associations;\r | |
3159 | delete data.belongsTo;\r | |
3160 | delete data.hasMany;\r | |
3161 | delete data.hasOne;\r | |
3162 | \r | |
3163 | if (associations) {\r | |
3164 | associations = Ext.isArray(associations) ? associations : [ associations ];\r | |
3165 | for (i = 0, length = associations.length; i < length; ++i) {\r | |
3166 | assoc = associations[i];\r | |
3167 | switch (assoc.type) {\r | |
3168 | case 'belongsTo':\r | |
3169 | schema.addLegacyBelongsTo(cls, assoc);\r | |
3170 | break;\r | |
3171 | case 'hasMany':\r | |
3172 | schema.addLegacyHasMany(cls, assoc);\r | |
3173 | break;\r | |
3174 | case 'hasOne':\r | |
3175 | schema.addLegacyHasOne(cls, assoc);\r | |
3176 | break;\r | |
3177 | \r | |
3178 | //<debug>\r | |
3179 | default:\r | |
3180 | Ext.raise('Invalid association type: "' + assoc.type + '"');\r | |
3181 | //</debug>\r | |
3182 | }\r | |
3183 | }\r | |
3184 | }\r | |
3185 | \r | |
3186 | if (belongsTo) {\r | |
3187 | belongsTo = Ext.isArray(belongsTo) ? belongsTo : [ belongsTo ];\r | |
3188 | for (i = 0, length = belongsTo.length; i < length; ++i) {\r | |
3189 | schema.addLegacyBelongsTo(cls, belongsTo[i]);\r | |
3190 | }\r | |
3191 | }\r | |
3192 | \r | |
3193 | if (hasMany) {\r | |
3194 | hasMany = Ext.isArray(hasMany) ? hasMany : [ hasMany ];\r | |
3195 | for (i = 0, length = hasMany.length; i < length; ++i) {\r | |
3196 | schema.addLegacyHasMany(cls, hasMany[i]);\r | |
3197 | }\r | |
3198 | }\r | |
3199 | \r | |
3200 | if (hasOne) {\r | |
3201 | hasOne = Ext.isArray(hasOne) ? hasOne : [ hasOne ];\r | |
3202 | for (i = 0, length = hasOne.length; i < length; ++i) {\r | |
3203 | schema.addLegacyHasOne(cls, hasOne[i]);\r | |
3204 | }\r | |
3205 | }\r | |
3206 | schema.afterLegacyAssociations(cls);\r | |
3207 | },\r | |
3208 | \r | |
3209 | initIdentifier: function (data, cls, proto) {\r | |
3210 | var identifier = data.identifier || data.idgen,\r | |
3211 | superIdent = proto.identifier || cls.schema._defaultIdentifier,\r | |
3212 | generatorPrefix;\r | |
3213 | \r | |
3214 | //<debug>\r | |
3215 | if (data.idgen) {\r | |
3216 | Ext.log.warn('Ext.data.Model: idgen has been deprecated. Please use identifier instead.');\r | |
3217 | }\r | |
3218 | //</debug>\r | |
3219 | \r | |
3220 | if (identifier) {\r | |
3221 | delete data.identifier;\r | |
3222 | delete data.idgen;\r | |
3223 | \r | |
3224 | // An idgen was specified on the definition, use it explicitly.\r | |
3225 | identifier = Ext.Factory.dataIdentifier(identifier);\r | |
3226 | } else if (superIdent) {\r | |
3227 | // If we have a cloneable instance, and we don't have an id\r | |
3228 | // clone it. If we have an id, then we should use the same\r | |
3229 | // instance since it's the same as looking it up via id.\r | |
3230 | if (superIdent.clone && !superIdent.getId()) {\r | |
3231 | identifier = superIdent.clone();\r | |
3232 | } else if (superIdent.isGenerator) {\r | |
3233 | identifier = superIdent;\r | |
3234 | } else {\r | |
3235 | identifier = Ext.Factory.dataIdentifier(superIdent);\r | |
3236 | }\r | |
3237 | }\r | |
3238 | \r | |
3239 | cls.identifier = proto.identifier = identifier;\r | |
3240 | \r | |
3241 | if (!identifier) {\r | |
3242 | // If we didn't find one, create it and push it onto the class.\r | |
3243 | // Don't put it on the prototype, so a subclass will create\r | |
3244 | // it's own generator. If we have an anonymous model, go ahead and\r | |
3245 | // generate a unique prefix for it.\r | |
3246 | generatorPrefix = cls.entityName;\r | |
3247 | if (!generatorPrefix) {\r | |
3248 | generatorPrefix = Ext.id(null, 'extModel');\r | |
3249 | }\r | |
3250 | cls.identifier = Ext.Factory.dataIdentifier({\r | |
3251 | type: 'sequential',\r | |
3252 | prefix: generatorPrefix + '-'\r | |
3253 | });\r | |
3254 | }\r | |
3255 | },\r | |
3256 | \r | |
3257 | findValidator: function(validators, name, cfg) {\r | |
3258 | var type = cfg.type || cfg,\r | |
3259 | field = validators[name],\r | |
3260 | len, i, item;\r | |
3261 | \r | |
3262 | if (field) {\r | |
3263 | for (i = 0, len = field.length; i < len; ++i) {\r | |
3264 | item = field[i];\r | |
3265 | if (item.type === type) {\r | |
3266 | return item;\r | |
3267 | }\r | |
3268 | }\r | |
3269 | }\r | |
3270 | return null;\r | |
3271 | },\r | |
3272 | \r | |
3273 | /**\r | |
3274 | * This method produces the `initializeFn` for this class. If there are no fields\r | |
3275 | * requiring {@link Ext.data.field.Field#cfg-convert conversion} and no fields requiring\r | |
3276 | * a {@link Ext.data.field.Field#defaultValue default value} then this method will\r | |
3277 | * return `null`.\r | |
3278 | * @return {Function} The `initializeFn` for this class (or null).\r | |
3279 | * @private\r | |
3280 | */\r | |
3281 | makeInitializeFn: function (cls) {\r | |
3282 | var code = ['var '],\r | |
3283 | body = ['\nreturn function (e) {\n var data = e.data, v;\n'],\r | |
3284 | fieldVars = [],\r | |
3285 | work = 0,\r | |
3286 | bc, ec, // == beginClone, endClone\r | |
3287 | convert, expr, factory, field, fields, fs, hasDefValue, i, length;\r | |
3288 | \r | |
3289 | if (!(fields = cls.rankedFields)) {\r | |
3290 | // On the first edit of a record of this type we need to ensure we have the\r | |
3291 | // topo-sort done:\r | |
3292 | fields = cls.rankFields();\r | |
3293 | }\r | |
3294 | \r | |
3295 | for (i = 0, length = fields.length; i < length; ++i) {\r | |
3296 | // The generated method declares vars for each field using "f0".."fN' as the\r | |
3297 | // name. These are used to access properties of the field (e.g., the convert\r | |
3298 | // method or defaultValue).\r | |
3299 | field = fields[i];\r | |
3300 | fieldVars[i] = fs = 'f' + i;\r | |
3301 | convert = field.convert;\r | |
3302 | \r | |
3303 | if (i) {\r | |
3304 | code.push(', \n ');\r | |
3305 | }\r | |
3306 | code.push(fs, ' = $fields[' + i + ']');\r | |
3307 | //<debug>\r | |
3308 | // this can be helpful when debugging (at least in Chrome):\r | |
3309 | code.push(' /* ', field.name, ' */');\r | |
3310 | //</debug>\r | |
3311 | \r | |
3312 | // NOTE: added string literals are "folded" by the compiler so we\r | |
3313 | // are better off doing an "'foo' + 'bar'" then "'foo', 'bar'". But\r | |
3314 | // for variables we are better off pushing them into the array for\r | |
3315 | // the final join.\r | |
3316 | \r | |
3317 | if ((hasDefValue = (field.defaultValue !== undefined)) || convert) {\r | |
3318 | // For non-calculated fields that have some work required (a convert method\r | |
3319 | // and/or defaultValue), generate a chunk of logic appropriate for the\r | |
3320 | // field.\r | |
3321 | //expr = data["fieldName"];\r | |
3322 | expr = 'data["' + field.name + '"]';\r | |
3323 | ++work;\r | |
3324 | \r | |
3325 | bc = ec = '';\r | |
3326 | if (field.cloneDefaultValue) {\r | |
3327 | bc = 'Ext.clone(';\r | |
3328 | ec = ')';\r | |
3329 | }\r | |
3330 | \r | |
3331 | body.push('\n');\r | |
3332 | if (convert && hasDefValue) {\r | |
3333 | // v = data.fieldName;\r | |
3334 | // if (v !== undefined) {\r | |
3335 | // v = f2.convert(v, e);\r | |
3336 | // }\r | |
3337 | // if (v === undefined) {\r | |
3338 | // v = f2.defaultValue;\r | |
3339 | // // or\r | |
3340 | // v = Ext.clone(f2.defaultValue);\r | |
3341 | // }\r | |
3342 | // data.fieldName = v;\r | |
3343 | //\r | |
3344 | body.push(' v = ', expr, ';\n' +\r | |
3345 | ' if (v !== undefined) {\n' +\r | |
3346 | ' v = ', fs, '.convert(v, e);\n' +\r | |
3347 | ' }\n' +\r | |
3348 | ' if (v === undefined) {\n' +\r | |
3349 | ' v = ', bc, fs, '.defaultValue',ec,';\n' +\r | |
3350 | ' }\n' +\r | |
3351 | ' ', expr, ' = v;');\r | |
3352 | } else if (convert) { // no defaultValue\r | |
3353 | // v = f2.convert(data.fieldName,e);\r | |
3354 | // if (v !== undefined) {\r | |
3355 | // data.fieldName = v;\r | |
3356 | // }\r | |
3357 | //\r | |
3358 | body.push(' v = ', fs, '.convert(', expr, ',e);\n' +\r | |
3359 | ' if (v !== undefined) {\n' +\r | |
3360 | ' ', expr, ' = v;\n' +\r | |
3361 | ' }\n');\r | |
3362 | } else if (hasDefValue) { // no convert\r | |
3363 | // if (data.fieldName === undefined) {\r | |
3364 | // data.fieldName = f2.defaultValue;\r | |
3365 | // // or\r | |
3366 | // data.fieldName = Ext.clone(f2.defaultValue);\r | |
3367 | // }\r | |
3368 | //\r | |
3369 | body.push(' if (', expr, ' === undefined) {\n' +\r | |
3370 | ' ', expr, ' = ',bc,fs,'.defaultValue',ec,';\n' +\r | |
3371 | ' }\n');\r | |
3372 | }\r | |
3373 | }\r | |
3374 | }\r | |
3375 | \r | |
3376 | if (!work) {\r | |
3377 | // There are no fields that need special processing\r | |
3378 | return Ext.emptyFn;\r | |
3379 | }\r | |
3380 | \r | |
3381 | code.push(';\n');\r | |
3382 | code.push.apply(code, body);\r | |
3383 | code.push('}');\r | |
3384 | code = code.join('');\r | |
3385 | \r | |
3386 | // Ensure that Ext in the function code refers to the same Ext that we are using here.\r | |
3387 | // If we are in a sandbox, global.Ext might be different.\r | |
3388 | factory = new Function('$fields', 'Ext', code);\r | |
3389 | \r | |
3390 | return factory(fields, Ext);\r | |
3391 | }\r | |
3392 | } // static\r | |
3393 | } // privates\r | |
3394 | },\r | |
3395 | function () {\r | |
3396 | var Model = this,\r | |
3397 | proto = Model.prototype,\r | |
3398 | Schema = Ext.data.schema.Schema,\r | |
3399 | defaultSchema;\r | |
3400 | \r | |
3401 | Model.proxyConfig = proto.proxy;\r | |
3402 | delete proto.proxy;\r | |
3403 | \r | |
3404 | // Base Model class may be used. It needs an empty fields array.\r | |
3405 | Model.fields = [];\r | |
3406 | \r | |
3407 | // Base Model class may be used. It needs an empty fieldsMap hash.\r | |
3408 | Model.fieldsMap = proto.fieldsMap = {};\r | |
3409 | \r | |
3410 | Model.schema = proto.schema = Schema.get(proto.schema);\r | |
3411 | proto.idField = new Ext.data.field.Field(proto.idProperty);\r | |
3412 | Model.identifier = new Ext.data.identifier.Sequential();\r | |
3413 | \r | |
3414 | Model.onExtended(function (cls, data) {\r | |
3415 | var proto = cls.prototype,\r | |
3416 | schemaName = data.schema,\r | |
3417 | superCls = proto.superclass.self,\r | |
3418 | schema, entityName, proxy;\r | |
3419 | \r | |
3420 | cls.idProperty = data.idProperty || proto.idProperty;\r | |
3421 | \r | |
3422 | if (schemaName) {\r | |
3423 | delete data.schema;\r | |
3424 | schema = Schema.get(schemaName);\r | |
3425 | } else if (!(schema = proto.schema)) {\r | |
3426 | schema = defaultSchema || (defaultSchema = Schema.get('default'));\r | |
3427 | }\r | |
3428 | \r | |
3429 | // These are in "privates" so we manually make them inherited:\r | |
3430 | cls.rankFields = Model.rankFields;\r | |
3431 | cls.topoAdd = Model.topoAdd;\r | |
3432 | \r | |
3433 | // if we picked up a schema from cls.prototype.schema, it is because it was found\r | |
3434 | // in the prototype chain on a base class.\r | |
3435 | proto.schema = cls.schema = schema;\r | |
3436 | \r | |
3437 | // Unless specified on the declaration data, we need to provide the entityName of\r | |
3438 | // the new Entity-derived class. Store it on the prototype and the class.\r | |
3439 | if (!(entityName = data.entityName)) {\r | |
3440 | proto.entityName = entityName = schema.getEntityName(cls);\r | |
3441 | //<debug>\r | |
3442 | if (!entityName) {\r | |
3443 | if (data.associations) {\r | |
3444 | Ext.raise('Anonymous entities cannot specify "associations"');\r | |
3445 | }\r | |
3446 | if (data.belongsTo) {\r | |
3447 | Ext.raise('Anonymous entities cannot specify "belongsTo"');\r | |
3448 | }\r | |
3449 | if (data.hasMany) {\r | |
3450 | Ext.raise('Anonymous entities cannot specify "hasMany"');\r | |
3451 | }\r | |
3452 | if (data.hasOne) {\r | |
3453 | Ext.raise('Anonymous entities cannot specify "hasOne"');\r | |
3454 | }\r | |
3455 | if (data.matrices) {\r | |
3456 | Ext.raise('Anonymous entities cannot specify "manyToMany"');\r | |
3457 | }\r | |
3458 | }\r | |
3459 | //</debug>\r | |
3460 | }\r | |
3461 | cls.entityName = entityName;\r | |
3462 | cls.fieldExtractors = {};\r | |
3463 | \r | |
3464 | Model.initIdentifier(data, cls, proto);\r | |
3465 | Model.initFields(data, cls, proto);\r | |
3466 | Model.initValidators(data, cls, proto);\r | |
3467 | \r | |
3468 | // This is a compat hack to allow "rec.fields.items" to work as it used to when\r | |
3469 | // fields was a MixedCollection\r | |
3470 | cls.fields.items = cls.fields;\r | |
3471 | \r | |
3472 | if (entityName) {\r | |
3473 | schema.addEntity(cls);\r | |
3474 | Model.initAssociations(schema, data, cls);\r | |
3475 | }\r | |
3476 | \r | |
3477 | proxy = data.proxy;\r | |
3478 | if (proxy) {\r | |
3479 | delete data.proxy;\r | |
3480 | } else if (superCls !== Model) {\r | |
3481 | proxy = superCls.proxyConfig || superCls.proxy;\r | |
3482 | }\r | |
3483 | \r | |
3484 | cls.proxyConfig = proxy;\r | |
3485 | });\r | |
3486 | });\r |