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.
18 * A Model represents some object that your application manages. For example, one might define a Model for Users,
19 * Products, Cars, or any other real-world object that we want to model in the system. Models are registered via the
20 * {@link Ext.ModelManager model manager}, and are used by {@link Ext.data.Store stores}, which are in turn used by many
21 * of the data-bound components in Ext.
23 * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:
25 * Ext.define('User', {
26 * extend: 'Ext.data.Model',
28 * {name: 'name', type: 'string'},
29 * {name: 'age', type: 'int'},
30 * {name: 'phone', type: 'string'},
31 * {name: 'alive', type: 'boolean', defaultValue: true}
34 * changeName: function() {
35 * var oldName = this.get('name'),
36 * newName = oldName + " The Barbarian";
38 * this.set('name', newName);
42 * The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link
43 * Ext.ModelManager ModelManager}, and all other functions and properties are copied to the new Model's prototype.
45 * Now we can create instances of our User model and call any model logic we defined:
47 * var user = Ext.create('User', {
50 * phone: '555-555-5555'
54 * user.get('name'); //returns "Conan The Barbarian"
58 * Models have built-in support for validations, which are executed against the validator functions in {@link
59 * Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to
62 * Ext.define('User', {
63 * extend: 'Ext.data.Model',
65 * {name: 'name', type: 'string'},
66 * {name: 'age', type: 'int'},
67 * {name: 'phone', type: 'string'},
68 * {name: 'gender', type: 'string'},
69 * {name: 'username', type: 'string'},
70 * {name: 'alive', type: 'boolean', defaultValue: true}
74 * {type: 'presence', field: 'age'},
75 * {type: 'length', field: 'name', min: 2},
76 * {type: 'inclusion', field: 'gender', list: ['Male', 'Female']},
77 * {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
78 * {type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
82 * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
85 * var instance = Ext.create('User', {
88 * username: 'edspencer'
91 * var errors = instance.validate();
95 * Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and {@link
96 * Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration
97 * application which deals with Users, Posts and Comments. We can express the relationships between these models like
100 * Ext.define('Post', {
101 * extend: 'Ext.data.Model',
102 * fields: ['id', 'user_id'],
105 * hasMany : {model: 'Comment', name: 'comments'}
108 * Ext.define('Comment', {
109 * extend: 'Ext.data.Model',
110 * fields: ['id', 'user_id', 'post_id'],
115 * Ext.define('User', {
116 * extend: 'Ext.data.Model',
121 * {model: 'Comment', name: 'comments'}
125 * See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the
126 * usage and configuration of associations. Note that associations can also be specified like this:
128 * Ext.define('User', {
129 * extend: 'Ext.data.Model',
133 * {type: 'hasMany', model: 'Post', name: 'posts'},
134 * {type: 'hasMany', model: 'Comment', name: 'comments'}
140 * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or
141 * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which
142 * can be set directly on the Model:
144 * Ext.define('User', {
145 * extend: 'Ext.data.Model',
146 * fields: ['id', 'name', 'email'],
154 * 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
155 * RESTful backend. Let's see how this works:
157 * var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
159 * user.save(); //POST /users
161 * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's
162 * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,
163 * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We
164 * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.
166 * Loading data via the Proxy is equally easy:
168 * //get a reference to the User model class
169 * var User = Ext.ModelManager.getModel('User');
171 * //Uses the configured RestProxy to make a GET request to /users/123
173 * success: function(user) {
174 * console.log(user.getId()); //logs 123
178 * Models can also be updated and destroyed easily:
180 * //the user Model we loaded in the last snippet:
181 * user.set('name', 'Edward Spencer');
183 * //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
185 * success: function() {
186 * console.log('The User was updated');
190 * //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
192 * success: function() {
193 * console.log('The User was destroyed!');
199 * It is very common to want to load a set of Model instances to be displayed and manipulated in the UI. We do this by
200 * creating a {@link Ext.data.Store Store}:
202 * var store = Ext.create('Ext.data.Store', {
206 * //uses the Proxy we set up on Model to load the Store data
209 * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a
210 * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link
211 * Ext.data.Store Store docs} for more information on Stores.
214 * Creates new Model instance.
215 * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
216 * @param {Number} id (optional) Unique ID to assign to this model instance
218 Ext.define('Ext.data.Model', {
219 alternateClassName: 'Ext.data.Record',
222 observable: 'Ext.util.Observable'
227 'Ext.data.IdGenerator',
230 'Ext.data.Operation',
231 'Ext.data.validations',
232 'Ext.data.proxy.Ajax',
233 'Ext.util.MixedCollection'
236 onClassExtended: function(cls, data) {
237 var onBeforeClassCreated = data.onBeforeClassCreated;
239 data.onBeforeClassCreated = function(cls, data) {
241 name = Ext.getClassName(cls),
242 prototype = cls.prototype,
243 superCls = cls.prototype.superclass,
245 validations = data.validations || [],
246 fields = data.fields || [],
247 associations = data.associations || [],
248 belongsTo = data.belongsTo,
249 hasMany = data.hasMany,
252 fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) {
256 associationsMixedCollection = new Ext.util.MixedCollection(false, function(association) {
257 return association.name;
260 superValidations = superCls.validations,
261 superFields = superCls.fields,
262 superAssociations = superCls.associations,
267 // Save modelName on class and its prototype
268 cls.modelName = name;
269 prototype.modelName = name;
271 // Merge the validations of the superclass and the new subclass
272 if (superValidations) {
273 validations = superValidations.concat(validations);
276 data.validations = validations;
278 // Merge the fields of the superclass and the new subclass
280 fields = superFields.items.concat(fields);
283 for (i = 0, ln = fields.length; i < ln; ++i) {
284 fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
287 data.fields = fieldsMixedCollection;
290 data.idgen = Ext.data.IdGenerator.get(idgen);
293 //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
294 //we support that here
296 belongsTo = Ext.Array.from(belongsTo);
298 for (i = 0, ln = belongsTo.length; i < ln; ++i) {
299 association = belongsTo[i];
301 if (!Ext.isObject(association)) {
302 association = {model: association};
305 association.type = 'belongsTo';
306 associations.push(association);
309 delete data.belongsTo;
313 hasMany = Ext.Array.from(hasMany);
314 for (i = 0, ln = hasMany.length; i < ln; ++i) {
315 association = hasMany[i];
317 if (!Ext.isObject(association)) {
318 association = {model: association};
321 association.type = 'hasMany';
322 associations.push(association);
328 if (superAssociations) {
329 associations = superAssociations.items.concat(associations);
332 for (i = 0, ln = associations.length; i < ln; ++i) {
333 dependencies.push('association.' + associations[i].type.toLowerCase());
337 if (typeof data.proxy === 'string') {
338 dependencies.push('proxy.' + data.proxy);
340 else if (typeof data.proxy.type === 'string') {
341 dependencies.push('proxy.' + data.proxy.type);
345 Ext.require(dependencies, function() {
346 Ext.ModelManager.registerType(name, cls);
348 for (i = 0, ln = associations.length; i < ln; ++i) {
349 association = associations[i];
351 Ext.apply(association, {
353 associatedModel: association.model
356 if (Ext.ModelManager.getModel(association.model) === undefined) {
357 Ext.ModelManager.registerDeferredAssociation(association);
359 associationsMixedCollection.add(Ext.data.Association.create(association));
363 data.associations = associationsMixedCollection;
365 onBeforeClassCreated.call(me, cls, data);
367 cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
369 // Fire the onModelDefined template method on ModelManager
370 Ext.ModelManager.onModelDefined(cls);
375 inheritableStatics: {
377 * Sets the Proxy to use for this model. Accepts any options that can be accepted by
378 * {@link Ext#createByAlias Ext.createByAlias}.
379 * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
380 * @return {Ext.data.proxy.Proxy}
384 setProxy: function(proxy) {
385 //make sure we have an Ext.data.proxy.Proxy object
386 if (!proxy.isProxy) {
387 if (typeof proxy == "string") {
392 proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
394 proxy.setModel(this);
395 this.proxy = this.prototype.proxy = proxy;
401 * Returns the configured Proxy for this Model
402 * @return {Ext.data.proxy.Proxy} The proxy
406 getProxy: function() {
411 * Asynchronously loads a model instance by id. Sample usage:
413 * MyApp.User = Ext.define('User', {
414 * extend: 'Ext.data.Model',
416 * {name: 'id', type: 'int'},
417 * {name: 'name', type: 'string'}
421 * MyApp.User.load(10, {
423 * failure: function(record, operation) {
424 * //do something if the load failed
426 * success: function(record, operation) {
427 * //do something if the load succeeded
429 * callback: function(record, operation) {
430 * //do something whether the load succeeded or failed
434 * @param {Number} id The id of the model to load
435 * @param {Object} config (optional) config object containing success, failure and callback functions, plus
440 load: function(id, config) {
441 config = Ext.apply({}, config);
442 config = Ext.applyIf(config, {
447 var operation = Ext.create('Ext.data.Operation', config),
448 scope = config.scope || this,
452 callback = function(operation) {
453 if (operation.wasSuccessful()) {
454 record = operation.getRecords()[0];
455 Ext.callback(config.success, scope, [record, operation]);
457 Ext.callback(config.failure, scope, [record, operation]);
459 Ext.callback(config.callback, scope, [record, operation]);
462 this.proxy.read(operation, callback, this);
467 PREFIX : 'ext-record',
474 * Generates a sequential id. This method is typically called when a record is {@link Ext#create
475 * create}d and {@link #constructor no id has been specified}. The id will automatically be assigned to the
476 * record. The returned id takes the form: {PREFIX}-{AUTO_ID}.
478 * - **PREFIX** : String - Ext.data.Model.PREFIX (defaults to 'ext-record')
479 * - **AUTO_ID** : String - Ext.data.Model.AUTO_ID (defaults to 1 initially)
481 * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}.
482 * @return {String} auto-generated string id, `"ext-record-i++"`;
486 var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
494 * @cfg {String/Object} idgen
495 * The id generator to use for this model. The default id generator does not generate
496 * values for the {@link #idProperty}.
498 * This can be overridden at the model level to provide a custom generator for a model.
499 * The simplest form of this would be:
501 * Ext.define('MyApp.data.MyModel', {
502 * extend: 'Ext.data.Model',
503 * requires: ['Ext.data.SequentialIdGenerator'],
504 * idgen: 'sequential',
508 * The above would generate {@link Ext.data.SequentialIdGenerator sequential} id's such
511 * Another useful id generator is {@link Ext.data.UuidGenerator}:
513 * Ext.define('MyApp.data.MyModel', {
514 * extend: 'Ext.data.Model',
515 * requires: ['Ext.data.UuidGenerator'],
520 * An id generation can also be further configured:
522 * Ext.define('MyApp.data.MyModel', {
523 * extend: 'Ext.data.Model',
525 * type: 'sequential',
531 * The above would generate id's such as ID_1000, ID_1001, ID_1002 etc..
533 * If multiple models share an id space, a single generator can be shared:
535 * Ext.define('MyApp.data.MyModelX', {
536 * extend: 'Ext.data.Model',
538 * type: 'sequential',
543 * Ext.define('MyApp.data.MyModelY', {
544 * extend: 'Ext.data.Model',
546 * type: 'sequential',
551 * For more complex, shared id generators, a custom generator is the best approach.
552 * See {@link Ext.data.IdGenerator} for details on creating custom id generators.
560 generate: function () {
563 getRecId: function (rec) {
564 return rec.modelName + '-' + rec.internalId;
569 * @property {Boolean} editing
570 * Internal flag used to track whether or not the model instance is currently being edited. Read-only.
575 * @property {Boolean} dirty
576 * True if this Record has been modified. Read-only.
581 * @cfg {String} persistenceProperty
582 * The property on this Persistable object that its data is saved to. Defaults to 'data'
583 * (e.g. all persistable data resides in this.data.)
585 persistenceProperty: 'data',
591 * @property {Boolean} phantom
592 * True when the record does not yet exist in a server-side database (see {@link #setDirty}).
593 * Any record which has a real database pk set as its id property is NOT a phantom -- it's real.
598 * @cfg {String} idProperty
599 * The name of the field treated as this Model's unique id. Defaults to 'id'.
604 * @cfg {String} defaultProxyType
605 * The string type of the default Model Proxy. Defaults to 'ajax'.
607 defaultProxyType: 'ajax',
609 // Fields config and property
611 * @cfg {Object[]/String[]} fields
612 * The fields for this model.
615 * @property {Ext.util.MixedCollection} fields
616 * The fields defined on this model.
620 * @cfg {Object[]} validations
621 * An array of {@link Ext.data.validations validations} for this model.
624 // Associations configs and properties
626 * @cfg {Object[]} associations
627 * An array of {@link Ext.data.Association associations} for this model.
630 * @cfg {String/Object/String[]/Object[]} hasMany
631 * One or more {@link Ext.data.HasManyAssociation HasMany associations} for this model.
634 * @cfg {String/Object/String[]/Object[]} belongsTo
635 * One or more {@link Ext.data.BelongsToAssociation BelongsTo associations} for this model.
638 * @property {Ext.util.MixedCollection} associations
639 * {@link Ext.data.Association Associations} defined on this model.
643 * @cfg {String/Object/Ext.data.proxy.Proxy} proxy
644 * The {@link Ext.data.proxy.Proxy proxy} to use for this model.
647 // raw not documented intentionally, meant to be used internally.
648 constructor: function(data, id, raw) {
658 isArray = Ext.isArray(data),
659 newData = isArray ? {} : null; // to hold mapped array data if needed
662 * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
663 * @property internalId
667 me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
670 * @property {Object} raw The raw data used to create this model if created via a reader.
679 * @property {Object} modified Key: value pairs of all fields whose values have changed
683 // Deal with spelling error in previous releases
684 if (me.persistanceProperty) {
686 if (Ext.isDefined(Ext.global.console)) {
687 Ext.global.console.warn('Ext.data.Model: persistanceProperty has been deprecated. Use persistenceProperty instead.');
690 me.persistenceProperty = me.persistanceProperty;
692 me[me.persistenceProperty] = {};
694 me.mixins.observable.constructor.call(me);
696 //add default field values if present
697 fields = me.fields.items;
698 length = fields.length;
700 for (i = 0; i < length; i++) {
705 // Have to map array data so the values get assigned to the named fields
706 // rather than getting set as the field names with undefined values.
707 newData[name] = data[i];
709 else if (data[name] === undefined) {
710 data[name] = field.defaultValue;
714 me.set(newData || data);
718 } else if (me.phantom) {
719 newId = me.idgen.generate();
720 if (newId !== null) {
725 // clear any dirty/modified since we're initializing
729 if (typeof me.init == 'function') {
733 me.id = me.idgen.getRecId(me);
737 * Returns the value of the given field
738 * @param {String} fieldName The field to fetch the value for
739 * @return {Object} The value
741 get: function(field) {
742 return this[this.persistenceProperty][field];
746 * Sets the given field to the given value, marks the instance as dirty
747 * @param {String/Object} fieldName The field to set, or an object containing key/value pairs
748 * @param {Object} value The value to set
750 set: function(fieldName, value) {
753 modified = me.modified,
755 field, key, i, currentValue, notEditing, count, length;
758 * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
759 * set those last so that all other possible data is set before the convert function is called
761 if (arguments.length == 1 && Ext.isObject(fieldName)) {
762 notEditing = !me.editing;
764 for (key in fieldName) {
765 if (fieldName.hasOwnProperty(key)) {
767 //here we check for the custom convert function. Note that if a field doesn't have a convert function,
768 //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
769 field = fields.get(key);
770 if (field && field.convert !== field.type.convert) {
771 convertFields.push(key);
775 if (!count && notEditing) {
779 me.set(key, fieldName[key]);
783 length = convertFields.length;
785 if (!count && notEditing) {
789 for (i = 0; i < length; i++) {
790 field = convertFields[i];
791 me.set(field, fieldName[field]);
795 if (notEditing && count) {
800 field = fields.get(fieldName);
802 if (field && field.convert) {
803 value = field.convert(value, me);
806 currentValue = me.get(fieldName);
807 me[me.persistenceProperty][fieldName] = value;
809 if (field && field.persist && !me.isEqual(currentValue, value)) {
810 if (me.isModified(fieldName)) {
811 if (me.isEqual(modified[fieldName], value)) {
812 // the original value in me.modified equals the new value, so the
813 // field is no longer modified
814 delete modified[fieldName];
815 // we might have removed the last modified field, so check to see if
816 // there are any modified fields remaining and correct me.dirty:
818 for (key in modified) {
819 if (modified.hasOwnProperty(key)){
827 modified[fieldName] = currentValue;
838 * Checks if two values are equal, taking into account certain
839 * special factors, for example dates.
841 * @param {Object} a The first value
842 * @param {Object} b The second value
843 * @return {Boolean} True if the values are equal
845 isEqual: function(a, b){
846 if (Ext.isDate(a) && Ext.isDate(b)) {
847 return a.getTime() === b.getTime();
853 * Begins an edit. While in edit mode, no events (e.g.. the `update` event) are relayed to the containing store.
854 * When an edit has begun, it must be followed by either {@link #endEdit} or {@link #cancelEdit}.
856 beginEdit : function(){
860 me.dirtySave = me.dirty;
861 me.dataSave = Ext.apply({}, me[me.persistenceProperty]);
862 me.modifiedSave = Ext.apply({}, me.modified);
867 * Cancels all changes made in the current edit operation.
869 cancelEdit : function(){
873 // reset the modified state, nothing changed since the edit began
874 me.modified = me.modifiedSave;
875 me[me.persistenceProperty] = me.dataSave;
876 me.dirty = me.dirtySave;
877 delete me.modifiedSave;
884 * Ends an edit. If any data was modified, the containing store is notified (ie, the store's `update` event will
886 * @param {Boolean} silent True to not notify the store of the change
888 endEdit : function(silent){
894 didChange = me.dirty || me.changedWhileEditing();
895 delete me.modifiedSave;
898 if (silent !== true && didChange) {
905 * Checks if the underlying data has changed during an edit. This doesn't necessarily
906 * mean the record is dirty, however we still need to notify the store since it may need
907 * to update any views.
909 * @return {Boolean} True if the underlying data has changed during an edit.
911 changedWhileEditing: function(){
914 data = me[me.persistenceProperty],
918 if (data.hasOwnProperty(key)) {
919 if (!me.isEqual(data[key], saved[key])) {
928 * Gets a hash of only the fields that have been modified since this Model was created or commited.
931 getChanges : function(){
932 var modified = this.modified,
936 for (field in modified) {
937 if (modified.hasOwnProperty(field)){
938 changes[field] = this.get(field);
946 * Returns true if the passed field name has been `{@link #modified}` since the load or last commit.
947 * @param {String} fieldName {@link Ext.data.Field#name}
950 isModified : function(fieldName) {
951 return this.modified.hasOwnProperty(fieldName);
955 * Marks this **Record** as `{@link #dirty}`. This method is used interally when adding `{@link #phantom}` records
956 * to a {@link Ext.data.proxy.Server#writer writer enabled store}.
958 * Marking a record `{@link #dirty}` causes the phantom to be returned by {@link Ext.data.Store#getUpdatedRecords}
959 * where it will have a create action composed for it during {@link Ext.data.Model#save model save} operations.
961 setDirty : function() {
967 me.fields.each(function(field) {
970 me.modified[name] = me.get(name);
976 markDirty : function() {
977 if (Ext.isDefined(Ext.global.console)) {
978 Ext.global.console.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
980 return this.setDirty.apply(this, arguments);
985 * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects
986 * all changes made to the model instance since either creation, or the last commit operation. Modified fields are
987 * reverted to their original values.
989 * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of reject
992 * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
995 reject : function(silent) {
997 modified = me.modified,
1000 for (field in modified) {
1001 if (modified.hasOwnProperty(field)) {
1002 if (typeof modified[field] != "function") {
1003 me[me.persistenceProperty][field] = modified[field];
1012 if (silent !== true) {
1018 * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the
1019 * instance since either creation or the last commit operation.
1021 * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of commit
1024 * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
1025 * Defaults to false.
1027 commit : function(silent) {
1030 me.phantom = me.dirty = me.editing = false;
1033 if (silent !== true) {
1039 * Creates a copy (clone) of this Model instance.
1041 * @param {String} [id] A new id, defaults to the id of the instance being copied.
1042 * See `{@link Ext.data.Model#id id}`. To generate a phantom instance with a new id use:
1044 * var rec = record.copy(); // clone the record
1045 * Ext.data.Model.id(rec); // automatically generate a unique sequential id
1047 * @return {Ext.data.Model}
1049 copy : function(newId) {
1052 return new me.self(Ext.apply({}, me[me.persistenceProperty]), newId || me.internalId);
1056 * Sets the Proxy to use for this model. Accepts any options that can be accepted by
1057 * {@link Ext#createByAlias Ext.createByAlias}.
1059 * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
1060 * @return {Ext.data.proxy.Proxy}
1062 setProxy: function(proxy) {
1063 //make sure we have an Ext.data.proxy.Proxy object
1064 if (!proxy.isProxy) {
1065 if (typeof proxy === "string") {
1070 proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
1072 proxy.setModel(this.self);
1079 * Returns the configured Proxy for this Model.
1080 * @return {Ext.data.proxy.Proxy} The proxy
1082 getProxy: function() {
1087 * Validates the current data against all of its configured {@link #validations}.
1088 * @return {Ext.data.Errors} The errors object
1090 validate: function() {
1091 var errors = Ext.create('Ext.data.Errors'),
1092 validations = this.validations,
1093 validators = Ext.data.validations,
1094 length, validation, field, valid, type, i;
1097 length = validations.length;
1099 for (i = 0; i < length; i++) {
1100 validation = validations[i];
1101 field = validation.field || validation.name;
1102 type = validation.type;
1103 valid = validators[type](validation, this.get(field));
1108 message: validation.message || validators[type + 'Message']
1118 * Checks if the model is valid. See {@link #validate}.
1119 * @return {Boolean} True if the model is valid.
1121 isValid: function(){
1122 return this.validate().isValid();
1126 * Saves the model instance using the configured proxy.
1127 * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
1128 * @return {Ext.data.Model} The Model instance
1130 save: function(options) {
1131 options = Ext.apply({}, options);
1134 action = me.phantom ? 'create' : 'update',
1136 scope = options.scope || me,
1140 Ext.apply(options, {
1145 operation = Ext.create('Ext.data.Operation', options);
1147 callback = function(operation) {
1148 if (operation.wasSuccessful()) {
1149 record = operation.getRecords()[0];
1150 //we need to make sure we've set the updated data here. Ideally this will be redundant once the
1151 //ModelCache is in place
1152 me.set(record.data);
1153 record.dirty = false;
1155 Ext.callback(options.success, scope, [record, operation]);
1157 Ext.callback(options.failure, scope, [record, operation]);
1160 Ext.callback(options.callback, scope, [record, operation]);
1163 me.getProxy()[action](operation, callback, me);
1169 * Destroys the model using the configured proxy.
1170 * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
1171 * @return {Ext.data.Model} The Model instance
1173 destroy: function(options){
1174 options = Ext.apply({}, options);
1178 scope = options.scope || me,
1182 Ext.apply(options, {
1187 operation = Ext.create('Ext.data.Operation', options);
1188 callback = function(operation) {
1189 if (operation.wasSuccessful()) {
1190 Ext.callback(options.success, scope, [record, operation]);
1192 Ext.callback(options.failure, scope, [record, operation]);
1194 Ext.callback(options.callback, scope, [record, operation]);
1197 me.getProxy().destroy(operation, callback, me);
1202 * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}.
1203 * @return {Number} The id
1206 return this.get(this.idProperty);
1210 * Sets the model instance's id field to the given id.
1211 * @param {Number} id The new id
1213 setId: function(id) {
1214 this.set(this.idProperty, id);
1218 * Tells this model instance that it has been added to a store.
1219 * @param {Ext.data.Store} store The store to which this model has been added.
1221 join : function(store) {
1223 * @property {Ext.data.Store} store
1224 * The {@link Ext.data.Store Store} to which this Record belongs.
1230 * Tells this model instance that it has been removed from the store.
1231 * @param {Ext.data.Store} store The store from which this model has been removed.
1233 unjoin: function(store) {
1239 * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
1240 * afterEdit method is called
1242 afterEdit : function() {
1243 this.callStore('afterEdit');
1248 * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
1249 * afterReject method is called
1251 afterReject : function() {
1252 this.callStore("afterReject");
1257 * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
1258 * afterCommit method is called
1260 afterCommit: function() {
1261 this.callStore('afterCommit');
1266 * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
1267 * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
1268 * will always be called with the model instance as its single argument.
1269 * @param {String} fn The function to call on the store
1271 callStore: function(fn) {
1272 var store = this.store;
1274 if (store !== undefined && typeof store[fn] == "function") {
1280 * Gets all of the data from this Models *loaded* associations. It does this recursively - for example if we have a
1281 * User which hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
1287 * status: 'shipped',
1295 * @return {Object} The nested data set for the Model's loaded associations
1297 getAssociatedData: function(){
1298 return this.prepareAssociatedData(this, [], null);
1303 * This complex-looking method takes a given Model instance and returns an object containing all data from
1304 * all of that Model's *loaded* associations. See (@link #getAssociatedData}
1305 * @param {Ext.data.Model} record The Model instance
1306 * @param {String[]} ids PRIVATE. The set of Model instance internalIds that have already been loaded
1307 * @param {String} associationType (optional) The name of the type of association to limit to.
1308 * @return {Object} The nested data set for the Model's loaded associations
1310 prepareAssociatedData: function(record, ids, associationType) {
1311 //we keep track of all of the internalIds of the models that we have loaded so far in here
1312 var associations = record.associations.items,
1313 associationCount = associations.length,
1314 associationData = {},
1315 associatedStore, associatedName, associatedRecords, associatedRecord,
1316 associatedRecordCount, association, id, i, j, type, allow;
1318 for (i = 0; i < associationCount; i++) {
1319 association = associations[i];
1320 type = association.type;
1322 if (associationType) {
1323 allow = type == associationType;
1325 if (allow && type == 'hasMany') {
1327 //this is the hasMany store filled with the associated data
1328 associatedStore = record[association.storeName];
1330 //we will use this to contain each associated record's data
1331 associationData[association.name] = [];
1333 //if it's loaded, put it into the association data
1334 if (associatedStore && associatedStore.data.length > 0) {
1335 associatedRecords = associatedStore.data.items;
1336 associatedRecordCount = associatedRecords.length;
1338 //now we're finally iterating over the records in the association. We do this recursively
1339 for (j = 0; j < associatedRecordCount; j++) {
1340 associatedRecord = associatedRecords[j];
1341 // Use the id, since it is prefixed with the model name, guaranteed to be unique
1342 id = associatedRecord.id;
1344 //when we load the associations for a specific model instance we add it to the set of loaded ids so that
1345 //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
1346 if (Ext.Array.indexOf(ids, id) == -1) {
1349 associationData[association.name][j] = associatedRecord.data;
1350 Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
1354 } else if (allow && type == 'belongsTo') {
1355 associatedRecord = record[association.instanceName];
1356 if (associatedRecord !== undefined) {
1357 id = associatedRecord.id;
1358 if (Ext.Array.indexOf(ids, id) == -1) {
1360 associationData[association.name] = associatedRecord.data;
1361 Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
1367 return associationData;