X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/7a654f8d43fdb43d78b63d90528bed6e86b608cc..refs/heads/master:/docs/source/Model.html diff --git a/docs/source/Model.html b/docs/source/Model.html index 596a172b..f199d143 100644 --- a/docs/source/Model.html +++ b/docs/source/Model.html @@ -1,235 +1,233 @@ -Sencha Documentation Project
/**
+
+
+
+  
+  The source code
+  
+  
+  
+  
+
+
+  
/**
  * @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',
@@ -252,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;
@@ -290,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) {
@@ -369,14 +372,17 @@ Ext.define('Ext.data.Model', {
                 // 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}
+        /**
+         * 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
@@ -394,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);
@@ -465,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>&#123;PREFIX}-&#123;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) {
@@ -486,69 +492,176 @@ 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} 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',
 
-    /**
-     * An array of the fields defined on this model
-     * @property fields
-     * @type {Array}
+    // Fields config and property
+    /**
+     * @cfg {Object[]/String[]} fields
+     * The fields for this model.
+     */
+    /**
+     * @property {Ext.util.MixedCollection} fields
+     * The fields defined on this model.
      */
 
-    constructor: function(data, id) {
+    /**
+     * @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
 
-        /**
+        /**
          * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
          * @property internalId
          * @type String
@@ -556,18 +669,30 @@ Ext.define('Ext.data.Model', {
          */
         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;
+
         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);
 
@@ -579,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];
@@ -590,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);
@@ -643,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);
@@ -662,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) {
@@ -674,8 +836,8 @@ Ext.define('Ext.data.Model', {
             }
         }
     },
-    
-    /**
+
+    /**
      * Checks if two values are equal, taking into account certain
      * special factors, for example dates.
      * @private
@@ -689,23 +851,22 @@ 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.
      */
     cancelEdit : function(){
@@ -714,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,
@@ -757,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) {
@@ -799,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,
@@ -817,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];
                 }
             }
         }
@@ -831,20 +1017,20 @@ 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) {
@@ -852,26 +1038,29 @@ 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
@@ -889,17 +1078,16 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id
         return proxy;
     },
 
-    /**
-     * Returns the configured Proxy for this Model
+    /**
+     * 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} 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() {
@@ -929,7 +1117,7 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id
         return errors;
     },
 
-    /**
+    /**
      * Checks if the model is valid. See {@link #validate}.
      * @return {Boolean} True if the model is valid.
      */
@@ -937,9 +1125,9 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id
         return this.validate().isValid();
     },
 
-    /**
-     * 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) {
@@ -980,9 +1168,9 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id
         return me;
     },
 
-    /**
-     * 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){
@@ -1013,43 +1201,43 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id
         return me;
     },
 
-    /**
-     * 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() {
         return this.get(this.idProperty);
     },
 
-    /**
-     * 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) {
         this.set(this.idProperty, 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;
     },
 
-    /**
+    /**
      * @private
      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
      * afterEdit method is called
@@ -1058,7 +1246,7 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id
         this.callStore('afterEdit');
     },
 
-    /**
+    /**
      * @private
      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
      * afterReject method is called
@@ -1067,7 +1255,7 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id
         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
@@ -1076,7 +1264,7 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id
         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
@@ -1091,33 +1279,34 @@ 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(){
         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 {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
      */
@@ -1181,4 +1370,6 @@ Ext.data.Model.id(rec); // automatically generate a unique sequential id
         return associationData;
     }
 });
-
\ No newline at end of file +
+ +