diff --git a/build/base-base/base-base-debug.js b/build/base-base/base-base-debug.js index fd9fd201856..bc17667f230 100644 --- a/build/base-base/base-base-debug.js +++ b/build/base-base/base-base-debug.js @@ -191,7 +191,6 @@ YUI.add('base-base', function(Y) { * invoking initializers for the class hierarchy. * * @method init - * @final * @chainable * @param {Object} config Object with configuration property name/value pairs * @return {Base} A reference to this object @@ -279,7 +278,6 @@ YUI.add('base-base', function(Y) { *

* @method destroy * @return {Base} A reference to this object - * @final * @chainable */ destroy: function() { diff --git a/build/base-base/base-base.js b/build/base-base/base-base.js index 1e6ae8d782b..921b29467df 100644 --- a/build/base-base/base-base.js +++ b/build/base-base/base-base.js @@ -190,7 +190,6 @@ YUI.add('base-base', function(Y) { * invoking initializers for the class hierarchy. * * @method init - * @final * @chainable * @param {Object} config Object with configuration property name/value pairs * @return {Base} A reference to this object @@ -277,7 +276,6 @@ YUI.add('base-base', function(Y) { *

* @method destroy * @return {Base} A reference to this object - * @final * @chainable */ destroy: function() { diff --git a/build/widget-base/widget-base-debug.js b/build/widget-base/widget-base-debug.js index 9f327984e28..c544a7c7824 100644 --- a/build/widget-base/widget-base-debug.js +++ b/build/widget-base/widget-base-debug.js @@ -426,6 +426,29 @@ Y.extend(Widget, Y.Base, { this._destroyBox(); }, + /** + *

+ * Destroy lifecycle method. Fires the destroy + * event, prior to invoking destructors for the + * class hierarchy. + * + * Overrides Base's implementation, to support arguments to destroy + *

+ *

+ * Subscribers to the destroy + * event can invoke preventDefault on the event object, to prevent destruction + * from proceeding. + *

+ * @method destroy + * @param destroyAllNodes {Boolean} If true, all nodes contained within the Widget are removd and destroyed. Defaults to false due to potentially high run-time cost. + * @return {Widget} A reference to this object + * @chainable + */ + destroy: function(destroyAllNodes) { + this._destroyAllNodes = destroyAllNodes; + return Widget.superclass.destroy.apply(this); + }, + /** * Removes and destroys the widgets rendered boundingBox, contentBox, * and detaches bound UI events. @@ -437,6 +460,7 @@ Y.extend(Widget, Y.Base, { var boundingBox = this.get(BOUNDING_BOX), contentBox = this.get(CONTENT_BOX), + deep = this._destroyAllNodes, same = boundingBox && boundingBox.compareTo(contentBox); if (this.UI_EVENTS) { @@ -445,12 +469,17 @@ Y.extend(Widget, Y.Base, { this._unbindUI(boundingBox); - if (contentBox) { - contentBox.remove(TRUE); - } - - if (!same) { + if (deep) { + // Removes and destroys all child nodes. + boundingBox.empty(); boundingBox.remove(TRUE); + } else { + if (contentBox) { + contentBox.remove(TRUE); + } + if (!same) { + boundingBox.remove(TRUE); + } } }, diff --git a/build/widget-base/widget-base-min.js b/build/widget-base/widget-base-min.js index 5ea194bdbc3..2db9e48edd1 100644 --- a/build/widget-base/widget-base-min.js +++ b/build/widget-base/widget-base-min.js @@ -1 +1 @@ -YUI.add("widget-base",function(b){var g=b.Lang,r=b.Node,e=b.ClassNameManager,w=e.getClassName,M,s=b.cached(function(L){return L.substring(0,1).toUpperCase()+L.substring(1);}),F="content",P="visible",K="hidden",y="disabled",B="focused",d="width",A="height",N="boundingBox",v="contentBox",k="parentNode",m="ownerDocument",x="auto",j="srcNode",I="body",H="tabIndex",q="id",i="render",J="rendered",n="destroyed",a="strings",o="
",z="Change",p="loading",E="_uiSet",D="",G=function(){},u=true,O=false,t,l={},f=[P,y,A,d,B],C=b.UA.webkit,h={};function c(Q){var T=this,L,S,R=T.constructor;T._strs={};T._cssPrefix=R.CSS_PREFIX||w(R.NAME.toLowerCase());c.superclass.constructor.apply(T,arguments);S=T.get(i);if(S){if(S!==u){L=S;}T.render(L);}}c.NAME="widget";t=c.UI_SRC="ui";c.ATTRS=l;l[q]={valueFn:"_guid",writeOnce:u};l[J]={value:O,readOnly:u};l[N]={value:null,setter:"_setBB",writeOnce:u};l[v]={valueFn:"_defaultCB",setter:"_setCB",writeOnce:u};l[H]={value:null,validator:"_validTabIndex"};l[B]={value:O,readOnly:u};l[y]={value:O};l[P]={value:u};l[A]={value:D};l[d]={value:D};l[a]={value:{},setter:"_strSetter",getter:"_strGetter"};l[i]={value:O,writeOnce:u};c.CSS_PREFIX=w(c.NAME.toLowerCase());c.getClassName=function(){return w.apply(e,[c.CSS_PREFIX].concat(b.Array(arguments),true));};M=c.getClassName;c.getByNode=function(L){var R,Q=M();L=r.one(L);if(L){L=L.ancestor("."+Q,true);if(L){R=h[b.stamp(L,u)];}}return R||null;};b.extend(c,b.Base,{getClassName:function(){return w.apply(e,[this._cssPrefix].concat(b.Array(arguments),true));},initializer:function(L){h[b.stamp(this.get(N))]=this;if(this._applyParser){this._applyParser(L);}},destructor:function(){var L=this.get(N),Q=b.stamp(L,u);if(Q in h){delete h[Q];}this._destroyBox();},_destroyBox:function(){var Q=this.get(N),L=this.get(v),R=Q&&Q.compareTo(L);if(this.UI_EVENTS){this._destroyUIEvents();}this._unbindUI(Q);if(L){L.remove(u);}if(!R){Q.remove(u);}},render:function(L){if(!this.get(n)&&!this.get(J)){this.publish(i,{queuable:O,fireOnce:u,defaultTargetOnly:u,defaultFn:this._defRenderFn});this.fire(i,{parentNode:(L)?r.one(L):null});}return this;},_defRenderFn:function(L){this._parentNode=L.parentNode;this.renderer();this._set(J,u);this._removeLoadingClassNames();},renderer:function(){var L=this;L._renderUI();L.renderUI();L._bindUI();L.bindUI();L._syncUI();L.syncUI();},bindUI:G,renderUI:G,syncUI:G,hide:function(){return this.set(P,O);},show:function(){return this.set(P,u);},focus:function(){return this._set(B,u);},blur:function(){return this._set(B,O);},enable:function(){return this.set(y,O);},disable:function(){return this.set(y,u);},_uiSizeCB:function(L){this.get(v).toggleClass(M(F,"expanded"),L);},_renderBox:function(L){var T=this,Q=T.get(v),R=T.get(N),V=T.get(j),S=T.DEF_PARENT_NODE,U=(V&&V.get(m))||R.get(m)||Q.get(m);if(V&&!V.compareTo(Q)&&!Q.inDoc(U)){V.replace(Q);}if(!R.compareTo(Q.get(k))&&!R.compareTo(Q)){if(Q.inDoc(U)){Q.replace(R);}R.appendChild(Q);}L=L||(S&&r.one(S));if(L){L.appendChild(R);}else{if(!R.inDoc(U)){r.one(I).insert(R,0);}}},_setBB:function(L){return this._setBox(this.get(q),L,this.BOUNDING_TEMPLATE);},_setCB:function(L){return(this.CONTENT_TEMPLATE===null)?this.get(N):this._setBox(null,L,this.CONTENT_TEMPLATE);},_defaultCB:function(L){return this.get(j)||null;},_setBox:function(R,Q,L){Q=r.one(Q)||r.create(L);if(!Q.get(q)){Q.set(q,R||b.guid());}return Q;},_renderUI:function(){this._renderBoxClassNames();this._renderBox(this._parentNode);},_renderBoxClassNames:function(){var S=this._getClasses(),L,Q=this.get(N),R;Q.addClass(M());for(R=S.length-3;R>=0;R--){L=S[R];Q.addClass(L.CSS_PREFIX||w(L.NAME.toLowerCase()));}this.get(v).addClass(this.getClassName(F));},_removeLoadingClassNames:function(){var R=this.get(N),L=this.get(v),Q=this.getClassName(p),S=M(p);R.removeClass(S).removeClass(Q);L.removeClass(S).removeClass(Q);},_bindUI:function(){this._bindAttrUI(this._UI_ATTRS.BIND);this._bindDOM();},_unbindUI:function(L){this._unbindDOM(L);},_bindDOM:function(){var L=this.get(N).get(m);this._hDocFocus=L.on("focus",this._onDocFocus,this);if(C){this._hDocMouseDown=L.on("mousedown",this._onDocMouseDown,this);}},_unbindDOM:function(L){if(this._hDocFocus){this._hDocFocus.detach();}if(C&&this._hDocMouseDown){this._hDocMouseDown.detach();}},_syncUI:function(){this._syncAttrUI(this._UI_ATTRS.SYNC);},_uiSetHeight:function(L){this._uiSetDim(A,L);this._uiSizeCB((L!==D&&L!==x));},_uiSetWidth:function(L){this._uiSetDim(d,L);},_uiSetDim:function(L,Q){this.get(N).setStyle(L,g.isNumber(Q)?Q+this.DEF_UNIT:Q);},_uiSetVisible:function(L){this.get(N).toggleClass(this.getClassName(K),!L);},_uiSetDisabled:function(L){this.get(N).toggleClass(this.getClassName(y),L);},_uiSetFocused:function(R,Q){var L=this.get(N);L.toggleClass(this.getClassName(B),R);if(Q!==t){if(R){L.focus();}else{L.blur();}}},_uiSetTabIndex:function(Q){var L=this.get(N);if(g.isNumber(Q)){L.set(H,Q);}else{L.removeAttribute(H);}},_onDocMouseDown:function(L){if(this._domFocus){this._onDocFocus(L);}},_onDocFocus:function(L){this._domFocus=this.get(N).contains(L.target);this._set(B,this._domFocus,{src:t});},toString:function(){return this.name+"["+this.get(q)+"]";},DEF_UNIT:"px",DEF_PARENT_NODE:null,CONTENT_TEMPLATE:o,BOUNDING_TEMPLATE:o,_guid:function(){return b.guid();},_validTabIndex:function(L){return(g.isNumber(L)||g.isNull(L));},_bindAttrUI:function(Q){var R,L=Q.length;for(R=0;R=0;R--){L=S[R];Q.addClass(L.CSS_PREFIX||w(L.NAME.toLowerCase()));}this.get(v).addClass(this.getClassName(F));},_removeLoadingClassNames:function(){var R=this.get(N),L=this.get(v),Q=this.getClassName(p),S=M(p);R.removeClass(S).removeClass(Q);L.removeClass(S).removeClass(Q);},_bindUI:function(){this._bindAttrUI(this._UI_ATTRS.BIND);this._bindDOM();},_unbindUI:function(L){this._unbindDOM(L);},_bindDOM:function(){var L=this.get(N).get(m);this._hDocFocus=L.on("focus",this._onDocFocus,this);if(C){this._hDocMouseDown=L.on("mousedown",this._onDocMouseDown,this);}},_unbindDOM:function(L){if(this._hDocFocus){this._hDocFocus.detach();}if(C&&this._hDocMouseDown){this._hDocMouseDown.detach();}},_syncUI:function(){this._syncAttrUI(this._UI_ATTRS.SYNC);},_uiSetHeight:function(L){this._uiSetDim(A,L);this._uiSizeCB((L!==D&&L!==x));},_uiSetWidth:function(L){this._uiSetDim(d,L);},_uiSetDim:function(L,Q){this.get(N).setStyle(L,g.isNumber(Q)?Q+this.DEF_UNIT:Q);},_uiSetVisible:function(L){this.get(N).toggleClass(this.getClassName(K),!L);},_uiSetDisabled:function(L){this.get(N).toggleClass(this.getClassName(y),L);},_uiSetFocused:function(R,Q){var L=this.get(N);L.toggleClass(this.getClassName(B),R);if(Q!==t){if(R){L.focus();}else{L.blur();}}},_uiSetTabIndex:function(Q){var L=this.get(N);if(g.isNumber(Q)){L.set(H,Q);}else{L.removeAttribute(H);}},_onDocMouseDown:function(L){if(this._domFocus){this._onDocFocus(L);}},_onDocFocus:function(L){this._domFocus=this.get(N).contains(L.target);this._set(B,this._domFocus,{src:t});},toString:function(){return this.name+"["+this.get(q)+"]";},DEF_UNIT:"px",DEF_PARENT_NODE:null,CONTENT_TEMPLATE:o,BOUNDING_TEMPLATE:o,_guid:function(){return b.guid();},_validTabIndex:function(L){return(g.isNumber(L)||g.isNull(L));},_bindAttrUI:function(Q){var R,L=Q.length;for(R=0;R + * Destroy lifecycle method. Fires the destroy + * event, prior to invoking destructors for the + * class hierarchy. + * + * Overrides Base's implementation, to support arguments to destroy + *

+ *

+ * Subscribers to the destroy + * event can invoke preventDefault on the event object, to prevent destruction + * from proceeding. + *

+ * @method destroy + * @param destroyAllNodes {Boolean} If true, all nodes contained within the Widget are removd and destroyed. Defaults to false due to potentially high run-time cost. + * @return {Widget} A reference to this object + * @chainable + */ + destroy: function(destroyAllNodes) { + this._destroyAllNodes = destroyAllNodes; + return Widget.superclass.destroy.apply(this); + }, + /** * Removes and destroys the widgets rendered boundingBox, contentBox, * and detaches bound UI events. @@ -434,6 +457,7 @@ Y.extend(Widget, Y.Base, { var boundingBox = this.get(BOUNDING_BOX), contentBox = this.get(CONTENT_BOX), + deep = this._destroyAllNodes, same = boundingBox && boundingBox.compareTo(contentBox); if (this.UI_EVENTS) { @@ -442,12 +466,17 @@ Y.extend(Widget, Y.Base, { this._unbindUI(boundingBox); - if (contentBox) { - contentBox.remove(TRUE); - } - - if (!same) { + if (deep) { + // Removes and destroys all child nodes. + boundingBox.empty(); boundingBox.remove(TRUE); + } else { + if (contentBox) { + contentBox.remove(TRUE); + } + if (!same) { + boundingBox.remove(TRUE); + } } }, diff --git a/src/base/js/Base.js b/src/base/js/Base.js index 4bfc080586d..df07a68cbf2 100644 --- a/src/base/js/Base.js +++ b/src/base/js/Base.js @@ -189,7 +189,6 @@ * invoking initializers for the class hierarchy. * * @method init - * @final * @chainable * @param {Object} config Object with configuration property name/value pairs * @return {Base} A reference to this object @@ -277,7 +276,6 @@ *

* @method destroy * @return {Base} A reference to this object - * @final * @chainable */ destroy: function() { diff --git a/src/widget/HISTORY.md b/src/widget/HISTORY.md index 94e06c6f6f9..9fd89acbeb2 100644 --- a/src/widget/HISTORY.md +++ b/src/widget/HISTORY.md @@ -14,6 +14,11 @@ Widget * Fixed UI_EVENTS invoking nested widget listeners more than once (also fixed regression to Parent-Child as a result of this change). + * Added destroy(true) support, to remove and destroy all Nodes contained + within a widget's boundingBox. This is useful for long-lived pages, + to limit the growth of Node cache. By default Widget only removes and + destroys the Nodes it references - the boundingBox and contentBox. + 3.3.0 ----- diff --git a/src/widget/js/Widget.js b/src/widget/js/Widget.js index b8cc0b051fb..9a53517e5bf 100644 --- a/src/widget/js/Widget.js +++ b/src/widget/js/Widget.js @@ -424,6 +424,29 @@ Y.extend(Widget, Y.Base, { this._destroyBox(); }, + /** + *

+ * Destroy lifecycle method. Fires the destroy + * event, prior to invoking destructors for the + * class hierarchy. + * + * Overrides Base's implementation, to support arguments to destroy + *

+ *

+ * Subscribers to the destroy + * event can invoke preventDefault on the event object, to prevent destruction + * from proceeding. + *

+ * @method destroy + * @param destroyAllNodes {Boolean} If true, all nodes contained within the Widget are removd and destroyed. Defaults to false due to potentially high run-time cost. + * @return {Widget} A reference to this object + * @chainable + */ + destroy: function(destroyAllNodes) { + this._destroyAllNodes = destroyAllNodes; + return Widget.superclass.destroy.apply(this); + }, + /** * Removes and destroys the widgets rendered boundingBox, contentBox, * and detaches bound UI events. @@ -435,6 +458,7 @@ Y.extend(Widget, Y.Base, { var boundingBox = this.get(BOUNDING_BOX), contentBox = this.get(CONTENT_BOX), + deep = this._destroyAllNodes, same = boundingBox && boundingBox.compareTo(contentBox); if (this.UI_EVENTS) { @@ -443,12 +467,17 @@ Y.extend(Widget, Y.Base, { this._unbindUI(boundingBox); - if (contentBox) { - contentBox.remove(TRUE); - } - - if (!same) { + if (deep) { + // Removes and destroys all child nodes. + boundingBox.empty(); boundingBox.remove(TRUE); + } else { + if (contentBox) { + contentBox.remove(TRUE); + } + if (!same) { + boundingBox.remove(TRUE); + } } }, diff --git a/src/widget/tests/widget.html b/src/widget/tests/widget.html index 17ad24d8a01..98210d43d84 100644 --- a/src/widget/tests/widget.html +++ b/src/widget/tests/widget.html @@ -97,6 +97,24 @@ Y.Assert.fail("w.destroy() on a rendered widget threw an exception" + e); } }, + + testRenderedDeepDestroy: function() { + var w = new Y.Widget({id:"foo"}).render(); + var nref = Y.Node.create('
Foo
'); + + w.get("contentBox").appendChild(nref); + + try { + w.destroy(true); + + Y.Assert.isNull(Y.Node.one("#foo"), "Bounding box still in DOM"); + Y.Assert.isNull(Y.Node.one("#deep"), "Deep content box still in DOM"); + Y.Assert.isNull(Y.Node.getDOMNode(nref), "Deep content still in Node cache"); + + } catch(e) { + Y.Assert.fail("w.destroy(true) on a rendered widget threw an exception" + e); + } + }, testUnrenderedDestroy: function() { var w = new Y.Widget(); @@ -121,6 +139,7 @@ var w = new MyWidget({ id:"foo" }); + w.render(); try { w.destroy(); @@ -128,7 +147,36 @@ } catch(e) { Y.Assert.fail("w.destroy() on a single box widget threw an exception" + e); } - } + }, + + testSingleBoxDeepDestroy: function() { + + function MyWidget() { + MyWidget.superclass.constructor.apply(this, arguments); + }; + MyWidget.NAME = "myWidget"; + + Y.extend(MyWidget, Y.Widget, { + CONTENT_TEMPLATE:null + }); + + var w = new MyWidget({ + id:"foo" + }); + w.render(); + + var nref = Y.Node.create('
Foo
'); + w.get("contentBox").appendChild(nref); + + try { + w.destroy(true); + Y.Assert.isNull(Y.Node.one("#foo"), "Bounding box still in DOM"); + Y.Assert.isNull(Y.Node.one("#deep_single"), "Deep content box still in DOM"); + Y.Assert.isNull(Y.Node.getDOMNode(nref), "Deep content still in Node cache"); + } catch(e) { + Y.Assert.fail("w.destroy(true) on a single box widget threw an exception" + e); + } + } })); suite.add(new Y.Test.Case({