RIAEasy之基础包(二)——Destroyable重构
Destroyable重构是_rias.js中比较重要的一项内容,完成了dijit/Destroyable的重构,使之实现了Owner属性,并能够用于非dijit类。
rias.destroy = function(/*riasWidget|dijit|DOMNode|String*/ node, preserveDom){
var w = rias.by(node);
if(w){
if(!w._beingDestroyed){
if(!w._destroyed){
if(w.destroyRecursive){
w.destroyRecursive(preserveDom);
}else if(w.destroy){
w.destroy(preserveDom)
}else if(w.remove){
w.remove(preserveDom)
}
w._destroyed = true;
}
w._beingDestroyed = true;
}
}else{
w = rias.byDomId(node);
if(rias.isDomNode(w)){
dojo.destroy(w);
}
}
};
rias.removeChild = function(parent, child){
if(child._riaswParent == parent){
//console.debug(parent, child);
delete child._riaswParent;
}
if(!rias.isDijit(child)){
return;
}
有些控件在 removeChild() 的时候会 destroy(), 比如 StackContainer,所以不能 removeChild()
///重写 StackContainer.removeChild 和 destroyDescendants
if(rias.isDijit(parent)){
if(parent.removeChild){
parent.removeChild(child);
}else{
(parent.containerNode || parent.domNode).removeChild(child.domNode);
}
}else if(rias.isDomNode(parent)){
parent.removeChild(child.domNode);
}else{///其它,仅仅是 Object
///暂时没有什么可以处理的。
}
};
rias.orphan = function(handle){
var i;
///一般情况下,orphan() 之前有可能已经 destroy domNode,所以不能在这里 removeChilde。移到 destroy() 和 destroyDescendants() 中处理。
//if(handle._riaswParent){
// rias.removeChild(handle._riaswParent, handle);
//}
if(handle._riaswOwner){
///_riaswOwner 不是 Parent,不应该 removeChild
//if(rias.isDijit(handle)){
// rias.removeChild(handle._riaswOwner, handle);
//}
i = rias.indexOfByAttr(handle._riaswOwner._riaswChildren, handle, "_handle");
if(i >= 0){
handle._riaswOwner._riaswChildren[i]._remove.remove();
handle._riaswOwner._riaswChildren.splice(i, 1);
}
delete handle._riaswOwner;
}
return handle;
};
rias.own = function(parent, handle, position){
if(parent.own){
if(position){
return parent.own(position, handle);///[]
}else{
return parent.own(handle);///[]
}
}
return [handle];
};
if(rias.hostBrowser){
rias.setObject("rias.Destroyable", Destroyable);
}else{
Destroyable = rias.declare("rias.Destroyable", null, {});
}
Destroyable.extend({
destroy: function(/*Boolean*/ preserveDom){
var self = this;
self._beingDestroyed = true;
if(!self._destroying){
self._destroying = true;
rias.forEach(self._riaswChildren, function(handle){
handle = handle._handle;
if(handle._riaswOwner && handle._riaswOwner != self){
rias.removeChild(self, handle);
}else if(handle.destroy){
handle.destroy(preserveDom);
}
});
self.orphan();
if(riasPage && riasPage.removeWidget){
riasPage.removeWidget(self.id);
}
if(self._riaswModule && self._riaswModule[self._riaswIdOfModule]){
delete self._riaswModule[self._riaswIdOfModule];
}
}
self._destroyed = true;
self._destroying = false;
},
_setOwnerAttr: function(owner){
if(rias.isFunction(owner.isInstanceOf) && owner.isInstanceOf(Destroyable)){
this._set("_riaswOwner", owner);
owner.own(this);
}else{
throw new Error("The owner of " + owner + " is not isInstanceOf rias.Destroyable.");
}
},
_getOwnerAttr: function(){
return this._riaswOwner;
},
orphan: function(){
return rias.orphan(this);
},
own: function(position, handles){
var self = this,
i, _i,
hds,
cleanupMethods = [
"destroyRecursive",
"destroy",
"remove"
];
if(!self._riaswChildren){
self._riaswChildren = [];
}
if(!position || rias.isNumber(position)){
i = (position < self._riaswChildren.length ? position : self._riaswChildren.length);
hds = rias.toArray(arguments, 1);
}else if(position === "first"){
i = 0;
hds = rias.toArray(arguments, 1);
}else{
i = self._riaswChildren.length;
hds = arguments;
}
rias.forEach(hds, function(handle){
// When this.destroy() is called, destroy handle. Since I'm using aspect.before(),
// the handle will be destroyed before a subclass's destroy() method starts running, before it calls
// this.inherited() or even if it doesn't call this.inherited() at all. If that's an issue, make an
// onDestroy() method and connect to that instead.
var destroyMethodName;
var odh = {
_handle: handle,
_remove: rias.before(self, "destroy", function (preserveDom){
if(handle._riaswParent){
rias.removeChild(handle._riaswParent, handle);
}
if(destroyMethodName === "remove" || destroyMethodName === "close"){
rias.hitch(handle, self.orphan)();
}
handle[destroyMethodName](preserveDom);
})
};
if(rias.isRiasw(handle)){
rias.hitch(handle, self.orphan)();
handle._riaswOwner = self;
self._riaswChildren.splice(i, 0, odh);
i++;
}
// Callback for when handle is manually destroyed.
var hdhs = [];
function onManualDestroy(){
odh._remove.remove();
rias.forEach(hdhs, function(hdh){
hdh._remove.remove();
});
}
// Setup listeners for manual destroy of handle.
// Also computes destroyMethodName, used in listener above.
if(handle.then){
// Special path for Promises. Detect when Promise is resolved, rejected, or
// canceled (nb: cancelling a Promise causes it to be rejected).
destroyMethodName = "cancel";
handle.then(onManualDestroy, onManualDestroy);
}else{
// Path for other handles. Just use AOP to detect when handle is manually destroyed.
rias.forEach(cleanupMethods, function(cleanupMethod){
if(typeof handle[cleanupMethod] === "function"){
if(!destroyMethodName){
// Use first matching method name in above listener (prefer destroyRecursive() to destroy())
destroyMethodName = cleanupMethod;
}
hdhs.push({
_handle: handle,
_remove: rias.after(handle, cleanupMethod, onManualDestroy, true)
});
}
});
}
}, self);
return hds; // handle
}
});
从代码中可以看出,Destroyable增加了_riaswOwner和_riaswChildren两个属性,和orphan方法,同时扩展了原有的own方法。
可能很多同学会有疑问,这样做有什么好处呢?
首先需要说明的是,如果你只是在做HTML,或者你的应用里面不涉及运行期动态创建、销毁,那么的确可以沿用dojo原有的Destroyable而无需扩展,dijit会在destroy的时候正确地销毁。如果你准备做SPA,或者涉及到运行期动态创建、销毁,特别是一个widget还要移动其dom结构,比如drag/drop、dock,那么,问题来了:当某个widget在运行期dock到一个不是其创建者(Owner)的另一个dom节点上(Parent),而这个节点(Parent)在某个时刻要销毁,_WidgetBase会同时也销毁掉这个widget的domNode,那么原来引用这个widget的东西(Owner)可能会出现错误,因为widget的domNode被销毁了!
所以,解决的办法就是把Owner和Parent明确地标示出来,并分离,分别处理。在destroy的时候,我们只销毁属于自身创建的东西,即 Owner 是自己,对于Parent是自己,但Owner不是自己的,则只是做一个 removeChild 操作。
至于Parent相关部分是怎样的,将在后面解析,毕竟这里是共用包,不包含dom结构。