前面两篇文章介绍了Dojo1.8的AMD以及Dojo core当中相关模块的变化。接下来我们介绍一下Dijit,Dojox这两个控件组件模块的变化。
Parser
在具体讲Dijit之前,我们先了解一下dojo/Parser模块,这个模块由原先dojo.parser转变而来。dojo对于控件的使用提供了编程式(programmatic)和声明式(declarative markup)两种方式。dojo/parser是用来翻译声明式的控件定义,并将它们渲染成控件的实例对象和控件相应的dom节点。
对于dojo/parser的调用方式如下:
require(["dojo/parser", "dojo/ready"], function(parser, ready){ ready(function(){ parser.parse(); }); });
尽管在dojoConfig中依然可以写parseOnLoad:true的配置,但是还是需要手动去require parser模块。所以代码段中的方式是推荐的方式。
在dojo/parser内部最大的变化是增强了对于符合HTML5规范的属性的支持。具体的举例如dojoType被改成data-dojo-type,对于控件属性的定义,也被统一放到data-dojo-props属性中。这种方式符合HTML5的属性定义规范。举例老的写法:
<button dojoType="dijit.form.Button" tabIndex=2
iconClass="checkmark">OK</button>
新的写法将是:
<button data-dojo-type="dijit/form/Button" tabIndex=2
data-dojo-props="iconClass: 'checkmark'">OK</button>
上面代码中的dijit/form/Button,替换了之前的dijit.form.Button,使用的是模块ID(MID),这也是我们之前在AMD的章节中讨论过的新写法。
这里需要注意的是,在使用某个MID之前,需要在require([...])调用中加载相应的模块,一般在<head>节点中,这样可以保证<body>中需要使用的控件都已经加载。
dijit widget
在1.8中,dijit包中的控件也遵循了新的模块方式的写法,即原先所有的全局的diji.*都被放到了各自相应的模块当中。如dijit.form.Button,现在为dijit/form/Button模块。
其实在dijit的这些变化当中,最先引起我注意的是dijit.byId()这个函数的变化。如今的dijit.byId()被移到了dijit/registry模块当中,相应的调用改成了registry.byId()。相应的dijit.byNode()也成了registry.byNode()。
下面我们具体介绍一下在dijit内部的一些机制和函数发生的变化,这些使我们在使用、扩展尤其是写自己的控件的时候需要注意的。
dojo/Stateful和dojo/Evented
在这里首先要引入在扩展或创建dijit时需要理解的两个比较重要的模块:dojo/Stateful和dojo/Evented。
dojo.Stateful提供了访问控件属性的统一接口:set()和get(),同时还提供了监测(watch)控件属性的能力。例如,你可以使用如下的代码:
require(["dijit/form/Button", "dojo/domReady!"], function(Button){ var button = new Button({ label: "A label" }, "someNode"); // Sets up a watch on button.label var handle = button.watch("label", function(attr, oldValue, newValue){ console.log("button." + attr + " changed from '" + oldValue + "' to '" + newValue + "'"); }); // Gets the current label var label = button.get("label"); console.log("button's current label: " + label); // This changes the value and should call the watch button.set("label", "A different label"); // This will stop watching button.label handle.unwatch(); button.set("label", "Even more different"); });
dojo/Evented的on()和emit()方法提供了事件监听和触发的功能。你可以有一下的写法:
define(["dojo/Evented", "dojo/_base/declare"], function(Evented, declare){ var MyComponent = declare([Evented], { startup: function(){ // once we are done with startup, fire the "ready" event this.emit("ready", {}); } }); component = new MyComponent(); component.on("ready", function(){ // this will be called when the "ready" event is emitted // ... }); component.startup(); });
on()方法被dijit引入用来做事件的绑定,后面会具体谈。
set(),get()
dojo1.6之前的版本,对于控件属性的获取使用的是getValue(),setValue(),或者attr()方法。而现在使用的是统一的get()和set()方法。如下:
//老的写法 myEditor.getValue() myTextBox.attr("value") myForm.setValue(...); //新的写法 myEditor.get("value") myTextBox.get("value") myForm.set("value", ...);
watch(), on()
1.6及之前版本我们只能通过dojo.connect/this.connect来监测某些事件的发生,而1.8可以以更加优雅的方式来监测某些属性的变化或者方法的调用。使用的方法是on()和watch().
//老的写法: dojo.connect(myForm, "onValidStateChange", function(){ ... }); dojo.connect(myButton, "onClick", clickCallback); //新的写法 myForm.watch("valid", function(name, oldVal, newVal){ console.log(myForm.id + ": " + name + " changed from " + oldVal + " to " + newVal); }); myButton.on("click", clickCallback);
基于模版的控件(Templated Widgets)
在1.6版本中的dijit._Templated被划分到了dijit/_TemplatedMixin和dijit/_WidgetsInTemplatedMixin两个模块。此外,dojoAttachPoint和dojoAttachEvent两个属性和事件的绑定也被改成了HTML5的形式:data-dojo-attach-point和data-dojo-attach-event。举例来说:
dojo.require("dojo.declare"); dojo.require("dijit._Widget"); dojo.require("dijit._Templated"); dojo.declare("SimpleTemplate", [dijit._Widget, dijit._Templated], { templateString: "<button><span dojoAttachPoint="label"></span></button>" });
新的写法:
require(["dojo/declare", "dijit/_WidgetBase", "dijit/_TemplatedMixin"], function(declare, _WidgetBase, _TemplatedMixin){ declare("SimpleTemplate", [_WidgetBase, _TemplatedMixin], { templateString: "<button><span data-dojo-attach-point="label"></span></button>" }); });
如果widget的template中包含其他的widget,需要使用_WidgetInTemplateMixin。
对于template文件的引用也发生了变化,不在支持templatePath,dojo.cache()也不再使用。
老的写法中对template的引用,会使用下面的写法:
templatePath: dojo.moduleUrl("templates", "myTemplate.html")
而在新的控件定义中:
define([..., "dojo/text!./templates/myTemplate.html", function(..., myTemplate){ ... templateString: myTemplate
另一个改变是控件中的waiRole和waiState属性不再支持,因为在现在的方式中可以更加直接的使用role和state属性。(查了一下,dojo的wai是跟accessbility resource相关的接口,大家可以Dijit Accessibility Resources这篇文章中去参考)。
this.connect(),this.subscribe(),this._supportingWidgets
这几个方法用来绑定控件的DOM元素事件或者某个内部方法被调用,_supportingWidgets用来注册创建或者改变过的内部控件。这些本来都可以由dojo的基本事件来完成,如dojo.connect(),dojo.subscribe(),这里以this的方式来调用,主要是将这些事件绑定记录在控件内部,从而在控件被销毁的时候,释放和销毁这些事件的绑定,以避免内存泄漏的发生。
在1.8中,这几个方法不再被使用,取而代之的是this.own()函数来记录所有的这些内部的事件和控件。而事件绑定的函数则使用dojo标准模块中的接口如dojo/on,dojo/aspect,dojo/tpoic等。this.own()可以被多次调用,每次可以包含多个事件绑定或者控件的创建。这些被this.own()注册的事件绑定和控件会在控件被销毁的时候一起被释放和销毁。下面是一个可能的使用场景:
this.own( // setup an event handler (automatically remove() when I'm destroyed) on(this.domNode, "click", function(){ ... }), // watch external object (automatically unwatch() when I'm destroyed) aStatefulObject.watch("x", function(name, oVal, nVal){ ... }), // create a supporting (internal) widget, to be destroyed when I'm destroyed new MySupportingWidget(...) );
DojoX
1.8中DojoX还是作为dojo包的一部分发布出来,但是到2.0的时候,DojoX的模块将被冲标准dojo包中移除。原先DojoX中的功能,有一部分将被移到Dojo和Dijit包中,还有一些将被放到另外的包当中。用户可以通过一些包管理工具从repository中获取。
关于让我欢喜让我忧的Dojo Grid,将由新一代的GridX替代。GridX的架构完全不同于DataGrid和EnhancedGrid,它采用模块化的方式组织起来,提供了更加简洁和直接的接口,更加灵活和精简。尽管我没有用过GridX,但希望它能够在功能和性能上都有更好的表现。
说实话,看到DojoX的这个变化我也有点吃惊,不过鉴于DojoX中的控件一直不是很稳定,而模块化轻量化也是现在前台框架发展的趋势,所以Dojo的这一动作也在情理之中。只是现在正在使用DojoX的朋友门要尽早做打算了,如果要升级到2.0的话还是会有很多的工作要做。正在准备使用1.8的朋友也要有一定的超前仪式,尽量避免对DojoX的过度依赖。使用AMD加载而不再使用dojox全局变量是必须要遵循的原则。
总结
好了,到这里我的这篇《从Dojo1.6到Dojo1.8》的系列文章(或翻译)终于介绍完了,希望能对关注Dojo的同学有所帮助,也希望能看到大家的评论和意见。
让我们一起期待Dojo2.0的发布吧,相信那会是Dojo新的春天!