diff --git a/distribution/deep-model.js b/distribution/deep-model.js
index 5307ffc..cf8453d 100644
--- a/distribution/deep-model.js
+++ b/distribution/deep-model.js
@@ -9,7 +9,17 @@
* Licensed under the MIT License
*/
-/**
+;(function(factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD
+ define(['underscore', 'backbone'], factory);
+ } else {
+ // globals
+ factory(_, Backbone);
+ }
+}(function(_, Backbone) {
+
+ /**
* Underscore mixins for deep objects
*
* Based on https://gist.github.com/echong/3861963
@@ -120,319 +130,310 @@
}).call(this);
/**
- * Main source
+* Main source
+*/
+
+
+
+/**
+ * Takes a nested object and returns a shallow object keyed with the path names
+ * e.g. { "level1.level2": "value" }
+ *
+ * @param {Object} Nested object e.g. { level1: { level2: 'value' } }
+ * @return {Object} Shallow object with path names e.g. { 'level1.level2': 'value' }
*/
+function objToPaths(obj) {
+ var ret = {},
+ separator = DeepModel.keyPathSeparator;
-;(function(factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD
- define(['underscore', 'backbone'], factory);
- } else {
- // globals
- factory(_, Backbone);
- }
-}(function(_, Backbone) {
-
- /**
- * Takes a nested object and returns a shallow object keyed with the path names
- * e.g. { "level1.level2": "value" }
- *
- * @param {Object} Nested object e.g. { level1: { level2: 'value' } }
- * @return {Object} Shallow object with path names e.g. { 'level1.level2': 'value' }
- */
- function objToPaths(obj) {
- var ret = {},
- separator = DeepModel.keyPathSeparator;
-
- for (var key in obj) {
- var val = obj[key];
-
- if (val && val.constructor === Object && !_.isEmpty(val)) {
- //Recursion for embedded objects
- var obj2 = objToPaths(val);
-
- for (var key2 in obj2) {
- var val2 = obj2[key2];
-
- ret[key + separator + key2] = val2;
- }
- } else {
- ret[key] = val;
+ for (var key in obj) {
+ var val = obj[key];
+
+ if (val && val.constructor === Object && !_.isEmpty(val)) {
+ //Recursion for embedded objects
+ var obj2 = objToPaths(val);
+
+ for (var key2 in obj2) {
+ var val2 = obj2[key2];
+
+ ret[key + separator + key2] = val2;
}
+ } else {
+ ret[key] = val;
}
-
- return ret;
}
- /**
- * @param {Object} Object to fetch attribute from
- * @param {String} Object path e.g. 'user.name'
- * @return {Mixed}
- */
- function getNested(obj, path, return_exists) {
- var separator = DeepModel.keyPathSeparator;
-
- var fields = path.split(separator);
- var result = obj;
- return_exists || (return_exists === false);
- for (var i = 0, n = fields.length; i < n; i++) {
- if (return_exists && !_.has(result, fields[i])) {
- return false;
- }
- result = result[fields[i]];
+ return ret;
+}
- if (result == null && i < n - 1) {
- result = {};
- }
-
- if (typeof result === 'undefined') {
- if (return_exists)
- {
- return true;
- }
- return result;
- }
+/**
+ * @param {Object} Object to fetch attribute from
+ * @param {String} Object path e.g. 'user.name'
+ * @return {Mixed}
+ */
+function getNested(obj, path, return_exists) {
+ var separator = DeepModel.keyPathSeparator;
+
+ var fields = path.split(separator);
+ var result = obj;
+ return_exists || (return_exists === false);
+ for (var i = 0, n = fields.length; i < n; i++) {
+ if (return_exists && !_.has(result, fields[i])) {
+ return false;
}
- if (return_exists)
- {
- return true;
+ result = result[fields[i]];
+
+ if (result == null && i < n - 1) {
+ result = {};
}
- return result;
- }
- /**
- * @param {Object} obj Object to fetch attribute from
- * @param {String} path Object path e.g. 'user.name'
- * @param {Object} [options] Options
- * @param {Boolean} [options.unset] Whether to delete the value
- * @param {Mixed} Value to set
- */
- function setNested(obj, path, val, options) {
- options = options || {};
-
- var separator = DeepModel.keyPathSeparator;
-
- var fields = path.split(separator);
- var result = obj;
- for (var i = 0, n = fields.length; i < n && result !== undefined ; i++) {
- var field = fields[i];
-
- //If the last in the path, set the value
- if (i === n - 1) {
- options.unset ? delete result[field] : result[field] = val;
- } else {
- //Create the child object if it doesn't exist, or isn't an object
- if (typeof result[field] === 'undefined' || ! _.isObject(result[field])) {
- result[field] = {};
- }
-
- //Move onto the next part of the path
- result = result[field];
+ if (typeof result === 'undefined') {
+ if (return_exists)
+ {
+ return true;
}
+ return result;
}
}
-
- function deleteNested(obj, path) {
- setNested(obj, path, null, { unset: true });
+ if (return_exists)
+ {
+ return true;
}
+ return result;
+}
- var DeepModel = Backbone.Model.extend({
-
- // Override constructor
- // Support having nested defaults by using _.deepExtend instead of _.extend
- constructor: function(attributes, options) {
- var defaults;
- var attrs = attributes || {};
- this.cid = _.uniqueId('c');
- this.attributes = {};
- if (options && options.collection) this.collection = options.collection;
- if (options && options.parse) attrs = this.parse(attrs, options) || {};
- if (defaults = _.result(this, 'defaults')) {
- //
- // Replaced the call to _.defaults with _.deepExtend.
- attrs = _.deepExtend({}, defaults, attrs);
- //
- }
- this.set(attrs, options);
- this.changed = {};
- this.initialize.apply(this, arguments);
- },
-
- // Return a copy of the model's `attributes` object.
- toJSON: function(options) {
- return _.deepClone(this.attributes);
- },
-
- // Override get
- // Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name'
- get: function(attr) {
- return getNested(this.attributes, attr);
- },
-
- // Override set
- // Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name'
- set: function(key, val, options) {
- var attr, attrs, unset, changes, silent, changing, prev, current;
- if (key == null) return this;
-
- // Handle both `"key", value` and `{key: value}` -style arguments.
- if (typeof key === 'object') {
- attrs = key;
- options = val || {};
- } else {
- (attrs = {})[key] = val;
- }
-
- options || (options = {});
-
- // Run validation.
- if (!this._validate(attrs, options)) return false;
-
- // Extract attributes and options.
- unset = options.unset;
- silent = options.silent;
- changes = [];
- changing = this._changing;
- this._changing = true;
-
- if (!changing) {
- this._previousAttributes = _.deepClone(this.attributes); //: Replaced _.clone with _.deepClone
- this.changed = {};
+/**
+ * @param {Object} obj Object to fetch attribute from
+ * @param {String} path Object path e.g. 'user.name'
+ * @param {Object} [options] Options
+ * @param {Boolean} [options.unset] Whether to delete the value
+ * @param {Mixed} Value to set
+ */
+function setNested(obj, path, val, options) {
+ options = options || {};
+
+ var separator = DeepModel.keyPathSeparator;
+
+ var fields = path.split(separator);
+ var result = obj;
+ for (var i = 0, n = fields.length; i < n && result !== undefined ; i++) {
+ var field = fields[i];
+
+ //If the last in the path, set the value
+ if (i === n - 1) {
+ options.unset ? delete result[field] : result[field] = val;
+ } else {
+ //Create the child object if it doesn't exist, or isn't an object
+ if (typeof result[field] === 'undefined' || ! _.isObject(result[field])) {
+ result[field] = {};
}
- current = this.attributes, prev = this._previousAttributes;
-
- // Check for changes of `id`.
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+ //Move onto the next part of the path
+ result = result[field];
+ }
+ }
+}
+
+function deleteNested(obj, path) {
+ setNested(obj, path, null, { unset: true });
+}
+
+var DeepModel = Backbone.Model.extend({
+
+ // Override constructor
+ // Support having nested defaults by using _.deepExtend instead of _.extend
+ constructor: function(attributes, options) {
+ var defaults;
+ var attrs = attributes || {};
+ this.cid = _.uniqueId('c');
+ this.attributes = {};
+ if (options && options.collection) this.collection = options.collection;
+ if (options && options.parse) attrs = this.parse(attrs, options) || {};
+ if (defaults = _.result(this, 'defaults')) {
//
- attrs = objToPaths(attrs);
+ // Replaced the call to _.defaults with _.deepExtend.
+ attrs = _.deepExtend({}, defaults, attrs);
//
+ }
+ this.set(attrs, options);
+ this.changed = {};
+ this.initialize.apply(this, arguments);
+ },
+
+ // Return a copy of the model's `attributes` object.
+ toJSON: function(options) {
+ return _.deepClone(this.attributes);
+ },
+
+ // Override get
+ // Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name'
+ get: function(attr) {
+ return getNested(this.attributes, attr);
+ },
+
+ // Override set
+ // Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name'
+ set: function(key, val, options) {
+ var attr, attrs, unset, changes, silent, changing, prev, current;
+ if (key == null) return this;
+
+ // Handle both `"key", value` and `{key: value}` -style arguments.
+ if (typeof key === 'object') {
+ attrs = key;
+ options = val || {};
+ } else {
+ (attrs = {})[key] = val;
+ }
- // For each `set` attribute, update or delete the current value.
- for (attr in attrs) {
- val = attrs[attr];
-
- //: Using getNested, setNested and deleteNested
- if (!_.isEqual(getNested(current, attr), val)) changes.push(attr);
- if (!_.isEqual(getNested(prev, attr), val)) {
- setNested(this.changed, attr, val);
- } else {
- deleteNested(this.changed, attr);
- }
- unset ? deleteNested(current, attr) : setNested(current, attr, val);
- //
- }
-
- // Trigger all relevant attribute changes.
- if (!silent) {
- if (changes.length) this._pending = true;
+ options || (options = {});
- //
- var separator = DeepModel.keyPathSeparator;
+ // Run validation.
+ if (!this._validate(attrs, options)) return false;
- for (var i = 0, l = changes.length; i < l; i++) {
- var key = changes[i];
+ // Extract attributes and options.
+ unset = options.unset;
+ silent = options.silent;
+ changes = [];
+ changing = this._changing;
+ this._changing = true;
- this.trigger('change:' + key, this, getNested(current, key), options);
+ if (!changing) {
+ this._previousAttributes = _.deepClone(this.attributes); //: Replaced _.clone with _.deepClone
+ this.changed = {};
+ }
+ current = this.attributes, prev = this._previousAttributes;
- var fields = key.split(separator);
+ // Check for changes of `id`.
+ if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
- //Trigger change events for parent keys with wildcard (*) notation
- for(var n = fields.length - 1; n > 0; n--) {
- var parentKey = _.first(fields, n).join(separator),
- wildcardKey = parentKey + separator + '*';
+ //
+ attrs = objToPaths(attrs);
+ //
- this.trigger('change:' + wildcardKey, this, getNested(current, parentKey), options);
- }
- //
- }
- }
+ // For each `set` attribute, update or delete the current value.
+ for (attr in attrs) {
+ val = attrs[attr];
- if (changing) return this;
- if (!silent) {
- while (this._pending) {
- this._pending = false;
- this.trigger('change', this, options);
- }
- }
- this._pending = false;
- this._changing = false;
- return this;
- },
-
- // Clear all attributes on the model, firing `"change"` unless you choose
- // to silence it.
- clear: function(options) {
- var attrs = {};
- var shallowAttributes = objToPaths(this.attributes);
- for (var key in shallowAttributes) attrs[key] = void 0;
- return this.set(attrs, _.extend({}, options, {unset: true}));
- },
-
- // Determine if the model has changed since the last `"change"` event.
- // If you specify an attribute name, determine if that attribute has changed.
- hasChanged: function(attr) {
- if (attr == null) return !_.isEmpty(this.changed);
- return getNested(this.changed, attr) !== undefined;
- },
-
- // Return an object containing all the attributes that have changed, or
- // false if there are no changed attributes. Useful for determining what
- // parts of a view need to be updated and/or what attributes need to be
- // persisted to the server. Unset attributes will be set to undefined.
- // You can also pass an attributes object to diff against the model,
- // determining if there *would be* a change.
- changedAttributes: function(diff) {
- //: objToPaths
- if (!diff) return this.hasChanged() ? objToPaths(this.changed) : false;
+ //: Using getNested, setNested and deleteNested
+ if (!_.isEqual(getNested(current, attr), val)) changes.push(attr);
+ if (!_.isEqual(getNested(prev, attr), val)) {
+ setNested(this.changed, attr, val);
+ } else {
+ deleteNested(this.changed, attr);
+ }
+ unset ? deleteNested(current, attr) : setNested(current, attr, val);
//
+ }
+
+ // Trigger all relevant attribute changes.
+ if (!silent) {
+ if (changes.length) this._pending = true;
- var old = this._changing ? this._previousAttributes : this.attributes;
-
//
- diff = objToPaths(diff);
- old = objToPaths(old);
- //
+ var separator = DeepModel.keyPathSeparator;
- var val, changed = false;
- for (var attr in diff) {
- if (_.isEqual(old[attr], (val = diff[attr]))) continue;
- (changed || (changed = {}))[attr] = val;
- }
- return changed;
- },
+ for (var i = 0, l = changes.length; i < l; i++) {
+ var key = changes[i];
- // Get the previous value of an attribute, recorded at the time the last
- // `"change"` event was fired.
- previous: function(attr) {
- if (attr == null || !this._previousAttributes) return null;
+ this.trigger('change:' + key, this, getNested(current, key), options);
- //
- return getNested(this._previousAttributes, attr);
- //
- },
+ var fields = key.split(separator);
- // Get all of the attributes of the model at the time of the previous
- // `"change"` event.
- previousAttributes: function() {
- //
- return _.deepClone(this._previousAttributes);
- //
+ //Trigger change events for parent keys with wildcard (*) notation
+ for(var n = fields.length - 1; n > 0; n--) {
+ var parentKey = _.first(fields, n).join(separator),
+ wildcardKey = parentKey + separator + '*';
+
+ this.trigger('change:' + wildcardKey, this, getNested(current, parentKey), options);
+ }
+ //
+ }
}
- });
+ if (changing) return this;
+ if (!silent) {
+ while (this._pending) {
+ this._pending = false;
+ this.trigger('change', this, options);
+ }
+ }
+ this._pending = false;
+ this._changing = false;
+ return this;
+ },
+
+ // Clear all attributes on the model, firing `"change"` unless you choose
+ // to silence it.
+ clear: function(options) {
+ var attrs = {};
+ var shallowAttributes = objToPaths(this.attributes);
+ for (var key in shallowAttributes) attrs[key] = void 0;
+ return this.set(attrs, _.extend({}, options, {unset: true}));
+ },
+
+ // Determine if the model has changed since the last `"change"` event.
+ // If you specify an attribute name, determine if that attribute has changed.
+ hasChanged: function(attr) {
+ if (attr == null) return !_.isEmpty(this.changed);
+ return getNested(this.changed, attr) !== undefined;
+ },
+
+ // Return an object containing all the attributes that have changed, or
+ // false if there are no changed attributes. Useful for determining what
+ // parts of a view need to be updated and/or what attributes need to be
+ // persisted to the server. Unset attributes will be set to undefined.
+ // You can also pass an attributes object to diff against the model,
+ // determining if there *would be* a change.
+ changedAttributes: function(diff) {
+ //: objToPaths
+ if (!diff) return this.hasChanged() ? objToPaths(this.changed) : false;
+ //
+
+ var old = this._changing ? this._previousAttributes : this.attributes;
+
+ //
+ diff = objToPaths(diff);
+ old = objToPaths(old);
+ //
+
+ var val, changed = false;
+ for (var attr in diff) {
+ if (_.isEqual(old[attr], (val = diff[attr]))) continue;
+ (changed || (changed = {}))[attr] = val;
+ }
+ return changed;
+ },
+
+ // Get the previous value of an attribute, recorded at the time the last
+ // `"change"` event was fired.
+ previous: function(attr) {
+ if (attr == null || !this._previousAttributes) return null;
+
+ //
+ return getNested(this._previousAttributes, attr);
+ //
+ },
+
+ // Get all of the attributes of the model at the time of the previous
+ // `"change"` event.
+ previousAttributes: function() {
+ //
+ return _.deepClone(this._previousAttributes);
+ //
+ }
+});
- //Config; override in your app to customise
- DeepModel.keyPathSeparator = '.';
+//Config; override in your app to customise
+DeepModel.keyPathSeparator = '.';
- //Exports
- Backbone.DeepModel = DeepModel;
- //For use in NodeJS
- if (typeof module != 'undefined') module.exports = DeepModel;
-
- return Backbone;
+//Exports
+Backbone.DeepModel = DeepModel;
+//For use in NodeJS
+if (typeof module != 'undefined') module.exports = DeepModel;
+
+ return Backbone;
}));
diff --git a/distribution/deep-model.min.js b/distribution/deep-model.min.js
index e488ad5..a87a946 100644
--- a/distribution/deep-model.min.js
+++ b/distribution/deep-model.min.js
@@ -9,4 +9,18 @@
* Licensed under the MIT License
*/
-(function(){var e,t,n,r,i,s,o=[].slice;n=function(e){var t,r;return!_.isObject(e)||_.isFunction(e)?e:e instanceof Backbone.Collection||e instanceof Backbone.Model?e:_.isDate(e)?new Date(e.getTime()):_.isRegExp(e)?new RegExp(e.source,e.toString().replace(/.*\//,"")):(r=_.isArray(e||_.isArguments(e)),t=function(e,t,i){return r?e.push(n(t)):e[i]=n(t),e},_.reduce(e,t,r?[]:{}))},s=function(e){return e==null?!1:(e.prototype==={}.prototype||e.prototype===Object.prototype)&&_.isObject(e)&&!_.isArray(e)&&!_.isFunction(e)&&!_.isDate(e)&&!_.isRegExp(e)&&!_.isArguments(e)},t=function(e){return _.filter(_.keys(e),function(t){return s(e[t])})},e=function(e){return _.filter(_.keys(e),function(t){return _.isArray(e[t])})},i=function(n,r,s){var o,u,a,f,l,c,h,p,d,v;s==null&&(s=20);if(s<=0)return console.warn("_.deepExtend(): Maximum depth of recursion hit."),_.extend(n,r);c=_.intersection(t(n),t(r)),u=function(e){return r[e]=i(n[e],r[e],s-1)};for(h=0,d=c.length;h0)e=i(e,n(r.shift()),t);return e},_.mixin({deepClone:n,isBasicObject:s,basicObjects:t,arrays:e,deepExtend:r})}).call(this),function(e){typeof define=="function"&&define.amd?define(["underscore","backbone"],e):e(_,Backbone)}(function(e,t){function n(t){var r={},i=o.keyPathSeparator;for(var s in t){var u=t[s];if(u&&u.constructor===Object&&!e.isEmpty(u)){var a=n(u);for(var f in a){var l=a[f];r[s+i+f]=l}}else r[s]=u}return r}function r(t,n,r){var i=o.keyPathSeparator,s=n.split(i),u=t;r||r===!1;for(var a=0,f=s.length;a0;E--){var S=e.first(w,E).join(g),x=S+g+"*";this.trigger("change:"+x,this,r(m,S),a)}}}if(d)return this;if(!p)while(this._pending)this._pending=!1,this.trigger("change",this,a);return this._pending=!1,this._changing=!1,this},clear:function(t){var r={},i=n(this.attributes);for(var s in i)r[s]=void 0;return this.set(r,e.extend({},t,{unset:!0}))},hasChanged:function(t){return t==null?!e.isEmpty(this.changed):r(this.changed,t)!==undefined},changedAttributes:function(t){if(!t)return this.hasChanged()?n(this.changed):!1;var r=this._changing?this._previousAttributes:this.attributes;t=n(t),r=n(r);var i,s=!1;for(var o in t){if(e.isEqual(r[o],i=t[o]))continue;(s||(s={}))[o]=i}return s},previous:function(e){return e==null||!this._previousAttributes?null:r(this._previousAttributes,e)},previousAttributes:function(){return e.deepClone(this._previousAttributes)}});return o.keyPathSeparator=".",t.DeepModel=o,typeof module!="undefined"&&(module.exports=o),t})
+;(function(factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD
+ define(['underscore', 'backbone'], factory);
+ } else {
+ // globals
+ factory(_, Backbone);
+ }
+}(function(_, Backbone) {
+
+ (function(e){typeof define=="function"&&define.amd?define(["underscore","backbone"],e):e(_,Backbone)})(function(e,t){function n(t){var r={},i=o.keyPathSeparator;for(var s in t){var u=t[s];if(u&&u.constructor===Object&&!e.isEmpty(u)){var a=n(u);for(var f in a){var l=a[f];r[s+i+f]=l}}else r[s]=u}return r}function r(t,n,r){var i=o.keyPathSeparator,s=n.split(i),u=t;r||r===!1;for(var a=0,f=s.length;a0)t=o(t,i(r.shift()),n);return t},e.mixin({deepClone:i,isBasicObject:u,basicObjects:r,arrays:n,deepExtend:s})}).call(this);var o=t.Model.extend({constructor:function(t,n){var r,i=t||{};this.cid=e.uniqueId("c"),this.attributes={},n&&n.collection&&(this.collection=n.collection),n&&n.parse&&(i=this.parse(i,n)||{});if(r=e.result(this,"defaults"))i=e.deepExtend({},r,i);this.set(i,n),this.changed={},this.initialize.apply(this,arguments)},toJSON:function(t){return e.deepClone(this.attributes)},get:function(e){return r(this.attributes,e)},set:function(t,u,a){var f,l,c,h,p,d,v,m;if(t==null)return this;typeof t=="object"?(l=t,a=u||{}):(l={})[t]=u,a||(a={});if(!this._validate(l,a))return!1;c=a.unset,p=a.silent,h=[],d=this._changing,this._changing=!0,d||(this._previousAttributes=e.deepClone(this.attributes),this.changed={}),m=this.attributes,v=this._previousAttributes,this.idAttribute in l&&(this.id=l[this.idAttribute]),l=n(l);for(f in l)u=l[f],e.isEqual(r(m,f),u)||h.push(f),e.isEqual(r(v,f),u)?s(this.changed,f):i(this.changed,f,u),c?s(m,f):i(m,f,u);if(!p){h.length&&(this._pending=!0);var g=o.keyPathSeparator;for(var y=0,b=h.length;y0;E--){var S=e.first(w,E).join(g),x=S+g+"*";this.trigger("change:"+x,this,r(m,S),a)}}}if(d)return this;if(!p)while(this._pending)this._pending=!1,this.trigger("change",this,a);return this._pending=!1,this._changing=!1,this},clear:function(t){var r={},i=n(this.attributes);for(var s in i)r[s]=void 0;return this.set(r,e.extend({},t,{unset:!0}))},hasChanged:function(t){return t==null?!e.isEmpty(this.changed):r(this.changed,t)!==undefined},changedAttributes:function(t){if(!t)return this.hasChanged()?n(this.changed):!1;var r=this._changing?this._previousAttributes:this.attributes;t=n(t),r=n(r);var i,s=!1;for(var o in t){if(e.isEqual(r[o],i=t[o]))continue;(s||(s={}))[o]=i}return s},previous:function(e){return e==null||!this._previousAttributes?null:r(this._previousAttributes,e)},previousAttributes:function(){return e.deepClone(this._previousAttributes)}});return o.keyPathSeparator=".",t.DeepModel=o,typeof module!="undefined"&&(module.exports=o),t})
+
+ return Backbone;
+}));
+
diff --git a/scripts/template.js b/scripts/template.js
index 95fe0f4..bf47c14 100644
--- a/scripts/template.js
+++ b/scripts/template.js
@@ -9,4 +9,18 @@
* Licensed under the MIT License
*/
-{{body}}
+;(function(factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD
+ define(['underscore', 'backbone'], factory);
+ } else {
+ // globals
+ factory(_, Backbone);
+ }
+}(function(_, Backbone) {
+
+ {{body}}
+
+ return Backbone;
+}));
+
diff --git a/src/deep-model.js b/src/deep-model.js
index 55c19dd..807e6bc 100644
--- a/src/deep-model.js
+++ b/src/deep-model.js
@@ -1,316 +1,304 @@
/**
- * Main source
+* Main source
+*/
+
+
+
+/**
+ * Takes a nested object and returns a shallow object keyed with the path names
+ * e.g. { "level1.level2": "value" }
+ *
+ * @param {Object} Nested object e.g. { level1: { level2: 'value' } }
+ * @return {Object} Shallow object with path names e.g. { 'level1.level2': 'value' }
*/
+function objToPaths(obj) {
+ var ret = {},
+ separator = DeepModel.keyPathSeparator;
-;(function(factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD
- define(['underscore', 'backbone'], factory);
- } else {
- // globals
- factory(_, Backbone);
- }
-}(function(_, Backbone) {
-
- /**
- * Takes a nested object and returns a shallow object keyed with the path names
- * e.g. { "level1.level2": "value" }
- *
- * @param {Object} Nested object e.g. { level1: { level2: 'value' } }
- * @return {Object} Shallow object with path names e.g. { 'level1.level2': 'value' }
- */
- function objToPaths(obj) {
- var ret = {},
- separator = DeepModel.keyPathSeparator;
-
- for (var key in obj) {
- var val = obj[key];
-
- if (val && val.constructor === Object && !_.isEmpty(val)) {
- //Recursion for embedded objects
- var obj2 = objToPaths(val);
-
- for (var key2 in obj2) {
- var val2 = obj2[key2];
-
- ret[key + separator + key2] = val2;
- }
- } else {
- ret[key] = val;
+ for (var key in obj) {
+ var val = obj[key];
+
+ if (val && val.constructor === Object && !_.isEmpty(val)) {
+ //Recursion for embedded objects
+ var obj2 = objToPaths(val);
+
+ for (var key2 in obj2) {
+ var val2 = obj2[key2];
+
+ ret[key + separator + key2] = val2;
}
+ } else {
+ ret[key] = val;
}
-
- return ret;
}
- /**
- * @param {Object} Object to fetch attribute from
- * @param {String} Object path e.g. 'user.name'
- * @return {Mixed}
- */
- function getNested(obj, path, return_exists) {
- var separator = DeepModel.keyPathSeparator;
-
- var fields = path.split(separator);
- var result = obj;
- return_exists || (return_exists === false);
- for (var i = 0, n = fields.length; i < n; i++) {
- if (return_exists && !_.has(result, fields[i])) {
- return false;
- }
- result = result[fields[i]];
+ return ret;
+}
- if (result == null && i < n - 1) {
- result = {};
- }
-
- if (typeof result === 'undefined') {
- if (return_exists)
- {
- return true;
- }
- return result;
- }
+/**
+ * @param {Object} Object to fetch attribute from
+ * @param {String} Object path e.g. 'user.name'
+ * @return {Mixed}
+ */
+function getNested(obj, path, return_exists) {
+ var separator = DeepModel.keyPathSeparator;
+
+ var fields = path.split(separator);
+ var result = obj;
+ return_exists || (return_exists === false);
+ for (var i = 0, n = fields.length; i < n; i++) {
+ if (return_exists && !_.has(result, fields[i])) {
+ return false;
}
- if (return_exists)
- {
- return true;
+ result = result[fields[i]];
+
+ if (result == null && i < n - 1) {
+ result = {};
}
- return result;
- }
- /**
- * @param {Object} obj Object to fetch attribute from
- * @param {String} path Object path e.g. 'user.name'
- * @param {Object} [options] Options
- * @param {Boolean} [options.unset] Whether to delete the value
- * @param {Mixed} Value to set
- */
- function setNested(obj, path, val, options) {
- options = options || {};
-
- var separator = DeepModel.keyPathSeparator;
-
- var fields = path.split(separator);
- var result = obj;
- for (var i = 0, n = fields.length; i < n && result !== undefined ; i++) {
- var field = fields[i];
-
- //If the last in the path, set the value
- if (i === n - 1) {
- options.unset ? delete result[field] : result[field] = val;
- } else {
- //Create the child object if it doesn't exist, or isn't an object
- if (typeof result[field] === 'undefined' || ! _.isObject(result[field])) {
- result[field] = {};
- }
-
- //Move onto the next part of the path
- result = result[field];
+ if (typeof result === 'undefined') {
+ if (return_exists)
+ {
+ return true;
}
+ return result;
}
}
-
- function deleteNested(obj, path) {
- setNested(obj, path, null, { unset: true });
+ if (return_exists)
+ {
+ return true;
}
+ return result;
+}
- var DeepModel = Backbone.Model.extend({
-
- // Override constructor
- // Support having nested defaults by using _.deepExtend instead of _.extend
- constructor: function(attributes, options) {
- var defaults;
- var attrs = attributes || {};
- this.cid = _.uniqueId('c');
- this.attributes = {};
- if (options && options.collection) this.collection = options.collection;
- if (options && options.parse) attrs = this.parse(attrs, options) || {};
- if (defaults = _.result(this, 'defaults')) {
- //
- // Replaced the call to _.defaults with _.deepExtend.
- attrs = _.deepExtend({}, defaults, attrs);
- //
- }
- this.set(attrs, options);
- this.changed = {};
- this.initialize.apply(this, arguments);
- },
-
- // Return a copy of the model's `attributes` object.
- toJSON: function(options) {
- return _.deepClone(this.attributes);
- },
-
- // Override get
- // Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name'
- get: function(attr) {
- return getNested(this.attributes, attr);
- },
-
- // Override set
- // Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name'
- set: function(key, val, options) {
- var attr, attrs, unset, changes, silent, changing, prev, current;
- if (key == null) return this;
-
- // Handle both `"key", value` and `{key: value}` -style arguments.
- if (typeof key === 'object') {
- attrs = key;
- options = val || {};
- } else {
- (attrs = {})[key] = val;
- }
-
- options || (options = {});
-
- // Run validation.
- if (!this._validate(attrs, options)) return false;
-
- // Extract attributes and options.
- unset = options.unset;
- silent = options.silent;
- changes = [];
- changing = this._changing;
- this._changing = true;
-
- if (!changing) {
- this._previousAttributes = _.deepClone(this.attributes); //: Replaced _.clone with _.deepClone
- this.changed = {};
+/**
+ * @param {Object} obj Object to fetch attribute from
+ * @param {String} path Object path e.g. 'user.name'
+ * @param {Object} [options] Options
+ * @param {Boolean} [options.unset] Whether to delete the value
+ * @param {Mixed} Value to set
+ */
+function setNested(obj, path, val, options) {
+ options = options || {};
+
+ var separator = DeepModel.keyPathSeparator;
+
+ var fields = path.split(separator);
+ var result = obj;
+ for (var i = 0, n = fields.length; i < n && result !== undefined ; i++) {
+ var field = fields[i];
+
+ //If the last in the path, set the value
+ if (i === n - 1) {
+ options.unset ? delete result[field] : result[field] = val;
+ } else {
+ //Create the child object if it doesn't exist, or isn't an object
+ if (typeof result[field] === 'undefined' || ! _.isObject(result[field])) {
+ result[field] = {};
}
- current = this.attributes, prev = this._previousAttributes;
-
- // Check for changes of `id`.
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+ //Move onto the next part of the path
+ result = result[field];
+ }
+ }
+}
+
+function deleteNested(obj, path) {
+ setNested(obj, path, null, { unset: true });
+}
+
+var DeepModel = Backbone.Model.extend({
+
+ // Override constructor
+ // Support having nested defaults by using _.deepExtend instead of _.extend
+ constructor: function(attributes, options) {
+ var defaults;
+ var attrs = attributes || {};
+ this.cid = _.uniqueId('c');
+ this.attributes = {};
+ if (options && options.collection) this.collection = options.collection;
+ if (options && options.parse) attrs = this.parse(attrs, options) || {};
+ if (defaults = _.result(this, 'defaults')) {
//
- attrs = objToPaths(attrs);
+ // Replaced the call to _.defaults with _.deepExtend.
+ attrs = _.deepExtend({}, defaults, attrs);
//
+ }
+ this.set(attrs, options);
+ this.changed = {};
+ this.initialize.apply(this, arguments);
+ },
+
+ // Return a copy of the model's `attributes` object.
+ toJSON: function(options) {
+ return _.deepClone(this.attributes);
+ },
+
+ // Override get
+ // Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name'
+ get: function(attr) {
+ return getNested(this.attributes, attr);
+ },
+
+ // Override set
+ // Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name'
+ set: function(key, val, options) {
+ var attr, attrs, unset, changes, silent, changing, prev, current;
+ if (key == null) return this;
+
+ // Handle both `"key", value` and `{key: value}` -style arguments.
+ if (typeof key === 'object') {
+ attrs = key;
+ options = val || {};
+ } else {
+ (attrs = {})[key] = val;
+ }
- // For each `set` attribute, update or delete the current value.
- for (attr in attrs) {
- val = attrs[attr];
-
- //: Using getNested, setNested and deleteNested
- if (!_.isEqual(getNested(current, attr), val)) changes.push(attr);
- if (!_.isEqual(getNested(prev, attr), val)) {
- setNested(this.changed, attr, val);
- } else {
- deleteNested(this.changed, attr);
- }
- unset ? deleteNested(current, attr) : setNested(current, attr, val);
- //
- }
-
- // Trigger all relevant attribute changes.
- if (!silent) {
- if (changes.length) this._pending = true;
+ options || (options = {});
- //
- var separator = DeepModel.keyPathSeparator;
+ // Run validation.
+ if (!this._validate(attrs, options)) return false;
- for (var i = 0, l = changes.length; i < l; i++) {
- var key = changes[i];
+ // Extract attributes and options.
+ unset = options.unset;
+ silent = options.silent;
+ changes = [];
+ changing = this._changing;
+ this._changing = true;
- this.trigger('change:' + key, this, getNested(current, key), options);
+ if (!changing) {
+ this._previousAttributes = _.deepClone(this.attributes); //: Replaced _.clone with _.deepClone
+ this.changed = {};
+ }
+ current = this.attributes, prev = this._previousAttributes;
- var fields = key.split(separator);
+ // Check for changes of `id`.
+ if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
- //Trigger change events for parent keys with wildcard (*) notation
- for(var n = fields.length - 1; n > 0; n--) {
- var parentKey = _.first(fields, n).join(separator),
- wildcardKey = parentKey + separator + '*';
+ //
+ attrs = objToPaths(attrs);
+ //
- this.trigger('change:' + wildcardKey, this, getNested(current, parentKey), options);
- }
- //
- }
- }
+ // For each `set` attribute, update or delete the current value.
+ for (attr in attrs) {
+ val = attrs[attr];
- if (changing) return this;
- if (!silent) {
- while (this._pending) {
- this._pending = false;
- this.trigger('change', this, options);
- }
- }
- this._pending = false;
- this._changing = false;
- return this;
- },
-
- // Clear all attributes on the model, firing `"change"` unless you choose
- // to silence it.
- clear: function(options) {
- var attrs = {};
- var shallowAttributes = objToPaths(this.attributes);
- for (var key in shallowAttributes) attrs[key] = void 0;
- return this.set(attrs, _.extend({}, options, {unset: true}));
- },
-
- // Determine if the model has changed since the last `"change"` event.
- // If you specify an attribute name, determine if that attribute has changed.
- hasChanged: function(attr) {
- if (attr == null) return !_.isEmpty(this.changed);
- return getNested(this.changed, attr) !== undefined;
- },
-
- // Return an object containing all the attributes that have changed, or
- // false if there are no changed attributes. Useful for determining what
- // parts of a view need to be updated and/or what attributes need to be
- // persisted to the server. Unset attributes will be set to undefined.
- // You can also pass an attributes object to diff against the model,
- // determining if there *would be* a change.
- changedAttributes: function(diff) {
- //: objToPaths
- if (!diff) return this.hasChanged() ? objToPaths(this.changed) : false;
+ //: Using getNested, setNested and deleteNested
+ if (!_.isEqual(getNested(current, attr), val)) changes.push(attr);
+ if (!_.isEqual(getNested(prev, attr), val)) {
+ setNested(this.changed, attr, val);
+ } else {
+ deleteNested(this.changed, attr);
+ }
+ unset ? deleteNested(current, attr) : setNested(current, attr, val);
//
+ }
+
+ // Trigger all relevant attribute changes.
+ if (!silent) {
+ if (changes.length) this._pending = true;
- var old = this._changing ? this._previousAttributes : this.attributes;
-
//
- diff = objToPaths(diff);
- old = objToPaths(old);
- //
+ var separator = DeepModel.keyPathSeparator;
- var val, changed = false;
- for (var attr in diff) {
- if (_.isEqual(old[attr], (val = diff[attr]))) continue;
- (changed || (changed = {}))[attr] = val;
- }
- return changed;
- },
+ for (var i = 0, l = changes.length; i < l; i++) {
+ var key = changes[i];
- // Get the previous value of an attribute, recorded at the time the last
- // `"change"` event was fired.
- previous: function(attr) {
- if (attr == null || !this._previousAttributes) return null;
+ this.trigger('change:' + key, this, getNested(current, key), options);
- //
- return getNested(this._previousAttributes, attr);
- //
- },
+ var fields = key.split(separator);
- // Get all of the attributes of the model at the time of the previous
- // `"change"` event.
- previousAttributes: function() {
- //
- return _.deepClone(this._previousAttributes);
- //
+ //Trigger change events for parent keys with wildcard (*) notation
+ for(var n = fields.length - 1; n > 0; n--) {
+ var parentKey = _.first(fields, n).join(separator),
+ wildcardKey = parentKey + separator + '*';
+
+ this.trigger('change:' + wildcardKey, this, getNested(current, parentKey), options);
+ }
+ //
+ }
}
- });
+ if (changing) return this;
+ if (!silent) {
+ while (this._pending) {
+ this._pending = false;
+ this.trigger('change', this, options);
+ }
+ }
+ this._pending = false;
+ this._changing = false;
+ return this;
+ },
+
+ // Clear all attributes on the model, firing `"change"` unless you choose
+ // to silence it.
+ clear: function(options) {
+ var attrs = {};
+ var shallowAttributes = objToPaths(this.attributes);
+ for (var key in shallowAttributes) attrs[key] = void 0;
+ return this.set(attrs, _.extend({}, options, {unset: true}));
+ },
+
+ // Determine if the model has changed since the last `"change"` event.
+ // If you specify an attribute name, determine if that attribute has changed.
+ hasChanged: function(attr) {
+ if (attr == null) return !_.isEmpty(this.changed);
+ return getNested(this.changed, attr) !== undefined;
+ },
+
+ // Return an object containing all the attributes that have changed, or
+ // false if there are no changed attributes. Useful for determining what
+ // parts of a view need to be updated and/or what attributes need to be
+ // persisted to the server. Unset attributes will be set to undefined.
+ // You can also pass an attributes object to diff against the model,
+ // determining if there *would be* a change.
+ changedAttributes: function(diff) {
+ //: objToPaths
+ if (!diff) return this.hasChanged() ? objToPaths(this.changed) : false;
+ //
+
+ var old = this._changing ? this._previousAttributes : this.attributes;
+
+ //
+ diff = objToPaths(diff);
+ old = objToPaths(old);
+ //
+
+ var val, changed = false;
+ for (var attr in diff) {
+ if (_.isEqual(old[attr], (val = diff[attr]))) continue;
+ (changed || (changed = {}))[attr] = val;
+ }
+ return changed;
+ },
+
+ // Get the previous value of an attribute, recorded at the time the last
+ // `"change"` event was fired.
+ previous: function(attr) {
+ if (attr == null || !this._previousAttributes) return null;
+
+ //
+ return getNested(this._previousAttributes, attr);
+ //
+ },
+
+ // Get all of the attributes of the model at the time of the previous
+ // `"change"` event.
+ previousAttributes: function() {
+ //
+ return _.deepClone(this._previousAttributes);
+ //
+ }
+});
- //Config; override in your app to customise
- DeepModel.keyPathSeparator = '.';
+//Config; override in your app to customise
+DeepModel.keyPathSeparator = '.';
- //Exports
- Backbone.DeepModel = DeepModel;
- //For use in NodeJS
- if (typeof module != 'undefined') module.exports = DeepModel;
-
- return Backbone;
+//Exports
+Backbone.DeepModel = DeepModel;
-}));
+//For use in NodeJS
+if (typeof module != 'undefined') module.exports = DeepModel;
\ No newline at end of file