DOJO组件生命周期the life cycle of dojo widget

Email:longsu2010 at yeah dot net

文章由来

在使用dojo过程中对于组件生命周期一直不是特别清楚,官方文档写的也不够恰当,所以特地研究了一下,做了公司内部培训,这就是为什么有了这篇文章。本文主要通过读DOJO的源码以及源码中的注释来了解DOJO组件的生命周期。今天分享给大家,供大家参考,不当之处欢迎大家指教。


1.组件生命周期预览

dojo组件生命周期比较重要的是以下几个部分

调用constructor方法

将创建组件时指定的参数mixin到组件实例中执行postMixInProperties方法执行buildRendering方法(创建组件的DOM对象)复制组件对象中的属性到DOM对象中执行postCreate方法执行startup方法组件销毁时执行destroy方法注:constructor方法在new后者parse时调用。startup方法可由外层组件或者placeAt或者addChild再或者手动调用。其他的方法均是在组件的create方法中调用。


2.各方法作用及调用时机

2.1constructor

创建一个组件对象时调用,可以做一些变量初始化.值得注意的是此时传递给组件的变量还没有mixin到组件对象中。

2.2postMixInProperties

在执行postMixInProperties方法之前传递给组件对象的属性已经mixin到组件对象中,可以在create方法中找到如下代码:

// mix in our passed parameters
if (params) {
	this.params = params;
	lang.mixin(this,params);
}
this.postMixInProperties();

但是此时组件的DOM对象还没有创建,如果想在DOM对象创建之前修改组件对象的属性在这个方法中做是适合的。但是值得注意的一点在执行完buildRendering之后且在执行postCreate方法之前会执行_applyAttributes方法,而_applyAttributes方法中会将创建组件对象时传递的参数再一次覆盖到组件对象中(在执行postMixinProperties方法前已经覆盖过一次),所以在执行_applyAttributes之前做的相关属性修改都会失效。

//create方法中片段
this.buildRendering();
var deleteSrcNodeRef;
if (this.domNode) {
	this._applyAttributes();
	var source = this.srcNodeRef;
	if (source && source.parentNode && this.domNode !== source) {
		source.parentNode.replaceChild(this.domNode,source);
		deleteSrcNodeRef = true;
	}
	this.domNode.setAttribute("widgetId",this.id);
}
this.postCreate();
//_applyAttributes代码片段
for (var param in this.params) {
	this.set(param,this.params[param]);
}

注意:虽然在postMixinProperties方法执行前已经将创建组件时配置对象中的属性mixin到组件对象中,但是对这些属性进行更改是不恰当的,dojo会在执行完成postMixinProperties后做第二次属性覆盖。所以建议在postCreate中进行修改。

2.3buildRendering

这个方法的作用是创建组件的DOM对象,并将DOM对象的指针保存在this.domNode中。先来看一下执行完postMixInProperties方法且在执行buildRendering方法之前做了什么:

1)如果组件对象没有id则根据组件类名字(带命名空间)生成组件id。生成规则是将类名字中的点(.)替换为下划线(_),并根据当前实例是组件类的第几个实例生成相应的下标,id类似于dojox_slickgrid_slickgrid_0。如果声明组件时指定了DOMid则组件的id使用DOMidid优先级规则为创建组件时指定的id>组件创建锚点的domid>自动生成的id。

2)将组件注册到dijit/registry模块中(registry.add(this);),这就是为什么能用registry.byId('组件id')获取到组件。

言归正传,接下来接着说buildRenderingbuildRendering创建DOM对象分以下几种情况:

1)有模板。一般来说在有模板的组件都会mixin_TemplatedMixin模块,该模块重写了buildRendering方法,重写方法利用模板生成DOM对象。

2)没有模板,声明式创建或者程序式创建指定了组件放置的DOM,如newGrid{},"grid-idv")中的第二参数即为放置组件的锚点。此时将声明组件的DOM对象作为this,domNode

3)没有模板,没有指定放置组件的DOM。此时生成一个DIVDOM对象作为this.domNode

注:buildRendering方法的另一个作用是将组件对象中的baseClass追加给this.domNode

模板中的data-dojo-attach-point='containerNode'有什么特殊用处?

当声明式创建有模板的组件时开始标签和结束标签中间包含的内容会自动包含进以'containerNode'为附着点的标签内,而且内容可以dojo组件。

在什么地方完成的上述操作?

_TemplatedMixinbuildRendering中。

模板中的containerNode存在其他内容会怎样?

如果模板中存在其他内容,声明式开始标签和结束标签之间的内容将追加在containerNode模板内容之后。

特别说明:

_TemplatedMixin模块中的buildRendering方法可以缓存模板生成的DOM节点,但有两个前提条件,如下:

