X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/3789b528d8dd8aad4558e38e22d775bcab1cbd36..refs/heads/master:/docs/source/Model.html diff --git a/docs/source/Model.html b/docs/source/Model.html index 7f44a7f0..f199d143 100644 --- a/docs/source/Model.html +++ b/docs/source/Model.html @@ -3,8 +3,8 @@
/** * @author Ed Spencer - * @class Ext.data.Model * - * <p>A Model represents some object that your application manages. For example, one might define a Model for Users, Products, - * 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}, - * and are used by {@link Ext.data.Store stores}, which are in turn used by many of the data-bound components in Ext.</p> + * A Model represents some object that your application manages. For example, one might define a Model for Users, + * Products, 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}, and are used by {@link Ext.data.Store stores}, which are in turn used by many + * of the data-bound components in Ext. * - * <p>Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:</p> + * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example: * -<pre><code> -Ext.define('User', { - extend: 'Ext.data.Model', - fields: [ - {name: 'name', type: 'string'}, - {name: 'age', type: 'int'}, - {name: 'phone', type: 'string'}, - {name: 'alive', type: 'boolean', defaultValue: true} - ], - - changeName: function() { - var oldName = this.get('name'), - newName = oldName + " The Barbarian"; - - this.set('name', newName); - } -}); -</code></pre> -* -* <p>The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link Ext.ModelManager ModelManager}, and all -* other functions and properties are copied to the new Model's prototype.</p> -* -* <p>Now we can create instances of our User model and call any model logic we defined:</p> -* -<pre><code> -var user = Ext.ModelManager.create({ - name : 'Conan', - age : 24, - phone: '555-555-5555' -}, 'User'); - -user.changeName(); -user.get('name'); //returns "Conan The Barbarian" -</code></pre> + * Ext.define('User', { + * extend: 'Ext.data.Model', + * fields: [ + * {name: 'name', type: 'string'}, + * {name: 'age', type: 'int'}, + * {name: 'phone', type: 'string'}, + * {name: 'alive', type: 'boolean', defaultValue: true} + * ], * - * <p><u>Validations</u></p> + * changeName: function() { + * var oldName = this.get('name'), + * newName = oldName + " The Barbarian"; * - * <p>Models have built-in support for validations, which are executed against the validator functions in - * {@link Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to models:</p> + * this.set('name', newName); + * } + * }); * -<pre><code> -Ext.define('User', { - extend: 'Ext.data.Model', - fields: [ - {name: 'name', type: 'string'}, - {name: 'age', type: 'int'}, - {name: 'phone', type: 'string'}, - {name: 'gender', type: 'string'}, - {name: 'username', type: 'string'}, - {name: 'alive', type: 'boolean', defaultValue: true} - ], - - validations: [ - {type: 'presence', field: 'age'}, - {type: 'length', field: 'name', min: 2}, - {type: 'inclusion', field: 'gender', list: ['Male', 'Female']}, - {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']}, - {type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/} - ] -}); -</code></pre> + * The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link + * Ext.ModelManager ModelManager}, and all other functions and properties are copied to the new Model's prototype. * - * <p>The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors} - * object:</p> + * Now we can create instances of our User model and call any model logic we defined: * -<pre><code> -var instance = Ext.ModelManager.create({ - name: 'Ed', - gender: 'Male', - username: 'edspencer' -}, 'User'); - -var errors = instance.validate(); -</code></pre> + * var user = Ext.create('User', { + * name : 'Conan', + * age : 24, + * phone: '555-555-5555' + * }); * - * <p><u>Associations</u></p> + * user.changeName(); + * user.get('name'); //returns "Conan The Barbarian" * - * <p>Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and - * {@link Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration - * application which deals with Users, Posts and Comments. We can express the relationships between these models like this:</p> + * # Validations * -<pre><code> -Ext.define('Post', { - extend: 'Ext.data.Model', - fields: ['id', 'user_id'], - - belongsTo: 'User', - hasMany : {model: 'Comment', name: 'comments'} -}); - -Ext.define('Comment', { - extend: 'Ext.data.Model', - fields: ['id', 'user_id', 'post_id'], - - belongsTo: 'Post' -}); - -Ext.define('User', { - extend: 'Ext.data.Model', - fields: ['id'], - - hasMany: [ - 'Post', - {model: 'Comment', name: 'comments'} - ] -}); -</code></pre> + * Models have built-in support for validations, which are executed against the validator functions in {@link + * Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to + * models: * - * <p>See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the usage - * and configuration of associations. Note that associations can also be specified like this:</p> + * Ext.define('User', { + * extend: 'Ext.data.Model', + * fields: [ + * {name: 'name', type: 'string'}, + * {name: 'age', type: 'int'}, + * {name: 'phone', type: 'string'}, + * {name: 'gender', type: 'string'}, + * {name: 'username', type: 'string'}, + * {name: 'alive', type: 'boolean', defaultValue: true} + * ], * -<pre><code> -Ext.define('User', { - extend: 'Ext.data.Model', - fields: ['id'], - - associations: [ - {type: 'hasMany', model: 'Post', name: 'posts'}, - {type: 'hasMany', model: 'Comment', name: 'comments'} - ] -}); -</code></pre> + * validations: [ + * {type: 'presence', field: 'age'}, + * {type: 'length', field: 'name', min: 2}, + * {type: 'inclusion', field: 'gender', list: ['Male', 'Female']}, + * {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']}, + * {type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/} + * ] + * }); * - * <p><u>Using a Proxy</u></p> + * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors} + * object: * - * <p>Models are great for representing types of data and relationships, but sooner or later we're going to want to - * load or save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, - * which can be set directly on the Model:</p> + * var instance = Ext.create('User', { + * name: 'Ed', + * gender: 'Male', + * username: 'edspencer' + * }); * -<pre><code> -Ext.define('User', { - extend: 'Ext.data.Model', - fields: ['id', 'name', 'email'], - - proxy: { - type: 'rest', - url : '/users' - } -}); -</code></pre> + * var errors = instance.validate(); * - * <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 - * RESTful backend. Let's see how this works:</p> + * # Associations * -<pre><code> -var user = Ext.ModelManager.create({name: 'Ed Spencer', email: 'ed@sencha.com'}, 'User'); - -user.save(); //POST /users -</code></pre> + * Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and {@link + * Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration + * application which deals with Users, Posts and Comments. We can express the relationships between these models like + * this: * - * <p>Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this - * Model's data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't - * have an id, and performs the appropriate action - in this case issuing a POST request to the url we configured - * (/users). We configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full - * list.</p> + * Ext.define('Post', { + * extend: 'Ext.data.Model', + * fields: ['id', 'user_id'], * - * <p>Loading data via the Proxy is equally easy:</p> + * belongsTo: 'User', + * hasMany : {model: 'Comment', name: 'comments'} + * }); * -<pre><code> -//get a reference to the User model class -var User = Ext.ModelManager.getModel('User'); - -//Uses the configured RestProxy to make a GET request to /users/123 -User.load(123, { - success: function(user) { - console.log(user.getId()); //logs 123 - } -}); -</code></pre> + * Ext.define('Comment', { + * extend: 'Ext.data.Model', + * fields: ['id', 'user_id', 'post_id'], * - * <p>Models can also be updated and destroyed easily:</p> + * belongsTo: 'Post' + * }); * -<pre><code> -//the user Model we loaded in the last snippet: -user.set('name', 'Edward Spencer'); - -//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 -user.save({ - success: function() { - console.log('The User was updated'); - } -}); - -//tells the Proxy to destroy the Model. Performs a DELETE request to /users/123 -user.destroy({ - success: function() { - console.log('The User was destroyed!'); - } -}); -</code></pre> + * Ext.define('User', { + * extend: 'Ext.data.Model', + * fields: ['id'], * - * <p><u>Usage in Stores</u></p> + * hasMany: [ + * 'Post', + * {model: 'Comment', name: 'comments'} + * ] + * }); * - * <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 - * by creating a {@link Ext.data.Store Store}:</p> + * See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the + * usage and configuration of associations. Note that associations can also be specified like this: * -<pre><code> -var store = new Ext.data.Store({ - model: 'User' -}); - -//uses the Proxy we set up on Model to load the Store data -store.load(); -</code></pre> + * Ext.define('User', { + * extend: 'Ext.data.Model', + * fields: ['id'], + * + * associations: [ + * {type: 'hasMany', model: 'Post', name: 'posts'}, + * {type: 'hasMany', model: 'Comment', name: 'comments'} + * ] + * }); + * + * # Using a Proxy + * + * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or + * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which + * can be set directly on the Model: + * + * Ext.define('User', { + * extend: 'Ext.data.Model', + * fields: ['id', 'name', 'email'], + * + * proxy: { + * type: 'rest', + * url : '/users' + * } + * }); * - * <p>A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain - * a set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the - * {@link Ext.data.Store Store docs} for more information on Stores.</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 + * RESTful backend. Let's see how this works: + * + * var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'}); + * + * user.save(); //POST /users + * + * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's + * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id, + * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We + * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list. + * + * Loading data via the Proxy is equally easy: + * + * //get a reference to the User model class + * var User = Ext.ModelManager.getModel('User'); + * + * //Uses the configured RestProxy to make a GET request to /users/123 + * User.load(123, { + * success: function(user) { + * console.log(user.getId()); //logs 123 + * } + * }); + * + * Models can also be updated and destroyed easily: + * + * //the user Model we loaded in the last snippet: + * user.set('name', 'Edward Spencer'); + * + * //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 + * user.save({ + * success: function() { + * console.log('The User was updated'); + * } + * }); + * + * //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123 + * user.destroy({ + * success: function() { + * console.log('The User was destroyed!'); + * } + * }); + * + * # Usage in Stores + * + * 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 + * creating a {@link Ext.data.Store Store}: + * + * var store = Ext.create('Ext.data.Store', { + * model: 'User' + * }); + * + * //uses the Proxy we set up on Model to load the Store data + * store.load(); + * + * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a + * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link + * Ext.data.Store Store docs} for more information on Stores. * * @constructor + * Creates new Model instance. * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values - * @param {Number} id Optional unique ID to assign to this model instance + * @param {Number} id (optional) Unique ID to assign to this model instance */ Ext.define('Ext.data.Model', { alternateClassName: 'Ext.data.Record', - + mixins: { observable: 'Ext.util.Observable' }, requires: [ 'Ext.ModelManager', + 'Ext.data.IdGenerator', 'Ext.data.Field', 'Ext.data.Errors', 'Ext.data.Operation', @@ -269,6 +250,7 @@ Ext.define('Ext.data.Model', { associations = data.associations || [], belongsTo = data.belongsTo, hasMany = data.hasMany, + idgen = data.idgen, fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) { return field.name; @@ -307,6 +289,10 @@ Ext.define('Ext.data.Model', { data.fields = fieldsMixedCollection; + if (idgen) { + data.idgen = Ext.data.IdGenerator.get(idgen); + } + //associations can be specified in the more convenient format (e.g. not inside an 'associations' array). //we support that here if (belongsTo) { @@ -390,10 +376,13 @@ Ext.define('Ext.data.Model', { }, inheritableStatics: { - /** - * Sets the Proxy to use for this model. Accepts any options that can be accepted by {@link Ext#createByAlias Ext.createByAlias} + /** + * Sets the Proxy to use for this model. Accepts any options that can be accepted by + * {@link Ext#createByAlias Ext.createByAlias}. * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy + * @return {Ext.data.proxy.Proxy} * @static + * @inheritable */ setProxy: function(proxy) { //make sure we have an Ext.data.proxy.Proxy object @@ -411,43 +400,45 @@ Ext.define('Ext.data.Model', { return proxy; }, - /** + /** * Returns the configured Proxy for this Model * @return {Ext.data.proxy.Proxy} The proxy + * @static + * @inheritable */ getProxy: function() { return this.proxy; }, - /** - * <b>Static</b>. Asynchronously loads a model instance by id. Sample usage: - <pre><code> - MyApp.User = Ext.define('User', { - extend: 'Ext.data.Model', - fields: [ - {name: 'id', type: 'int'}, - {name: 'name', type: 'string'} - ] - }); - - MyApp.User.load(10, { - scope: this, - failure: function(record, operation) { - //do something if the load failed - }, - success: function(record, operation) { - //do something if the load succeeded - }, - callback: function(record, operation) { - //do something whether the load succeeded or failed - } - }); - </code></pre> + /** + * Asynchronously loads a model instance by id. Sample usage: + * + * MyApp.User = Ext.define('User', { + * extend: 'Ext.data.Model', + * fields: [ + * {name: 'id', type: 'int'}, + * {name: 'name', type: 'string'} + * ] + * }); + * + * MyApp.User.load(10, { + * scope: this, + * failure: function(record, operation) { + * //do something if the load failed + * }, + * success: function(record, operation) { + * //do something if the load succeeded + * }, + * callback: function(record, operation) { + * //do something whether the load succeeded or failed + * } + * }); + * * @param {Number} id The id of the model to load - * @param {Object} config Optional config object containing success, failure and callback functions, plus optional scope - * @member Ext.data.Model - * @method load + * @param {Object} config (optional) config object containing success, failure and callback functions, plus + * optional scope * @static + * @inheritable */ load: function(id, config) { config = Ext.apply({}, config); @@ -482,18 +473,16 @@ Ext.define('Ext.data.Model', { REJECT : 'reject', COMMIT : 'commit', - /** - * Generates a sequential id. This method is typically called when a record is {@link #create}d - * and {@link #Record no id has been specified}. The id will automatically be assigned - * to the record. The returned id takes the form: - * <tt>{PREFIX}-{AUTO_ID}</tt>.<div class="mdetail-params"><ul> - * <li><b><tt>PREFIX</tt></b> : String<p class="sub-desc"><tt>Ext.data.Model.PREFIX</tt> - * (defaults to <tt>'ext-record'</tt>)</p></li> - * <li><b><tt>AUTO_ID</tt></b> : String<p class="sub-desc"><tt>Ext.data.Model.AUTO_ID</tt> - * (defaults to <tt>1</tt> initially)</p></li> - * </ul></div> - * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}. - * @return {String} auto-generated string id, <tt>"ext-record-i++'</tt>; + /** + * Generates a sequential id. This method is typically called when a record is {@link Ext#create + * create}d and {@link #constructor no id has been specified}. The id will automatically be assigned to the + * record. The returned id takes the form: {PREFIX}-{AUTO_ID}. + * + * - **PREFIX** : String - Ext.data.Model.PREFIX (defaults to 'ext-record') + * - **AUTO_ID** : String - Ext.data.Model.AUTO_ID (defaults to 1 initially) + * + * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}. + * @return {String} auto-generated string id, `"ext-record-i++"`; * @static */ id: function(rec) { @@ -503,66 +492,172 @@ Ext.define('Ext.data.Model', { return id; } }, - + + /** + * @cfg {String/Object} idgen + * The id generator to use for this model. The default id generator does not generate + * values for the {@link #idProperty}. + * + * This can be overridden at the model level to provide a custom generator for a model. + * The simplest form of this would be: + * + * Ext.define('MyApp.data.MyModel', { + * extend: 'Ext.data.Model', + * requires: ['Ext.data.SequentialIdGenerator'], + * idgen: 'sequential', + * ... + * }); + * + * The above would generate {@link Ext.data.SequentialIdGenerator sequential} id's such + * as 1, 2, 3 etc.. + * + * Another useful id generator is {@link Ext.data.UuidGenerator}: + * + * Ext.define('MyApp.data.MyModel', { + * extend: 'Ext.data.Model', + * requires: ['Ext.data.UuidGenerator'], + * idgen: 'uuid', + * ... + * }); + * + * An id generation can also be further configured: + * + * Ext.define('MyApp.data.MyModel', { + * extend: 'Ext.data.Model', + * idgen: { + * type: 'sequential', + * seed: 1000, + * prefix: 'ID_' + * } + * }); + * + * The above would generate id's such as ID_1000, ID_1001, ID_1002 etc.. + * + * If multiple models share an id space, a single generator can be shared: + * + * Ext.define('MyApp.data.MyModelX', { + * extend: 'Ext.data.Model', + * idgen: { + * type: 'sequential', + * id: 'xy' + * } + * }); + * + * Ext.define('MyApp.data.MyModelY', { + * extend: 'Ext.data.Model', + * idgen: { + * type: 'sequential', + * id: 'xy' + * } + * }); + * + * For more complex, shared id generators, a custom generator is the best approach. + * See {@link Ext.data.IdGenerator} for details on creating custom id generators. + * + * @markdown + */ + idgen: { + isGenerator: true, + type: 'default', + + generate: function () { + return null; + }, + getRecId: function (rec) { + return rec.modelName + '-' + rec.internalId; + } + }, + /** - * Internal flag used to track whether or not the model instance is currently being edited. Read-only - * @property editing - * @type Boolean + * @property {Boolean} editing + * Internal flag used to track whether or not the model instance is currently being edited. Read-only. */ editing : false, /** - * Readonly flag - true if this Record has been modified. - * @type Boolean + * @property {Boolean} dirty + * True if this Record has been modified. Read-only. */ dirty : false, - /** - * @cfg {String} persistanceProperty The property on this Persistable object that its data is saved to. - * Defaults to 'data' (e.g. all persistable data resides in this.data.) + /** + * @cfg {String} persistenceProperty + * The property on this Persistable object that its data is saved to. Defaults to 'data' + * (e.g. all persistable data resides in this.data.) */ - persistanceProperty: 'data', + persistenceProperty: 'data', evented: false, isModel: true, /** - * <tt>true</tt> when the record does not yet exist in a server-side database (see - * {@link #setDirty}). Any record which has a real database pk set as its id property - * is NOT a phantom -- it's real. - * @property phantom - * @type {Boolean} + * @property {Boolean} phantom + * True when the record does not yet exist in a server-side database (see {@link #setDirty}). + * Any record which has a real database pk set as its id property is NOT a phantom -- it's real. */ phantom : false, /** - * @cfg {String} idProperty The name of the field treated as this Model's unique id (defaults to 'id'). + * @cfg {String} idProperty + * The name of the field treated as this Model's unique id. Defaults to 'id'. */ idProperty: 'id', - /** - * The string type of the default Model Proxy. Defaults to 'ajax' - * @property defaultProxyType - * @type String + /** + * @cfg {String} defaultProxyType + * The string type of the default Model Proxy. Defaults to 'ajax'. */ defaultProxyType: 'ajax', + // Fields config and property + /** + * @cfg {Object[]/String[]} fields + * The fields for this model. + */ /** - * An array of the fields defined on this model - * @property fields - * @type {Array} + * @property {Ext.util.MixedCollection} fields + * The fields defined on this model. + */ + + /** + * @cfg {Object[]} validations + * An array of {@link Ext.data.validations validations} for this model. + */ + + // Associations configs and properties + /** + * @cfg {Object[]} associations + * An array of {@link Ext.data.Association associations} for this model. + */ + /** + * @cfg {String/Object/String[]/Object[]} hasMany + * One or more {@link Ext.data.HasManyAssociation HasMany associations} for this model. + */ + /** + * @cfg {String/Object/String[]/Object[]} belongsTo + * One or more {@link Ext.data.BelongsToAssociation BelongsTo associations} for this model. + */ + /** + * @property {Ext.util.MixedCollection} associations + * {@link Ext.data.Association Associations} defined on this model. + */ + + /** + * @cfg {String/Object/Ext.data.proxy.Proxy} proxy + * The {@link Ext.data.proxy.Proxy proxy} to use for this model. */ // raw not documented intentionally, meant to be used internally. constructor: function(data, id, raw) { data = data || {}; - + var me = this, fields, length, field, name, i, + newId, isArray = Ext.isArray(data), newData = isArray ? {} : null; // to hold mapped array data if needed @@ -573,26 +668,31 @@ Ext.define('Ext.data.Model', { * @private */ me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me); - + /** - * The raw data used to create this model if created via a reader. - * @property raw - * @type Object + * @property {Object} raw The raw data used to create this model if created via a reader. */ me.raw = raw; Ext.applyIf(me, { - data: {} + data: {} }); - + /** - * Key: value pairs of all fields whose values have changed - * @property modified - * @type Object + * @property {Object} modified Key: value pairs of all fields whose values have changed */ me.modified = {}; - me[me.persistanceProperty] = {}; + // Deal with spelling error in previous releases + if (me.persistanceProperty) { + //<debug> + if (Ext.isDefined(Ext.global.console)) { + Ext.global.console.warn('Ext.data.Model: persistanceProperty has been deprecated. Use persistenceProperty instead.'); + } + //</debug> + me.persistenceProperty = me.persistanceProperty; + } + me[me.persistenceProperty] = {}; me.mixins.observable.constructor.call(me); @@ -604,7 +704,7 @@ Ext.define('Ext.data.Model', { field = fields[i]; name = field.name; - if (isArray){ + if (isArray){ // Have to map array data so the values get assigned to the named fields // rather than getting set as the field names with undefined values. newData[name] = data[i]; @@ -615,52 +715,58 @@ Ext.define('Ext.data.Model', { } me.set(newData || data); - // clear any dirty/modified since we're initializing - me.dirty = false; - me.modified = {}; if (me.getId()) { me.phantom = false; + } else if (me.phantom) { + newId = me.idgen.generate(); + if (newId !== null) { + me.setId(newId); + } } + // clear any dirty/modified since we're initializing + me.dirty = false; + me.modified = {}; + if (typeof me.init == 'function') { me.init(); } - me.id = me.modelName + '-' + me.internalId; - - Ext.ModelManager.register(me); + me.id = me.idgen.getRecId(me); }, - + /** * Returns the value of the given field * @param {String} fieldName The field to fetch the value for - * @return {Mixed} The value + * @return {Object} The value */ get: function(field) { - return this[this.persistanceProperty][field]; + return this[this.persistenceProperty][field]; }, - + /** * Sets the given field to the given value, marks the instance as dirty - * @param {String|Object} fieldName The field to set, or an object containing key/value pairs - * @param {Mixed} value The value to set + * @param {String/Object} fieldName The field to set, or an object containing key/value pairs + * @param {Object} value The value to set */ set: function(fieldName, value) { var me = this, fields = me.fields, modified = me.modified, convertFields = [], - field, key, i, currentValue; + field, key, i, currentValue, notEditing, count, length; /* * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and * set those last so that all other possible data is set before the convert function is called */ if (arguments.length == 1 && Ext.isObject(fieldName)) { + notEditing = !me.editing; + count = 0; for (key in fieldName) { if (fieldName.hasOwnProperty(key)) { - + //here we check for the custom convert function. Note that if a field doesn't have a convert function, //we default it to its type's convert function, so we have to check that here. This feels rather dirty. field = fields.get(key); @@ -668,16 +774,30 @@ Ext.define('Ext.data.Model', { convertFields.push(key); continue; } - + + if (!count && notEditing) { + me.beginEdit(); + } + ++count; me.set(key, fieldName[key]); } } - for (i = 0; i < convertFields.length; i++) { - field = convertFields[i]; - me.set(field, fieldName[field]); + length = convertFields.length; + if (length) { + if (!count && notEditing) { + me.beginEdit(); + } + count += length; + for (i = 0; i < length; i++) { + field = convertFields[i]; + me.set(field, fieldName[field]); + } } + if (notEditing && count) { + me.endEdit(); + } } else { if (fields) { field = fields.get(fieldName); @@ -687,11 +807,28 @@ Ext.define('Ext.data.Model', { } } currentValue = me.get(fieldName); - me[me.persistanceProperty][fieldName] = value; - + me[me.persistenceProperty][fieldName] = value; + if (field && field.persist && !me.isEqual(currentValue, value)) { - me.dirty = true; - me.modified[fieldName] = currentValue; + if (me.isModified(fieldName)) { + if (me.isEqual(modified[fieldName], value)) { + // the original value in me.modified equals the new value, so the + // field is no longer modified + delete modified[fieldName]; + // we might have removed the last modified field, so check to see if + // there are any modified fields remaining and correct me.dirty: + me.dirty = false; + for (key in modified) { + if (modified.hasOwnProperty(key)){ + me.dirty = true; + break; + } + } + } + } else { + me.dirty = true; + modified[fieldName] = currentValue; + } } if (!me.editing) { @@ -699,7 +836,7 @@ Ext.define('Ext.data.Model', { } } }, - + /** * Checks if two values are equal, taking into account certain * special factors, for example dates. @@ -714,22 +851,21 @@ Ext.define('Ext.data.Model', { } return a === b; }, - + /** - * Begin an edit. While in edit mode, no events (e.g.. the <code>update</code> event) - * are relayed to the containing store. When an edit has begun, it must be followed - * by either {@link #endEdit} or {@link #cancelEdit}. + * Begins an edit. While in edit mode, no events (e.g.. the `update` event) are relayed to the containing store. + * When an edit has begun, it must be followed by either {@link #endEdit} or {@link #cancelEdit}. */ beginEdit : function(){ var me = this; if (!me.editing) { me.editing = true; me.dirtySave = me.dirty; - me.dataSave = Ext.apply({}, me[me.persistanceProperty]); + me.dataSave = Ext.apply({}, me[me.persistenceProperty]); me.modifiedSave = Ext.apply({}, me.modified); } }, - + /** * Cancels all changes made in the current edit operation. */ @@ -739,35 +875,61 @@ Ext.define('Ext.data.Model', { me.editing = false; // reset the modified state, nothing changed since the edit began me.modified = me.modifiedSave; - me[me.persistanceProperty] = me.dataSave; + me[me.persistenceProperty] = me.dataSave; me.dirty = me.dirtySave; delete me.modifiedSave; delete me.dataSave; delete me.dirtySave; } }, - + /** - * End an edit. If any data was modified, the containing store is notified - * (ie, the store's <code>update</code> event will fire). + * Ends an edit. If any data was modified, the containing store is notified (ie, the store's `update` event will + * fire). * @param {Boolean} silent True to not notify the store of the change */ endEdit : function(silent){ - var me = this; + var me = this, + didChange; + if (me.editing) { me.editing = false; + didChange = me.dirty || me.changedWhileEditing(); delete me.modifiedSave; delete me.dataSave; delete me.dirtySave; - if (silent !== true && me.dirty) { + if (silent !== true && didChange) { me.afterEdit(); } } }, + /** + * Checks if the underlying data has changed during an edit. This doesn't necessarily + * mean the record is dirty, however we still need to notify the store since it may need + * to update any views. + * @private + * @return {Boolean} True if the underlying data has changed during an edit. + */ + changedWhileEditing: function(){ + var me = this, + saved = me.dataSave, + data = me[me.persistenceProperty], + key; + + for (key in data) { + if (data.hasOwnProperty(key)) { + if (!me.isEqual(data[key], saved[key])) { + return true; + } + } + } + return false; + }, + /** * Gets a hash of only the fields that have been modified since this Model was created or commited. - * @return Object + * @return {Object} */ getChanges : function(){ var modified = this.modified, @@ -782,30 +944,27 @@ Ext.define('Ext.data.Model', { return changes; }, - + /** - * Returns <tt>true</tt> if the passed field name has been <code>{@link #modified}</code> - * since the load or last commit. + * Returns true if the passed field name has been `{@link #modified}` since the load or last commit. * @param {String} fieldName {@link Ext.data.Field#name} * @return {Boolean} */ isModified : function(fieldName) { return this.modified.hasOwnProperty(fieldName); }, - + /** - * <p>Marks this <b>Record</b> as <code>{@link #dirty}</code>. This method - * is used interally when adding <code>{@link #phantom}</code> records to a - * {@link Ext.data.Store#writer writer enabled store}.</p> - * <br><p>Marking a record <code>{@link #dirty}</code> causes the phantom to - * be returned by {@link Ext.data.Store#getModifiedRecords} where it will - * have a create action composed for it during {@link Ext.data.Store#save store save} - * operations.</p> + * Marks this **Record** as `{@link #dirty}`. This method is used interally when adding `{@link #phantom}` records + * to a {@link Ext.data.proxy.Server#writer writer enabled store}. + * + * Marking a record `{@link #dirty}` causes the phantom to be returned by {@link Ext.data.Store#getUpdatedRecords} + * where it will have a create action composed for it during {@link Ext.data.Model#save model save} operations. */ setDirty : function() { var me = this, name; - + me.dirty = true; me.fields.each(function(field) { @@ -824,15 +983,17 @@ Ext.define('Ext.data.Model', { return this.setDirty.apply(this, arguments); }, //</debug> - + /** - * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. - * Rejects all changes made to the model instance since either creation, or the last commit operation. - * Modified fields are reverted to their original values. - * <p>Developers should subscribe to the {@link Ext.data.Store#update} event - * to have their code notified of reject operations.</p> - * @param {Boolean} silent (optional) True to skip notification of the owning - * store of the change (defaults to false) + * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects + * all changes made to the model instance since either creation, or the last commit operation. Modified fields are + * reverted to their original values. + * + * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of reject + * operations. + * + * @param {Boolean} silent (optional) True to skip notification of the owning store of the change. + * Defaults to false. */ reject : function(silent) { var me = this, @@ -842,7 +1003,7 @@ Ext.define('Ext.data.Model', { for (field in modified) { if (modified.hasOwnProperty(field)) { if (typeof modified[field] != "function") { - me[me.persistanceProperty][field] = modified[field]; + me[me.persistenceProperty][field] = modified[field]; } } } @@ -857,19 +1018,19 @@ Ext.define('Ext.data.Model', { }, /** - * Usually called by the {@link Ext.data.Store} which owns the model instance. - * Commits all changes made to the instance since either creation or the last commit operation. - * <p>Developers should subscribe to the {@link Ext.data.Store#update} event - * to have their code notified of commit operations.</p> - * @param {Boolean} silent (optional) True to skip notification of the owning - * store of the change (defaults to false) + * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the + * instance since either creation or the last commit operation. + * + * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of commit + * operations. + * + * @param {Boolean} silent (optional) True to skip notification of the owning store of the change. + * Defaults to false. */ commit : function(silent) { var me = this; - - me.dirty = false; - me.editing = false; + me.phantom = me.dirty = me.editing = false; me.modified = {}; if (silent !== true) { @@ -879,24 +1040,27 @@ Ext.define('Ext.data.Model', { /** * Creates a copy (clone) of this Model instance. - * @param {String} id (optional) A new id, defaults to the id - * of the instance being copied. See <code>{@link #id}</code>. - * To generate a phantom instance with a new id use:<pre><code> -var rec = record.copy(); // clone the record -Ext.data.Model.id(rec); // automatically generate a unique sequential id - * </code></pre> - * @return {Record} + * + * @param {String} [id] A new id, defaults to the id of the instance being copied. + * See `{@link Ext.data.Model#id id}`. To generate a phantom instance with a new id use: + * + * var rec = record.copy(); // clone the record + * Ext.data.Model.id(rec); // automatically generate a unique sequential id + * + * @return {Ext.data.Model} */ copy : function(newId) { var me = this; - - return new me.self(Ext.apply({}, me[me.persistanceProperty]), newId || me.internalId); + + return new me.self(Ext.apply({}, me[me.persistenceProperty]), newId || me.internalId); }, /** - * Sets the Proxy to use for this model. Accepts any options that can be accepted by {@link Ext#createByAlias Ext.createByAlias} + * Sets the Proxy to use for this model. Accepts any options that can be accepted by + * {@link Ext#createByAlias Ext.createByAlias}. + * * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy - * @static + * @return {Ext.data.proxy.Proxy} */ setProxy: function(proxy) { //make sure we have an Ext.data.proxy.Proxy object @@ -915,7 +1079,7 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id }, /** - * Returns the configured Proxy for this Model + * Returns the configured Proxy for this Model. * @return {Ext.data.proxy.Proxy} The proxy */ getProxy: function() { @@ -923,8 +1087,7 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id }, /** - * Validates the current data against all of its configured {@link #validations} and returns an - * {@link Ext.data.Errors Errors} object + * Validates the current data against all of its configured {@link #validations}. * @return {Ext.data.Errors} The errors object */ validate: function() { @@ -963,8 +1126,8 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id }, /** - * Saves the model instance using the configured proxy - * @param {Object} options Options to pass to the proxy + * Saves the model instance using the configured proxy. + * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}. * @return {Ext.data.Model} The Model instance */ save: function(options) { @@ -1006,8 +1169,8 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id }, /** - * Destroys the model using the configured proxy - * @param {Object} options Options to pass to the proxy + * Destroys the model using the configured proxy. + * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}. * @return {Ext.data.Model} The Model instance */ destroy: function(options){ @@ -1039,7 +1202,7 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id }, /** - * Returns the unique ID allocated to this model instance as defined by {@link #idProperty} + * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}. * @return {Number} The id */ getId: function() { @@ -1047,7 +1210,7 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id }, /** - * Sets the model instance's id field to the given id + * Sets the model instance's id field to the given id. * @param {Number} id The new id */ setId: function(id) { @@ -1055,22 +1218,22 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id }, /** - * Tells this model instance that it has been added to a store - * @param {Ext.data.Store} store The store that the model has been added to + * Tells this model instance that it has been added to a store. + * @param {Ext.data.Store} store The store to which this model has been added. */ join : function(store) { /** - * The {@link Ext.data.Store} to which this Record belongs. - * @property store - * @type {Ext.data.Store} + * @property {Ext.data.Store} store + * The {@link Ext.data.Store Store} to which this Record belongs. */ this.store = store; }, /** - * Tells this model instance that it has been removed from the store + * Tells this model instance that it has been removed from the store. + * @param {Ext.data.Store} store The store from which this model has been removed. */ - unjoin: function() { + unjoin: function(store) { delete this.store; }, @@ -1117,20 +1280,21 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id }, /** - * Gets all of the data from this Models *loaded* associations. - * It does this recursively - for example if we have a User which - * hasMany Orders, and each Order hasMany OrderItems, it will return an object like this: - * { - * orders: [ - * { - * id: 123, - * status: 'shipped', - * orderItems: [ - * ... - * ] - * } - * ] - * } + * Gets all of the data from this Models *loaded* associations. It does this recursively - for example if we have a + * User which hasMany Orders, and each Order hasMany OrderItems, it will return an object like this: + * + * { + * orders: [ + * { + * id: 123, + * status: 'shipped', + * orderItems: [ + * ... + * ] + * } + * ] + * } + * * @return {Object} The nested data set for the Model's loaded associations */ getAssociatedData: function(){ @@ -1142,7 +1306,7 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id * This complex-looking method takes a given Model instance and returns an object containing all data from * all of that Model's *loaded* associations. See (@link #getAssociatedData} * @param {Ext.data.Model} record The Model instance - * @param {Array} ids PRIVATE. The set of Model instance internalIds that have already been loaded + * @param {String[]} ids PRIVATE. The set of Model instance internalIds that have already been loaded * @param {String} associationType (optional) The name of the type of association to limit to. * @return {Object} The nested data set for the Model's loaded associations */