Upgrade to ExtJS 4.0.2 - Released 06/09/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-persistenceProperty'>    /**
521 </span>     * @cfg {String} persistenceProperty 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     persistenceProperty: '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         // Deal with spelling error in previous releases
596         if (me.persistanceProperty) {
597             //&lt;debug&gt;
598             if (Ext.isDefined(Ext.global.console)) {
599                 Ext.global.console.warn('Ext.data.Model: persistanceProperty has been deprecated. Use persistenceProperty instead.');
600             }
601             //&lt;/debug&gt;
602             me.persistenceProperty = me.persistanceProperty;
603         }
604         me[me.persistenceProperty] = {};
605
606         me.mixins.observable.constructor.call(me);
607
608         //add default field values if present
609         fields = me.fields.items;
610         length = fields.length;
611
612         for (i = 0; i &lt; length; i++) {
613             field = fields[i];
614             name  = field.name;
615
616             if (isArray){ 
617                 // Have to map array data so the values get assigned to the named fields
618                 // rather than getting set as the field names with undefined values.
619                 newData[name] = data[i];
620             }
621             else if (data[name] === undefined) {
622                 data[name] = field.defaultValue;
623             }
624         }
625
626         me.set(newData || data);
627         // clear any dirty/modified since we're initializing
628         me.dirty = false;
629         me.modified = {};
630
631         if (me.getId()) {
632             me.phantom = false;
633         }
634
635         if (typeof me.init == 'function') {
636             me.init();
637         }
638
639         me.id = me.modelName + '-' + me.internalId;
640     },
641     
642 <span id='Ext-data-Model-method-get'>    /**
643 </span>     * Returns the value of the given field
644      * @param {String} fieldName The field to fetch the value for
645      * @return {Mixed} The value
646      */
647     get: function(field) {
648         return this[this.persistenceProperty][field];
649     },
650     
651 <span id='Ext-data-Model-method-set'>    /**
652 </span>     * Sets the given field to the given value, marks the instance as dirty
653      * @param {String|Object} fieldName The field to set, or an object containing key/value pairs
654      * @param {Mixed} value The value to set
655      */
656     set: function(fieldName, value) {
657         var me = this,
658             fields = me.fields,
659             modified = me.modified,
660             convertFields = [],
661             field, key, i, currentValue;
662
663         /*
664          * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
665          * set those last so that all other possible data is set before the convert function is called
666          */
667         if (arguments.length == 1 &amp;&amp; Ext.isObject(fieldName)) {
668             for (key in fieldName) {
669                 if (fieldName.hasOwnProperty(key)) {
670                 
671                     //here we check for the custom convert function. Note that if a field doesn't have a convert function,
672                     //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
673                     field = fields.get(key);
674                     if (field &amp;&amp; field.convert !== field.type.convert) {
675                         convertFields.push(key);
676                         continue;
677                     }
678                     
679                     me.set(key, fieldName[key]);
680                 }
681             }
682
683             for (i = 0; i &lt; convertFields.length; i++) {
684                 field = convertFields[i];
685                 me.set(field, fieldName[field]);
686             }
687
688         } else {
689             if (fields) {
690                 field = fields.get(fieldName);
691
692                 if (field &amp;&amp; field.convert) {
693                     value = field.convert(value, me);
694                 }
695             }
696             currentValue = me.get(fieldName);
697             me[me.persistenceProperty][fieldName] = value;
698             
699             if (field &amp;&amp; field.persist &amp;&amp; !me.isEqual(currentValue, value)) {
700                 if (me.isModified(fieldName)) {
701                     if (me.isEqual(modified[fieldName], value)) {
702                         // the original value in me.modified equals the new value, so the
703                         // field is no longer modified
704                         delete modified[fieldName];
705                         // we might have removed the last modified field, so check to see if
706                         // there are any modified fields remaining and correct me.dirty:
707                         me.dirty = false;
708                         for (key in modified) {
709                             if (modified.hasOwnProperty(key)){
710                                 me.dirty = true;
711                                 break;
712                             }
713                         }
714                     }
715                 } else {
716                     me.dirty = true;
717                     modified[fieldName] = currentValue;
718                 }
719             }
720
721             if (!me.editing) {
722                 me.afterEdit();
723             }
724         }
725     },
726     
727 <span id='Ext-data-Model-method-isEqual'>    /**
728 </span>     * Checks if two values are equal, taking into account certain
729      * special factors, for example dates.
730      * @private
731      * @param {Object} a The first value
732      * @param {Object} b The second value
733      * @return {Boolean} True if the values are equal
734      */
735     isEqual: function(a, b){
736         if (Ext.isDate(a) &amp;&amp; Ext.isDate(b)) {
737             return a.getTime() === b.getTime();
738         }
739         return a === b;
740     },
741     
742 <span id='Ext-data-Model-method-beginEdit'>    /**
743 </span>     * Begin an edit. While in edit mode, no events (e.g.. the &lt;code&gt;update&lt;/code&gt; event)
744      * are relayed to the containing store. When an edit has begun, it must be followed
745      * by either {@link #endEdit} or {@link #cancelEdit}.
746      */
747     beginEdit : function(){
748         var me = this;
749         if (!me.editing) {
750             me.editing = true;
751             me.dirtySave = me.dirty;
752             me.dataSave = Ext.apply({}, me[me.persistenceProperty]);
753             me.modifiedSave = Ext.apply({}, me.modified);
754         }
755     },
756     
757 <span id='Ext-data-Model-method-cancelEdit'>    /**
758 </span>     * Cancels all changes made in the current edit operation.
759      */
760     cancelEdit : function(){
761         var me = this;
762         if (me.editing) {
763             me.editing = false;
764             // reset the modified state, nothing changed since the edit began
765             me.modified = me.modifiedSave;
766             me[me.persistenceProperty] = me.dataSave;
767             me.dirty = me.dirtySave;
768             delete me.modifiedSave;
769             delete me.dataSave;
770             delete me.dirtySave;
771         }
772     },
773     
774 <span id='Ext-data-Model-method-endEdit'>    /**
775 </span>     * End an edit. If any data was modified, the containing store is notified
776      * (ie, the store's &lt;code&gt;update&lt;/code&gt; event will fire).
777      * @param {Boolean} silent True to not notify the store of the change
778      */
779     endEdit : function(silent){
780         var me = this;
781         if (me.editing) {
782             me.editing = false;
783             delete me.modifiedSave;
784             delete me.dataSave;
785             delete me.dirtySave;
786             if (silent !== true &amp;&amp; me.dirty) {
787                 me.afterEdit();
788             }
789         }
790     },
791     
792 <span id='Ext-data-Model-method-getChanges'>    /**
793 </span>     * Gets a hash of only the fields that have been modified since this Model was created or commited.
794      * @return Object
795      */
796     getChanges : function(){
797         var modified = this.modified,
798             changes  = {},
799             field;
800
801         for (field in modified) {
802             if (modified.hasOwnProperty(field)){
803                 changes[field] = this.get(field);
804             }
805         }
806
807         return changes;
808     },
809     
810 <span id='Ext-data-Model-method-isModified'>    /**
811 </span>     * Returns &lt;tt&gt;true&lt;/tt&gt; if the passed field name has been &lt;code&gt;{@link #modified}&lt;/code&gt;
812      * since the load or last commit.
813      * @param {String} fieldName {@link Ext.data.Field#name}
814      * @return {Boolean}
815      */
816     isModified : function(fieldName) {
817         return this.modified.hasOwnProperty(fieldName);
818     },
819     
820 <span id='Ext-data-Model-method-setDirty'>    /**
821 </span>     * &lt;p&gt;Marks this &lt;b&gt;Record&lt;/b&gt; as &lt;code&gt;{@link #dirty}&lt;/code&gt;.  This method
822      * is used interally when adding &lt;code&gt;{@link #phantom}&lt;/code&gt; records to a
823      * {@link Ext.data.Store#writer writer enabled store}.&lt;/p&gt;
824      * &lt;br&gt;&lt;p&gt;Marking a record &lt;code&gt;{@link #dirty}&lt;/code&gt; causes the phantom to
825      * be returned by {@link Ext.data.Store#getModifiedRecords} where it will
826      * have a create action composed for it during {@link Ext.data.Store#save store save}
827      * operations.&lt;/p&gt;
828      */
829     setDirty : function() {
830         var me = this,
831             name;
832         
833         me.dirty = true;
834
835         me.fields.each(function(field) {
836             if (field.persist) {
837                 name = field.name;
838                 me.modified[name] = me.get(name);
839             }
840         }, me);
841     },
842
843     //&lt;debug&gt;
844     markDirty : function() {
845         if (Ext.isDefined(Ext.global.console)) {
846             Ext.global.console.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
847         }
848         return this.setDirty.apply(this, arguments);
849     },
850     //&lt;/debug&gt;
851     
852 <span id='Ext-data-Model-method-reject'>    /**
853 </span>     * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}.
854      * Rejects all changes made to the model instance since either creation, or the last commit operation.
855      * Modified fields are reverted to their original values.
856      * &lt;p&gt;Developers should subscribe to the {@link Ext.data.Store#update} event
857      * to have their code notified of reject operations.&lt;/p&gt;
858      * @param {Boolean} silent (optional) True to skip notification of the owning
859      * store of the change (defaults to false)
860      */
861     reject : function(silent) {
862         var me = this,
863             modified = me.modified,
864             field;
865
866         for (field in modified) {
867             if (modified.hasOwnProperty(field)) {
868                 if (typeof modified[field] != &quot;function&quot;) {
869                     me[me.persistenceProperty][field] = modified[field];
870                 }
871             }
872         }
873
874         me.dirty = false;
875         me.editing = false;
876         me.modified = {};
877
878         if (silent !== true) {
879             me.afterReject();
880         }
881     },
882
883 <span id='Ext-data-Model-method-commit'>    /**
884 </span>     * Usually called by the {@link Ext.data.Store} which owns the model instance.
885      * Commits all changes made to the instance since either creation or the last commit operation.
886      * &lt;p&gt;Developers should subscribe to the {@link Ext.data.Store#update} event
887      * to have their code notified of commit operations.&lt;/p&gt;
888      * @param {Boolean} silent (optional) True to skip notification of the owning
889      * store of the change (defaults to false)
890      */
891     commit : function(silent) {
892         var me = this;
893         
894         me.dirty = false;
895         me.editing = false;
896
897         me.modified = {};
898
899         if (silent !== true) {
900             me.afterCommit();
901         }
902     },
903
904 <span id='Ext-data-Model-method-copy'>    /**
905 </span>     * Creates a copy (clone) of this Model instance.
906      * @param {String} id (optional) A new id, defaults to the id
907      * of the instance being copied. See &lt;code&gt;{@link #id}&lt;/code&gt;.
908      * To generate a phantom instance with a new id use:&lt;pre&gt;&lt;code&gt;
909 var rec = record.copy(); // clone the record
910 Ext.data.Model.id(rec); // automatically generate a unique sequential id
911      * &lt;/code&gt;&lt;/pre&gt;
912      * @return {Record}
913      */
914     copy : function(newId) {
915         var me = this;
916         
917         return new me.self(Ext.apply({}, me[me.persistenceProperty]), newId || me.internalId);
918     },
919
920 <span id='Ext-data-Model-method-setProxy'>    /**
921 </span>     * Sets the Proxy to use for this model. Accepts any options that can be accepted by {@link Ext#createByAlias Ext.createByAlias}
922      * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
923      * @static
924      */
925     setProxy: function(proxy) {
926         //make sure we have an Ext.data.proxy.Proxy object
927         if (!proxy.isProxy) {
928             if (typeof proxy === &quot;string&quot;) {
929                 proxy = {
930                     type: proxy
931                 };
932             }
933             proxy = Ext.createByAlias(&quot;proxy.&quot; + proxy.type, proxy);
934         }
935         proxy.setModel(this.self);
936         this.proxy = proxy;
937
938         return proxy;
939     },
940
941 <span id='Ext-data-Model-method-getProxy'>    /**
942 </span>     * Returns the configured Proxy for this Model
943      * @return {Ext.data.proxy.Proxy} The proxy
944      */
945     getProxy: function() {
946         return this.proxy;
947     },
948
949 <span id='Ext-data-Model-method-validate'>    /**
950 </span>     * Validates the current data against all of its configured {@link #validations} and returns an
951      * {@link Ext.data.Errors Errors} object
952      * @return {Ext.data.Errors} The errors object
953      */
954     validate: function() {
955         var errors      = Ext.create('Ext.data.Errors'),
956             validations = this.validations,
957             validators  = Ext.data.validations,
958             length, validation, field, valid, type, i;
959
960         if (validations) {
961             length = validations.length;
962
963             for (i = 0; i &lt; length; i++) {
964                 validation = validations[i];
965                 field = validation.field || validation.name;
966                 type  = validation.type;
967                 valid = validators[type](validation, this.get(field));
968
969                 if (!valid) {
970                     errors.add({
971                         field  : field,
972                         message: validation.message || validators[type + 'Message']
973                     });
974                 }
975             }
976         }
977
978         return errors;
979     },
980
981 <span id='Ext-data-Model-method-isValid'>    /**
982 </span>     * Checks if the model is valid. See {@link #validate}.
983      * @return {Boolean} True if the model is valid.
984      */
985     isValid: function(){
986         return this.validate().isValid();
987     },
988
989 <span id='Ext-data-Model-method-save'>    /**
990 </span>     * Saves the model instance using the configured proxy
991      * @param {Object} options Options to pass to the proxy
992      * @return {Ext.data.Model} The Model instance
993      */
994     save: function(options) {
995         options = Ext.apply({}, options);
996
997         var me     = this,
998             action = me.phantom ? 'create' : 'update',
999             record = null,
1000             scope  = options.scope || me,
1001             operation,
1002             callback;
1003
1004         Ext.apply(options, {
1005             records: [me],
1006             action : action
1007         });
1008
1009         operation = Ext.create('Ext.data.Operation', options);
1010
1011         callback = function(operation) {
1012             if (operation.wasSuccessful()) {
1013                 record = operation.getRecords()[0];
1014                 //we need to make sure we've set the updated data here. Ideally this will be redundant once the
1015                 //ModelCache is in place
1016                 me.set(record.data);
1017                 record.dirty = false;
1018
1019                 Ext.callback(options.success, scope, [record, operation]);
1020             } else {
1021                 Ext.callback(options.failure, scope, [record, operation]);
1022             }
1023
1024             Ext.callback(options.callback, scope, [record, operation]);
1025         };
1026
1027         me.getProxy()[action](operation, callback, me);
1028
1029         return me;
1030     },
1031
1032 <span id='Ext-data-Model-method-destroy'>    /**
1033 </span>     * Destroys the model using the configured proxy
1034      * @param {Object} options Options to pass to the proxy
1035      * @return {Ext.data.Model} The Model instance
1036      */
1037     destroy: function(options){
1038         options = Ext.apply({}, options);
1039
1040         var me     = this,
1041             record = null,
1042             scope  = options.scope || me,
1043             operation,
1044             callback;
1045
1046         Ext.apply(options, {
1047             records: [me],
1048             action : 'destroy'
1049         });
1050
1051         operation = Ext.create('Ext.data.Operation', options);
1052         callback = function(operation) {
1053             if (operation.wasSuccessful()) {
1054                 Ext.callback(options.success, scope, [record, operation]);
1055             } else {
1056                 Ext.callback(options.failure, scope, [record, operation]);
1057             }
1058             Ext.callback(options.callback, scope, [record, operation]);
1059         };
1060
1061         me.getProxy().destroy(operation, callback, me);
1062         return me;
1063     },
1064
1065 <span id='Ext-data-Model-method-getId'>    /**
1066 </span>     * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}
1067      * @return {Number} The id
1068      */
1069     getId: function() {
1070         return this.get(this.idProperty);
1071     },
1072
1073 <span id='Ext-data-Model-method-setId'>    /**
1074 </span>     * Sets the model instance's id field to the given id
1075      * @param {Number} id The new id
1076      */
1077     setId: function(id) {
1078         this.set(this.idProperty, id);
1079     },
1080
1081 <span id='Ext-data-Model-method-join'>    /**
1082 </span>     * Tells this model instance that it has been added to a store
1083      * @param {Ext.data.Store} store The store that the model has been added to
1084      */
1085     join : function(store) {
1086 <span id='Ext-data-Model-property-store'>        /**
1087 </span>         * The {@link Ext.data.Store} to which this Record belongs.
1088          * @property store
1089          * @type {Ext.data.Store}
1090          */
1091         this.store = store;
1092     },
1093
1094 <span id='Ext-data-Model-method-unjoin'>    /**
1095 </span>     * Tells this model instance that it has been removed from the store
1096      */
1097     unjoin: function() {
1098         delete this.store;
1099     },
1100
1101 <span id='Ext-data-Model-method-afterEdit'>    /**
1102 </span>     * @private
1103      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
1104      * afterEdit method is called
1105      */
1106     afterEdit : function() {
1107         this.callStore('afterEdit');
1108     },
1109
1110 <span id='Ext-data-Model-method-afterReject'>    /**
1111 </span>     * @private
1112      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
1113      * afterReject method is called
1114      */
1115     afterReject : function() {
1116         this.callStore(&quot;afterReject&quot;);
1117     },
1118
1119 <span id='Ext-data-Model-method-afterCommit'>    /**
1120 </span>     * @private
1121      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
1122      * afterCommit method is called
1123      */
1124     afterCommit: function() {
1125         this.callStore('afterCommit');
1126     },
1127
1128 <span id='Ext-data-Model-method-callStore'>    /**
1129 </span>     * @private
1130      * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
1131      * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
1132      * will always be called with the model instance as its single argument.
1133      * @param {String} fn The function to call on the store
1134      */
1135     callStore: function(fn) {
1136         var store = this.store;
1137
1138         if (store !== undefined &amp;&amp; typeof store[fn] == &quot;function&quot;) {
1139             store[fn](this);
1140         }
1141     },
1142
1143 <span id='Ext-data-Model-method-getAssociatedData'>    /**
1144 </span>     * Gets all of the data from this Models *loaded* associations.
1145      * It does this recursively - for example if we have a User which
1146      * hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
1147      * {
1148      *     orders: [
1149      *         {
1150      *             id: 123,
1151      *             status: 'shipped',
1152      *             orderItems: [
1153      *                 ...
1154      *             ]
1155      *         }
1156      *     ]
1157      * }
1158      * @return {Object} The nested data set for the Model's loaded associations
1159      */
1160     getAssociatedData: function(){
1161         return this.prepareAssociatedData(this, [], null);
1162     },
1163
1164 <span id='Ext-data-Model-method-prepareAssociatedData'>    /**
1165 </span>     * @private
1166      * This complex-looking method takes a given Model instance and returns an object containing all data from
1167      * all of that Model's *loaded* associations. See (@link #getAssociatedData}
1168      * @param {Ext.data.Model} record The Model instance
1169      * @param {Array} ids PRIVATE. The set of Model instance internalIds that have already been loaded
1170      * @param {String} associationType (optional) The name of the type of association to limit to.
1171      * @return {Object} The nested data set for the Model's loaded associations
1172      */
1173     prepareAssociatedData: function(record, ids, associationType) {
1174         //we keep track of all of the internalIds of the models that we have loaded so far in here
1175         var associations     = record.associations.items,
1176             associationCount = associations.length,
1177             associationData  = {},
1178             associatedStore, associatedName, associatedRecords, associatedRecord,
1179             associatedRecordCount, association, id, i, j, type, allow;
1180
1181         for (i = 0; i &lt; associationCount; i++) {
1182             association = associations[i];
1183             type = association.type;
1184             allow = true;
1185             if (associationType) {
1186                 allow = type == associationType;
1187             }
1188             if (allow &amp;&amp; type == 'hasMany') {
1189
1190                 //this is the hasMany store filled with the associated data
1191                 associatedStore = record[association.storeName];
1192
1193                 //we will use this to contain each associated record's data
1194                 associationData[association.name] = [];
1195
1196                 //if it's loaded, put it into the association data
1197                 if (associatedStore &amp;&amp; associatedStore.data.length &gt; 0) {
1198                     associatedRecords = associatedStore.data.items;
1199                     associatedRecordCount = associatedRecords.length;
1200
1201                     //now we're finally iterating over the records in the association. We do this recursively
1202                     for (j = 0; j &lt; associatedRecordCount; j++) {
1203                         associatedRecord = associatedRecords[j];
1204                         // Use the id, since it is prefixed with the model name, guaranteed to be unique
1205                         id = associatedRecord.id;
1206
1207                         //when we load the associations for a specific model instance we add it to the set of loaded ids so that
1208                         //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
1209                         if (Ext.Array.indexOf(ids, id) == -1) {
1210                             ids.push(id);
1211
1212                             associationData[association.name][j] = associatedRecord.data;
1213                             Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
1214                         }
1215                     }
1216                 }
1217             } else if (allow &amp;&amp; type == 'belongsTo') {
1218                 associatedRecord = record[association.instanceName];
1219                 if (associatedRecord !== undefined) {
1220                     id = associatedRecord.id;
1221                     if (Ext.Array.indexOf(ids, id) == -1) {
1222                         ids.push(id);
1223                         associationData[association.name] = associatedRecord.data;
1224                         Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
1225                     }
1226                 }
1227             }
1228         }
1229
1230         return associationData;
1231     }
1232 });
1233 </pre>
1234 </body>
1235 </html>