diff --git a/src/base.js b/src/base.js index 77fa8ef..1de9776 100644 --- a/src/base.js +++ b/src/base.js @@ -375,6 +375,13 @@ define(["sprout/util", "sprout/pubsub", "sprout/env"], function (_, pubsub, env) else { valueChanged = changeAttribute.call(this, attribute, name, oldValue, value); } + + if (valueChanged) { + // If the attribute should auto-destroy the value then do so + if (attribute.destroy && _.isObject(oldValue) && _.isFunction(oldValue.destroy)) { + oldValue.destroy(); + } + } } return valueChanged; diff --git a/src/collection.js b/src/collection.js index b723bc0..5d7032f 100644 --- a/src/collection.js +++ b/src/collection.js @@ -196,6 +196,15 @@ define(["sprout/util", "sprout/base", "sprout/model", "sprout/data", "sprout/dom */ destructor: function () { + // Destroy the items if necessary + if (this.destroyItems) { + _.each(this.items, function (item) { + if (item && _.isFunction(item.destroy)) { + item.destroy(); + } + }); + } + this.items = null; this.itemsById = null; this.itemsByCid = null; @@ -279,6 +288,14 @@ define(["sprout/util", "sprout/base", "sprout/model", "sprout/data", "sprout/dom * @type Boolean */ sparse: false, + + /** + * Whether or not the items should be destroyed when they are removed from the collection. + * An item will be destroyed when removed from the collection or when the collection is destroyed. + * @property + * @type Boolean + */ + destroyItems: false, /** * Returns an array of the collection's items for JSON stringification. The results of this function can be used as the argument for collection.parse. @@ -449,6 +466,11 @@ define(["sprout/util", "sprout/base", "sprout/model", "sprout/data", "sprout/dom delete this.itemsById[this.items[index].get("id")]; delete this.itemsByCid[item.get("cid")]; this.items.splice(index, 1); + + // Destroy the item if necessary. If this is a move modification then the items are being added back so don't destroy them. + if (this.destroyItems && !options.move && item && _.isFunction(item.destroy)) { + item.destroy(); + } } }, this); @@ -524,10 +546,15 @@ define(["sprout/util", "sprout/base", "sprout/model", "sprout/data", "sprout/dom */ reset: createListModifier("reset", function (items) { - // Detach event handlers + // Detach event handlers and destroy items this.each(function (item) { item.detachAfter("sync", afterItemSynced, this); - }); + + // Destroy the item if necessary + if (this.destroyItems && item && _.isFunction(item.destroy)) { + item.destroy(); + } + }, this); // Set the items array equal to a new empty array this.items = []; diff --git a/test/base-test.js b/test/base-test.js index b8ec341..176b506 100644 --- a/test/base-test.js +++ b/test/base-test.js @@ -960,6 +960,67 @@ TestCase("base", ["sprout/util", "sprout/base", "sprout/env"], function (_, Base a.set('children', ['a', 'b']); a.set('children', ['a', 'b']); }, + + "test base.attribute.destroy does destroy old value on change": function () { + expectAsserts(2); + + var Animal = Base.extend({ + attributes: { + head: { + destroy: true + } + } + }); + + var a = Animal.create(); + var head = Base.create(); + + a.set('head', head); + + assert('head is destroyed', !head.get('destroyed')); + a.set('head', null); + assert('head is not destroyed', head.get('destroyed')); + }, + + "test base.attribute.destroy does not call destroy on old value with no destroy method": function () { + expectAsserts(2); + + var Animal = Base.extend({ + attributes: { + head: { + destroy: true + } + } + }); + + var a = Animal.create(); + var head = {}; + + a.set('head', head); + + assertObject('head does not exist before', a.get('head')); + a.set('head', null); + assertObject('head does not exist after', head); + }, + + "test base.attribute.destroy is false does not destroy old value on change": function () { + expectAsserts(2); + + var Animal = Base.extend({ + attributes: { + head: {} + } + }); + + var a = Animal.create(); + var head = Base.create(); + + a.set('head', head); + + assert('head is destroyed before', !head.get('destroyed')); + a.set('head', null); + assert('head is destroyed after', !head.get('destroyed')); + }, "test base.get with no parameters": function () { diff --git a/test/collection-test.js b/test/collection-test.js index 6852172..1007097 100644 --- a/test/collection-test.js +++ b/test/collection-test.js @@ -2870,6 +2870,189 @@ TestCase("collection", ["sprout/util", "sprout/collection", "sprout/model"], fun assertSame('col[0] has incorrect index value after sort', 0, col.get('0.index')); assertSame('col[1] has incorrect index value after sort', 1, col.get('1.index')); assertSame('col[2] has incorrect index value after sort', 2, col.get('2.index')); + }, + + "test collection.destroyItems on collection.destroy": function () + { + var col = collection.create(); + col.destroyItems = true; + + var mod1 = model.create({ + id: 1 + }); + col.add(mod1); + + var mod2 = model.create({ + id: 2 + }); + col.add(mod2); + + var mod3 = model.create({ + id: 3 + }); + col.add(mod3); + + assert('mod1 is destroyed before', !mod1.get('destroyed')); + assert('mod2 is destroyed before', !mod2.get('destroyed')); + assert('mod3 is destroyed before', !mod3.get('destroyed')); + + col.destroy(); + + assert('mod1 is not destroyed after', mod1.get('destroyed')); + assert('mod2 is not destroyed after', mod2.get('destroyed')); + assert('mod3 is not destroyed after', mod3.get('destroyed')); + }, + + "test collection.destroyItems is false on collection.destroy": function () + { + var col = collection.create(); + + var mod1 = model.create({ + id: 1 + }); + col.add(mod1); + + var mod2 = model.create({ + id: 2 + }); + col.add(mod2); + + var mod3 = model.create({ + id: 3 + }); + col.add(mod3); + + assert('mod1 is destroyed before', !mod1.get('destroyed')); + assert('mod2 is destroyed before', !mod2.get('destroyed')); + assert('mod3 is destroyed before', !mod3.get('destroyed')); + + col.destroy(); + + assert('mod1 is destroyed after', !mod1.get('destroyed')); + assert('mod2 is destroyed after', !mod2.get('destroyed')); + assert('mod3 is destroyed after', !mod3.get('destroyed')); + }, + + "test collection.destroyItems on collection.reset": function () + { + var col = collection.create(); + col.destroyItems = true; + + var mod1 = model.create({ + id: 1 + }); + col.add(mod1); + + var mod2 = model.create({ + id: 2 + }); + col.add(mod2); + + var mod3 = model.create({ + id: 3 + }); + col.add(mod3); + + assert('mod1 is destroyed before', !mod1.get('destroyed')); + assert('mod2 is destroyed before', !mod2.get('destroyed')); + assert('mod3 is destroyed before', !mod3.get('destroyed')); + + col.reset(null); + + assert('mod1 is not destroyed after', mod1.get('destroyed')); + assert('mod2 is not destroyed after', mod2.get('destroyed')); + assert('mod3 is not destroyed after', mod3.get('destroyed')); + }, + + "test collection.destroyItems is false on collection.reset": function () + { + var col = collection.create(); + + var mod1 = model.create({ + id: 1 + }); + col.add(mod1); + + var mod2 = model.create({ + id: 2 + }); + col.add(mod2); + + var mod3 = model.create({ + id: 3 + }); + col.add(mod3); + + assert('mod1 is destroyed before', !mod1.get('destroyed')); + assert('mod2 is destroyed before', !mod2.get('destroyed')); + assert('mod3 is destroyed before', !mod3.get('destroyed')); + + col.reset(null); + + assert('mod1 is destroyed after', !mod1.get('destroyed')); + assert('mod2 is destroyed after', !mod2.get('destroyed')); + assert('mod3 is destroyed after', !mod3.get('destroyed')); + }, + + "test collection.destroyItems on collection.remove": function () + { + var col = collection.create(); + col.destroyItems = true; + + var mod1 = model.create({ + id: 1 + }); + col.add(mod1); + + var mod2 = model.create({ + id: 2 + }); + col.add(mod2); + + var mod3 = model.create({ + id: 3 + }); + col.add(mod3); + + assert('mod1 is destroyed before', !mod1.get('destroyed')); + assert('mod2 is destroyed before', !mod2.get('destroyed')); + assert('mod3 is destroyed before', !mod3.get('destroyed')); + + col.remove(mod2); + + assert('mod1 is destroyed after', !mod1.get('destroyed')); + assert('mod2 is not destroyed after', mod2.get('destroyed')); + assert('mod3 is destroyed after', !mod3.get('destroyed')); + }, + + "test collection.destroyItems is false on collection.remove": function () + { + var col = collection.create(); + + var mod1 = model.create({ + id: 1 + }); + col.add(mod1); + + var mod2 = model.create({ + id: 2 + }); + col.add(mod2); + + var mod3 = model.create({ + id: 3 + }); + col.add(mod3); + + assert('mod1 is destroyed before', !mod1.get('destroyed')); + assert('mod2 is destroyed before', !mod2.get('destroyed')); + assert('mod3 is destroyed before', !mod3.get('destroyed')); + + col.remove(mod2); + + assert('mod1 is destroyed after', !mod1.get('destroyed')); + assert('mod2 is destroyed after', !mod2.get('destroyed')); + assert('mod3 is destroyed after', !mod3.get('destroyed')); } }; }); \ No newline at end of file