File: ../src/model.js
define([
'aeris/util',
'backbone',
'aeris/events',
'aeris/errors/validationerror'
], function(_, Backbone, Events, ValidationError) {
/**
* The base model class for Aeris JS Libraries
*
* See http://backbonejs.org/#Model for documentation
*
* @constructor
*
* @param {Object=} opt_attrs
*
* @param {Object=} opt_options
* @param {Boolean=} opt_options.validate
* If set to true, model will immediately check validation
* on instantiation.
*
* @class aeris.Model
* @extends Backbone.Model
* @uses aeris.Events
*/
var Model = function(opt_attrs, opt_options) {
var options = _.defaults(opt_options || {}, {
validate: false
});
// We don't want ctor to validate before we
// have bound validation error handlers
var parentCtorOptions = _.omit(options, 'validate');
/**
* @property idAttribute
*/
this.idAttribute = options.idAttribute || 'id';
/**
* @property defaults
*/
this.defaults = this.defaults || options.defaults;
/**
* The options used when constructing
* the model.
*
* @type {Object}
* @private
* @property options_
*/
this.options_ = opt_options || {};
Backbone.Model.call(this, opt_attrs, parentCtorOptions);
Events.call(this);
// Handle validation errors
this.on('invalid', this.onValidationError_, this);
if (options.validate) {
this.isValid();
}
/**
* When a model's attribute changes
* @event change
* @param {aeris.Model} model
*/
/**
* When a model's attribute changes,
* where [attribute] is the name of the attribute.
*
* @event change:[attribute]
* @param {aeris.Model} model
* @param {*} value
*/
/**
* When a model is added to a
* {aeris.Collection}.
*
* @event add
* @param {aeris.Model} model
* @param {aeris.Collection} collection
*/
/**
* When a model is removed from a {aeris.Collection}.
*
* @event remove
* @param {aeris.Model} model
* @param {aeris.Collection} collection
*/
};
_.inherits(Model, Backbone.Model);
_.extend(Model.prototype, Events.prototype);
/**
* Handle 'invalid' events thrown by the model.
*
* @param {aeris.Model} model
* @param {aeris.errors.ValidationError} error
* @private
* @method onValidationError_
*/
Model.prototype.onValidationError_ = function(model, error) {
error = (error instanceof Error) ? error : new ValidationError(error);
throw error;
};
/**
* Validate the model's attributes.
*
* @override
* @method isValid
* @throws {aeris.errors.ValidationError}
*/
/**
* Normalize attributes before setting
*
* @param {Object} config
* @protected
* @method set
*/
Model.prototype.set = function(key, value, opts) {
var config;
// Convert args to { key: value } format
if (_.isString(key)) {
(config = {})[key] = value;
}
else {
config = key;
// Options are the second argument
opts = value;
}
// Normalize config before setting
config = this.normalize_(config);
if (!config) {
throw Error('Invalid model attributes. ' +
'Make sure that Model#normalize_ is returning an object.');
}
return Backbone.Model.prototype.set.call(this, config, opts);
};
/**
* This method is called every time attributes
* are set on the model.
*
* Override to provide any additional processing
* needed for options object structure, etc.
*
* @param {Object} attrs
* @return {attrs} Normalized attrs.
*
* @protected
* @method normalize_
*/
Model.prototype.normalize_ = function(attrs) {
return attrs;
};
/**
* Returns a deep-nested property
* of a model attribute.
*
* Example:
* model.set('deepObj', {
* levelA: {
* levelB: {
* foo: 'bar'
* }
* }
* });
*
* model.getAtPath('deepObj.levelA.levelB.foo'); // 'bar'
*
* Returns undefined if the path cannot be resolved.
*
* @param {string} path
* @return {*|undefined}
* @method getAtPath
*/
Model.prototype.getAtPath = function(path) {
var pathParts = path.split('.');
var attrName = pathParts.splice(0, 1)[0];
var attrObj = this.get(attrName);
return pathParts.length ? _.path(pathParts.join('.'), attrObj) : attrObj;
};
/**
* Create a copy of the model.
*
* @method clone
* @param {Object=} opt_attrs Attributes to set on the cloned model.
* @param {Object=} opt_options Options to pass to cloned mode.
* @return {aeris.Model}
*/
Model.prototype.clone = function(opt_attrs, opt_options) {
var attributes = _.extend({}, this.attributes, opt_attrs);
var options = _.extend({}, this.options_, opt_options);
return new this.constructor(attributes, options);
};
/**
* Keep this model updated with values
* from the target model.
*
* Immediately updates the model with the specified
* attributes, and updates this model whenever the
* target model's attributes change.
*
* @method bindAttributesTo
* @param {aeris.Model} target Model to bind to.
* @param {Array.<string>} attrs Attributes to bind.
*/
Model.prototype.bindAttributesTo = function(target, attrs) {
var update = this.updateWithAttributesOf_.bind(this, target, attrs);
var attrEvents = attrs.map(function(attr) {
return 'change:' + attr;
});
// Sync to target immediately
update();
// Sync to changes in target
this.listenTo(target, attrEvents.join(' '), update);
};
/**
* Update the attributes of the model
* with attributes from another model.
*
* @private
*
* @method updateWithAttributesOf_
* @param {aeris.Model} target Source model.
* @param {Array.<string>} attrs List of attributes to update.
*/
Model.prototype.updateWithAttributesOf_ = function(target, attrs) {
// Create { attrName: targetValue } hash
// for all attributes
var attrValues = attrs.reduce(function(obj, attr) {
obj[attr] = target.get(attr);
return obj;
}, {});
this.set(attrValues, { validate: true });
};
return Model;
});
/**
* See http://backbonejs.org/ for full documentation.
* Included here to provide documentation for
* inherited aeris.Model classes
*
* @class Backbone.Model
*
* @param {Object=} opt_attrs Attributes to set on the model.
*
* @param {Object=} opt_options
* @param {Backbone.Collection=} opt_options.collection
* @param {Boolean=} opt_options.parse
*/
/**
* @method get
* @protected
*
* @param {string} attribute
* @return {*}
*/
/**
* @method set
* @protected
*
* @param {Object|string} attributes
* @param {Object|*=} opt_options
* @param {Boolean=} opt_options
*/
/**
* @protected
* @method has
* @return {Boolean}
*/
/**
* @param {string} attribute
* @protected
* @method unset
*/
/**
* @protected
* @method
*/
/**
* @protected
* @property idAttribute
* @type {string}
*/
/**
* @protected
* @property id
* @type {number|string}
*/
/**
* @protected
* @property cid
* @type {number|string}
*/
/**
* @protected
* @property attributes
* @type {Object}
*/
/**
* @protected
* @property defaults
* @type {Object}
*/
/**
* @method toJSON
* @return {Object} A shallow copy of the model's attributes.
*/
/**
* @protected
* @method sync
* @param {string} method
* @param {Backbone.Model} model
* @param {Object=} options
*/
/**
* Fetch model data.
*
* @method fetch
*/
/**
* @method validate
* @protected
*/
/**
* @method parse
* @protected
*/
/**
* @method sync
* @protected
*/