From c9d6c3ee14747179a1038bc21055f2d6ccd492c4 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 7 May 2010 17:56:16 -0500 Subject: [PATCH] Add `Element.purge` for cleaning up event listeners and element storage keys on elements that will be removed from the page. Make `Element.update` perform similar cleanup automatically. (Andrew Dupont, Tobie Langel) --- CHANGELOG | 10 +++++---- src/dom/dom.js | 50 ++++++++++++++++++++++++++++++++++++++++--- test/unit/dom_test.js | 30 ++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dc415ca9b..804393a99 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,10 +1,12 @@ -Fix issue where `Element.Layout#get` would fail to interpret negative pixel values. (Sebastien Gruhier, Andrew Dupont) +* Add `Element.purge` for cleaning up event listeners and element storage keys on elements that will be removed from the page. Make `Element.update` perform similar cleanup automatically. (Andrew Dupont, Tobie Langel) -Fix bugs in layout.js. Add tests for `Element.Layout#toCSS`, `#toObject`, and `#toHash`. (RStankov, Andrew Dupont) +* Fix issue where `Element.Layout#get` would fail to interpret negative pixel values. (Sebastien Gruhier, Andrew Dupont) -Add `Element.Layout#toObject` and `Element.Layout.toHash`. (Andrew Dupont) +* Fix bugs in layout.js. Add tests for `Element.Layout#toCSS`, `#toObject`, and `#toHash`. (RStankov, Andrew Dupont) -Make `Element.Layout#toCSS` return camelized property names, as expected by `Element.setStyle`. [#1021 state:resolved] (njakobsen, Andrew Dupont) +* Add `Element.Layout#toObject` and `Element.Layout.toHash`. (Andrew Dupont) + +* Make `Element.Layout#toCSS` return camelized property names, as expected by `Element.setStyle`. [#1021 state:resolved] (njakobsen, Andrew Dupont) *1.7_rc1* (April 1, 2010) diff --git a/src/dom/dom.js b/src/dom/dom.js index 75958e6d4..55c74d21d 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -189,6 +189,19 @@ if (!Node.ELEMENT_NODE) { Element.idCounter = 1; Element.cache = { }; +// Performs cleanup on an element before it is removed from the page. +// See `Element#purge`. +function purgeElement(element) { + // Must go first because it relies on Element.Storage. + Element.stopObserving(element); + + var uid = element._prototypeUID; + if (uid) { + element._prototypeUID = void 0; + delete Element.Storage[uid]; + } +} + /** * mixin Element.Methods * @@ -449,6 +462,11 @@ Element.Methods = { * Note that this method allows seamless content update of table related * elements in Internet Explorer 6 and beyond. * + * Any nodes replaced with `Element.update` will first have event + * listeners unregistered and storage keys removed. This frees up memory + * and prevents leaks in certain versions of Internet Explorer. (See + * [[Element.purge]]). + * * ##### Examples * * language: html @@ -551,7 +569,14 @@ Element.Methods = { function update(element, content) { element = $(element); - + + // Purge the element's existing contents of all storage keys and + // event listeners, since said content will be replaced no matter + // what. + var descendants = Element.select(element, '*'), + i = descendants.length; + while (i--) purgeElement(descendants[i]); + if (content && content.toElement) content = content.toElement(); @@ -3715,8 +3740,8 @@ Element.addMethods({ uid = 0; } else { if (typeof element._prototypeUID === "undefined") - element._prototypeUID = [Element.Storage.UID++]; - uid = element._prototypeUID[0]; + element._prototypeUID = Element.Storage.UID++; + uid = element._prototypeUID; } if (!Element.Storage[uid]) @@ -3786,5 +3811,24 @@ Element.addMethods({ } } return Element.extend(clone); + }, + + /** + * Element.purge(@element) -> null + * + * Removes all event listeners and storage keys from an element. + * + * To be used just before removing an element from the page. + **/ + purge: function(element) { + if (!(element = $(element))) return; + purgeElement(element); + + var descendants = Element.select(element, '*'), + i = descendants.length; + + while (i--) purgeElement(descendants[i]); + + return null; } }); diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index 2b406083a..00f596d0d 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -1501,6 +1501,36 @@ new Test.Unit.Runner({ this.assert(deepClone.firstChild); this.assertEqual('SPAN', deepClone.firstChild.nodeName.toUpperCase()); this.assert(!deepClone.down('span')._prototypeUID); + }, + + testElementPurge: function() { + var element = new Element('div'); + element.store('foo', 'bar'); + + var uid = element._prototypeUID; + this.assert(uid in Element.Storage, "newly-created element's uid should exist in `Element.Storage`"); + + element.purge(); + + this.assert(!(uid in Element.Storage), "purged element's UID should no longer exist in `Element.Storage`"); + this.assert(!(Object.isNumber(element._prototypeUID)), "purged element's UID should no longer exist in `Element.Storage`"); + + // Should purge elements replaced via innerHTML. + var parent = new Element('div'); + var child = new Element('p').update('lorem ipsum'); + + parent.insert(child); + child.store('foo', 'bar'); + child.observe('test:event', function(event) { event.stop(); }); + var childUID = child._prototypeUID; + + parent.update(""); + + // At this point, `child` should have been purged. + this.assert(!(childUID in Element.Storage), "purged element's UID should no longer exist in `Element.Storage`"); + + var event = child.fire('test:event'); + this.assert(!event.stopped, "fired event should not have been stopped"); } });