1、2006 年初,dojo 还是 0.22 的时候就很关注它的发展,可一直没有在实际项目中使用。一来是由于文档的缺少,而来是 dojo 的相关介绍总是让人望而生畏。到现在都如此,第一个 hello world 就搞了一大堆东西,比如 widget 组件,自定义的script 标签等,加上要引入什么 css 文件,djConfig、dojo.require 等等,让人很迷惑,这么复杂,到底 dojo 该怎么使用呢?我只是想把 dojo 当作一个普通的 js 类库,就像 prototype 那样?OK ,闲话少说,来看看如何使用 dojo。 第一步,引入 dojo.js dojo 的发行包里有 4
2、个子目录,要引入的文件是名叫 “dojo“的子目录里的 dojo.js。 假设你是这样的目录结构: project | +-dojo-lib | | | +-dijit | +-dojo | +-dojox | +-util | +-dojo_hello_world.html 开始使用 dojo 现在开始使用 dojo 的第一个函数 :dojo.byId ,dojo.byId 就等同于常用的document.getElement 。var username = dojo.byId(username).value alert(username); OK,是不是和普通的 js 库一样,没有任何玄机
3、? dojo.addOnLoad 现在我们想在 window.onload 里面处理一点东西,就像 Ext.onReady,这个东西在 dojo里叫做 dojo.addOnLoad。dojo.addOnLoad(function()var username = dojo.byId(username).valuealert(username););dojo.connect OK,window.onload 搞定了,那么如何监听普通的 dom 事件呢?没问题,强大的dojo.connect 出场。function sayHello(event)alert(“Hello“);dojo.addOnLo
4、ad(function()var btn = dojo.byId(hello);dojo.connect(btn,“onclick“,sayHello););是不是和 prototype 的 Event.observe($(btnAdd), “load“, doAdd)差不多? 用 prototype 时最烦的就是那个长长的 bindAsListener 了,使用 dojo.conncect,可以在第三个参数中指定当前的 scope:var name = “Mark“function sayHello()alert(“Hello “ + this.name);var obj = name: “
5、Karl“dojo.addOnLoad(function()var btn = dojo.byId(hello);dojo.connect(btn,“onclick“,obj,sayHello);/注意这行的第三个和第四个参数);OK,点击按钮,将输出:Hello Karl。这里 dojo.connect 的第三个参数变成了 scope,而 handler 函数是第四个,实际上 dojo.connect(btn,“onclick“,sayHello); 与dojo.connect(btn,“onclick“,null,sayHello); 相同。 更加复杂的用法这里不作介绍,写太多就越搞越复杂
6、了,后面再写文章详细介绍dojo.connect,这里只简单介绍如何绑定 DOM 事件。xmlhttp dojo.xhrGet OK,介绍了简单的 DOM 操作方法,接下来该到 Ajax 的传统项目-XmlHttp 了。在使用 xmlhttp 时,需要注意到编码的问题,要让 dojo 默认绑定为 utf-8 怎么办呢?很简单,只需要修改一下引入 dojo.js 时的标签: 多了一个 djConfig 属性,很简单,第一个 isDebug 是说是否打开 FireBug 的 Console,第二个是 xmlhttp 使用的编码。第二个才是重点,设置了就一劳永逸了。 这次我们要点击了 hello 按
7、钮后发出一个 xmlhttp 请求: function sayHello() dojo.xhrGet(url: “http:/localhost/hello/sayHello.jsp“,handleAs: “text“,load: function(responseText)alert(responseText);dojo.byId(“divHello“).innerHTML = responseText;,error: function(response)alert(“Error“););dojo.connect(btn,“onclick“,sayHello);看看,够不够一目了然? url
8、 就是 url ;handleAs 把获取的内容作为 text/html ;load 成功时的回调函数;error 失败时的回调函数 那如果要传入参数怎么办? var params = username:Mark,id:105dojo.xhrGet(url: “http:/localhost/hello/sayHello.jsp“,content:params,/.);注意那个 content 参数,你要传入的参数是个关联数组/object,dojo 会自动把参数解析出来,要使用 post 方法? dojo.xhrGet - dojo.xhrPost ,其他的还有,dojo.xhrPut、do
9、jo.xhrDelete。json那要是我想更换获取到的数据类型,比如 json?xml?修改 handleAs 即可,如: handleAs: “json“ dojo.xhrGet(url: “http:/localhost/hello/sayHello.jsp“,handleAs: “json“,load: function(json)alert(json.name)/.);handleAs: “json-comment-filtered“ 使用注释符号/*/把 json 数据包含起来,推荐使用 handleAs: “json-comment-optional“ 首先尝试使用 json-c
10、omment-filtered,如果执行错误,再使用普通的 json 格式解析 handleAs: “javascript“ dojo 尝试把服务器返回的数据当作 javascript 执行,并把结果作为参数传递给 load 函数 handleAs: “xml“ xml 对象。注意在 Mozilla 和 IE 中的 xml 是不同的,推荐使用 sarissa 至于 json 和 object 的转换等,在 http:/dojotoolkit.org/book/dojo-book-0-9/part-3-programmatic-dijit-and-dojo/other-miscellaneous
11、-function/converting-json 有一个表格,应该能找到你需要的。想要直接提交一个表单就这样: dojo.xhrGet(url: “http:/localhost/hello/sayHello.jsp“,form: dojo.byId(“form1“)/.);要解决 IE 下那个臭名昭著的缓存问题,就这样,preventCache 会帮你自动生成一个timestamp dojo.xhrGet(url: “http:/localhost/hello/sayHello.jsp“,preventCache: true/.);dojo.hitch scope/context 既然用到
12、了 xmlhttp,一个常见的问题就是回调函数的 scope/context。在prototype、mootools 里我们常用 Function.bind,在 dojo 中,做相同事情的东西叫做dojo.hitch。var handler = name:Mark,execute1: function()dojo.xhrGet(url: “http:/localhost/hello/sayHello.jsp“,handleAs: “text“,error: function(text)console.dir(this);alert(this.name);/输出 undefined,这里的 th
13、is 表示当前 io 参数/.);,load: function(text)alert(this.name);,execute2: function()dojo.xhrGet(url: “http:/localhost/hello/sayHello.jsp“,handleAs: “text“,error: dojo.hitch(this,“load“) /输出 Mark /error: dojo.hitch(this,this.load); /与上一句相同,知道为什么要用方法名字而不是引用了吧?省去了长长的一串 this.xxx/.);OK,基本的东西解决了,还有很多常用的函数没有介绍,比如:
14、dojo.query,dojo.forEach,dojo.marginBox ,dojo.contentBox 等等。这个就没事翻翻dojo.js.uncompressed.js 源代码,dojo 的文档是没啥好指望的了。面向对象,定义 Class 下一步我们看看 dojo 里如何定义 Class: dojo.declare(“Customer“,null,constructor:function(name)this.name = name;,say:function()alert(“Hello “ + this.name);,getDiscount:function()alert(“Disc
15、ount is 1.0“););var customer1 = new Customer(“Mark“);customer1.say();declare 有三个参数: 第一个 class 名字;第二个 父类的引用 ;第三个 . 构造函数的名字就叫做“construnctor“ 再来看看如何继承dojo.declare(“VIP“,Customer,getDiscount:function()alert(“Discount is 0.8“););var vip = new VIP(“Mark“);vip.say();vip.getDiscount();使用 this.inherited 方法调用
16、父类dojo.declare(“VIP“,Customer,getDiscount:function()this.inherited(arguments);/this.inherited(“getDiscount“,arguments););关于构造函数父类构造函数总是被自动调用的,所以看下面的例子: dojo.declare(“Customer“,null,constructor:function(name)this.name = name;alert(“base class“);,say:function()alert(this.name););dojo.declare(“VIP“,Cus
17、tomer,constructor:function(age)this.age = age;alert(“child class“);,say:function()alert(“name:“ + this.name);alert(“age:“ + this.age););var vip = new VIP(“123“);/1vip.say();/21 将打印出两条 alert 语句,先是父类的构造函数,再是子类的。 2 将输出“name: 123“ “age: 123“ 。个人认为,这个特性并不好,因为 javascript 这种弱类型的语言中,根本无法确定构造函数中的参数是传递给谁的,就比如
18、上面的语句执行后,name=“123“,age=“123“,那哪个才是正确的?这个问题在使用 dojo Grid 的 model 里就很麻烦,定义一个 model 得这样:new dojox.grid._data.Table(null,null,data);我要是想扩展这个Model,更麻烦,所有子类的构造函数都被父类给搞乱了。所以推荐的做法是使用关联数组作为构造函数的参数,就像 Python 里的关键字参数。constructor:function(args)var args = args | ;this.name = args.name;this.age = args.age;多继承,mi
19、xin 说到继承,多继承的问题又来了。dojo 支持多继承,准确地说,是 mixin。还记得dojo.declare 的第二个参数吗,就是表示父类的那个参数,这个参数可以是一个数组,数组的第一个元素作为声明的类的父类,其他的作为 mixin。子类自动获得父类和 mixin 的所有方法,后面的 mixin 的同名方法覆盖前面的方法。dojo.declare(“Customer“,null,say:function()alert(“Hello Customer“);,getDiscount:function()alert(“Discount in Customer“););dojo.declare
20、(“MixinClass“,null,say:function()alert(“Hello mixin“);,foo:function()alert(“foo in MixinClass“););dojo.declare(“VIP“,Customer,MixinClass,);var vip = new VIP();vip.getDiscount();vip.foo();vip.say();/输出“Hello MixinClass“其他的比较有用的函数就是 dojo.mixin 和 dojo.extend 了,顾名思义,一个是作用于对象实例,一个是用于扩展 class,翻文档和源码吧。 pac
21、kage 机制说完了 dojo 里的类继承机制,不得不说说 package 机制。 主要用到的有 dojo.require dojo.provide dojo.registerModulePath dojo.require dojo.require 就是引入相应路径文件下的 js 文件,现在已经有很多 library 这样做了。现在我们假设要用 project/dojo-lib/dojo/string.js dojo 中的顶层目录就是 dojo.js 所在目录的上一层,即“project/dojo-lib/“,而 dojo.js 放在project/dojo-lib/dojo/dojo.js
22、所以我们就这样 : dojo.require(“dojo.string“); 比如要引用其他目录下的: project/dojo-lib/dojox/dtl/_base.js,则这样:dojo.require(“dojox.dtl._base“); project/dojo-lib/dojox/grid/Grid.js dojo.require(“dojox.grid.Grid“); 说白了,就和 ruby 之类的 require 很相似。dojo.provide要自己编写一个 package 怎么办,那就利用 dojo.provide。比如要写在:project/dojo-lib/com/j
23、avaeye/fyting/Package1.js 那么在对应的 Package1.js 中第一行需要这样写: dojo.provide(“com.javaeye.fyting.Package1“); 类似 java 里的 package 声明,是吧?dojo.registerModulePath 那要是我写的 js 文件不想和 dojo 放在一起怎么办呢,那就用 registerModulePath。假设要放在: project/js/com/javaeye/fyting/Package2.js Package2.js 和上面的 Package1.js 一样的写法,不需要作特殊变化,就这样就
24、行: dojo.provide(“com.javaeye.fyting.Package2“); 在使用时,需要指名这个 Package2.js 所在的位置, dojo.registerModulePath(“com“,“/js/com“); 只需要注意这里的相对路径是相对 dojo.js 来的。我们假设所有以 com.javaeye 开头的 js 都放在一起,而 com.microsoft 的放在另外的地方,为了防止冲突,可以这样: dojo.registerModulePath(“com.javaeye“,“/js/com/javaeye“); dojo.registerModulePath(“com.microsoft“,“/javascript/com/microsoft“); 总得来说,package 机制是开发大型项目必须的,但是造成了调试困难,使用dojo.require 引入 js 出错时,根本不知道是什么原因,所以调试时最好手动引入 js,dojo 的test 也是这么搞的。还有 js 框架中的各种实现类继承的手法,也造成调试困难, dojo 还随地抛出个 Error,又缺少 java 那样的 error statck,根本不知道错误根源在哪儿。所以,期待js 原生地支持这些。