/** * @class Ext.util.Stateful * @extends Ext.util.Observable * Represents any object whose data can be saved by a {@link Ext.data.Proxy Proxy}. Ext.Model * and Ext.View both inherit from this class as both can save state (Models save field state, * Views save configuration) */ Ext.util.Stateful = Ext.extend(Ext.util.Observable, { /** * Internal flag used to track whether or not the model instance is currently being edited. Read-only * @property editing * @type Boolean */ editing : false, /** * Readonly flag - true if this Record has been modified. * @type Boolean */ 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.) */ persistanceProperty: 'data', constructor: function(config) { Ext.applyIf(this, { data: {} }); /** * Key: value pairs of all fields whose values have changed * @property modified * @type Object */ this.modified = {}; this[this.persistanceProperty] = {}; Ext.util.Stateful.superclass.constructor.call(this, config); }, /** * Returns the value of the given field * @param {String} fieldName The field to fetch the value for * @return {Mixed} The value */ get: function(field) { return this[this.persistanceProperty][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 */ set: function(fieldName, value) { var me = this, fields = me.fields, modified = me.modified, convertFields = [], field, key, i; /* * 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)) { for (key in fieldName) { if (!fieldName.hasOwnProperty(key)) { continue; } //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); if (field && field.convert !== field.type.convert) { convertFields.push(key); continue; } me.set(key, fieldName[key]); } for (i = 0; i < convertFields.length; i++) { field = convertFields[i]; me.set(field, fieldName[field]); } } else { if (fields) { field = fields.get(fieldName); if (field && field.convert) { value = field.convert(value, me); } } me[me.persistanceProperty][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 (!me.editing) { me.afterEdit(); } } }, /** * 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[this.persistanceProperty][field]; } } return changes; }, /** * Returns <tt>true</tt> if the passed field name has been <code>{@link #modified}</code> * since the load or last commit. * @param {String} fieldName {@link Ext.data.Field#name} * @return {Boolean} */ isModified : function(fieldName) { return !!(this.modified && 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> */ setDirty : function() { this.dirty = true; if (!this.modified) { this.modified = {}; } this.fields.each(function(field) { this.modified[field.name] = this[this.persistanceProperty][field.name]; }, this); }, //<debug> markDirty : function() { throw new Error("Stateful: markDirty has been deprecated. Please use setDirty."); }, //</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) */ reject : function(silent) { var modified = this.modified, field; for (field in modified) { if (!modified.hasOwnProperty(field)) { continue; } if (typeof modified[field] != "function") { this[this.persistanceProperty][field] = modified[field]; } } this.dirty = false; this.editing = false; delete this.modified; if (silent !== true) { this.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. * <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) */ commit : function(silent) { this.dirty = false; this.editing = false; delete this.modified; if (silent !== true) { this.afterCommit(); } }, /** * 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} */ copy : function(newId) { return new this.constructor(Ext.apply({}, this[this.persistanceProperty]), newId || this.internalId); } });