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