diff --git a/ampersand-state.js b/ampersand-state.js index 79a742e..7b190f9 100644 --- a/ampersand-state.js +++ b/ampersand-state.js @@ -10,6 +10,8 @@ function Base(attrs, options) { this.cid || (this.cid = _.uniqueId('state')); this._events = {}; this._values = {}; + this._childInstances = {}; + this._collectionInstances = {}; this._definition = Object.create(this._definition); if (options.parse) attrs = this.parse(attrs, options); this.parent = options.parent; @@ -431,7 +433,7 @@ _.extend(Base.prototype, BBEvents, { var coll; if (!this._collections) return; for (coll in this._collections) { - this[coll] = new this._collections[coll](null, {parent: this}); + this._collectionInstances[coll] = new this._collections[coll](null, {parent: this}); } }, @@ -439,8 +441,8 @@ _.extend(Base.prototype, BBEvents, { var child; if (!this._children) return; for (child in this._children) { - this[child] = new this._children[child]({}, {parent: this}); - this.listenTo(this[child], 'all', this._getEventBubblingHandler(child)); + this._childInstances[child] = new this._children[child]({}, {parent: this}); + this.listenTo(this._childInstances[child], 'all', this._getEventBubblingHandler(child)); } }, @@ -578,6 +580,30 @@ function createDerivedProperty(modelProto, name, definition) { }); } +// helper for creating appropriate getters/setters for collections +function createCollectionProperty(object, name) { + Object.defineProperty(object, name, { + set: function (val) { + this.set(name, val); + }, + get: function () { + return this._collectionInstances[name]; + } + }); +} + +// helper for creating appropriate getters/setters for children +function createChildProperty(object, name) { + Object.defineProperty(object, name, { + set: function (val) { + this.set(name, val); + }, + get: function () { + return this._childInstances[name]; + } + }); +} + var dataTypes = { string: { default: function () { @@ -748,11 +774,13 @@ function extend(protoProps) { if (def.collections) { _.each(def.collections, function (constructor, name) { child.prototype._collections[name] = constructor; + createCollectionProperty(child.prototype, name); }); } if (def.children) { _.each(def.children, function (constructor, name) { child.prototype._children[name] = constructor; + createChildProperty(child.prototype, name); }); } _.extend(child.prototype, _.omit(def, omitFromExtend)); diff --git a/test/full.js b/test/full.js index e6eaab2..3a07d1d 100644 --- a/test/full.js +++ b/test/full.js @@ -1096,6 +1096,97 @@ test('Should be able to declare derived properties that have nested deps', funct first.child.grandChild.name = 'something'; }); +test('collections will update data when set by assignment', function (t) { + var Child = State.extend({ + props: { + id: 'string' + }, + collections: { + nicknames: Collection + } + }); + + var StateObj = State.extend({ + props: { + id: 'string' + }, + children: { + child: Child + } + }); + + var data = { + id: 'parent', + child: { + id: 'child', + nicknames: [ + {name: 'munchkin'}, + {name: 'kiddo'} + ] + } + }; + + var obj = new StateObj(data); + + var originalCollection = obj.child.nicknames; + + // assigning array to collection should call through to .set + obj.child.nicknames = [{name: 'runt'}, {name: 'tot'}]; + t.ok(obj.child.nicknames instanceof Collection, 'and should still be instanceof'); + t.equal(obj.child.nicknames, originalCollection, 'and should be the same instance'); + t.equal(obj.child.nicknames.length, 4, 'and the collection should have been updated'); + + t.end(); +}); + +test('children will update data when set by assignment', function (t) { + var Child = State.extend({ + props: { + id: 'string' + } + }); + + var StateObj = State.extend({ + props: { + id: 'string' + }, + children: { + child: Child + } + }); + + var data = { + id: 'parent', + child: { + id: 'child', + grandChild: { + id: 'grandChild', + nicknames: [ + {name: 'munchkin'}, + {name: 'kiddo'} + ] + } + } + }; + + var obj = new StateObj(data); + + var originalChild = obj.child; + + // assigning object to child should call through to .set + obj.child = { + id: 'bar', + grandChild: { + nicknames: [{name: 'runt'}] + } + }; + t.ok(obj.child instanceof Child, 'and should still be instanceof'); + t.equal(obj.child, originalChild, 'and should be the same instance'); + t.equal(obj.child.id, 'bar', 'and the change should have been applied'); + + t.end(); +}); + test('`state` properties', function (t) { var Person = State.extend({ props: {