From 492d374b59600275a0a1a44d63d6dd21deac1c54 Mon Sep 17 00:00:00 2001 From: David Durman Date: Tue, 23 Apr 2013 20:05:10 +0200 Subject: [PATCH 1/2] trigger changes on all parent attributes --- README.md | 4 ++++ src/deep-model.js | 19 +++++++++++++++++++ test/deep-model.test.js | 4 ++++ 3 files changed, 27 insertions(+) diff --git a/README.md b/README.md index bdc6a08..3adabc8 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,10 @@ Example code: console.log(val); }); + //... and all parent attributes as well + model.bind('change:user.name', function(model, val) { console.log(val); }); + model.bind('change:user', function(model, val) { console.log(val); }); + //Wildcards are supported model.bind('change:user.*', function() {}); diff --git a/src/deep-model.js b/src/deep-model.js index 55c19dd..b9a154e 100644 --- a/src/deep-model.js +++ b/src/deep-model.js @@ -225,6 +225,25 @@ } // } + // + // Flag triggered events so that they are not triggered more then once. + var triggered = {}; + _.each(changes, function(key) { + + var fields = key.split(separator); + + //Trigger change events for parent keys without wildcard (*) notation. + + for(var n = fields.length - 1; n > 0; n--) { + var parentKey = _.first(fields, n).join(separator); + + if (!triggered[parentKey]) { + this.trigger('change:' + parentKey, this, getNested(current, parentKey), options); + } + triggered[parentKey] = true; + } + }, this); + // } if (changing) return this; diff --git a/test/deep-model.test.js b/test/deep-model.test.js index bbd64ba..a39722f 100644 --- a/test/deep-model.test.js +++ b/test/deep-model.test.js @@ -262,6 +262,8 @@ test("set: Triggers model change:[attribute] events", function() { 'change:user.name.first', 'change:user.name.*', 'change:user.*', + 'change:user.name', + 'change:user', 'change' ]); })(); @@ -503,6 +505,8 @@ test("unset: Triggers model change:[attribute] events", function() { 'change:user.name.first', 'change:user.name.*', 'change:user.*', + 'change:user.name', + 'change:user', 'change' ]); })(); From 5e9702ba57158076fadc248878a58bf3c930b839 Mon Sep 17 00:00:00 2001 From: David Durman Date: Wed, 24 Apr 2013 10:19:07 +0200 Subject: [PATCH 2/2] skip prototype properties when looping over objects in objToPaths() --- distribution/deep-model.js | 23 +++++++++++++++++++++++ distribution/deep-model.min.js | 2 +- src/deep-model.js | 4 ++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/distribution/deep-model.js b/distribution/deep-model.js index 5307ffc..bdcc81c 100644 --- a/distribution/deep-model.js +++ b/distribution/deep-model.js @@ -145,6 +145,8 @@ separator = DeepModel.keyPathSeparator; for (var key in obj) { + if (!obj.hasOwnProperty(key)) continue; + var val = obj[key]; if (val && val.constructor === Object && !_.isEmpty(val)) { @@ -152,6 +154,8 @@ var obj2 = objToPaths(val); for (var key2 in obj2) { + if (!obj2.hasOwnProperty(key2)) continue; + var val2 = obj2[key2]; ret[key + separator + key2] = val2; @@ -346,6 +350,25 @@ } // } + // + // Flag triggered events so that they are not triggered more then once. + var triggered = {}; + _.each(changes, function(key) { + + var fields = key.split(separator); + + //Trigger change events for parent keys without wildcard (*) notation. + + for(var n = fields.length - 1; n > 0; n--) { + var parentKey = _.first(fields, n).join(separator); + + if (!triggered[parentKey]) { + this.trigger('change:' + parentKey, this, getNested(current, parentKey), options); + } + triggered[parentKey] = true; + } + }, this); + // } if (changing) return this; diff --git a/distribution/deep-model.min.js b/distribution/deep-model.min.js index e488ad5..9b20190 100644 --- a/distribution/deep-model.min.js +++ b/distribution/deep-model.min.js @@ -9,4 +9,4 @@ * 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(){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){if(!t.hasOwnProperty(s))continue;var u=t[s];if(u&&u.constructor===Object&&!e.isEmpty(u)){var a=n(u);for(var f in a){if(!a.hasOwnProperty(f))continue;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)}}var T={};e.each(h,function(t){var n=t.split(g);for(var i=n.length-1;i>0;i--){var s=e.first(n,i).join(g);T[s]||this.trigger("change:"+s,this,r(m,s),a),T[s]=!0}},this)}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}) diff --git a/src/deep-model.js b/src/deep-model.js index b9a154e..6ab541f 100644 --- a/src/deep-model.js +++ b/src/deep-model.js @@ -24,6 +24,8 @@ separator = DeepModel.keyPathSeparator; for (var key in obj) { + if (!obj.hasOwnProperty(key)) continue; + var val = obj[key]; if (val && val.constructor === Object && !_.isEmpty(val)) { @@ -31,6 +33,8 @@ var obj2 = objToPaths(val); for (var key2 in obj2) { + if (!obj2.hasOwnProperty(key2)) continue; + var val2 = obj2[key2]; ret[key + separator + key2] = val2;