a)组件对象的全局属性_skipNodeCache为false。

b)模板中没有模板变量。

另外如果用templatePath指定模板而不是用templateString指定模板,_TemplatedMixin方法会将templatePath指定的模板缓存在templateString中。

2.4postCreate

这是一个常用的组件扩展点,执行这个方法的时候this.domNode已经被创建了,这个时候做事件绑定是合适的。虽然组件的DOM对象已经创建了,但是做尺寸相关的计算是有风险的,原因是执行该方法的时候this.domNode并不一定被添加到文档树中,外联样式不会对该DOM生效。

在执行postCreate之前会做如下操作:

1)将组件对象中的属性复制到this.domNode对象中

2)如果是声明式创建或者程序式创建并且指定了组件要放置的DOM描点则将this.domNode添加到文档中,如果程序式不指定组件要放置的DOM则不添加到文档中(这就是在postCreate中做尺寸计算存在风险的原因)。示例代码依次如下:

//声明式:
<div data-dojo-type="dijit.layout.ContentPane"></div>
//程序式指定组件要放置的DOM:
//js
new Grid({},'grid-div');
//html
<div id="grid-div"></div>
//程序式不指定组件要放置的DOM
//js
var panel = new ContentPane({});
panel.palceAt("xx-dom")
2.5startup

这个方法一般对于layout组件特别有用,看官方文档:

Ifyouneedtobesureparsingandcreationofanychildwidgetshascompleted,usestartup.ThisisoftenusedforlayoutwidgetslikeBorderContainer.IfthewidgetdoesJSsizing,thenstartup()shouldcallresize(),whichdoesthesizing.

startup方法的调用规则:

1)组件包含在其他容器组件之内(包括程序式和声明式,这也是为什么完全程序式创建的项目最外层布局组件一定要手动调用startup的原因),startup方法会由外层容器组件调用。可以在_WidgetBase_WidgetInTemplateMixin中的startup中窥视到这一点,如下:

//_WidgetBasestartup方法
startup : function () {
	// summary:
	//Processing after the DOM fragment is added to the document
	// description:
	//Called after a widget and its children have been created and added to the page,//and all related widgets have finished their create() cycle,up through postCreate().
	//This is useful for composite widgets that need to control or layout sub-widgets.
	//Many layout widgets can use this as a wiring phase.
	if (this._started) {
		return;
	}
	this._started = true;
	array.forEach(this.getChildren(),function (obj) {
		if (!obj._started && !obj._destroyed && lang.isFunction(obj.startup)) {
			obj.startup();
			obj._started = true;
		}
	});
}
//WidgetInTemplateMixin方法中的startup方法
startup : function () {
	array.forEach(this._startupWidgets,function (w) {
		if (w && !w._started && w.startup) {
			w.startup();
		}
	});
	this.inherited(arguments);
}

需要注意的是如下的写法widget组件是包含在容器组件ContentPane中的,所以最外层的ContentPane会调用内层的组件的startup。

<div data-dojo-type="dijit.layout.ContentPane">
    <div>
		<a>
			<div id="c2" data-dojo-type="dijit.layout.ContentPane">
				<div data-dojo-type="demos.slickgrid.widget"></div>
			</div>
		</a>
    </div>
</div>

2)组件不包含在任何容器组件之内(如,直接放在body内),此时startup方法会由parser调用(声明式)。

3)程序式创建不包含在其他组件之内的时候可以由以下几种方式调用:

A)手动调用。

B)将组件placeAt到指定DOM对象时placeAt会调用startup方法。但是这样情况是有条件的,首先是startup没有被调用过,其次是placeAt后组件有父组件(父组件必须先于子组件创建),再次父组件的startup已经调用过。

//placeAt中代码片段
// Start this iff it has a parent widget that's already started.
if (!this._started && (this.getParent() || {})._started) {
	this.startup();
}

C)容器组件调用自己的addChild方法将组件添加到自己之中是会调用子组件的startup

if (this._started && !widget._started) {
	widget.startup();
}

注:addChildplaceAt方法功能类似,addChild必须是容器组件,placeAt可以把组件放在任何DOM对象内。addChildplaceAt均可接收第二参数,即组件放在容器组件中的位置。

2.6非生命周期的重要方法resize

所有做js尺寸计算的组件都应该实现resize方法,官方文档如下:

AllwidgetsthatdoJSsizingshouldhaveamethodcalledresize(),thatlaysoutthewidget.Resize()shouldbecalledfromstartup()andwillalsobecalledbyparentwidgetslikedijit.layout.ContentPane.Theresizefunctionforwidgetslayoutwidgetsservestwopurposes:setthesizeofthewidgetmakethewidgetadjustthesizeofitschildrenresizechildrenrecursively

