引言
Dojo 工具包为程序员提供了很多功能丰富的控件,但是在实际应用中,很多时候程序员需要自定义控件来满足实际需求,如开发统一 UI 风格的控件库,开发具有通用逻辑组合的 Dojo 控件和更方便使用的 Dojo 控件库。自定义的控件可以在项目、团队中复用,从而可以充分提高开发效率和增加可维护性。
准备工作
在创建自定义控件之前需要就有以下知识:
了解 Dojo 和 Dojo 工具包
请参考:http://dojotoolkit.org/reference-guide/quickstart/
Dojo 开发环境的搭建
下载 Dojo 工具包,http://dojotoolkit.org/download/
调试工具安装,如 FireFox 浏览器下的 FireBug
开发和调试 Dojo 程序的基本步骤:
使用 djConfig 加载 Dojo,请参考:http://dojotoolkit.org/reference-guide/djConfig.html#djconfig
使用 dojo.require 加载 Dojo 控件,请参考:http://dojotoolkit.org/reference-guide/dojo/require.html#dojo-require
申明和使用 Dojo 控件,请参考:http://dojotoolkit.org/reference-guide/dojo/index.html
异常和调试,使用 try/catch 捕获异常,在 FireBug 中使用 console 进行日志记录和跟踪
创建自定义控件基础知识
在创建一个自定义控件时要使用到 Dojo 工具包的 dojo.provide、dojo.declare 函数和基础控件 Dijit._Templated、Dijit._widget,本节将介绍它们。
dojo.provide
dojo.provide 函数用于定义 Dojo 模块,dojo.provide 函数的定义方式为:
dojo.provide("模块名称");
Dojo 模块的名称在当前 Dojo 包下必须唯一,而且定义模块时模块名和模块的文件名称必须相同。
例如定义 my.module 模块,在 my/module.js 模块文件中代码如下:
清单 1. 使用 dojo.provide 定义 Dojo 模块
<script type="text/javascript"> dojo.provide("my.module"); dojo.require("dojo.io.script"); my.module.name = "my module"; </script>
dojo.declare
dojo.declare 函数使用类似面向对象的方式定义 JavaScript 类,但是它不是面向对象的。
表 1. dojo.declare 参数
父类 null 没有父类 Object 一个父类
Object[] 多个父类
在 dojo.declare 中可以定义 constructor 函数用于申明类初始化时需要执行的代码,这很类似于 Java 类的构造方法。
下面是一个使用 dojo.declare 定义类和使用定义类的简单实例:
清单 2. 使用 dojo.declare 定义类和使用类
dojo.declare("my.FirstSample", null, { name: null, constructor: function(args){ dojo.safeMixin(this, args); }, setName: function(name){ this.name = name; } }); var sample1= new my.FirstSample({ name:"Jack"}); // 创建对象 console.log(sample1.name); sample1.setName("Mark"); // 调用对象函数 console.log(sample1.name);
函数定义方式为,“函数名:function( 参数 ){ 函数体 }”,如 my.FirstSample 中定义的函数
setName: function(name){ this.name = name; }使用 dojo.declare 定义继承
dojo.declare 可以定义当前类继承的父类,与面向对象不同的是可以是继承多个父类。
在默认情况下,父类的构造函数会在子类的构造函数之前执行。
如果在子类中覆写了父类的方法,可用 this.inherited(arguments) 来调用父类的同名方法。
清单 3. 使用 dojo.declare 定义继承示例
dojo.declare("A", null, { constructor: function() { console.debug ("mixing in A"); } }); dojo.declare("B", null, { constructor: function() { console.debug("mixing in B"); }, kind: "type b", run: function() { } }); dojo.declare("C", [A, B,], { constructor: function() { console.debug("A blizzard with " + this.kind + " M and Ms and " ); } run: function() { // call base class run this.inherited(); // now do something else } });
dijit._Templated 根据指定的 HTML 模板来创建 Widget 的 DOM 树,它可以作为创建 Widget 时的辅助。
指定 Widget 对应的 HTML 模板的方法有以下两种:
清单 4. 在 templateString 属性中申明 DOM 对象字符串
dojo.declare("MyWidget", [dijit._Widget, dijit._Templated], { templateString: "<div>hello world</div>" });
清单 5. 在 templateString 属性中引用创建好的 HTML 文件
dojo.declare("MyWidget", [dijit._Widget, dijit._Templated], { templateString: dojo.cache("myNameSpace", "templates/MyWidget.html"), });
在 HTML 文件中对象的唯一标示为 ID,在 Widget 的 HTML 模板中,可以用 dojoAttachPoint 属性来标示 DOM 对象,在 Widget 对象中可以直接用 this.${dojoAttachPoint} 来操作 DOM 对象,如清单 6 中的定义,可用 this.rootNode 来引用外层 div 对象,如:
清单 6. HTML 模板文件中的声明
<div dojoAttachPoint="rootNode"> <div dojoAttachPoint="chartNode"> </div > <div dojoAttachPoint="legendHolder"> </div></div>
dijit._ widget
dijit._widget 是所有 Widget 的基础类,在 Widget 的生命周期中重要的方法如下:
1 ) constructor 方法
创建 Widget 时首先执行的方法,此时你的 Widget 中变量还没有被赋值所以最好不要此时操作 Widget 的变量;
2 ) postMixInProperties 方法
在 DOM 对象创建前和 render 发生前执行,此时你的 Widget 的变量已经赋值,如果你想在控件的 DOM 对象创建前对 Widget 的变量进行处理,可以在子类中覆写这个方法;
3 ) postCreate 方法
在 render 之后,但是子 Widget 还没有创建时执行。此时你可以操作当前 Widget 的 DOM 对象,所以大部分可以初始化操作可以在这处理,如绑定事件和设置 CSS 属性。
注意:此时 Widget 可能还没有放到 DOM 树中,因此在此函数中不要处理 DOM 对象的大小。
4 ) startup 方法
在所有的子 Widget 创建完成和 parsing 完成后执行;
5 ) destroy 方法
Widget 销毁前执行。
Widget 事件管理
Widget 可以对来自于 DOM 节点或对象的外部事件做出反应。这类事件可通过使用 Widget 的 connect 方法被手动连接,该方法如下(非常类似于 dojo.connect 方法):
connect: function(/*Object|null*/ obj, /*String*/ event, /*String|Function*/ method);
如果在 Widget 生命周期内事件连接不需再要,可以调用 disconnect 方法来手动断开连接 .
Widget 的事件定制可以用 Widget 的 subscribe 方法来实现,改方法如下(类似于 dojo.subscribe 方法):
subscribe:function(/*String*/ topic,/*String|Function*/ method);
如果在 Widget 的生命周期内事件定制不需要,可以调用 unsubscribe 来取消定制。
注意 :
1)在自定义的 Widget 中,要进行初始化操作时覆写 postCreate 方法。
2)在进行 Widget 销毁前处理时,最好不好覆盖父类的 destroy,因为 _widget 类的 destroy 方法会自动销毁 Widget 及子 Widget 相关的资源,可以复写 uninitialize 方法来销毁需要手动销毁的资源,在 destroy 时会调用 uninitialize 方法。
3)在使用 Widget 时,如果需要绑定事件或者定制事件时可以用 Widget 的 connect 和 subscribe 方法,在 Widget 销毁时会销毁相关的 connect 和 subscribe 句柄。如果需要手动销毁事件句柄可用 disconnect 和 unsubscribe 方法。
4)对 Widget 的属性操作方法有:
赋值:
赋值方式一,在构造方法中赋值, 如:var fSmaple = new my.FirstSample({ name:"Jack"}); fSmaple 对象的 name 属性将在创建对象时被赋值;
赋值方式二,创建完对象后赋值,attr("属性名",“属性值”),如:fSmaple.attr("name","Jack");dojo1.5 及以上版本推荐使用 widget.set('property', 'value');
取值:
获取 Widget 属性值用 attr("属性名"),如:fSmaple.attr("name");
Dojo1.5 及以上版本推荐使用 widget.get('property');
定义自定义控件步骤
根据第二节的介绍,本节将以 Radio Widget 为例介绍创建一个自定义控件的步骤。在 Dojo 库中有 dijit.form.RadioButton 这个控件,但是使用起来比较麻烦,下面是一个改进的自定义 RadioButton 的创建过程。
第一步,创建模板文件 RadioGroup.html
使用 dojoAttachPoint 属性定义 DOM 对象的唯一 ID,文件代码如下:
清单 7. RadioGroup 模板文件代码
<div dojoAttachPoint="myRatioNode"></div>
第二步,创建 RadioGroup.js 文件,创建步骤为:
1 引用 Dojo 工具包控件:
清单 8. 引用 Dojo 工具包控件
dojo.provide("health.RadioGroup"); dojo.require("dijit._Widget"); dojo.require("dijit._Templated"); dojo.require("dijit.form.CheckBox");
2 定义 health.RadioGroup 类
使用 dojo.declare 定义 health.RadioGroup 类,继承 dijit._Widget 和 dijit._Templated
清单 9. health.RadioGroup 定义语句
dojo.declare("health.RadioGroup", [dijit._Widget, dijit._Templated]
3 绑定模板文件
使用 dojo.cache 绑定第一步中定义的模板文件
清单 10. 绑定模板文件
templateString: dojo.cache("health", "templates/RadioGroup.html") |-------10--------20--------30--------40--------50--------60--------70--------80--------9| |-------- XML error: The previous line is longer than the max of 90 characters ---------|
4 定义成员变量
定义变量时需要对变量进行初始化,如果无初始化值给变量赋值 null,格式如下:
清单 11. 定义成员变量
onChangeCallback : null, // 当前 Widget 的成员变量 id: null, relativeChart: null, relativeChartGroup[]:null, name: "testName", // 在定义变量时可以进行赋值
5 定义初始化函数
覆写构造函数 constructor(根据需要覆写);
覆写初始化函数 postCreate(根据需要覆写);
代码如下:
清单 12. 定义构造函数和初始化函数
constructor: function(params) {//构造函数 this.relativeChartGroup = []; }, postMixInProperties: function(){ if(this.name==null){ //在此时可以获取到变量的值 this.name = "testName"; } }, postCreate: function(arguments){ console.debug("myRatioNode==",this.myRatioNode); },
6 定义添加 Radio 选项函数
定义函数的格式为 :"函数名 : function( 参数 1, 参数 2, 参数 3,...)"。
清单 13. 添加 Radio 选项函数
addItem: function(value,lableText,checked){ if(checked == null){ checked = false; } console.debug("checked==",checked); console.debug("value==",value); console.debug("name==",this.name); var self = this; var radioOne = new dijit.form.RadioButton({ checked: checked, value: value, name: self.id, onChange: function(checked){ if(checked){ self.onChangeCallback( this.value); } } }); this.domNode.appendChild(radioOne.domNode); var label = document.createElement("label"); label.innerHTML = lableText; this.domNode.appendChild(label); } });注意:在函数中引用 widget 的变量或者调用其他函数时要用"this. 变量名 / 函数名"。
7 覆写 uninitialize 方法
在 uninitialize 中释放和销毁相关资源。
清单 14. Uninitialize 方法
uninitialize: function(arguments){ If(this. relativeChart) this. relativeChart.destroy(); },应用自定义的控件
第一步,引入自定义控件
在应用中引入自定义控件的方式有两种:
1) 将自定义的控件打包到 Dojo 包中
请参考:http://www.ibm.com/developerworks/cn/web/0912_shenjc_dojobuild/
2) 用 dojo.registerModulePath 注册自定义模块;
用 dojo.require 引用自定义控件。
例如,在第 3 节中创建的 Widget 可以用如下方式引用
清单 15. 引入自定义控件
dojo.registerModulePath("health","[path]/health"); // 注册自定义模块 dojo.require("health.RadioGroup"); // 引用自定义控件第二步,使用自定义控件
1)用 new 创建自定义控件实例;
2)在创建实例时用“属性名:属性值”初始化实例变量;
3)在创建完自定义控件实例后调用对象的方法。
使用自定义控件代码为:
清单 16. 创建自定义控件
var rateSelectRadio = new health.RadioGroup({ id:"rate_radio", relativeChart:chart }); rateSelectRadio.addItem("weekly","周基准",true); rateSelectRadio.addItem("monthly","月基准",false);图 1:Radio Group 控件代码效果
应用自定义控件的创建方法的好处
在未使用自定义控件前,要绘制一个如图 1 效果的 Radio Group 代码如下:
清单 17. 未使用自定义控件时创建 Radio Group 的代码
var radioContainerNode= document.createElement("div"); var radioOne = new dijit.form.RadioButton({ checked: checked, value: "weekly", name: "rate_radio", onChange: function(checked){ if(checked){ doChangeRate( this.value); }}}); this.domNode.appendChild(radioOne.domNode); var label = document.createElement("label"); label.innerHTML = "周基准"; radioContainerNode.appendChild(label); var radioOne = new dijit.form.RadioButton({ checked: checked, value: "monthly", name: "rate_radio", onChange: function(checked){ if(checked){ doChangeRate( this.value); }}}); this.domNode.appendChild(radioOne.domNode); var label = document.createElement("label"); label.innerHTML = "月基准"; radioContainerNode.appendChild(label);对比清单 16 和清单 17,可见使用自定义控件减少了开发工作。
结束语
使用自定义控件,可以充分复用 Dojo 代码,降低代码冗余,提高效率。建议大家在实际应用中,充分利用 Dojo 这一特征,写出干净漂亮的代码。
转载自https://www.oschina.net/question/129540_28352