3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
17 * @class Ext.data.Model
19 * <p>A Model represents some object that your application manages. For example, one might define a Model for Users, Products,
20 * 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},
21 * and are used by {@link Ext.data.Store stores}, which are in turn used by many of the data-bound components in Ext.</p>
23 * <p>Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:</p>
27 extend: 'Ext.data.Model',
29 {name: 'name', type: 'string'},
30 {name: 'age', type: 'int'},
31 {name: 'phone', type: 'string'},
32 {name: 'alive', type: 'boolean', defaultValue: true}
35 changeName: function() {
36 var oldName = this.get('name'),
37 newName = oldName + " The Barbarian";
39 this.set('name', newName);
44 * <p>The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link Ext.ModelManager ModelManager}, and all
45 * other functions and properties are copied to the new Model's prototype.</p>
47 * <p>Now we can create instances of our User model and call any model logic we defined:</p>
50 var user = Ext.ModelManager.create({
57 user.get('name'); //returns "Conan The Barbarian"
60 * <p><u>Validations</u></p>
62 * <p>Models have built-in support for validations, which are executed against the validator functions in
63 * {@link Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to models:</p>
67 extend: 'Ext.data.Model',
69 {name: 'name', type: 'string'},
70 {name: 'age', type: 'int'},
71 {name: 'phone', type: 'string'},
72 {name: 'gender', type: 'string'},
73 {name: 'username', type: 'string'},
74 {name: 'alive', type: 'boolean', defaultValue: true}
78 {type: 'presence', field: 'age'},
79 {type: 'length', field: 'name', min: 2},
80 {type: 'inclusion', field: 'gender', list: ['Male', 'Female']},
81 {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
82 {type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
87 * <p>The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
91 var instance = Ext.ModelManager.create({
97 var errors = instance.validate();
100 * <p><u>Associations</u></p>
102 * <p>Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and
103 * {@link Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration
104 * application which deals with Users, Posts and Comments. We can express the relationships between these models like this:</p>
108 extend: 'Ext.data.Model',
109 fields: ['id', 'user_id'],
112 hasMany : {model: 'Comment', name: 'comments'}
115 Ext.define('Comment', {
116 extend: 'Ext.data.Model',
117 fields: ['id', 'user_id', 'post_id'],
123 extend: 'Ext.data.Model',
128 {model: 'Comment', name: 'comments'}
133 * <p>See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the usage
134 * and configuration of associations. Note that associations can also be specified like this:</p>
138 extend: 'Ext.data.Model',
142 {type: 'hasMany', model: 'Post', name: 'posts'},
143 {type: 'hasMany', model: 'Comment', name: 'comments'}
148 * <p><u>Using a Proxy</u></p>
150 * <p>Models are great for representing types of data and relationships, but sooner or later we're going to want to
151 * load or save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy},
152 * which can be set directly on the Model:</p>
156 extend: 'Ext.data.Model',
157 fields: ['id', 'name', 'email'],
166 * <p>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
167 * RESTful backend. Let's see how this works:</p>
170 var user = Ext.ModelManager.create({name: 'Ed Spencer', email: 'ed@sencha.com'}, 'User');
172 user.save(); //POST /users
175 * <p>Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this
176 * Model's data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't
177 * have an id, and performs the appropriate action - in this case issuing a POST request to the url we configured
178 * (/users). We configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full
181 * <p>Loading data via the Proxy is equally easy:</p>
184 //get a reference to the User model class
185 var User = Ext.ModelManager.getModel('User');
187 //Uses the configured RestProxy to make a GET request to /users/123
189 success: function(user) {
190 console.log(user.getId()); //logs 123
195 * <p>Models can also be updated and destroyed easily:</p>
198 //the user Model we loaded in the last snippet:
199 user.set('name', 'Edward Spencer');
201 //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
203 success: function() {
204 console.log('The User was updated');
208 //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
210 success: function() {
211 console.log('The User was destroyed!');
216 * <p><u>Usage in Stores</u></p>
218 * <p>It is very common to want to load a set of Model instances to be displayed and manipulated in the UI. We do this
219 * by creating a {@link Ext.data.Store Store}:</p>
222 var store = new Ext.data.Store({
226 //uses the Proxy we set up on Model to load the Store data
230 * <p>A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain
231 * a set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the
232 * {@link Ext.data.Store Store docs} for more information on Stores.</p>
235 * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
236 * @param {Number} id (optional) Unique ID to assign to this model instance
238 Ext.define('Ext.data.Model', {
239 alternateClassName: 'Ext.data.Record',
242 observable: 'Ext.util.Observable'
249 'Ext.data.Operation',
250 'Ext.data.validations',
251 'Ext.data.proxy.Ajax',
252 'Ext.util.MixedCollection'
255 onClassExtended: function(cls, data) {
256 var onBeforeClassCreated = data.onBeforeClassCreated;
258 data.onBeforeClassCreated = function(cls, data) {
260 name = Ext.getClassName(cls),
261 prototype = cls.prototype,
262 superCls = cls.prototype.superclass,
264 validations = data.validations || [],
265 fields = data.fields || [],
266 associations = data.associations || [],
267 belongsTo = data.belongsTo,
268 hasMany = data.hasMany,
270 fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) {
274 associationsMixedCollection = new Ext.util.MixedCollection(false, function(association) {
275 return association.name;
278 superValidations = superCls.validations,
279 superFields = superCls.fields,
280 superAssociations = superCls.associations,
285 // Save modelName on class and its prototype
286 cls.modelName = name;
287 prototype.modelName = name;
289 // Merge the validations of the superclass and the new subclass
290 if (superValidations) {
291 validations = superValidations.concat(validations);
294 data.validations = validations;
296 // Merge the fields of the superclass and the new subclass
298 fields = superFields.items.concat(fields);
301 for (i = 0, ln = fields.length; i < ln; ++i) {
302 fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
305 data.fields = fieldsMixedCollection;
307 //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
308 //we support that here
310 belongsTo = Ext.Array.from(belongsTo);
312 for (i = 0, ln = belongsTo.length; i < ln; ++i) {
313 association = belongsTo[i];
315 if (!Ext.isObject(association)) {
316 association = {model: association};
319 association.type = 'belongsTo';
320 associations.push(association);
323 delete data.belongsTo;
327 hasMany = Ext.Array.from(hasMany);
328 for (i = 0, ln = hasMany.length; i < ln; ++i) {
329 association = hasMany[i];
331 if (!Ext.isObject(association)) {
332 association = {model: association};
335 association.type = 'hasMany';
336 associations.push(association);
342 if (superAssociations) {
343 associations = superAssociations.items.concat(associations);
346 for (i = 0, ln = associations.length; i < ln; ++i) {
347 dependencies.push('association.' + associations[i].type.toLowerCase());
351 if (typeof data.proxy === 'string') {
352 dependencies.push('proxy.' + data.proxy);
354 else if (typeof data.proxy.type === 'string') {
355 dependencies.push('proxy.' + data.proxy.type);
359 Ext.require(dependencies, function() {
360 Ext.ModelManager.registerType(name, cls);
362 for (i = 0, ln = associations.length; i < ln; ++i) {
363 association = associations[i];
365 Ext.apply(association, {
367 associatedModel: association.model
370 if (Ext.ModelManager.getModel(association.model) === undefined) {
371 Ext.ModelManager.registerDeferredAssociation(association);
373 associationsMixedCollection.add(Ext.data.Association.create(association));
377 data.associations = associationsMixedCollection;
379 onBeforeClassCreated.call(me, cls, data);
381 cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
383 // Fire the onModelDefined template method on ModelManager
384 Ext.ModelManager.onModelDefined(cls);
389 inheritableStatics: {
391 * Sets the Proxy to use for this model. Accepts any options that can be accepted by {@link Ext#createByAlias Ext.createByAlias}
392 * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
395 setProxy: function(proxy) {
396 //make sure we have an Ext.data.proxy.Proxy object
397 if (!proxy.isProxy) {
398 if (typeof proxy == "string") {
403 proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
405 proxy.setModel(this);
406 this.proxy = this.prototype.proxy = proxy;
412 * Returns the configured Proxy for this Model
413 * @return {Ext.data.proxy.Proxy} The proxy
415 getProxy: function() {
420 * <b>Static</b>. Asynchronously loads a model instance by id. Sample usage:
422 MyApp.User = Ext.define('User', {
423 extend: 'Ext.data.Model',
425 {name: 'id', type: 'int'},
426 {name: 'name', type: 'string'}
430 MyApp.User.load(10, {
432 failure: function(record, operation) {
433 //do something if the load failed
435 success: function(record, operation) {
436 //do something if the load succeeded
438 callback: function(record, operation) {
439 //do something whether the load succeeded or failed
443 * @param {Number} id The id of the model to load
444 * @param {Object} config Optional config object containing success, failure and callback functions, plus optional scope
445 * @member Ext.data.Model
449 load: function(id, config) {
450 config = Ext.apply({}, config);
451 config = Ext.applyIf(config, {
456 var operation = Ext.create('Ext.data.Operation', config),
457 scope = config.scope || this,
461 callback = function(operation) {
462 if (operation.wasSuccessful()) {
463 record = operation.getRecords()[0];
464 Ext.callback(config.success, scope, [record, operation]);
466 Ext.callback(config.failure, scope, [record, operation]);
468 Ext.callback(config.callback, scope, [record, operation]);
471 this.proxy.read(operation, callback, this);
476 PREFIX : 'ext-record',
483 * Generates a sequential id. This method is typically called when a record is {@link #create}d
484 * and {@link #Record no id has been specified}. The id will automatically be assigned
485 * to the record. The returned id takes the form:
486 * <tt>{PREFIX}-{AUTO_ID}</tt>.<div class="mdetail-params"><ul>
487 * <li><b><tt>PREFIX</tt></b> : String<p class="sub-desc"><tt>Ext.data.Model.PREFIX</tt>
488 * (defaults to <tt>'ext-record'</tt>)</p></li>
489 * <li><b><tt>AUTO_ID</tt></b> : String<p class="sub-desc"><tt>Ext.data.Model.AUTO_ID</tt>
490 * (defaults to <tt>1</tt> initially)</p></li>
492 * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}.
493 * @return {String} auto-generated string id, <tt>"ext-record-i++'</tt>;
497 var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
505 * Internal flag used to track whether or not the model instance is currently being edited. Read-only
512 * Readonly flag - true if this Record has been modified.
518 * @cfg {String} persistenceProperty The property on this Persistable object that its data is saved to.
519 * Defaults to 'data' (e.g. all persistable data resides in this.data.)
521 persistenceProperty: 'data',
527 * <tt>true</tt> when the record does not yet exist in a server-side database (see
528 * {@link #setDirty}). Any record which has a real database pk set as its id property
529 * is NOT a phantom -- it's real.
536 * @cfg {String} idProperty The name of the field treated as this Model's unique id (defaults to 'id').
541 * The string type of the default Model Proxy. Defaults to 'ajax'
542 * @property defaultProxyType
545 defaultProxyType: 'ajax',
548 * An array of the fields defined on this model
553 // raw not documented intentionally, meant to be used internally.
554 constructor: function(data, id, raw) {
563 isArray = Ext.isArray(data),
564 newData = isArray ? {} : null; // to hold mapped array data if needed
567 * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
568 * @property internalId
572 me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
575 * The raw data used to create this model if created via a reader.
586 * Key: value pairs of all fields whose values have changed
592 // Deal with spelling error in previous releases
593 if (me.persistanceProperty) {
595 if (Ext.isDefined(Ext.global.console)) {
596 Ext.global.console.warn('Ext.data.Model: persistanceProperty has been deprecated. Use persistenceProperty instead.');
599 me.persistenceProperty = me.persistanceProperty;
601 me[me.persistenceProperty] = {};
603 me.mixins.observable.constructor.call(me);
605 //add default field values if present
606 fields = me.fields.items;
607 length = fields.length;
609 for (i = 0; i < length; i++) {
614 // Have to map array data so the values get assigned to the named fields
615 // rather than getting set as the field names with undefined values.
616 newData[name] = data[i];
618 else if (data[name] === undefined) {
619 data[name] = field.defaultValue;
623 me.set(newData || data);
624 // clear any dirty/modified since we're initializing
632 if (typeof me.init == 'function') {
636 me.id = me.modelName + '-' + me.internalId;
640 * Returns the value of the given field
641 * @param {String} fieldName The field to fetch the value for
642 * @return {Mixed} The value
644 get: function(field) {
645 return this[this.persistenceProperty][field];
649 * Sets the given field to the given value, marks the instance as dirty
650 * @param {String|Object} fieldName The field to set, or an object containing key/value pairs
651 * @param {Mixed} value The value to set
653 set: function(fieldName, value) {
656 modified = me.modified,
658 field, key, i, currentValue;
661 * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
662 * set those last so that all other possible data is set before the convert function is called
664 if (arguments.length == 1 && Ext.isObject(fieldName)) {
665 for (key in fieldName) {
666 if (fieldName.hasOwnProperty(key)) {
668 //here we check for the custom convert function. Note that if a field doesn't have a convert function,
669 //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
670 field = fields.get(key);
671 if (field && field.convert !== field.type.convert) {
672 convertFields.push(key);
676 me.set(key, fieldName[key]);
680 for (i = 0; i < convertFields.length; i++) {
681 field = convertFields[i];
682 me.set(field, fieldName[field]);
687 field = fields.get(fieldName);
689 if (field && field.convert) {
690 value = field.convert(value, me);
693 currentValue = me.get(fieldName);
694 me[me.persistenceProperty][fieldName] = value;
696 if (field && field.persist && !me.isEqual(currentValue, value)) {
697 if (me.isModified(fieldName)) {
698 if (me.isEqual(modified[fieldName], value)) {
699 // the original value in me.modified equals the new value, so the
700 // field is no longer modified
701 delete modified[fieldName];
702 // we might have removed the last modified field, so check to see if
703 // there are any modified fields remaining and correct me.dirty:
705 for (key in modified) {
706 if (modified.hasOwnProperty(key)){
714 modified[fieldName] = currentValue;
725 * Checks if two values are equal, taking into account certain
726 * special factors, for example dates.
728 * @param {Object} a The first value
729 * @param {Object} b The second value
730 * @return {Boolean} True if the values are equal
732 isEqual: function(a, b){
733 if (Ext.isDate(a) && Ext.isDate(b)) {
734 return a.getTime() === b.getTime();
740 * Begin an edit. While in edit mode, no events (e.g.. the <code>update</code> event)
741 * are relayed to the containing store. When an edit has begun, it must be followed
742 * by either {@link #endEdit} or {@link #cancelEdit}.
744 beginEdit : function(){
748 me.dirtySave = me.dirty;
749 me.dataSave = Ext.apply({}, me[me.persistenceProperty]);
750 me.modifiedSave = Ext.apply({}, me.modified);
755 * Cancels all changes made in the current edit operation.
757 cancelEdit : function(){
761 // reset the modified state, nothing changed since the edit began
762 me.modified = me.modifiedSave;
763 me[me.persistenceProperty] = me.dataSave;
764 me.dirty = me.dirtySave;
765 delete me.modifiedSave;
772 * End an edit. If any data was modified, the containing store is notified
773 * (ie, the store's <code>update</code> event will fire).
774 * @param {Boolean} silent True to not notify the store of the change
776 endEdit : function(silent){
780 delete me.modifiedSave;
783 if (silent !== true && me.dirty) {
790 * Gets a hash of only the fields that have been modified since this Model was created or commited.
793 getChanges : function(){
794 var modified = this.modified,
798 for (field in modified) {
799 if (modified.hasOwnProperty(field)){
800 changes[field] = this.get(field);
808 * Returns <tt>true</tt> if the passed field name has been <code>{@link #modified}</code>
809 * since the load or last commit.
810 * @param {String} fieldName {@link Ext.data.Field#name}
813 isModified : function(fieldName) {
814 return this.modified.hasOwnProperty(fieldName);
818 * <p>Marks this <b>Record</b> as <code>{@link #dirty}</code>. This method
819 * is used interally when adding <code>{@link #phantom}</code> records to a
820 * {@link Ext.data.Store#writer writer enabled store}.</p>
821 * <br><p>Marking a record <code>{@link #dirty}</code> causes the phantom to
822 * be returned by {@link Ext.data.Store#getModifiedRecords} where it will
823 * have a create action composed for it during {@link Ext.data.Store#save store save}
826 setDirty : function() {
832 me.fields.each(function(field) {
835 me.modified[name] = me.get(name);
841 markDirty : function() {
842 if (Ext.isDefined(Ext.global.console)) {
843 Ext.global.console.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
845 return this.setDirty.apply(this, arguments);
850 * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}.
851 * Rejects all changes made to the model instance since either creation, or the last commit operation.
852 * Modified fields are reverted to their original values.
853 * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
854 * to have their code notified of reject operations.</p>
855 * @param {Boolean} silent (optional) True to skip notification of the owning
856 * store of the change (defaults to false)
858 reject : function(silent) {
860 modified = me.modified,
863 for (field in modified) {
864 if (modified.hasOwnProperty(field)) {
865 if (typeof modified[field] != "function") {
866 me[me.persistenceProperty][field] = modified[field];
875 if (silent !== true) {
881 * Usually called by the {@link Ext.data.Store} which owns the model instance.
882 * Commits all changes made to the instance since either creation or the last commit operation.
883 * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
884 * to have their code notified of commit operations.</p>
885 * @param {Boolean} silent (optional) True to skip notification of the owning
886 * store of the change (defaults to false)
888 commit : function(silent) {
896 if (silent !== true) {
902 * Creates a copy (clone) of this Model instance.
903 * @param {String} id (optional) A new id, defaults to the id
904 * of the instance being copied. See <code>{@link #id}</code>.
905 * To generate a phantom instance with a new id use:<pre><code>
906 var rec = record.copy(); // clone the record
907 Ext.data.Model.id(rec); // automatically generate a unique sequential id
911 copy : function(newId) {
914 return new me.self(Ext.apply({}, me[me.persistenceProperty]), newId || me.internalId);
918 * Sets the Proxy to use for this model. Accepts any options that can be accepted by {@link Ext#createByAlias Ext.createByAlias}
919 * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
922 setProxy: function(proxy) {
923 //make sure we have an Ext.data.proxy.Proxy object
924 if (!proxy.isProxy) {
925 if (typeof proxy === "string") {
930 proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
932 proxy.setModel(this.self);
939 * Returns the configured Proxy for this Model
940 * @return {Ext.data.proxy.Proxy} The proxy
942 getProxy: function() {
947 * Validates the current data against all of its configured {@link #validations} and returns an
948 * {@link Ext.data.Errors Errors} object
949 * @return {Ext.data.Errors} The errors object
951 validate: function() {
952 var errors = Ext.create('Ext.data.Errors'),
953 validations = this.validations,
954 validators = Ext.data.validations,
955 length, validation, field, valid, type, i;
958 length = validations.length;
960 for (i = 0; i < length; i++) {
961 validation = validations[i];
962 field = validation.field || validation.name;
963 type = validation.type;
964 valid = validators[type](validation, this.get(field));
969 message: validation.message || validators[type + 'Message']
979 * Checks if the model is valid. See {@link #validate}.
980 * @return {Boolean} True if the model is valid.
983 return this.validate().isValid();
987 * Saves the model instance using the configured proxy
988 * @param {Object} options Options to pass to the proxy
989 * @return {Ext.data.Model} The Model instance
991 save: function(options) {
992 options = Ext.apply({}, options);
995 action = me.phantom ? 'create' : 'update',
997 scope = options.scope || me,
1001 Ext.apply(options, {
1006 operation = Ext.create('Ext.data.Operation', options);
1008 callback = function(operation) {
1009 if (operation.wasSuccessful()) {
1010 record = operation.getRecords()[0];
1011 //we need to make sure we've set the updated data here. Ideally this will be redundant once the
1012 //ModelCache is in place
1013 me.set(record.data);
1014 record.dirty = false;
1016 Ext.callback(options.success, scope, [record, operation]);
1018 Ext.callback(options.failure, scope, [record, operation]);
1021 Ext.callback(options.callback, scope, [record, operation]);
1024 me.getProxy()[action](operation, callback, me);
1030 * Destroys the model using the configured proxy
1031 * @param {Object} options Options to pass to the proxy
1032 * @return {Ext.data.Model} The Model instance
1034 destroy: function(options){
1035 options = Ext.apply({}, options);
1039 scope = options.scope || me,
1043 Ext.apply(options, {
1048 operation = Ext.create('Ext.data.Operation', options);
1049 callback = function(operation) {
1050 if (operation.wasSuccessful()) {
1051 Ext.callback(options.success, scope, [record, operation]);
1053 Ext.callback(options.failure, scope, [record, operation]);
1055 Ext.callback(options.callback, scope, [record, operation]);
1058 me.getProxy().destroy(operation, callback, me);
1063 * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}
1064 * @return {Number} The id
1067 return this.get(this.idProperty);
1071 * Sets the model instance's id field to the given id
1072 * @param {Number} id The new id
1074 setId: function(id) {
1075 this.set(this.idProperty, id);
1079 * Tells this model instance that it has been added to a store
1080 * @param {Ext.data.Store} store The store that the model has been added to
1082 join : function(store) {
1084 * The {@link Ext.data.Store} to which this Record belongs.
1086 * @type {Ext.data.Store}
1092 * Tells this model instance that it has been removed from the store
1094 unjoin: function() {
1100 * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
1101 * afterEdit method is called
1103 afterEdit : function() {
1104 this.callStore('afterEdit');
1109 * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
1110 * afterReject method is called
1112 afterReject : function() {
1113 this.callStore("afterReject");
1118 * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
1119 * afterCommit method is called
1121 afterCommit: function() {
1122 this.callStore('afterCommit');
1127 * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
1128 * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
1129 * will always be called with the model instance as its single argument.
1130 * @param {String} fn The function to call on the store
1132 callStore: function(fn) {
1133 var store = this.store;
1135 if (store !== undefined && typeof store[fn] == "function") {
1141 * Gets all of the data from this Models *loaded* associations.
1142 * It does this recursively - for example if we have a User which
1143 * hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
1148 * status: 'shipped',
1155 * @return {Object} The nested data set for the Model's loaded associations
1157 getAssociatedData: function(){
1158 return this.prepareAssociatedData(this, [], null);
1163 * This complex-looking method takes a given Model instance and returns an object containing all data from
1164 * all of that Model's *loaded* associations. See (@link #getAssociatedData}
1165 * @param {Ext.data.Model} record The Model instance
1166 * @param {Array} ids PRIVATE. The set of Model instance internalIds that have already been loaded
1167 * @param {String} associationType (optional) The name of the type of association to limit to.
1168 * @return {Object} The nested data set for the Model's loaded associations
1170 prepareAssociatedData: function(record, ids, associationType) {
1171 //we keep track of all of the internalIds of the models that we have loaded so far in here
1172 var associations = record.associations.items,
1173 associationCount = associations.length,
1174 associationData = {},
1175 associatedStore, associatedName, associatedRecords, associatedRecord,
1176 associatedRecordCount, association, id, i, j, type, allow;
1178 for (i = 0; i < associationCount; i++) {
1179 association = associations[i];
1180 type = association.type;
1182 if (associationType) {
1183 allow = type == associationType;
1185 if (allow && type == 'hasMany') {
1187 //this is the hasMany store filled with the associated data
1188 associatedStore = record[association.storeName];
1190 //we will use this to contain each associated record's data
1191 associationData[association.name] = [];
1193 //if it's loaded, put it into the association data
1194 if (associatedStore && associatedStore.data.length > 0) {
1195 associatedRecords = associatedStore.data.items;
1196 associatedRecordCount = associatedRecords.length;
1198 //now we're finally iterating over the records in the association. We do this recursively
1199 for (j = 0; j < associatedRecordCount; j++) {
1200 associatedRecord = associatedRecords[j];
1201 // Use the id, since it is prefixed with the model name, guaranteed to be unique
1202 id = associatedRecord.id;
1204 //when we load the associations for a specific model instance we add it to the set of loaded ids so that
1205 //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
1206 if (Ext.Array.indexOf(ids, id) == -1) {
1209 associationData[association.name][j] = associatedRecord.data;
1210 Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
1214 } else if (allow && type == 'belongsTo') {
1215 associatedRecord = record[association.instanceName];
1216 if (associatedRecord !== undefined) {
1217 id = associatedRecord.id;
1218 if (Ext.Array.indexOf(ids, id) == -1) {
1220 associationData[association.name] = associatedRecord.data;
1221 Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
1227 return associationData;