看一下_ContentPaneResizeMixin是怎么调用子组件的resize方法的,如下:

_layoutChildren : function () {
	if (this.doLayout) {
		this._checkIfSingleChild();
	}
	if (this._singleChild && this._singleChild.resize) {
		var cb = this._contentBox || domGeometry.getContentBox(this.containerNode);
		this._singleChild.resize({
			w : cb.w,h : cb.h
		});
	} else {
		array.forEach(this.getChildren(),function (widget) {
			if (widget.resize) {
				widget.resize();
			}
		});
	}
}
2.7维护继承链的重要方法inherited

在绝大部分时候是有必要在重写方法中调用父类中的方法,掉用的形式是this.inherited(arguments),比如_WidgetInTemplateMixin中的startup最后一句执行this.inherited(arguments)(见2.5)。

2.8程序式创建应用与生命周期及最佳实践

1、Creation。程序式创建应用的时候一般先创建外层容器,之后向容器中add子组件,最后调用最外层容器组件的starttup

注意:

A)startup方法仅仅在最外层子组件调用一次就好了。

B)如果可能,最后调用startup方法,即在子组件添加到容器组件后。

C)在调用startup方法前顶层组件必须已经添加到document中,这样节点才能正确的获取到尺寸。

D)最外层组件也许需要指定尺寸,里层组件通常不需要指定尺寸(bordercontainer的各区域除外),因为他们的尺寸可以被父组件侦测调整。

2、AddChildren

执行完startup之后仍然可以通过容器组件的addChild方法添加子组件,且addChild会调用子组件的startup

3、RemoveChildren

容器组件的removeChild方法用于解除容器组件与子组件的关系,这不是销毁子组件。

4、Destruction

销毁容器组件和及其子组件典型的方法是调用容器组件的destroyRecursize方法。


3.dijit/_WidgetBase类提供的其他功能

3.1set、getwatch

分别用于设置、获取组件属性和监听指定属性的变化,举例如下:

var w = new widget({},'widget');
w.startup();
w.watch('p',function (property,oldValue,newValue) {
	console.log(property + " -- " + oldValue + " -- " + newValue);
});
w.set("p",'new');

3.2emit和on

分别是在组件上发布事件和绑定事件,举例如下:

//发布事件
myWidget.emit("attrmodified-selectedChildWidget",{});
//绑定事件
myWidget.on("click",function () {
	//...
});

4.参考资料:

1.DOJO的源码以及源码中的注释

2.参考的官方文档主要为如下两篇:

http://dojotoolkit.org/reference-guide/1.8/quickstart/writingWidgets.html

http://dojotoolkit.org/reference-guide/1.8/dijit/_WidgetBase.html

3.参考CSDN上一篇文章:

http://www.jb51.cc/article/p-tyxrueds-tz.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


