Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / source / Model.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5   <title>The source code</title>
6   <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
8   <style type="text/css">
9     .highlight { display: block; background-color: #ddd; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js"><span id='Ext-data-Model-method-constructor'><span id='Ext-data-Model'>/**
19 </span></span> * @author Ed Spencer
20  *
21  * A Model represents some object that your application manages. For example, one might define a Model for Users,
22  * Products, Cars, or any other real-world object that we want to model in the system. Models are registered via the
23  * {@link Ext.ModelManager model manager}, and are used by {@link Ext.data.Store stores}, which are in turn used by many
24  * of the data-bound components in Ext.
25  *
26  * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:
27  *
28  *     Ext.define('User', {
29  *         extend: 'Ext.data.Model',
30  *         fields: [
31  *             {name: 'name',  type: 'string'},
32  *             {name: 'age',   type: 'int'},
33  *             {name: 'phone', type: 'string'},
34  *             {name: 'alive', type: 'boolean', defaultValue: true}
35  *         ],
36  *
37  *         changeName: function() {
38  *             var oldName = this.get('name'),
39  *                 newName = oldName + &quot; The Barbarian&quot;;
40  *
41  *             this.set('name', newName);
42  *         }
43  *     });
44  *
45  * The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link
46  * Ext.ModelManager ModelManager}, and all other functions and properties are copied to the new Model's prototype.
47  *
48  * Now we can create instances of our User model and call any model logic we defined:
49  *
50  *     var user = Ext.create('User', {
51  *         name : 'Conan',
52  *         age  : 24,
53  *         phone: '555-555-5555'
54  *     });
55  *
56  *     user.changeName();
57  *     user.get('name'); //returns &quot;Conan The Barbarian&quot;
58  *
59  * # Validations
60  *
61  * Models have built-in support for validations, which are executed against the validator functions in {@link
62  * Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to
63  * models:
64  *
65  *     Ext.define('User', {
66  *         extend: 'Ext.data.Model',
67  *         fields: [
68  *             {name: 'name',     type: 'string'},
69  *             {name: 'age',      type: 'int'},
70  *             {name: 'phone',    type: 'string'},
71  *             {name: 'gender',   type: 'string'},
72  *             {name: 'username', type: 'string'},
73  *             {name: 'alive',    type: 'boolean', defaultValue: true}
74  *         ],
75  *
76  *         validations: [
77  *             {type: 'presence',  field: 'age'},
78  *             {type: 'length',    field: 'name',     min: 2},
79  *             {type: 'inclusion', field: 'gender',   list: ['Male', 'Female']},
80  *             {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
81  *             {type: 'format',    field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
82  *         ]
83  *     });
84  *
85  * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
86  * object:
87  *
88  *     var instance = Ext.create('User', {
89  *         name: 'Ed',
90  *         gender: 'Male',
91  *         username: 'edspencer'
92  *     });
93  *
94  *     var errors = instance.validate();
95  *
96  * # Associations
97  *
98  * Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and {@link
99  * Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration
100  * application which deals with Users, Posts and Comments. We can express the relationships between these models like
101  * this:
102  *
103  *     Ext.define('Post', {
104  *         extend: 'Ext.data.Model',
105  *         fields: ['id', 'user_id'],
106  *
107  *         belongsTo: 'User',
108  *         hasMany  : {model: 'Comment', name: 'comments'}
109  *     });
110  *
111  *     Ext.define('Comment', {
112  *         extend: 'Ext.data.Model',
113  *         fields: ['id', 'user_id', 'post_id'],
114  *
115  *         belongsTo: 'Post'
116  *     });
117  *
118  *     Ext.define('User', {
119  *         extend: 'Ext.data.Model',
120  *         fields: ['id'],
121  *
122  *         hasMany: [
123  *             'Post',
124  *             {model: 'Comment', name: 'comments'}
125  *         ]
126  *     });
127  *
128  * See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the
129  * usage and configuration of associations. Note that associations can also be specified like this:
130  *
131  *     Ext.define('User', {
132  *         extend: 'Ext.data.Model',
133  *         fields: ['id'],
134  *
135  *         associations: [
136  *             {type: 'hasMany', model: 'Post',    name: 'posts'},
137  *             {type: 'hasMany', model: 'Comment', name: 'comments'}
138  *         ]
139  *     });
140  *
141  * # Using a Proxy
142  *
143  * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or
144  * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which
145  * can be set directly on the Model:
146  *
147  *     Ext.define('User', {
148  *         extend: 'Ext.data.Model',
149  *         fields: ['id', 'name', 'email'],
150  *
151  *         proxy: {
152  *             type: 'rest',
153  *             url : '/users'
154  *         }
155  *     });
156  *
157  * 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
158  * RESTful backend. Let's see how this works:
159  *
160  *     var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
161  *
162  *     user.save(); //POST /users
163  *
164  * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's
165  * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,
166  * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We
167  * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.
168  *
169  * Loading data via the Proxy is equally easy:
170  *
171  *     //get a reference to the User model class
172  *     var User = Ext.ModelManager.getModel('User');
173  *
174  *     //Uses the configured RestProxy to make a GET request to /users/123
175  *     User.load(123, {
176  *         success: function(user) {
177  *             console.log(user.getId()); //logs 123
178  *         }
179  *     });
180  *
181  * Models can also be updated and destroyed easily:
182  *
183  *     //the user Model we loaded in the last snippet:
184  *     user.set('name', 'Edward Spencer');
185  *
186  *     //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
187  *     user.save({
188  *         success: function() {
189  *             console.log('The User was updated');
190  *         }
191  *     });
192  *
193  *     //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
194  *     user.destroy({
195  *         success: function() {
196  *             console.log('The User was destroyed!');
197  *         }
198  *     });
199  *
200  * # Usage in Stores
201  *
202  * 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
203  * creating a {@link Ext.data.Store Store}:
204  *
205  *     var store = Ext.create('Ext.data.Store', {
206  *         model: 'User'
207  *     });
208  *
209  *     //uses the Proxy we set up on Model to load the Store data
210  *     store.load();
211  *
212  * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a
213  * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link
214  * Ext.data.Store Store docs} for more information on Stores.
215  *
216  * @constructor
217  * Creates new Model instance.
218  * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
219  * @param {Number} id (optional) Unique ID to assign to this model instance
220  */
221 Ext.define('Ext.data.Model', {
222     alternateClassName: 'Ext.data.Record',
223
224     mixins: {
225         observable: 'Ext.util.Observable'
226     },
227
228     requires: [
229         'Ext.ModelManager',
230         'Ext.data.IdGenerator',
231         'Ext.data.Field',
232         'Ext.data.Errors',
233         'Ext.data.Operation',
234         'Ext.data.validations',
235         'Ext.data.proxy.Ajax',
236         'Ext.util.MixedCollection'
237     ],
238
239     onClassExtended: function(cls, data) {
240         var onBeforeClassCreated = data.onBeforeClassCreated;
241
242         data.onBeforeClassCreated = function(cls, data) {
243             var me = this,
244                 name = Ext.getClassName(cls),
245                 prototype = cls.prototype,
246                 superCls = cls.prototype.superclass,
247
248                 validations = data.validations || [],
249                 fields = data.fields || [],
250                 associations = data.associations || [],
251                 belongsTo = data.belongsTo,
252                 hasMany = data.hasMany,
253                 idgen = data.idgen,
254
255                 fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) {
256                     return field.name;
257                 }),
258
259                 associationsMixedCollection = new Ext.util.MixedCollection(false, function(association) {
260                     return association.name;
261                 }),
262
263                 superValidations = superCls.validations,
264                 superFields = superCls.fields,
265                 superAssociations = superCls.associations,
266
267                 association, i, ln,
268                 dependencies = [];
269
270             // Save modelName on class and its prototype
271             cls.modelName = name;
272             prototype.modelName = name;
273
274             // Merge the validations of the superclass and the new subclass
275             if (superValidations) {
276                 validations = superValidations.concat(validations);
277             }
278
279             data.validations = validations;
280
281             // Merge the fields of the superclass and the new subclass
282             if (superFields) {
283                 fields = superFields.items.concat(fields);
284             }
285
286             for (i = 0, ln = fields.length; i &lt; ln; ++i) {
287                 fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
288             }
289
290             data.fields = fieldsMixedCollection;
291
292             if (idgen) {
293                 data.idgen = Ext.data.IdGenerator.get(idgen);
294             }
295
296             //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
297             //we support that here
298             if (belongsTo) {
299                 belongsTo = Ext.Array.from(belongsTo);
300
301                 for (i = 0, ln = belongsTo.length; i &lt; ln; ++i) {
302                     association = belongsTo[i];
303
304                     if (!Ext.isObject(association)) {
305                         association = {model: association};
306                     }
307
308                     association.type = 'belongsTo';
309                     associations.push(association);
310                 }
311
312                 delete data.belongsTo;
313             }
314
315             if (hasMany) {
316                 hasMany = Ext.Array.from(hasMany);
317                 for (i = 0, ln = hasMany.length; i &lt; ln; ++i) {
318                     association = hasMany[i];
319
320                     if (!Ext.isObject(association)) {
321                         association = {model: association};
322                     }
323
324                     association.type = 'hasMany';
325                     associations.push(association);
326                 }
327
328                 delete data.hasMany;
329             }
330
331             if (superAssociations) {
332                 associations = superAssociations.items.concat(associations);
333             }
334
335             for (i = 0, ln = associations.length; i &lt; ln; ++i) {
336                 dependencies.push('association.' + associations[i].type.toLowerCase());
337             }
338
339             if (data.proxy) {
340                 if (typeof data.proxy === 'string') {
341                     dependencies.push('proxy.' + data.proxy);
342                 }
343                 else if (typeof data.proxy.type === 'string') {
344                     dependencies.push('proxy.' + data.proxy.type);
345                 }
346             }
347
348             Ext.require(dependencies, function() {
349                 Ext.ModelManager.registerType(name, cls);
350
351                 for (i = 0, ln = associations.length; i &lt; ln; ++i) {
352                     association = associations[i];
353
354                     Ext.apply(association, {
355                         ownerModel: name,
356                         associatedModel: association.model
357                     });
358
359                     if (Ext.ModelManager.getModel(association.model) === undefined) {
360                         Ext.ModelManager.registerDeferredAssociation(association);
361                     } else {
362                         associationsMixedCollection.add(Ext.data.Association.create(association));
363                     }
364                 }
365
366                 data.associations = associationsMixedCollection;
367
368                 onBeforeClassCreated.call(me, cls, data);
369
370                 cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
371
372                 // Fire the onModelDefined template method on ModelManager
373                 Ext.ModelManager.onModelDefined(cls);
374             });
375         };
376     },
377
378     inheritableStatics: {
379 <span id='Ext-data-Model-static-method-setProxy'>        /**
380 </span>         * Sets the Proxy to use for this model. Accepts any options that can be accepted by
381          * {@link Ext#createByAlias Ext.createByAlias}.
382          * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
383          * @return {Ext.data.proxy.Proxy}
384          * @static
385          * @inheritable
386          */
387         setProxy: function(proxy) {
388             //make sure we have an Ext.data.proxy.Proxy object
389             if (!proxy.isProxy) {
390                 if (typeof proxy == &quot;string&quot;) {
391                     proxy = {
392                         type: proxy
393                     };
394                 }
395                 proxy = Ext.createByAlias(&quot;proxy.&quot; + proxy.type, proxy);
396             }
397             proxy.setModel(this);
398             this.proxy = this.prototype.proxy = proxy;
399
400             return proxy;
401         },
402
403 <span id='Ext-data-Model-static-method-getProxy'>        /**
404 </span>         * Returns the configured Proxy for this Model
405          * @return {Ext.data.proxy.Proxy} The proxy
406          * @static
407          * @inheritable
408          */
409         getProxy: function() {
410             return this.proxy;
411         },
412
413 <span id='Ext-data-Model-static-method-load'>        /**
414 </span>         * Asynchronously loads a model instance by id. Sample usage:
415          *
416          *     MyApp.User = Ext.define('User', {
417          *         extend: 'Ext.data.Model',
418          *         fields: [
419          *             {name: 'id', type: 'int'},
420          *             {name: 'name', type: 'string'}
421          *         ]
422          *     });
423          *
424          *     MyApp.User.load(10, {
425          *         scope: this,
426          *         failure: function(record, operation) {
427          *             //do something if the load failed
428          *         },
429          *         success: function(record, operation) {
430          *             //do something if the load succeeded
431          *         },
432          *         callback: function(record, operation) {
433          *             //do something whether the load succeeded or failed
434          *         }
435          *     });
436          *
437          * @param {Number} id The id of the model to load
438          * @param {Object} config (optional) config object containing success, failure and callback functions, plus
439          * optional scope
440          * @static
441          * @inheritable
442          */
443         load: function(id, config) {
444             config = Ext.apply({}, config);
445             config = Ext.applyIf(config, {
446                 action: 'read',
447                 id    : id
448             });
449
450             var operation  = Ext.create('Ext.data.Operation', config),
451                 scope      = config.scope || this,
452                 record     = null,
453                 callback;
454
455             callback = function(operation) {
456                 if (operation.wasSuccessful()) {
457                     record = operation.getRecords()[0];
458                     Ext.callback(config.success, scope, [record, operation]);
459                 } else {
460                     Ext.callback(config.failure, scope, [record, operation]);
461                 }
462                 Ext.callback(config.callback, scope, [record, operation]);
463             };
464
465             this.proxy.read(operation, callback, this);
466         }
467     },
468
469     statics: {
470         PREFIX : 'ext-record',
471         AUTO_ID: 1,
472         EDIT   : 'edit',
473         REJECT : 'reject',
474         COMMIT : 'commit',
475
476 <span id='Ext-data-Model-static-method-id'>        /**
477 </span>         * Generates a sequential id. This method is typically called when a record is {@link Ext#create
478          * create}d and {@link #constructor no id has been specified}. The id will automatically be assigned to the
479          * record. The returned id takes the form: {PREFIX}-{AUTO_ID}.
480          *
481          * - **PREFIX** : String - Ext.data.Model.PREFIX (defaults to 'ext-record')
482          * - **AUTO_ID** : String - Ext.data.Model.AUTO_ID (defaults to 1 initially)
483          *
484          * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}.
485          * @return {String} auto-generated string id, `&quot;ext-record-i++&quot;`;
486          * @static
487          */
488         id: function(rec) {
489             var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
490             rec.phantom = true;
491             rec.internalId = id;
492             return id;
493         }
494     },
495
496 <span id='Ext-data-Model-cfg-idgen'>    /**
497 </span>     * @cfg {String/Object} idgen
498      * The id generator to use for this model. The default id generator does not generate
499      * values for the {@link #idProperty}.
500      *
501      * This can be overridden at the model level to provide a custom generator for a model.
502      * The simplest form of this would be:
503      *
504      *      Ext.define('MyApp.data.MyModel', {
505      *          extend: 'Ext.data.Model',
506      *          requires: ['Ext.data.SequentialIdGenerator'],
507      *          idgen: 'sequential',
508      *          ...
509      *      });
510      *
511      * The above would generate {@link Ext.data.SequentialIdGenerator sequential} id's such
512      * as 1, 2, 3 etc..
513      *
514      * Another useful id generator is {@link Ext.data.UuidGenerator}:
515      *
516      *      Ext.define('MyApp.data.MyModel', {
517      *          extend: 'Ext.data.Model',
518      *          requires: ['Ext.data.UuidGenerator'],
519      *          idgen: 'uuid',
520      *          ...
521      *      });
522      *
523      * An id generation can also be further configured:
524      *
525      *      Ext.define('MyApp.data.MyModel', {
526      *          extend: 'Ext.data.Model',
527      *          idgen: {
528      *              type: 'sequential',
529      *              seed: 1000,
530      *              prefix: 'ID_'
531      *          }
532      *      });
533      *
534      * The above would generate id's such as ID_1000, ID_1001, ID_1002 etc..
535      *
536      * If multiple models share an id space, a single generator can be shared:
537      *
538      *      Ext.define('MyApp.data.MyModelX', {
539      *          extend: 'Ext.data.Model',
540      *          idgen: {
541      *              type: 'sequential',
542      *              id: 'xy'
543      *          }
544      *      });
545      *
546      *      Ext.define('MyApp.data.MyModelY', {
547      *          extend: 'Ext.data.Model',
548      *          idgen: {
549      *              type: 'sequential',
550      *              id: 'xy'
551      *          }
552      *      });
553      *
554      * For more complex, shared id generators, a custom generator is the best approach.
555      * See {@link Ext.data.IdGenerator} for details on creating custom id generators.
556      *
557      * @markdown
558      */
559     idgen: {
560         isGenerator: true,
561         type: 'default',
562
563         generate: function () {
564             return null;
565         },
566         getRecId: function (rec) {
567             return rec.modelName + '-' + rec.internalId;
568         }
569     },
570
571 <span id='Ext-data-Model-property-editing'>    /**
572 </span>     * @property {Boolean} editing
573      * Internal flag used to track whether or not the model instance is currently being edited. Read-only.
574      */
575     editing : false,
576
577 <span id='Ext-data-Model-property-dirty'>    /**
578 </span>     * @property {Boolean} dirty
579      * True if this Record has been modified. Read-only.
580      */
581     dirty : false,
582
583 <span id='Ext-data-Model-cfg-persistenceProperty'>    /**
584 </span>     * @cfg {String} persistenceProperty
585      * The property on this Persistable object that its data is saved to. Defaults to 'data'
586      * (e.g. all persistable data resides in this.data.)
587      */
588     persistenceProperty: 'data',
589
590     evented: false,
591     isModel: true,
592
593 <span id='Ext-data-Model-property-phantom'>    /**
594 </span>     * @property {Boolean} phantom
595      * True when the record does not yet exist in a server-side database (see {@link #setDirty}).
596      * Any record which has a real database pk set as its id property is NOT a phantom -- it's real.
597      */
598     phantom : false,
599
600 <span id='Ext-data-Model-cfg-idProperty'>    /**
601 </span>     * @cfg {String} idProperty
602      * The name of the field treated as this Model's unique id. Defaults to 'id'.
603      */
604     idProperty: 'id',
605
606 <span id='Ext-data-Model-cfg-defaultProxyType'>    /**
607 </span>     * @cfg {String} defaultProxyType
608      * The string type of the default Model Proxy. Defaults to 'ajax'.
609      */
610     defaultProxyType: 'ajax',
611
612     // Fields config and property
613 <span id='Ext-data-Model-cfg-fields'>    /**
614 </span>     * @cfg {Object[]/String[]} fields
615      * The fields for this model.
616      */
617 <span id='Ext-data-Model-property-fields'>    /**
618 </span>     * @property {Ext.util.MixedCollection} fields
619      * The fields defined on this model.
620      */
621
622 <span id='Ext-data-Model-cfg-validations'>    /**
623 </span>     * @cfg {Object[]} validations
624      * An array of {@link Ext.data.validations validations} for this model.
625      */
626
627     // Associations configs and properties
628 <span id='Ext-data-Model-cfg-associations'>    /**
629 </span>     * @cfg {Object[]} associations
630      * An array of {@link Ext.data.Association associations} for this model.
631      */
632 <span id='Ext-data-Model-cfg-hasMany'>    /**
633 </span>     * @cfg {String/Object/String[]/Object[]} hasMany
634      * One or more {@link Ext.data.HasManyAssociation HasMany associations} for this model.
635      */
636 <span id='Ext-data-Model-cfg-belongsTo'>    /**
637 </span>     * @cfg {String/Object/String[]/Object[]} belongsTo
638      * One or more {@link Ext.data.BelongsToAssociation BelongsTo associations} for this model.
639      */
640 <span id='Ext-data-Model-property-associations'>    /**
641 </span>     * @property {Ext.util.MixedCollection} associations
642      * {@link Ext.data.Association Associations} defined on this model.
643      */
644
645 <span id='Ext-data-Model-cfg-proxy'>    /**
646 </span>     * @cfg {String/Object/Ext.data.proxy.Proxy} proxy
647      * The {@link Ext.data.proxy.Proxy proxy} to use for this model.
648      */
649
650     // raw not documented intentionally, meant to be used internally.
651     constructor: function(data, id, raw) {
652         data = data || {};
653
654         var me = this,
655             fields,
656             length,
657             field,
658             name,
659             i,
660             newId,
661             isArray = Ext.isArray(data),
662             newData = isArray ? {} : null; // to hold mapped array data if needed
663
664 <span id='Ext-data-Model-property-internalId'>        /**
665 </span>         * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
666          * @property internalId
667          * @type String
668          * @private
669          */
670         me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
671
672 <span id='Ext-data-Model-property-raw'>        /**
673 </span>         * @property {Object} raw The raw data used to create this model if created via a reader.
674          */
675         me.raw = raw;
676
677         Ext.applyIf(me, {
678             data: {}
679         });
680
681 <span id='Ext-data-Model-property-modified'>        /**
682 </span>         * @property {Object} modified Key: value pairs of all fields whose values have changed
683          */
684         me.modified = {};
685
686         // Deal with spelling error in previous releases
687         if (me.persistanceProperty) {
688             //&lt;debug&gt;
689             if (Ext.isDefined(Ext.global.console)) {
690                 Ext.global.console.warn('Ext.data.Model: persistanceProperty has been deprecated. Use persistenceProperty instead.');
691             }
692             //&lt;/debug&gt;
693             me.persistenceProperty = me.persistanceProperty;
694         }
695         me[me.persistenceProperty] = {};
696
697         me.mixins.observable.constructor.call(me);
698
699         //add default field values if present
700         fields = me.fields.items;
701         length = fields.length;
702
703         for (i = 0; i &lt; length; i++) {
704             field = fields[i];
705             name  = field.name;
706
707             if (isArray){
708                 // Have to map array data so the values get assigned to the named fields
709                 // rather than getting set as the field names with undefined values.
710                 newData[name] = data[i];
711             }
712             else if (data[name] === undefined) {
713                 data[name] = field.defaultValue;
714             }
715         }
716
717         me.set(newData || data);
718
719         if (me.getId()) {
720             me.phantom = false;
721         } else if (me.phantom) {
722             newId = me.idgen.generate();
723             if (newId !== null) {
724                 me.setId(newId);
725             }
726         }
727
728         // clear any dirty/modified since we're initializing
729         me.dirty = false;
730         me.modified = {};
731
732         if (typeof me.init == 'function') {
733             me.init();
734         }
735
736         me.id = me.idgen.getRecId(me);
737     },
738
739 <span id='Ext-data-Model-method-get'>    /**
740 </span>     * Returns the value of the given field
741      * @param {String} fieldName The field to fetch the value for
742      * @return {Object} The value
743      */
744     get: function(field) {
745         return this[this.persistenceProperty][field];
746     },
747
748 <span id='Ext-data-Model-method-set'>    /**
749 </span>     * Sets the given field to the given value, marks the instance as dirty
750      * @param {String/Object} fieldName The field to set, or an object containing key/value pairs
751      * @param {Object} value The value to set
752      */
753     set: function(fieldName, value) {
754         var me = this,
755             fields = me.fields,
756             modified = me.modified,
757             convertFields = [],
758             field, key, i, currentValue, notEditing, count, length;
759
760         /*
761          * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
762          * set those last so that all other possible data is set before the convert function is called
763          */
764         if (arguments.length == 1 &amp;&amp; Ext.isObject(fieldName)) {
765             notEditing = !me.editing;
766             count = 0;
767             for (key in fieldName) {
768                 if (fieldName.hasOwnProperty(key)) {
769
770                     //here we check for the custom convert function. Note that if a field doesn't have a convert function,
771                     //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
772                     field = fields.get(key);
773                     if (field &amp;&amp; field.convert !== field.type.convert) {
774                         convertFields.push(key);
775                         continue;
776                     }
777
778                     if (!count &amp;&amp; notEditing) {
779                         me.beginEdit();
780                     }
781                     ++count;
782                     me.set(key, fieldName[key]);
783                 }
784             }
785
786             length = convertFields.length;
787             if (length) {
788                 if (!count &amp;&amp; notEditing) {
789                     me.beginEdit();
790                 }
791                 count += length;
792                 for (i = 0; i &lt; length; i++) {
793                     field = convertFields[i];
794                     me.set(field, fieldName[field]);
795                 }
796             }
797
798             if (notEditing &amp;&amp; count) {
799                 me.endEdit();
800             }
801         } else {
802             if (fields) {
803                 field = fields.get(fieldName);
804
805                 if (field &amp;&amp; field.convert) {
806                     value = field.convert(value, me);
807                 }
808             }
809             currentValue = me.get(fieldName);
810             me[me.persistenceProperty][fieldName] = value;
811
812             if (field &amp;&amp; field.persist &amp;&amp; !me.isEqual(currentValue, value)) {
813                 if (me.isModified(fieldName)) {
814                     if (me.isEqual(modified[fieldName], value)) {
815                         // the original value in me.modified equals the new value, so the
816                         // field is no longer modified
817                         delete modified[fieldName];
818                         // we might have removed the last modified field, so check to see if
819                         // there are any modified fields remaining and correct me.dirty:
820                         me.dirty = false;
821                         for (key in modified) {
822                             if (modified.hasOwnProperty(key)){
823                                 me.dirty = true;
824                                 break;
825                             }
826                         }
827                     }
828                 } else {
829                     me.dirty = true;
830                     modified[fieldName] = currentValue;
831                 }
832             }
833
834             if (!me.editing) {
835                 me.afterEdit();
836             }
837         }
838     },
839
840 <span id='Ext-data-Model-method-isEqual'>    /**
841 </span>     * Checks if two values are equal, taking into account certain
842      * special factors, for example dates.
843      * @private
844      * @param {Object} a The first value
845      * @param {Object} b The second value
846      * @return {Boolean} True if the values are equal
847      */
848     isEqual: function(a, b){
849         if (Ext.isDate(a) &amp;&amp; Ext.isDate(b)) {
850             return a.getTime() === b.getTime();
851         }
852         return a === b;
853     },
854
855 <span id='Ext-data-Model-method-beginEdit'>    /**
856 </span>     * Begins an edit. While in edit mode, no events (e.g.. the `update` event) are relayed to the containing store.
857      * When an edit has begun, it must be followed by either {@link #endEdit} or {@link #cancelEdit}.
858      */
859     beginEdit : function(){
860         var me = this;
861         if (!me.editing) {
862             me.editing = true;
863             me.dirtySave = me.dirty;
864             me.dataSave = Ext.apply({}, me[me.persistenceProperty]);
865             me.modifiedSave = Ext.apply({}, me.modified);
866         }
867     },
868
869 <span id='Ext-data-Model-method-cancelEdit'>    /**
870 </span>     * Cancels all changes made in the current edit operation.
871      */
872     cancelEdit : function(){
873         var me = this;
874         if (me.editing) {
875             me.editing = false;
876             // reset the modified state, nothing changed since the edit began
877             me.modified = me.modifiedSave;
878             me[me.persistenceProperty] = me.dataSave;
879             me.dirty = me.dirtySave;
880             delete me.modifiedSave;
881             delete me.dataSave;
882             delete me.dirtySave;
883         }
884     },
885
886 <span id='Ext-data-Model-method-endEdit'>    /**
887 </span>     * Ends an edit. If any data was modified, the containing store is notified (ie, the store's `update` event will
888      * fire).
889      * @param {Boolean} silent True to not notify the store of the change
890      */
891     endEdit : function(silent){
892         var me = this,
893             didChange;
894             
895         if (me.editing) {
896             me.editing = false;
897             didChange = me.dirty || me.changedWhileEditing();
898             delete me.modifiedSave;
899             delete me.dataSave;
900             delete me.dirtySave;
901             if (silent !== true &amp;&amp; didChange) {
902                 me.afterEdit();
903             }
904         }
905     },
906     
907 <span id='Ext-data-Model-method-changedWhileEditing'>    /**
908 </span>     * Checks if the underlying data has changed during an edit. This doesn't necessarily
909      * mean the record is dirty, however we still need to notify the store since it may need
910      * to update any views.
911      * @private
912      * @return {Boolean} True if the underlying data has changed during an edit.
913      */
914     changedWhileEditing: function(){
915         var me = this,
916             saved = me.dataSave,
917             data = me[me.persistenceProperty],
918             key;
919             
920         for (key in data) {
921             if (data.hasOwnProperty(key)) {
922                 if (!me.isEqual(data[key], saved[key])) {
923                     return true;
924                 }
925             }
926         }
927         return false; 
928     },
929
930 <span id='Ext-data-Model-method-getChanges'>    /**
931 </span>     * Gets a hash of only the fields that have been modified since this Model was created or commited.
932      * @return {Object}
933      */
934     getChanges : function(){
935         var modified = this.modified,
936             changes  = {},
937             field;
938
939         for (field in modified) {
940             if (modified.hasOwnProperty(field)){
941                 changes[field] = this.get(field);
942             }
943         }
944
945         return changes;
946     },
947
948 <span id='Ext-data-Model-method-isModified'>    /**
949 </span>     * Returns true if the passed field name has been `{@link #modified}` since the load or last commit.
950      * @param {String} fieldName {@link Ext.data.Field#name}
951      * @return {Boolean}
952      */
953     isModified : function(fieldName) {
954         return this.modified.hasOwnProperty(fieldName);
955     },
956
957 <span id='Ext-data-Model-method-setDirty'>    /**
958 </span>     * Marks this **Record** as `{@link #dirty}`. This method is used interally when adding `{@link #phantom}` records
959      * to a {@link Ext.data.proxy.Server#writer writer enabled store}.
960      *
961      * Marking a record `{@link #dirty}` causes the phantom to be returned by {@link Ext.data.Store#getUpdatedRecords}
962      * where it will have a create action composed for it during {@link Ext.data.Model#save model save} operations.
963      */
964     setDirty : function() {
965         var me = this,
966             name;
967
968         me.dirty = true;
969
970         me.fields.each(function(field) {
971             if (field.persist) {
972                 name = field.name;
973                 me.modified[name] = me.get(name);
974             }
975         }, me);
976     },
977
978     //&lt;debug&gt;
979     markDirty : function() {
980         if (Ext.isDefined(Ext.global.console)) {
981             Ext.global.console.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
982         }
983         return this.setDirty.apply(this, arguments);
984     },
985     //&lt;/debug&gt;
986
987 <span id='Ext-data-Model-method-reject'>    /**
988 </span>     * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects
989      * all changes made to the model instance since either creation, or the last commit operation. Modified fields are
990      * reverted to their original values.
991      *
992      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of reject
993      * operations.
994      *
995      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
996      * Defaults to false.
997      */
998     reject : function(silent) {
999         var me = this,
1000             modified = me.modified,
1001             field;
1002
1003         for (field in modified) {
1004             if (modified.hasOwnProperty(field)) {
1005                 if (typeof modified[field] != &quot;function&quot;) {
1006                     me[me.persistenceProperty][field] = modified[field];
1007                 }
1008             }
1009         }
1010
1011         me.dirty = false;
1012         me.editing = false;
1013         me.modified = {};
1014
1015         if (silent !== true) {
1016             me.afterReject();
1017         }
1018     },
1019
1020 <span id='Ext-data-Model-method-commit'>    /**
1021 </span>     * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the
1022      * instance since either creation or the last commit operation.
1023      *
1024      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of commit
1025      * operations.
1026      *
1027      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
1028      * Defaults to false.
1029      */
1030     commit : function(silent) {
1031         var me = this;
1032
1033         me.phantom = me.dirty = me.editing = false;
1034         me.modified = {};
1035
1036         if (silent !== true) {
1037             me.afterCommit();
1038         }
1039     },
1040
1041 <span id='Ext-data-Model-method-copy'>    /**
1042 </span>     * Creates a copy (clone) of this Model instance.
1043      *
1044      * @param {String} [id] A new id, defaults to the id of the instance being copied.
1045      * See `{@link Ext.data.Model#id id}`. To generate a phantom instance with a new id use:
1046      *
1047      *     var rec = record.copy(); // clone the record
1048      *     Ext.data.Model.id(rec); // automatically generate a unique sequential id
1049      *
1050      * @return {Ext.data.Model}
1051      */
1052     copy : function(newId) {
1053         var me = this;
1054
1055         return new me.self(Ext.apply({}, me[me.persistenceProperty]), newId || me.internalId);
1056     },
1057
1058 <span id='Ext-data-Model-method-setProxy'>    /**
1059 </span>     * Sets the Proxy to use for this model. Accepts any options that can be accepted by
1060      * {@link Ext#createByAlias Ext.createByAlias}.
1061      *
1062      * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
1063      * @return {Ext.data.proxy.Proxy}
1064      */
1065     setProxy: function(proxy) {
1066         //make sure we have an Ext.data.proxy.Proxy object
1067         if (!proxy.isProxy) {
1068             if (typeof proxy === &quot;string&quot;) {
1069                 proxy = {
1070                     type: proxy
1071                 };
1072             }
1073             proxy = Ext.createByAlias(&quot;proxy.&quot; + proxy.type, proxy);
1074         }
1075         proxy.setModel(this.self);
1076         this.proxy = proxy;
1077
1078         return proxy;
1079     },
1080
1081 <span id='Ext-data-Model-method-getProxy'>    /**
1082 </span>     * Returns the configured Proxy for this Model.
1083      * @return {Ext.data.proxy.Proxy} The proxy
1084      */
1085     getProxy: function() {
1086         return this.proxy;
1087     },
1088
1089 <span id='Ext-data-Model-method-validate'>    /**
1090 </span>     * Validates the current data against all of its configured {@link #validations}.
1091      * @return {Ext.data.Errors} The errors object
1092      */
1093     validate: function() {
1094         var errors      = Ext.create('Ext.data.Errors'),
1095             validations = this.validations,
1096             validators  = Ext.data.validations,
1097             length, validation, field, valid, type, i;
1098
1099         if (validations) {
1100             length = validations.length;
1101
1102             for (i = 0; i &lt; length; i++) {
1103                 validation = validations[i];
1104                 field = validation.field || validation.name;
1105                 type  = validation.type;
1106                 valid = validators[type](validation, this.get(field));
1107
1108                 if (!valid) {
1109                     errors.add({
1110                         field  : field,
1111                         message: validation.message || validators[type + 'Message']
1112                     });
1113                 }
1114             }
1115         }
1116
1117         return errors;
1118     },
1119
1120 <span id='Ext-data-Model-method-isValid'>    /**
1121 </span>     * Checks if the model is valid. See {@link #validate}.
1122      * @return {Boolean} True if the model is valid.
1123      */
1124     isValid: function(){
1125         return this.validate().isValid();
1126     },
1127
1128 <span id='Ext-data-Model-method-save'>    /**
1129 </span>     * Saves the model instance using the configured proxy.
1130      * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
1131      * @return {Ext.data.Model} The Model instance
1132      */
1133     save: function(options) {
1134         options = Ext.apply({}, options);
1135
1136         var me     = this,
1137             action = me.phantom ? 'create' : 'update',
1138             record = null,
1139             scope  = options.scope || me,
1140             operation,
1141             callback;
1142
1143         Ext.apply(options, {
1144             records: [me],
1145             action : action
1146         });
1147
1148         operation = Ext.create('Ext.data.Operation', options);
1149
1150         callback = function(operation) {
1151             if (operation.wasSuccessful()) {
1152                 record = operation.getRecords()[0];
1153                 //we need to make sure we've set the updated data here. Ideally this will be redundant once the
1154                 //ModelCache is in place
1155                 me.set(record.data);
1156                 record.dirty = false;
1157
1158                 Ext.callback(options.success, scope, [record, operation]);
1159             } else {
1160                 Ext.callback(options.failure, scope, [record, operation]);
1161             }
1162
1163             Ext.callback(options.callback, scope, [record, operation]);
1164         };
1165
1166         me.getProxy()[action](operation, callback, me);
1167
1168         return me;
1169     },
1170
1171 <span id='Ext-data-Model-method-destroy'>    /**
1172 </span>     * Destroys the model using the configured proxy.
1173      * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
1174      * @return {Ext.data.Model} The Model instance
1175      */
1176     destroy: function(options){
1177         options = Ext.apply({}, options);
1178
1179         var me     = this,
1180             record = null,
1181             scope  = options.scope || me,
1182             operation,
1183             callback;
1184
1185         Ext.apply(options, {
1186             records: [me],
1187             action : 'destroy'
1188         });
1189
1190         operation = Ext.create('Ext.data.Operation', options);
1191         callback = function(operation) {
1192             if (operation.wasSuccessful()) {
1193                 Ext.callback(options.success, scope, [record, operation]);
1194             } else {
1195                 Ext.callback(options.failure, scope, [record, operation]);
1196             }
1197             Ext.callback(options.callback, scope, [record, operation]);
1198         };
1199
1200         me.getProxy().destroy(operation, callback, me);
1201         return me;
1202     },
1203
1204 <span id='Ext-data-Model-method-getId'>    /**
1205 </span>     * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}.
1206      * @return {Number} The id
1207      */
1208     getId: function() {
1209         return this.get(this.idProperty);
1210     },
1211
1212 <span id='Ext-data-Model-method-setId'>    /**
1213 </span>     * Sets the model instance's id field to the given id.
1214      * @param {Number} id The new id
1215      */
1216     setId: function(id) {
1217         this.set(this.idProperty, id);
1218     },
1219
1220 <span id='Ext-data-Model-method-join'>    /**
1221 </span>     * Tells this model instance that it has been added to a store.
1222      * @param {Ext.data.Store} store The store to which this model has been added.
1223      */
1224     join : function(store) {
1225 <span id='Ext-data-Model-property-store'>        /**
1226 </span>         * @property {Ext.data.Store} store
1227          * The {@link Ext.data.Store Store} to which this Record belongs.
1228          */
1229         this.store = store;
1230     },
1231
1232 <span id='Ext-data-Model-method-unjoin'>    /**
1233 </span>     * Tells this model instance that it has been removed from the store.
1234      * @param {Ext.data.Store} store The store from which this model has been removed.
1235      */
1236     unjoin: function(store) {
1237         delete this.store;
1238     },
1239
1240 <span id='Ext-data-Model-method-afterEdit'>    /**
1241 </span>     * @private
1242      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
1243      * afterEdit method is called
1244      */
1245     afterEdit : function() {
1246         this.callStore('afterEdit');
1247     },
1248
1249 <span id='Ext-data-Model-method-afterReject'>    /**
1250 </span>     * @private
1251      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
1252      * afterReject method is called
1253      */
1254     afterReject : function() {
1255         this.callStore(&quot;afterReject&quot;);
1256     },
1257
1258 <span id='Ext-data-Model-method-afterCommit'>    /**
1259 </span>     * @private
1260      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
1261      * afterCommit method is called
1262      */
1263     afterCommit: function() {
1264         this.callStore('afterCommit');
1265     },
1266
1267 <span id='Ext-data-Model-method-callStore'>    /**
1268 </span>     * @private
1269      * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
1270      * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
1271      * will always be called with the model instance as its single argument.
1272      * @param {String} fn The function to call on the store
1273      */
1274     callStore: function(fn) {
1275         var store = this.store;
1276
1277         if (store !== undefined &amp;&amp; typeof store[fn] == &quot;function&quot;) {
1278             store[fn](this);
1279         }
1280     },
1281
1282 <span id='Ext-data-Model-method-getAssociatedData'>    /**
1283 </span>     * Gets all of the data from this Models *loaded* associations. It does this recursively - for example if we have a
1284      * User which hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
1285      *
1286      *     {
1287      *         orders: [
1288      *             {
1289      *                 id: 123,
1290      *                 status: 'shipped',
1291      *                 orderItems: [
1292      *                     ...
1293      *                 ]
1294      *             }
1295      *         ]
1296      *     }
1297      *
1298      * @return {Object} The nested data set for the Model's loaded associations
1299      */
1300     getAssociatedData: function(){
1301         return this.prepareAssociatedData(this, [], null);
1302     },
1303
1304 <span id='Ext-data-Model-method-prepareAssociatedData'>    /**
1305 </span>     * @private
1306      * This complex-looking method takes a given Model instance and returns an object containing all data from
1307      * all of that Model's *loaded* associations. See (@link #getAssociatedData}
1308      * @param {Ext.data.Model} record The Model instance
1309      * @param {String[]} ids PRIVATE. The set of Model instance internalIds that have already been loaded
1310      * @param {String} associationType (optional) The name of the type of association to limit to.
1311      * @return {Object} The nested data set for the Model's loaded associations
1312      */
1313     prepareAssociatedData: function(record, ids, associationType) {
1314         //we keep track of all of the internalIds of the models that we have loaded so far in here
1315         var associations     = record.associations.items,
1316             associationCount = associations.length,
1317             associationData  = {},
1318             associatedStore, associatedName, associatedRecords, associatedRecord,
1319             associatedRecordCount, association, id, i, j, type, allow;
1320
1321         for (i = 0; i &lt; associationCount; i++) {
1322             association = associations[i];
1323             type = association.type;
1324             allow = true;
1325             if (associationType) {
1326                 allow = type == associationType;
1327             }
1328             if (allow &amp;&amp; type == 'hasMany') {
1329
1330                 //this is the hasMany store filled with the associated data
1331                 associatedStore = record[association.storeName];
1332
1333                 //we will use this to contain each associated record's data
1334                 associationData[association.name] = [];
1335
1336                 //if it's loaded, put it into the association data
1337                 if (associatedStore &amp;&amp; associatedStore.data.length &gt; 0) {
1338                     associatedRecords = associatedStore.data.items;
1339                     associatedRecordCount = associatedRecords.length;
1340
1341                     //now we're finally iterating over the records in the association. We do this recursively
1342                     for (j = 0; j &lt; associatedRecordCount; j++) {
1343                         associatedRecord = associatedRecords[j];
1344                         // Use the id, since it is prefixed with the model name, guaranteed to be unique
1345                         id = associatedRecord.id;
1346
1347                         //when we load the associations for a specific model instance we add it to the set of loaded ids so that
1348                         //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
1349                         if (Ext.Array.indexOf(ids, id) == -1) {
1350                             ids.push(id);
1351
1352                             associationData[association.name][j] = associatedRecord.data;
1353                             Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
1354                         }
1355                     }
1356                 }
1357             } else if (allow &amp;&amp; type == 'belongsTo') {
1358                 associatedRecord = record[association.instanceName];
1359                 if (associatedRecord !== undefined) {
1360                     id = associatedRecord.id;
1361                     if (Ext.Array.indexOf(ids, id) == -1) {
1362                         ids.push(id);
1363                         associationData[association.name] = associatedRecord.data;
1364                         Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
1365                     }
1366                 }
1367             }
1368         }
1369
1370         return associationData;
1371     }
1372 });
1373 </pre>
1374 </body>
1375 </html>