从Dojo1.6到Dojo1.8(三)—— Parser,Dijit,DojoX

前面两篇文章介绍了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新的春天!

你可能感兴趣的:(parser)