X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/6746dc89c47ed01b165cc1152533605f97eb8e8d..HEAD:/src/data/Model.js diff --git a/src/data/Model.js b/src/data/Model.js index 045bfba2..2df497b1 100644 --- a/src/data/Model.js +++ b/src/data/Model.js @@ -14,236 +14,217 @@ If you are unsure which license is appropriate for your use, please contact the */ /** * @author Ed Spencer - * @class Ext.data.Model * - *

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.

+ * 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. * - *

Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:

+ * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example: * -

-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);
-    }
-});
-
-* -*

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.

-* -*

Now we can create instances of our User model and call any model logic we defined:

-* -

-var user = Ext.ModelManager.create({
-    name : 'Conan',
-    age  : 24,
-    phone: '555-555-5555'
-}, 'User');
-
-user.changeName();
-user.get('name'); //returns "Conan The Barbarian"
-
+ * 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} + * ], * - *

Validations

+ * changeName: function() { + * var oldName = this.get('name'), + * newName = oldName + " The Barbarian"; * - *

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:

+ * this.set('name', newName); + * } + * }); * -

-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}/}
-    ]
-});
-
+ * 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. * - *

The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors} - * object:

+ * Now we can create instances of our User model and call any model logic we defined: * -

-var instance = Ext.ModelManager.create({
-    name: 'Ed',
-    gender: 'Male',
-    username: 'edspencer'
-}, 'User');
-
-var errors = instance.validate();
-
+ * var user = Ext.create('User', { + * name : 'Conan', + * age : 24, + * phone: '555-555-5555' + * }); * - *

Associations

+ * user.changeName(); + * user.get('name'); //returns "Conan The Barbarian" * - *

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:

+ * # Validations * -

-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'}
-    ]
-});
-
+ * 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: * - *

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:

+ * 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} + * ], * -

-Ext.define('User', {
-    extend: 'Ext.data.Model',
-    fields: ['id'],
-
-    associations: [
-        {type: 'hasMany', model: 'Post',    name: 'posts'},
-        {type: 'hasMany', model: 'Comment', name: 'comments'}
-    ]
-});
-
+ * 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}/} + * ] + * }); * - *

Using a Proxy

+ * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors} + * object: * - *

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:

+ * var instance = Ext.create('User', { + * name: 'Ed', + * gender: 'Male', + * username: 'edspencer' + * }); * -

-Ext.define('User', {
-    extend: 'Ext.data.Model',
-    fields: ['id', 'name', 'email'],
-
-    proxy: {
-        type: 'rest',
-        url : '/users'
-    }
-});
-
+ * var errors = instance.validate(); * - *

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:

+ * # Associations * -

-var user = Ext.ModelManager.create({name: 'Ed Spencer', email: 'ed@sencha.com'}, 'User');
-
-user.save(); //POST /users
-
+ * 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: * - *

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.

+ * Ext.define('Post', { + * extend: 'Ext.data.Model', + * fields: ['id', 'user_id'], * - *

Loading data via the Proxy is equally easy:

+ * belongsTo: 'User', + * hasMany : {model: 'Comment', name: 'comments'} + * }); * -

-//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
-    }
-});
-
+ * Ext.define('Comment', { + * extend: 'Ext.data.Model', + * fields: ['id', 'user_id', 'post_id'], * - *

Models can also be updated and destroyed easily:

+ * belongsTo: 'Post' + * }); * -

-//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!');
-    }
-});
-
+ * Ext.define('User', { + * extend: 'Ext.data.Model', + * fields: ['id'], * - *

Usage in Stores

+ * hasMany: [ + * 'Post', + * {model: 'Comment', name: 'comments'} + * ] + * }); * - *

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}:

+ * 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: * -

-var store = new Ext.data.Store({
-    model: 'User'
-});
-
-//uses the Proxy we set up on Model to load the Store data
-store.load();
-
+ * 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' + * } + * }); + * + * 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: * - *

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.

+ * 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 */ 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', @@ -266,6 +247,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; @@ -304,6 +286,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) { @@ -388,9 +374,12 @@ 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,40 +400,42 @@ Ext.define('Ext.data.Model', { /** * Returns the configured Proxy for this Model * @return {Ext.data.proxy.Proxy} The proxy + * @static + * @inheritable */ getProxy: function() { return this.proxy; }, /** - * Static. 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
-        }
-    });
-    
+ * 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); @@ -480,17 +471,15 @@ Ext.define('Ext.data.Model', { 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: - * {PREFIX}-{AUTO_ID}.
- * @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++'; + * 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) { @@ -500,23 +489,98 @@ Ext.define('Ext.data.Model', { return id; } }, - + /** - * Internal flag used to track whether or not the model instance is currently being edited. Read-only - * @property editing - * @type Boolean + * @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; + } + }, + + /** + * @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} 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.) + * @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.) */ persistenceProperty: 'data', @@ -524,42 +588,73 @@ Ext.define('Ext.data.Model', { isModel: true, /** - * 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. - * @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 @@ -570,22 +665,18 @@ 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 = {}; @@ -610,7 +701,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]; @@ -621,50 +712,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; + 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.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); @@ -672,16 +771,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); @@ -692,7 +805,7 @@ Ext.define('Ext.data.Model', { } currentValue = me.get(fieldName); me[me.persistenceProperty][fieldName] = value; - + if (field && field.persist && !me.isEqual(currentValue, value)) { if (me.isModified(fieldName)) { if (me.isEqual(modified[fieldName], value)) { @@ -720,7 +833,7 @@ Ext.define('Ext.data.Model', { } } }, - + /** * Checks if two values are equal, taking into account certain * special factors, for example dates. @@ -735,11 +848,10 @@ Ext.define('Ext.data.Model', { } return a === b; }, - + /** - * Begin 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}. + * 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; @@ -750,7 +862,7 @@ Ext.define('Ext.data.Model', { me.modifiedSave = Ext.apply({}, me.modified); } }, - + /** * Cancels all changes made in the current edit operation. */ @@ -767,28 +879,54 @@ Ext.define('Ext.data.Model', { delete me.dirtySave; } }, - + /** - * End an edit. If any data was modified, the containing store is notified - * (ie, the store's update 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, @@ -803,30 +941,27 @@ Ext.define('Ext.data.Model', { return changes; }, - + /** - * Returns true if the passed field name has been {@link #modified} - * 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); }, - + /** - *

Marks this Record as {@link #dirty}. This method - * is used interally when adding {@link #phantom} records to a - * {@link Ext.data.Store#writer writer enabled store}.

- *

Marking a record {@link #dirty} 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.

+ * 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) { @@ -845,15 +980,17 @@ Ext.define('Ext.data.Model', { return this.setDirty.apply(this, arguments); }, // - + /** - * 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) + * 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, @@ -878,19 +1015,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. - *

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) + * 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) { @@ -900,24 +1037,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 {@link #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 {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.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 @@ -936,7 +1076,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() { @@ -944,8 +1084,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() { @@ -984,8 +1123,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) { @@ -1027,8 +1166,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){ @@ -1060,7 +1199,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() { @@ -1068,7 +1207,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) { @@ -1076,22 +1215,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; }, @@ -1138,20 +1277,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(){ @@ -1163,7 +1303,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 */