/ext-4.1.0_b3/src/data/Model.js
https://bitbucket.org/srogerf/javascript · JavaScript · 1519 lines · 701 code · 162 blank · 656 comment · 149 complexity · 48e53a990f903d50d13e37ab49add77f MD5 · raw file
- /**
- * @author Ed Spencer
- *
- * 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:
- *
- * 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.create('User', {
- * name : 'Conan',
- * age : 24,
- * phone: '555-555-5555'
- * });
- *
- * user.changeName();
- * user.get('name'); //returns "Conan The Barbarian"
- *
- * # Validations
- *
- * 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:
- *
- * 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 validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
- * object:
- *
- * var instance = Ext.create('User', {
- * name: 'Ed',
- * gender: 'Male',
- * username: 'edspencer'
- * });
- *
- * var errors = instance.validate();
- *
- * # Associations
- *
- * Models can have associations with other Models via {@link Ext.data.association.HasOne},
- * {@link Ext.data.association.BelongsTo belongsTo} and {@link Ext.data.association.HasMany 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:
- *
- * 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'}
- * ]
- * });
- *
- * See the docs for {@link Ext.data.association.HasOne}, {@link Ext.data.association.BelongsTo} and
- * {@link Ext.data.association.HasMany} 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: ['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:
- *
- * 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
- */
- 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',
- 'Ext.data.validations',
- 'Ext.data.proxy.Ajax',
- 'Ext.util.MixedCollection'
- ],
- sortConvertFields: function(f1, f2) {
- var f1SpecialConvert = f1.type && f1.convert !== f1.type.convert,
- f2SpecialConvert = f2.type && f2.convert !== f2.type.convert;
- if (f1SpecialConvert && !f2SpecialConvert) {
- return 1;
- }
- if (!f1SpecialConvert && f2SpecialConvert) {
- return -1;
- }
- return 0;
- },
- itemNameFn: function(item) {
- return item.name;
- },
- onClassExtended: function(cls, data, hooks) {
- var onBeforeClassCreated = hooks.onBeforeCreated;
- hooks.onBeforeCreated = function(cls, data) {
- var me = this,
- name = Ext.getClassName(cls),
- prototype = cls.prototype,
- superCls = cls.prototype.superclass,
- validations = data.validations || [],
- fields = data.fields || [],
- associations = data.associations || [],
- belongsTo = data.belongsTo,
- hasMany = data.hasMany,
- hasOne = data.hasOne,
- addAssociations = function(items, type) {
- var i = 0,
- len,
- item;
- if (items) {
- items = Ext.Array.from(items);
- for (len = items.length; i < len; ++i) {
- item = items[i];
- if (!Ext.isObject(item)) {
- item = {model: item};
- }
- item.type = type;
- associations.push(item);
- }
- }
- },
- idgen = data.idgen,
- fieldsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
- associationsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
- superValidations = superCls.validations,
- superFields = superCls.fields,
- superAssociations = superCls.associations,
- association, i, ln,
- dependencies = [],
- idProperty = data.idProperty || cls.prototype.idProperty,
- fieldConvertSortFn = Ext.Function.bind(
- fieldsMixedCollection.sortBy,
- fieldsMixedCollection,
- [prototype.sortConvertFields], false);
- // Save modelName on class and its prototype
- cls.modelName = name;
- prototype.modelName = name;
- // Merge the validations of the superclass and the new subclass
- if (superValidations) {
- validations = superValidations.concat(validations);
- }
- data.validations = validations;
- // Merge the fields of the superclass and the new subclass
- if (superFields) {
- fields = superFields.items.concat(fields);
- }
- fieldsMixedCollection.on({
- add: fieldConvertSortFn,
- replace: fieldConvertSortFn
- });
- for (i = 0, ln = fields.length; i < ln; ++i) {
- fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
- }
- if (!fieldsMixedCollection.get(idProperty)) {
- fieldsMixedCollection.add(new Ext.data.Field(idProperty));
- }
- 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
- addAssociations(data.belongsTo, 'belongsTo');
- delete data.belongsTo;
- addAssociations(data.hasMany, 'hasMany');
- delete data.hasMany;
- addAssociations(data.hasOne, 'hasOne');
- delete data.hasOne;
- if (superAssociations) {
- associations = superAssociations.items.concat(associations);
- }
- for (i = 0, ln = associations.length; i < ln; ++i) {
- dependencies.push('association.' + associations[i].type.toLowerCase());
- }
- if (data.proxy) {
- if (typeof data.proxy === 'string') {
- dependencies.push('proxy.' + data.proxy);
- }
- else if (typeof data.proxy.type === 'string') {
- dependencies.push('proxy.' + data.proxy.type);
- }
- }
- Ext.require(dependencies, function() {
- Ext.ModelManager.registerType(name, cls);
- for (i = 0, ln = associations.length; i < ln; ++i) {
- association = associations[i];
- Ext.apply(association, {
- ownerModel: name,
- associatedModel: association.model
- });
- if (Ext.ModelManager.getModel(association.model) === undefined) {
- Ext.ModelManager.registerDeferredAssociation(association);
- } else {
- associationsMixedCollection.add(Ext.data.association.Association.create(association));
- }
- }
- data.associations = associationsMixedCollection;
- onBeforeClassCreated.call(me, cls, data, hooks);
- cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
- // Fire the onModelDefined template method on ModelManager
- Ext.ModelManager.onModelDefined(cls);
- });
- };
- },
- inheritableStatics: {
- /**
- * 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
- if (!proxy.isProxy) {
- if (typeof proxy == "string") {
- proxy = {
- type: proxy
- };
- }
- proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
- }
- proxy.setModel(this);
- this.proxy = this.prototype.proxy = proxy;
- return proxy;
- },
- /**
- * Returns the configured Proxy for this Model
- * @return {Ext.data.proxy.Proxy} The proxy
- * @static
- * @inheritable
- */
- getProxy: function() {
- return this.proxy;
- },
- /**
- * Apply a new set of field definitions to the existing model. This will replace any existing
- * fields, including fields inherited from superclasses. Mainly for reconfiguring the
- * model based on changes in meta data (called from Reader's onMetaChange method).
- * @static
- * @inheritable
- */
- setFields: function(fields) {
- var me = this,
- prototypeFields = me.prototype.fields,
- len = fields.length,
- i = 0;
- if (prototypeFields) {
- prototypeFields.clear();
- }
- else {
- prototypeFields = me.prototype.fields = new Ext.util.MixedCollection(false, function(field) {
- return field.name;
- });
- }
- for (; i < len; i++) {
- prototypeFields.add(new Ext.data.Field(fields[i]));
- }
- me.fields = prototypeFields;
- return prototypeFields;
- },
- getFields: function() {
- return this.fields;
- },
- /**
- * Asynchronously loads a model instance by id. Sample usage:
- *
- * Ext.define('MyApp.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/String} id The id of the model to 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);
- config = Ext.applyIf(config, {
- action: 'read',
- id : id
- });
- var operation = new Ext.data.Operation(config),
- scope = config.scope || this,
- record = null,
- callback;
- callback = function(operation) {
- if (operation.wasSuccessful()) {
- record = operation.getRecords()[0];
- Ext.callback(config.success, scope, [record, operation]);
- } else {
- Ext.callback(config.failure, scope, [record, operation]);
- }
- Ext.callback(config.callback, scope, [record, operation]);
- };
- this.proxy.read(operation, callback, this);
- }
- },
- statics: {
- PREFIX : 'ext-record',
- AUTO_ID: 1,
- EDIT : 'edit',
- REJECT : 'reject',
- COMMIT : 'commit',
- /**
- * 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) {
- var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
- rec.phantom = true;
- rec.internalId = id;
- 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;
- }
- },
- /**
- * @property {Boolean} editing
- * Internal flag used to track whether or not the model instance is currently being edited.
- * @readonly
- */
- editing : false,
- /**
- * @property {Boolean} dirty
- * True if this Record has been modified.
- * @readonly
- */
- dirty : false,
- /**
- * @cfg {String} persistenceProperty
- * The name of the property on this Persistable object that its data is saved to. Defaults to 'data'
- * (i.e: all persistable data resides in `this.data`.)
- */
- persistenceProperty: 'data',
- evented: false,
- /**
- * @property {Boolean} isModel
- * `true` in this class to identify an objact as an instantiated Model, or subclass thereof.
- */
- isModel: true,
- /**
- * @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'.
- */
- idProperty: 'id',
- /**
- * @cfg {String} [clientIdProperty='clientId']
- * The name of a property that is used for submitting this Model's unique client-side identifier
- * to the server when multiple phantom records are saved as part of the same {@link Ext.data.Operation Operation}.
- * In such a case, the server response should include the client id for each record
- * so that the server response data can be used to update the client-side records if necessary.
- * This property cannot have the same name as any of this Model's fields.
- */
- clientIdProperty: 'clientId',
- /**
- * @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. This is an Array of **{@link Ext.data.Field Field}** definition objects. A Field
- * definition may simply be the *name* of the Field, but a Field encapsulates {@link Ext.data.Field#type data type},
- * {@link Ext.data.Field#convert custom conversion} of raw data, and a {@link Ext.data.Field#mapping mapping}
- * property to specify by name of index, how to extract a field's value from a raw data object, so it is best practice
- * to specify a full set of {@link Ext.data.Field Field} config objects.
- */
- /**
- * @property {Ext.util.MixedCollection} fields
- * A {@link Ext.util.MixedCollection Collection} of the fields defined for this Model (including fields defined in superclasses)
- *
- * This is a collection of {@link Ext.data.Field} instances, each of which encapsulates information that the field was configured with.
- * By default, you can specify a field as simply a String, representing the *name* of the field, but a Field encapsulates
- * {@link Ext.data.Field#type data type}, {@link Ext.data.Field#convert custom conversion} of raw data, and a {@link Ext.data.Field#mapping mapping}
- * property to specify by name of index, how to extract a field's value from a raw data object.
- */
- /**
- * @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.
- */
- /**
- * @cfg {String/Object/Ext.data.proxy.Proxy} proxy
- * The {@link Ext.data.proxy.Proxy proxy} to use for this model.
- */
- /**
- * @event idchanged
- * Fired when this model's id changes
- * @param {Ext.data.Model} this
- * @param {Number/String} oldId The old id
- * @param {Number/String} newId The new id
- */
- // id, raw and convertedData not documented intentionally, meant to be used internally.
- constructor: function(data, id, raw, convertedData) {
- data = data || {};
- var me = this,
- fields,
- length,
- field,
- name,
- value,
- newId,
- persistenceProperty,
- i;
- /**
- * @property {Number/String} internalId
- * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
- * @private
- */
- me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
- /**
- * @property {Object} raw The raw data used to create this model if created via a reader.
- */
- me.raw = raw;
- if (!me.data) {
- me.data = {};
- }
- /**
- * @property {Object} modified Key: value pairs of all fields whose values have changed
- */
- me.modified = {};
- // 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] = convertedData || {};
- me.mixins.observable.constructor.call(me);
- if (!convertedData) {
- //add default field values if present
- fields = me.fields.items;
- length = fields.length;
- i = 0;
- persistenceProperty = me[me.persistenceProperty];
- if (Ext.isArray(data)) {
- for (; i < length; i++) {
- field = fields[i];
- name = field.name;
- value = data[i];
- if (value === undefined) {
- value = field.defaultValue;
- }
- // 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.
- if (field.convert) {
- value = field.convert(value, me);
- }
- persistenceProperty[name] = value ;
- }
- } else {
- for (; i < length; i++) {
- field = fields[i];
- name = field.name;
- value = data[name];
- if (value === undefined) {
- value = field.defaultValue;
- }
- if (field.convert) {
- value = field.convert(value, me);
- }
- persistenceProperty[name] = value ;
- }
- }
- }
- /**
- * @property {Array} stores
- * An array of {@link Ext.data.AbstractStore} objects that this record is bound to.
- */
- me.stores = [];
- 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.idgen.getRecId(me);
- },
- /**
- * Returns the value of the given field
- * @param {String} fieldName The field to fetch the value for
- * @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 {Object} value The value to set
- */
- set: function(fieldName, value) {
- var me = this,
- fields = me.fields,
- modified = me.modified,
- modifiedFieldNames = [],
- field, key, i, currentValue, notEditing, count, length;
- /*
- * If we're passed an object, iterate over that object.
- */
- if (arguments.length == 1 && Ext.isObject(fieldName)) {
- notEditing = !me.editing;
- count = 0;
- fields = me.fields.items;
- length = fields.length;
- for (i = 0; i < length; i++) {
- field = fields[i].name;
- if (fieldName.hasOwnProperty(field)) {
- if (!count && notEditing) {
- me.beginEdit();
- }
- ++count;
- me.set(field, fieldName[field]);
- }
- }
- if (notEditing && count) {
- me.endEdit(false, modifiedFieldNames);
- }
- } else {
- fields = me.fields;
- if (fields) {
- field = fields.get(fieldName);
- if (field && field.convert) {
- value = field.convert(value, me);
- }
- }
- 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)) {
- // 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(fieldName === me.idProperty && currentValue !== value) {
- me.fireEvent('idchanged', me, currentValue, value);
- }
- if (!me.editing) {
- me.afterEdit([fieldName]);
- }
- }
- },
- /**
- * Checks if two values are equal, taking into account certain
- * special factors, for example dates.
- * @private
- * @param {Object} a The first value
- * @param {Object} b The second value
- * @return {Boolean} True if the values are equal
- */
- isEqual: function(a, b){
- if (Ext.isDate(a) && Ext.isDate(b)) {
- return Ext.Date.isEqual(a, b);
- }
- return a === b;
- },
- /**
- * 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.persistenceProperty]);
- me.modifiedSave = Ext.apply({}, me.modified);
- }
- },
- /**
- * Cancels all changes made in the current edit operation.
- */
- cancelEdit : function(){
- var me = this;
- if (me.editing) {
- me.editing = false;
- // reset the modified state, nothing changed since the edit began
- me.modified = me.modifiedSave;
- me[me.persistenceProperty] = me.dataSave;
- me.dirty = me.dirtySave;
- delete me.modifiedSave;
- delete me.dataSave;
- delete me.dirtySave;
- }
- },
- /**
- * 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
- * @param {String[]} modifiedFieldNames Array of field names changed during edit.
- */
- endEdit : function(silent, modifiedFieldNames){
- var me = this,
- changed;
- if (me.editing) {
- me.editing = false;
- if(!modifiedFieldNames) {
- modifiedFieldNames = me.getModifiedFieldNames();
- }
- changed = me.dirty || modifiedFieldNames.length > 0;
- delete me.modifiedSave;
- delete me.dataSave;
- delete me.dirtySave;
- if (changed && silent !== true) {
- me.afterEdit(modifiedFieldNames);
- }
- }
- },
- /**
- * Gets the names of all the fields that were modified during an edit
- * @private
- * @return {String[]} An array of modified field names
- */
- getModifiedFieldNames: function(){
- var me = this,
- saved = me.dataSave,
- data = me[me.persistenceProperty],
- modified = [],
- key;
- for (key in data) {
- if (data.hasOwnProperty(key)) {
- if (!me.isEqual(data[key], saved[key])) {
- modified.push(key);
- }
- }
- }
- return modified;
- },
- /**
- * Gets a hash of only the fields that have been modified since this Model was created or commited.
- * @return {Object}
- */
- getChanges : function(){
- var modified = this.modified,
- changes = {},
- field;
- for (field in modified) {
- if (modified.hasOwnProperty(field)){
- changes[field] = this.get(field);
- }
- }
- return changes;
- },
- /**
- * 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.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,
- fields = me.fields.items,
- fLen = fields.length,
- field, name, f;
- me.dirty = true;
- for (f = 0; f < fLen; f++) {
- field = fields[f];
- if (field.persist) {
- name = field.name;
- me.modified[name] = me.get(name);
- }
- }
- },
- //<debug>
- markDirty : function() {
- if (Ext.isDefined(Ext.global.console)) {
- Ext.global.console.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
- }
- 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.
- *
- * 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,
- modified = me.modified,
- field;
- for (field in modified) {
- if (modified.hasOwnProperty(field)) {
- if (typeof modified[field] != "function") {
- me[me.persistenceProperty][field] = modified[field];
- }
- }
- }
- me.dirty = false;
- me.editing = false;
- me.modified = {};
- if (silent !== true) {
- me.afterReject();
- }
- },
- /**
- * 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.phantom = me.dirty = me.editing = false;
- me.modified = {};
- if (silent !== true) {
- me.afterCommit();
- }
- },
- /**
- * Creates a copy (clone) of this Model instance.
- *
- * @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);
- },
- /**
- * 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}
- */
- setProxy: function(proxy) {
- //make sure we have an Ext.data.proxy.Proxy object
- if (!proxy.isProxy) {
- if (typeof proxy === "string") {
- proxy = {
- type: proxy
- };
- }
- proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
- }
- proxy.setModel(this.self);
- this.proxy = proxy;
- return proxy;
- },
- /**
- * Returns the configured Proxy for this Model.
- * @return {Ext.data.proxy.Proxy} The proxy
- */
- getProxy: function() {
- return this.proxy;
- },
- /**
- * Validates the current data against all of its configured {@link #validations}.
- * @return {Ext.data.Errors} The errors object
- */
- validate: function() {
- var errors = new Ext.data.Errors(),
- validations = this.validations,
- validators = Ext.data.validations,
- length, validation, field, valid, type, i;
- if (validations) {
- length = validations.length;
- for (i = 0; i < length; i++) {
- validation = validations[i];
- field = validation.field || validation.name;
- type = validation.type;
- valid = validators[type](validation, this.get(field));
- if (!valid) {
- errors.add({
- field : field,
- message: validation.message || validators[type + 'Message']
- });
- }
- }
- }
- return errors;
- },
- /**
- * Checks if the model is valid. See {@link #validate}.
- * @return {Boolean} True if the model is valid.
- */
- isValid: function(){
- return this.validate().isValid();
- },
- /**
- * 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) {
- options = Ext.apply({}, options);
- var me = this,
- action = me.phantom ? 'create' : 'update',
- scope = options.scope || me,
- stores = me.stores,
- i = 0,
- storeCount,
- store,
- args,
- operation,
- callback;
- Ext.apply(options, {
- records: [me],
- action : action
- });
- operation = new Ext.data.Operation(options);
- callback = function(operation) {
- args = [me, operation];
- if (operation.wasSuccessful()) {
- for(storeCount = stores.length; i < storeCount; i++) {
- store = stores[i];
- store.fireEvent('write', store, operation);
- store.fireEvent('datachanged', store);
- // Not firing refresh here, since it's a single record
- }
- Ext.callback(options.success, scope, args);
- } else {
- Ext.callback(options.failure, scope, args);
- }
- Ext.callback(options.callback, scope, args);
- };
- me.getProxy()[action](operation, callback, me);
- return me;
- },
- /**
- * 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){
- options = Ext.apply({}, options);
- var me = this,
- scope = options.scope || me,
- stores = me.stores,
- i = 0,
- storeCount,
- store,
- args,
- operation,
- callback;
- Ext.apply(options, {
- records: [me],
- action : 'destroy'
- });
- operation = new Ext.data.Operation(options);
- callback = function(operation) {
- args = [me, operation];
- if (operation.wasSuccessful()) {
- for(storeCount = stores.length; i < storeCount; i++) {
- store = stores[i];
- store.fireEvent('write', store, operation);
- store.fireEvent('datachanged', store);
- // Not firing refresh here, since it's a single record
- }
- me.clearListeners();
- Ext.callback(options.success, scope, args);
- } else {
- Ext.callback(options.failure, scope, args);
- }
- Ext.callback(options.callback, scope, args);
- };
- me.getProxy().destroy(operation, callback, me);
- return me;
- },
- /**
- * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}.
- * @return {Number/String} The id
- */
- getId: function() {
- return this.get(this.idProperty);
- },
- /**
- * @private
- */
- getObservableId: function() {
- return this.id;
- },
- /**
- * Sets the model instance's id field to the given id.
- * @param {Number/String} id The new id
- */
- setId: function(id) {
- this.set(this.idProperty, id);
- this.phantom = !(id || id === 0);
- },
- /**
- * 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) {
- Ext.Array.include(this.stores, 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(store) {
- Ext.Array.remove(this.stores, store);
- },
- /**
- * @private
- * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
- * afterEdit method is called
- * @param {String[]} modifiedFieldNames Array of field names changed during edit.
- */
- afterEdit : function(modifiedFieldNames) {
- this.callStore('afterEdit', modifiedFieldNames);
- },
- /**
- * @private
- * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
- * afterReject method is called
- */
- afterReject : function() {
- this.callStore("afterReject");
- },
- /**
- * @private
- * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
- * afterCommit method is called
- */
- afterCommit: function() {
- this.callStore('afterCommit');
- },
- /**
- * @private
- * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
- * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
- * will always be called with the model instance as its single argument. If this model is joined to
- * a Ext.data.NodeStore, then this method calls the given method on the NodeStore and the associated Ext.data.TreeStore
- * @param {String} fn The function to call on the store
- */
- callStore: function(fn) {
- var args = Ext.Array.clone(arguments),
- stores = this.stores,
- i = 0,
- len = stores.length,
- store, treeStore;
- args[0] = this;
- for (; i < len; ++i) {
- store = stores[i];
- if (store && typeof store[fn] == "function") {
- store[fn].apply(store, args);
- }
- // if the record is bound to a NodeStore call the TreeStore's method as well
- treeStore = store.treeStore;
- if (treeStore && typeof treeStore[fn] == "function") {
- treeStore[fn].apply(treeStore, args);
- }
- }
- },
- /**
- * Gets all values for each field in this model and returns an object
- * containing the current data.
- * @param {Boolean} includeAssociated True to also include associated data. Defaults to false.
- * @return {Object} An object hash containing all the values in this model
- */
- getData: function(includeAssociated){
- var me = this,
- fields = me.fields.items,
- fLen = fields.length,
- data = {},
- name, f;
- for (f = 0; f < fLen; f++) {
- name = fields[f].name;
- data[name] = me.get(name);
- }
- if (includeAssociated === true) {
- Ext.apply(data, me.getAssociatedData());
- }
- return data;
- },
- /**
- * 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(){
- return this.prepareAssociatedData(this, [], null);
- },
- /**
- * @private
- * 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 {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
- */
- prepareAssociatedData: function(record, ids, associationType) {
- //we keep track of all of the internalIds of the models that we have loaded so far in here
- var associations = record.associations.items,
- associationCount = associations.length,
- associationData = {},
- associatedStore, associatedRecords, associatedRecord,
- associatedRecordCount, association, id, i, j, type, allow;
- for (i = 0; i < associationCount; i++) {
- association = associations[i];
- type = association.type;
- allow = true;
- if (associationType) {
- allow = type == associationType;
- }
- if (allow && type == 'hasMany') {
- //this is the hasMany store filled with the associated data
- associatedStore = record[association.storeName];
- //we will use this to contain each associated record's data
- associationData[association.name] = [];
- //if it's loaded, put it into the association data
- if (associatedStore && associatedStore.getCount() > 0) {
- associatedRecords = associatedStore.data.items;
- associatedRecordCount = associatedRecords.length;
- //now we're finally iterating over the records in the association. We do this recursively
- for (j = 0; j < associatedRecordCount; j++) {
- associatedRecord = associatedRecords[j];
- // Use the id, since it is prefixed with the model name, guaranteed to be unique
- id = associatedRecord.id;
- //when we load the associations for a specific model instance we add it to the set of loaded ids so that
- //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
- if (Ext.Array.indexOf(ids, id) == -1) {
- ids.push(id);
- associationData[association.name][j] = associatedRecord.getData();
- Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
- }
- }
- }
- } else if (allow && (type == 'belongsTo' || type == 'hasOne')) {
- associatedRecord = record[association.instanceName];
- if (associatedRecord !== undefined) {
- id = associatedRecord.id;
- if (Ext.Array.indexOf(ids, id) === -1) {
- ids.push(id);
- associationData[association.name] = associatedRecord.getData();
- Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
- }
- }
- }
- }
- return associationData;
- }
- });