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