/** * @author Ed Spencer * @class Ext.data.Model * @extends Ext.util.Stateful * *

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.ModelMgr 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.regModel('User', {
    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.ModelMgr ModelMgr}, 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.ModelMgr.create({
    name : 'Conan',
    age  : 24,
    phone: '555-555-5555'
}, 'User');

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.regModel('User', {
    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.ModelMgr.create({
    name: 'Ed',
    gender: 'Male',
    username: 'edspencer'
}, 'User');

var errors = instance.validate();
* *

Associations

* *

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:

*

Ext.regModel('Post', {
    fields: ['id', 'user_id'],

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

Ext.regModel('Comment', {
    fields: ['id', 'user_id', 'post_id'],

    belongsTo: 'Post'
});

Ext.regModel('User', {
    fields: ['id'],

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

See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the usage * and configuration of associations. Note that associations can also be specified like this:

*

Ext.regModel('User', {
    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}, * which can be set directly on the Model:

*

Ext.regModel('User', {
    fields: ['id', 'name', 'email'],

    proxy: {
        type: 'rest',
        url : '/users'
    }
});
* *

Here we've set up a {@link Ext.data.RestProxy 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.ModelMgr.create({name: 'Ed Spencer', email: '[email protected]'}, 'User');

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} for a full * list.

* *

Loading data via the Proxy is equally easy:

*

//get a reference to the User model class
var User = Ext.ModelMgr.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 = new 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 * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values * @param {Number} id Optional unique ID to assign to this model instance */ Ext.define('Ext.data.Model', { extend: 'Ext.util.Stateful', requires: ['Ext.data.Errors', 'Ext.data.Operation', 'Ext.data.Proxy', 'Ext.data.validations'], 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 #create}d * and {@link #Record no id has been specified}. The returned id takes the form: * {PREFIX}-{AUTO_ID}.
* @param {Record} 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) { rec.phantom = true; return [Ext.data.Model.PREFIX, '-', Ext.data.Model.AUTO_ID++].join(''); },
/** * Sets the Proxy to use for this model. Accepts any options that can be accepted by {@link Ext#createByAlias} * @param {String/Object/Ext.data.Proxy} proxy The proxy * @static */ setProxy: function(proxy) { //make sure we have an Ext.data.Proxy object if (typeof proxy == "string") { proxy = { type: proxy }; } proxy = Ext.createByAlias("proxy." + proxy.type, proxy); proxy.setModel(this); this.proxy = proxy; return proxy; },
/** * Static. Asynchronously loads a model instance by id. Sample usage:

    MyApp.User = Ext.regModel('User', {
        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 * @static */ load: function(id, config) { config = Ext.applyIf(config || {}, { action: 'read', id : id }); var operation = Ext.create('Ext.data.Operation', config), callbackFn = config.callback, successFn = config.success, failureFn = config.failure, scope = config.scope, record, callback; callback = function(operation) { record = operation.getRecords()[0]; if (operation.wasSuccessful()) { if (typeof successFn == 'function') { successFn.call(scope, record, operation); } } else { if (typeof failureFn == 'function') { failureFn.call(scope, record, operation); } } if (typeof callbackFn == 'function') { callbackFn.call(scope, record, operation); } }; this.proxy.read(operation, callback, this); } }, evented: false, isModel: true,
/** * true when the record does not yet exist in a server-side database (see * {@link #setDirty}). Any record which has a real database pk set as its id property * is NOT a phantom -- it's real. * @property phantom * @type {Boolean} */ phantom : false,
/** * @cfg {String} idProperty The name of the field treated as this Model's unique id (defaults to 'id'). */ idProperty: 'id', constructor: function(data, id) { data = data || {}; /** * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet * @property internalId * @type String * @private */ this.internalId = (id || id === 0) ? id : Ext.data.Model.id(this); Ext.data.Model.superclass.constructor.apply(this); //add default field values if present var fields = this.fields.items, length = fields.length, field, name, i; for (i = 0; i < length; i++) { field = fields[i]; name = field.name; if (data[name] === undefined) { data[name] = field.defaultValue; } } this.set(data); this.dirty = false; if (this.getId()) { this.phantom = false; } if (typeof this.init == 'function') { this.init(); } },
/** * Validates the current data against all of its configured {@link #validations} and returns an * {@link Ext.data.Errors Errors} object * @return {Ext.data.Errors} The errors object */ validate: function() { var errors = Ext.create('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; },
/** * Returns the configured Proxy for this Model * @return {Ext.data.Proxy} The proxy */ getProxy: function() { return this.constructor.proxy; },
/** * Saves the model instance using the configured proxy * @param {Object} options Options to pass to the proxy * @return {Ext.data.Model} The Model instance */ save: function(options) { var me = this, action = me.phantom ? 'create' : 'update'; options = options || {}; Ext.apply(options, { records: [me], action : action }); var operation = Ext.create('Ext.data.Operation', options), successFn = options.success, failureFn = options.failure, callbackFn = options.callback, scope = options.scope, record; var callback = function(operation) { record = operation.getRecords()[0]; if (operation.wasSuccessful()) { //we need to make sure we've set the updated data here. Ideally this will be redundant once the //ModelCache is in place me.set(record.data); record.dirty = false; if (typeof successFn == 'function') { successFn.call(scope, record, operation); } } else { if (typeof failureFn == 'function') { failureFn.call(scope, record, operation); } } if (typeof callbackFn == 'function') { callbackFn.call(scope, record, operation); } }; me.getProxy()[action](operation, callback, me); return me; },
/** * 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 * @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 */ join : function(store) {
/** * The {@link Ext.data.Store} to which this Record belongs. * @property store * @type {Ext.data.Store} */ this.store = store; },
/** * Tells this model instance that it has been removed from the store * @param {Ext.data.Store} store The store to unjoin */ 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 */ afterEdit : function() { 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 */ 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. * @param {String} fn The function to call on the store */ callStore: function(fn) { var store = this.store; if (store !== undefined && typeof store[fn] == "function") { store[fn](this); } } }, function() { // TODO return; // alert(this.setProxy); // // var me = Ext.data.Model; // setTimeout(function() { // alert(Ext.data.Model.setProxy); // }, 100); // // setTimeout(function() { // alert(Ext.data.Model.setProxy); // }, 1000); }); //[deprecated 5.0] Ext.ns('Ext.data.Record'); //Backwards compat // Ext.data.Record.id = Ext.data.Model.id; //[end]