]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/data/Model.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / data / Model.js
CommitLineData
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
314Ext.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
3395function () {\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