我有一个网格,可以根据更大的树结构编辑小块数据.为了更容易知道用户保存了什么,我希望当用户第一次看到网格时,网格处于不可编辑状态.当用户准备好后,他们可以单击编辑按钮,这将使网格的某些部分可编辑.然后,有一个保存或取消按钮可以保存更改或还原.在大多数情况下它是有效的.但
我即将开始开发一款教育性的视频游戏.我已经决定以一种我可以轻松打包为Web,Mobiles和可能的Standalone版本的方式来实现这一目标.我不想使用Flash.因此,我确信(无论如何我会听取建议)使用JavaScript和SVG.我正在对这个问题进行大量研究,但我很难把各个部分放在一起.我知道Raphae
我正在使用带有Grails2.3.9的Dojo1.9.DojoNumberTextBox小部件–我在表单中使用–将固定格式(JavaScript基本格式)的实数值(例如:12.56)设置为HTML表单输入字段(但根据浏览器区域设置显示/编辑它们,所以用户总是看到格式正确的数字).另一方面,Grails期望输入字段根据浏览器
1.引言鉴于个人需求的转变,本系列将记录自学arcgisapiforjavaScript的学习历程,本篇将从最开始的arcgisapiforjavaScript部署开始,个人声明:博文不在传道受业解惑,旨在方便日后复习查阅。由于是自学,文章中可能会出现一些纰漏,请留言指出,不必留有情面哦!2.下载ArcGISforDe
我正在阅读使用dojo’sdeclare进行类创建的语法.描述令人困惑:Thedeclarefunctionisdefinedinthedojo/_base/declaremodule.declareacceptsthreearguments:className,superClass,andproperties.ClassNameTheclassNameargumentrepresentsthenameofthec
我的团队由更多的java人员和JavaScript经验丰富组成.我知道这个问题曾多次被问到,但为了弄清楚我的事实,我需要澄清一些事情,因为我在客户端技术方面的经验非常有限.我们决定使用GWT而不是纯JavaScript框架构建我们的解决方案(假设有更多的Java经验).这些是支持我的决定的事实.>
路由dojo/framework/srcouting/README.mdcommitb682b06ace25eea86d190e56dd81042565b35ed1Dojo应用程序的路由路由FeaturesRoute配置路径参数RouterHistoryManagersHashHistoryStateHistoryMemoryHistoryOutletEventRouterContextInjectionOutl
请原谅我的无知,因为我对jquery并不熟悉.是否有dojo.connect()的等价物?我找到了这个解决方案:http:/hink-robot.com/2009/06/hitch-object-oriented-event-handlers-with-jquery/但是没有断开功能!你知道jquery的其他解决方案吗?有jquery.connect但这个插件在我的测试中不起作用.
与java类一样,在dojo里也可以定义constructor 构造函数,在创建一个实例时可以对需要的属性进行初始化。//定义一个类mqsy_yjvar mqsy_yj=declare(null,{     //thedefaultusername    username: "yanjun",          //theconstructor   
我一直在寻找一些最佳实践,并想知道Dojo是否具有框架特定的最佳实践,还是最好只使用通用的Javascript标准?特别是我主要是寻找一些功能和类评论的指导方针?解决方法:对于初学者来说,这是项目的风格指南:DojoStyleGuide
我有’05/17/2010’的价值我想通过使用dojo.date.locale将其作为“2010年5月17日”.我尝试过使用dojo.date.locale.parse,如下所示:x='05/17/2010'varx=dojo.date.locale.parse(x,{datePattern:"MM/dd/yyyy",selector:"date"});alert(x)这并没有给我所需的日期
我正在尝试创建一个包含函数的dojo类,这些函数又调用此类中的其他函数,如下所示:dojo.provide("my.drawing");dojo.declare("my.drawing",null,{constructor:function(/*Object*/args){dojo.safeMixin(this,args);this.container=args[0];
我知道你可以使用jQuery.noConflict为jQuery做这件事.有没有办法与Dojo做类似的事情?解决方法:我相信你可以.有关在页面上运行多个版本的Dojo,请参阅thispage.它很繁琐,但似乎是你正在寻找的东西.一般来说,Dojo和jQuery都非常小心,不会破坏彼此或其他任何人的变量名.
我有一个EnhancedGrid,用户经常使用复杂的过滤器.有没有办法允许用户保存或标记过滤器,以便将来可以轻松地重新应用它?我知道我可以通过编程方式设置过滤器,但我无法预测用户想要的过滤器.谢谢!编辑:自己做了一些进展…使用grid.getFilter()返回过滤器的JSON表示,然后使用json.strin
我有这个代码:dojo.declare("City",null,{constructor:function(cityid,cityinfo){}});dojo.declare("TPolyline",GPolyline,{constructor:function(points,color){},initialize:function(map){});应该是什
我遇到的问题是我的所有javascript错误似乎来自dojo.xd.js或子模块.我正在使用chrome调试器和许多dijit功能,如dijit.declaration和dojo.parser.这有点烦人,因为它很难找到简单的错误或滑倒.我希望我可以添加一个选项,允许我的调试器在我的非dojo代码中显示选项会发生的位置.我是
我正在使用DojoToolkit数字/解析函数来处理格式化和使用ICU模式语法解析字符串.有没有人知道有可能采取任意ICU模式字符串并以某种方式使用Dojo(或其他)库将其分解为它的部分(例如,对于数字模式,它可以被分解为小数位数,数千个分组等…).我希望这样做,而不需要让我的代码密切了
我有两个看似相关的问题,访问在不同的地方定义的javascript函数.我遇到的第一个问题是调用我在firgbug或safari控制台中定义的函数.我定义了一个名为getRed的函数,如下所示:functiongetRed(row,col){//dosomethingstuffandreturntheredvalueasa
我想添加一个在Ajax调用中指定的外部样式表.我已经找到了一种方法来使用jQuery(参见下面的示例),但是我需要使该方法适应dojoJavaScript框架.JQuery示例$('head').append('<linkrel="stylesheet"type="text/css"href="lightbox_stylesheet.css">');谢谢.解决方法:一旦你
我正在尝试使用dojo.connect将onMouseDown事件连接到图像,如:dojo.connect(dojo.byId("workpic"),"onMouseDown",workpicDown);functionworkpicDown(){alert("mousedown");}类似的代码几行后,我将onMouse*事件连接到dojo.body确实完全正常工作.但是当我点击图像时