内容


使用 Dojo 的 Ajax 应用开发进阶教程,第 7 部分

Dojo 核心库深入介绍

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 使用 Dojo 的 Ajax 应用开发进阶教程,第 7 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:使用 Dojo 的 Ajax 应用开发进阶教程,第 7 部分

敬请期待该系列的后续内容。

Dojo 核心库构建于 Dojo 基本库之上,为 Ajax 应用的开发提供了更加丰富的功能。掌握 Dojo 核心库中包含的内容,可以开发人员减少代码量,而把工作精力集中在与业务逻辑相关的组件的实现上。Dojo 核心库中包含的内容也比较多,本文只是介绍其中一些比较重要或是复杂的部分,更多的内容请见 参考资料。首先从数据模型开始介绍。

数据模型

在传统的 Web 应用中,客户端部分所承担的职责很少,基本上只负责数据展现。应用所涉及的复杂数据模型的处理工作由服务器端代码来完成。而在 Ajax 应用中,客户端部分也需要处理一部分的业务逻辑,其中就包括数据模型的处理。复杂用户界面组件,如表格、树等的出现,也要求客户端能够处理组件后台复杂的数据模型。一般的 Ajax 应用都会使用复杂的 JSON 对象作为其客户端的数据模型,由应用自己来维护此模型。这样造成的问题是维护的代价过高,不同的模型之间无法方便的共享数据。Dojo 核心库中提供了 dojo.data模块用来解决这个问题。它定义了一些数据访问和操作的标准接口,以及相关的实现。数据消费者都使用同样的接口来访问和操作数据,提高了它们之间的互操作性。统一的接口也使得开发人员的代码可移植性更好。开发人员也不需要学习和理解很多私有的数据访问接口和协议。

dojo.data对数据的抽象非常简单。负责管理数据的是数据仓库(data store),它是条目(item)的集合。而条目则是属性(attribute)的集合。这个抽象在概念上类似于关系数据库中的表。关系数据库中的表是行的集合,而行是字段的集合。关键的不同在于,关系数据库中表的行是同构的,包含相同的字段;dojo.data中的条目可以是异构的,包含完全不同的属性。dojo.data的 API 都围绕这个抽象展开。其中包含了 4 类 API,分别是读(dojo.data.api.Read)、写(dojo.data.api.Write)、标识符(dojo.data.api.Identity)和通知(dojo.data.api.Notification)。读和写的 API 分别用来对数据仓库中的条目进行读取和更新操作。标识符 API 用来为数据仓库中的每个条目提供惟一的标识符,并可以通过此标识符来获取条目。通知 API 用来在数据仓库中的条目发生变化的时候得到通知,这些变化包括条目的创建、删除和属性的更新等。

限于篇幅,本文并不会详细介绍 dojo.data的全部 API 的细节,而是讨论一些需要注意的地方与最佳实践。在 dojo.data.api.Read接口中的 fetch()方法是异步执行的,需要传入回调方法。即便数据仓库中的条目都是可以从本地获取的,也是需要使用回调方法。从 API 的角度来说这是必须的,不过可能会造成一些代码编写时候的麻烦。通过与 dojo.Deferred配合起来使用,可以简化代码的编写。

dojo.data数据仓库作为用户界面组件后台的数据来源时,dojo.data.api.Notification API 非常有用。它可以用来实现典型的观察者设计模式。当数据仓库中的数据发生改变时,用户界面组件也需要相应的更新。使用 dojo.connect()连接到数据仓库的 onNew()onDelete()onSet()方法上,就可以在创建条目、删除条目和更新条目属性的时候得到通知。这里需要注意的是当一个条目被删除的时候,在 onDelete()方法中可以得到该条目的对象,但是该条目已经不属于原来的数据仓库了,不能再通过 getValue()等方法来获取条目中的属性。onSet()在每个属性更新的时候都会被调用。如果同时更新了条目的多个属性,需要注意 onSet()方法的重复调用。

Dojo 核心库提供了两个基于 JavaScript 对象的数据仓库的实现 dojo.data.ItemFileReadStoredojo.data.ItemFileWriteStore。这两个仓库可以作为服务器端返回的数据与 Dojo 用户界面组件之间的桥梁。很多 Dojo 用户界面组件,如表格、树和下拉列表等,都使用 Dojo 数据仓库作为其后台数据模型。一般的做法是从服务器端数据中创建出一个 dojo.data.ItemFileReadStore,提供给这些用户界面组件使用。dojo.data.ItemFileWriteStore还可以作为 JavaScript 数组的良好替代。Ajax 应用中通常会使用数组来维护内部的领域对象。使用数组在查找、更新和删除对象时不太方便,一般需要添加额外的代码。使用 dojo.data.ItemFileWriteStore可以很好解决这个问题。它提供的通知 API 的支持,也能帮助编写更加简洁的代码来处理对象的变化。在使用这两个数据仓库的时候,需要注意 JavaScript 对象中的数组类型的属性的存取方式。对于数组里面包含的多个元素,需要通过 getValues()方法才能读取出来。如果使用 getValue()方法的话,只能读取到数组的第一个元素。如果希望能够统一成使用 getValue()方法,则在该数组存放到数据仓库之前,需要把它转换成只有一个元素的新数组。代码清单 1 中给出了在数据仓库中的条目和 JavaScript 对象之间相互转换的示例。

清单 1. 在数据仓库中的条目和 JavaScript 对象之间相互转换
 var item = { 
    name : "Alex", 
    skills : ["JavaScript", "Java"] 
 }; 

 function addJsonToStore(store, json) { 
    for (var key in json) { 
        if (json.hasOwnProperty(key) && dojo.isArray(json[key]) { 
            json[key] = [json[key]]; 
        } 
    } 
    store.newItem(json); 
 } 

 function storeItemToJson(store, item) { 
    var attrs = store.getAttributes(item); 
    var result = {}; 
    dojo.forEach(attrs, function(attr) { 
        result[attr] = store.getValue(item, attr); 
    }); 
    return result; 
 }

代码清单 1 所示,addJsonToStore()方法对 JavaScript 对象中的数组进行了处理。在通过 storeItemToJson()方法把数据仓库中的条目转换成 JavaScript 对象的时候,就可以直接使用 getValue()方法而不会出现错误。

在介绍完 Dojo 核心库提供的数据模型之后,下面介绍与 I/O 请求相关的内容。

I/O 请求

在 Ajax 应用中最常见的 I/O 请求方式是使用 XMLHTTPRequest。Dojo 基本库中提供了对于 XMLHTTPRequest 请求的支持。XMLHTTPRequest 在大多数情况下能够满足应用的需求,但是也有其自身的局限性,如不能跨域访问资源和上传文件等。当 XMLHTTPRequest 不能满足需求的时候,就需要使用其它 I/O 请求方式。Dojo 核心库中提供了脚本和 iframe 两种额外的 I/O 请求方式。下面分别对这两种方式进行介绍。

脚本

XMLHTTPRequest 请求受到浏览器安全模型的限制,不能访问与当前页面不同的域上的资源。跨域的请求需要由服务器端的代理来完成。与 XMLHTTPRequest 不同的是,<script> 元素并没有这样的限制,其 src 属性可以指向任何域上的资源。浏览器会负责加载对应的 JavaScript 脚本并执行。这就提供了一种简便的方式来访问跨域的资源。不过这种方式只适用于传输的内容是 JavaScript 脚本或是 JSON 格式数据的情况。

典型的做法是使用 JSONP 协议来传输 JSON 数据。服务器端把 JSON 数据作为参数,封装在一个 JavaScript 方法的调用中。浏览器获取到这段 JavaScript 脚本并执行之后,JavaScript 方法会被调用,JSON 数据就被成功的传递给客户端应用。JavaScript 方法的名称由客户端来指定。在 JSONP 请求的 URL 中,一般会有一个查询参数用来指定此 JavaScript 方法的名称。该参数的名称由服务器端确定。关于 JSONP 协议的更多内容,见 参考资料

Dojo 核心库中包含的支持脚本 I/O 的 dojo.io.script模块可以很方便的使用 JSONP 协议。使用脚本只能发送 HTTP GET 请求。dojo.io.script.get()方法用来获取数据,其参数与 dojo.xhrGet()方法类似,也是一个包含一系列属性的 JavaScript 对象。在此 JavaScript 对象中,除了可以使用 dojo.xhrGet()中允许的属性之外,还可以使用 callbackParamNamejsonpcheckStringframeDoc等属性。属性 callbackParamNamejsonp的含义相同,都是表示 JSONP 请求中代表回调 JavaScript 方法名称的参数的名字。从 Dojo 1.4 开始,推荐使用属性 jsonp。属性 checkString表示的字符串用来检测脚本是否加载成功。一般来说,当脚本加载成功并执行之后,会创建一些新的变量。通过检测某个变量是否存在,就可以判断脚本是否加载成功。比如 checkString的值是 myData,那么会通过 eval("typeof(myData) != 'undefined'")来判断脚本是否加载成功。属性 frameDoc表示的是脚本 <script> 元素所添加到的文档对象。如果不指定则默认是当前页面的文档对象。dojo.io.script.get()的使用也比较简单,如 代码清单 2 所示。

清单 2. dojo.io.script.get() 的使用
 dojo.io.script.get({ 
    url : "http://search.yahooapis.com/WebSearchService/V1/webSearch?appid=YahooDemo& 
        query=ajax&output=json", 
    jsonp : "callback", 
    load : function(data) { 
        alert(data.ResultSet.Result[0].Title); 
    } 
 });

代码清单 2 中使用了雅虎的 Web 搜索服务,通过属性 jsonp指定了回调 JavaScript 方法的名称由参数 callback来指定。可以看到,dojo.io.script.get()的使用方式基本上和 dojo.xhrGet()相同。Dojo 为开发人员屏蔽了底层实现相关的细节。

iframe

除了脚本之外的另外一种 I/O 请求方式是使用 iframe。使用 iframe 与脚本一样,没有跨域访问的限制,而且除了 HTTP GET 请求之外,还可以发送 HTTP POST 请求。使用 iframe 的一个典型的场景就是进行文件上传。使用 XMLHTTPRequest 没有比较好的实现方式,一般需要依赖 Applet 或 Flash 等插件支持。如果不依赖浏览器插件的话,使用 iframe 是最好的选择,因为浏览器本身已经提供了文件选择框用来选择并上传文件。

Dojo 核心库提供的 iframe I/O 请求支持封装在模块 dojo.io.iframe中。其中最重要的就是 dojo.io.iframe.send()方法用来通过 iframe 发出请求。与 dojo.io.script.get()方法相同,该方法的参数也是一个 JavaScript 对象。需要注意的是属性 handleAs的值只能是 texthtmlxmljsonjavascript。对于 textjsonjavascript这三种结果返回格式来说,服务器端返回的必须是一个 HTML 文件。该 HTML 页面中必须包含一个 <textarea> 元素,<textarea> 中的内容才是真正的返回数据。如果应用中使用 Dojo 的 iframe 来传输数据的话,在服务器端的实现要格外注意这一点。使用 dojo.io.iframe进行文件上传的实现见 代码清单 3

清单 3. 使用 dojo.io.iframe 上传文件
 <form enctype="multipart/form-data" method="post" id="formNode"> 
    <input type="file" name="fileUpload"></input> 
    <input type="button" value="上传"></input> 
 </form>   

 dojo.io.iframe.send({ 
    url : "/upload", 
    form : "formNode", 
    load : function() { 
    }, 
    error : function() { 
    } 
 });

代码清单 3 中所示,在 HTML 代码中定义一个 <form>元素,其中包含一个文件选择框控件。dojo.io.iframe.send()方法会负责提交此表单来完成文件的上传。

在介绍完 Dojo 核心库提供的脚本和 iframe I/O 请求支持之后,下面介绍如何实现拖放操作。

拖放

在 Ajax 应用中,拖放是一个比较常见的操作。拖放操作最早的时候比较多的出现在桌面应用中,在传统的 Web 应用中比较少见。随着 Ajax 应用的流行,拖放操作越来越多的出现,可以提供与桌面应用类似的用户体验。在 Ajax 应用中实现拖放操作并不是一件容易的事情,需要考虑很多浏览器兼容性的问题。Dojo 核心库的 dojo.dnd模块提供了对拖放操作的良好支持。

dojo.dnd模块中包含了对两类拖放相关的操作的支持:一类是一般意义上的拖放,即拖拽页面上的一个元素并把它放到其它位置;另外一类是在页面上自由的移动某个元素。两类操作在实现上有所不同,下面会分别介绍。

拖放操作

从拖放操作本身来说,需要一个源和一个目标。源和目标分别是通过 dojo.dnd.Sourcedojo.dnd.Target类来表示的。从实现上来说,dojo.dnd.Target继承自 dojo.dnd.Source。如果同时是源和目标的话,应该使用 dojo.dnd.Source来表示。在拖放的源中可以包含多个能够被拖动的条目,需要对这些条目进行管理。dojo.dnd.Container表示的是包含能被拖动的条目的容器,用来实现对条目的管理。对于 dojo.dnd.Container中包含的条目,需要有一种方式允许用户进行选择,比如一次选中一个或多个条目。dojo.dnd.Selector继承自 dojo.dnd.Container,并添加了与选择条目相关的功能。dojo.dnd.Source类则是继承自 dojo.dnd.Selector的。当用户拖拽一个包含在 dojo.dnd.Source中的条目在页面上移动的时候,需要以直观的方式告知用户条目的当前位置。dojo.dnd.Avatar用来实现这样的功能。除了上面提到的四个类之外,dojo.dnd.Manager用来管理整个拖放的过程。

在 Ajax 应用中使用拖放操作的时候,首先需要根据应用场景定义清楚拖放的源和目标。这些操作要符合用户的使用习惯,以免影响用户体验。比如用拖放操作来改变一个列表中元素的顺序,或是拖放所选择的物品到购物车中,这些都是比较合理的拖放操作的场景。完成这一步之后,就可以从表示拖放源的 DOM 节点中创建 dojo.dnd.Source对象了。dojo.dnd.Source的构造方法有两个参数:第一个参数 node表示的是拖放源的 DOM 节点,第二个参数 params是一个包含配置项的 JavaScript 对象。对于拖放源中所包含的条目,有抽象和具体两种表示方式。抽象的表示方式是一个 JavaScript 对象,其中包含的内容由应用自己来定义。具体的表示方式则是 DOM 节点。创建 dojo.dnd.Source对象的时候,需要传入一个 JavaScript 方法用来从抽象的 JavaScript 对象中创建出具体的 DOM 节点。该方法由 params对象的 creator属性来指定。该方法有两个参数,第一个 item表示的是代表拖放条目的 JavaScript 对象,第二个 hint用来说明创建出的具体 DOM 节点的用途,目前只支持一个值 avatar,表示创建的 DOM 节点是给 dojo.dnd.Avatar使用的。该方法需要返回一个包含属性 nodedatatype属性的 JavaScript 对象。其中属性 node表示的是创建出来的 DOM 节点,data表示的是抽象的 JavaScript 对象,type表示的是拖放条目的类型,用来判断是否可以被拖放到某个目标上。一般来说,拖放条目的 DOM 节点是作为拖放源节点的直接子节点的。如果想使用其它节点的话,可以通过 params对象的 dropParent属性来指定。

接下来就是在拖放源中添加拖放条目。这些条目有可能是固定的,也可能是动态变化的。对于条目固定的情况,可以直接以声明式的方式来增加条目。只需要在拖放源的 DOM 节点下定义包含 CSS 类 dojoDndItem的节点即可。dojo.dnd提供了默认的 creator方法来完成转换。每个节点本身、其属性 dndDatadndType分别作为 creator方法返回值中的 nodedatatype属性的值。对于动态的条目,则需要使用 dojo.dnd.Source提供的 insertNodes(addSelected, data, before, anchor)方法。该方法的参数 addSelected表示添加的条目是否为被选中的状态;data表示的是包含条目抽象内容 JavaScript 对象的数组,会交给 creator方法来创建具体的 DOM 节点;beforeanchor用来表示条目节点的插入位置,anchor表示的是参考节点,而 before表示是插入的位置在参考节点之前还是之后。

创建了拖放源和其中的条目之后,下一步就是创建拖放目标。创建 dojo.dnd.Target的实例就可以表示一个拖放目标。创建目标的时候,通过第二个参数 params对象的属性 accept可以设置该目标接受的条目类型。只有当 accept 属性的值与创建条目时 creator方法返回值中 type属性的值相匹配的时候,拖放才能完成。这样就可以进行基本的拖放操作。代码清单 4 中给出了使用 dojo.dnd实现的简单拖放操作。

清单 4. 简单拖放操作
 <div id="source"> 
    <div class="dojoDndItem" dndType="myItem"> 测试条目 1</div> 
    <div class="dojoDndItem" dndType="myItem"> 测试条目 2</div> 
    <div class="dojoDndItem" dndType="myItem"> 测试条目 3</div> 
 </div> 
 <div id="target"> 
 </div> 
	
 var source = new dojo.dnd.Source("source"); 
 var target = new dojo.dnd.Target("target", { 
	 accept : ["myItem"] 
 });

代码清单 4 中给出了采用声明式的方式实现的简单拖放操作。默认情况下,被拖拽的条目会从拖放源中删除,添加到拖放目标中。如果希望只是从拖放源复制到目标的话,在创建 dojo.dnd.Source的时候,可以指定 params的属性 copyOnly的值为 true

如果希望定制拖放条目被拖动时的显示方式,需要利用 creator方法创建出所需的节点。代码清单 5 中给出了动态添加拖放条目和定制条目拖动时显示方式的一个示例。

清单 5. 动态添加拖放条目和定制条目拖动时显示方式
 var source = new dojo.dnd.Source("source", { 
    creator : function(item, hint) { 
        var n; 
        if (hint == "avatar") { 
            n = dojo.create("div", { 
                innerHTML : dojo.replace("<span>{title}</span>", item) 
            }); 
        } 
        else { 
            n = dojo.create("div", { 
                innerHTML : dojo.replace("<h4>{title}</h4><div>{description}</div>", item) 
            }); 
        } 
        return { 
            node : n, data : item, type : ["book"] 
        }; 
    }, 
    copyOnly : true 
 }); 
 var target = new dojo.dnd.Target("target", { 
    creator : function(item, hint) { 
        return { 
            node : dojo.create("span", { 
                innerHTML : item.title 
            }), 
            data : item, 
            type : ["book"] 
        }; 
    }, 
    accept : ["book"] 
 }); 
 source.insertNodes(false, [ 
    {title : "图书 1", description : "一本不错的书。"}, 
    {title : "图书 2", description : "这也是一本好书。"} 
 ]);

代码清单 5 所示,注意其中 insertNodes()方法和 creator方法的参数 hint的使用。当条目被拖动到目标上方并放下的时候,表示目标的 dojo.dnd.Target对象会首先通过 checkAcceptance(source, nodes)方法来检查是否允许拖动的条目放下。如果允许的话,目标上的 onDrop(source, nodes, copy)方法会被调用,其三个参数 sourcenodescopy分别表示拖放源、被拖动的节点以及是否需要进行复制。默认提供的 onDrop()方法的实现会根据拖放源和目标是否相同来调用不同的方法:当源和目标不相同的时候,调用的是 onDropExternal(source, nodes, copy);相同的时候调用的则是 onDropInternal(nodes, copy)。拖动的节点在目标上被放下之后的默认行为取决于在创建目标 dojo.dnd.Target对象的时候,是否定义了 creator方法。如果定义了 creator方法,就通过此方法在拖放目标中创建出新的 DOM 节点;如果没有的话,就直接进行 DOM 节点的复制或移动。从 代码清单 5 中可以看到,拖放目标 target定义了 creator方法,因此当条目从 source拖放到其上之后,条目的显示方式发生了变化。

在有些拖放操作中,当条目在目标上放下的时候,并不需要进行 DOM 节点的复制或移动。拖放行为本身只是作为一种触发的动作。这个时候就需要自定义 onDrop()或是 onDropExternal()onDropInternal()方法的实现。代码清单 6 中给出了一个自定义拖动条目被放下时行为的示例。该示例的场景是用户拖动想要购买的图书到购物车中,购物车会自动更新图书的总价。

清单 6. 自定义拖动条目被放下时的行为
 var sum = 0; 
 var target = new dojo.dnd.Target("target", { 
    accept : ["book"], 
    onDropExternal : function(source, nodes, copy) { 
        for (var i = 0, n = nodes.length; i < n; i++) { 
            var node = nodes[i]; 
            var item = source.getItem(node.id); 
            sum += item.data.price; 
        } 
        dojo.byId("target").innerHTML = "总价合计为:" + sum + " 元"; 
    } 
 });

代码清单 6 中所示,在 onDropExternal方法中,通过 source.getItem()方法来获取被放下的条目的抽象表示,即 creator方法返回的结果。通过得到的条目的抽象表示,就可以获取到所需的数据。有了所需的数据,就可以更新图书的总价。

最后要介绍的是 dojo.dnd.Manager的使用。在每个页面上只存在一个 dojo.dnd.Manager类的实例。通过 dojo.dnd.manager()可以获取该实例。该实例负责协调拖放操作,其中维护了一些与当前正在进行的拖放相关的状态,包括 sourcetargetavatarnodescopy等。

当与拖放相关的操作发生时,dojo.dnd会发布一些主题,应用可以监听这些主题来执行额外的逻辑。这些主题包括:

  • /dnd/start:当开始拖动的时候,发布此主题。
  • /dnd/drop:当拖动的条目被放下的时候,发布此主题。
  • /dnd/cancel:当拖放操作被取消的时候,发布此主题。
  • /dnd/source/over:当正在拖动的条目移动到某个拖放目标的上方时,发布此主题。
  • /dnd/drop/before:在拖放的条目被放下之前,发布此主题。

移动操作

在 Ajax 应用中,允许用户自由的移动页面上的元素也是一个常见的操作。比如在画图的应用中允许用户自由的摆放绘制元素。针对这样的需求,dojo.dnd模块中提供了 dojo.dnd.Moveabledojo.dnd.Mover,其中 dojo.dnd.Moveable表示的是可以被移动的元素,dojo.dnd.Mover是移动操作的具体执行者。

确定好需要进行移动的 DOM 节点之后,就可以创建出一个 dojo.dnd.Moveable对象来表示它。dojo.dnd.Moveable的构造方法有两个参数:nodeparams,其中 node表示的是 DOM 节点,params表示的是与配置相关的 JavaScript 对象。比较重要的配置项有 handle,用来表示拖动时的手柄,默认是当前节点。当用户在手柄的区域内点击并拖动的时候,就可以移动此节点。在移动的过程中,dojo.dnd.Moveable的一些方法会被调用。应用可以通过覆写这些方法来添加额外的处理。这些方法包括:

  • onFirstMove(mover):第一次被移动的时候调用。参数 mover表示用来移动节点的 dojo.dnd.Mover对象。
  • onMoveStart(mover):当开始移动的时候调用。
  • onMoveStop(mover):当停止移动的时候调用。
  • onMove(mover, leftTop):当每次移动的时候调用。参数 leftTop表示将要移动到的位置的坐标。
  • onMoving(mover, leftTop):由 onMove()方法在完成移动之前调用。
  • onMoved(mover, leftTop):由 onMove()方法在完成移动之后调用。

dojo.dnd.Mover的实例只有在移动的时候才会存在,一般由 dojo.dnd.Moveable来自动创建。如果希望提供自己的 dojo.dnd.Mover的实现,可以通过 dojo.dnd.Moveable构造方法的 params参数的 mover属性来指定。代码清单 7 中给出了自由移动节点的一个示例。

清单 7. 自由移动节点
 var position = dojo.cookie("position"); 
 if (position) { 
    var p = dojo.fromJson(position); 
    dojo.style("box", { 
        left : p.l + "px", 
        top : p.t + "px"
    }); 
 } 
 var moveable = new dojo.dnd.move.parentConstrainedMoveable ("box", { 
    area : "content", 
    within : true 
 }); 
 dojo.connect(moveable, "onMove", null, function(mover, leftTop) { 
    dojo.cookie("position", dojo.toJson(leftTop)); 
 });

代码清单 7 中使用了 dojo.dnd.move.parentConstrainedMoveable,这是 dojo.dnd提供了几个辅助类之一。它用来限制节点移动时不能超出其父节点的范围。与之类似的还有:dojo.dnd.move.boxConstrainedMoveable,用来限制移动范围在某个矩形区域内;dojo.dnd.TimedMoveable用来限制两次移动之间的时间间隔。

关于 dojo.dnd模块的更多介绍,见 参考资料。在介绍完与拖放操作相关的内容之后,下面介绍与处理常用数据类型相关的内容。

常用数据类型处理

在 Ajax 应用开发中,经常需要处理 JavaScript 中的常用数据类型,包括字符串、数字和日期格式等。JavaScript 本身提供的对这些数据类型的处理方法相当有限,不能满足应用的绝大多数需求。Dojo 核心库中提供了一些处理这些数据类型的实用方法。

字符串处理

Dojo 核心库中提供的对字符串处理的方法有下面几个:

  • dojo.string.rep(str, num):该方法用来快速的复制字符串。其参数 str表示要复制的字符串,num表示要复制的次数。如 dojo.string.rep("Hello", 2)的返回结果是 “HelloHello”
  • dojo.string.pad(text, size, ch, end):该方法用来填充字符串。其参数 text表示原始的字符串;size表示填充之后的字符串的长度;ch表示用来填充的字符串,默认为 '0'end表示填充的位置是在原始字符串的开头还是末尾,默认是在开头。该方法的一个常见的使用场景是以右对齐的方式显示数字。这个时候需要在数字的前面填充一些空格。假设数字的最大长度是 30,使用 dojo.string.pad(numberString, 30, ' ')就可以实现。
  • dojo.string.substitute(template, map, transform, thisObject):该方法用来实现基于模板的字符串替换。该方法提供的字符串模板功能非常强大。模板由参数 template表示。模板中声明占位符的方式是 ${key}${key:format}。其中 key表示的是属性的名称或是数组中的序号。format表示的是一个 JavaScript 方法的名称,该方法可以用来对进行替换的值做进一步的格式化处理。参数 map是一个 JavaScript 对象或是数组,其中的属性与模板中占位符的名称相对应。参数 transform表示的是一个 JavaScript 方法,用来对所有进行替换的值做变换处理。参数 thisObject表示的对象中包含了通过 format指定的方法,默认为全局对象 dojo.global代码清单 8中给出了该方法的使用示例。
  • dojo.string.trim(str):该方法用来去掉字符串开头和末尾的空白字符,其作用与 dojo.trim()相同。一般直接使用 dojo.trim()即可。
清单 8. dojo.string.substitute 使用示例
 var template1 = "我的姓名是 ${name},邮件地址:${email}"; 
 var data = { 
    name : "Alex", 
    email : "alex@example.org"
 }; 
 alert(dojo.string.substitute(template1, data)); 

 var template2 = "我的姓名是 ${0},邮件地址:${1}"; 
 alert(dojo.string.substitute(template2, ["Bob", "bob@example.org"])); 

 alert(dojo.string.substitute(template1, data, function(value, key) { 
    if (key == "email") { 
        return "<a href='mailto:" + value + "'>" + value +"</a>"; 
    } 
    return value; 
 })); 

 function salaryFormatter(value, key) { 
    return "¥" + dojo.string.pad(value, 20, ' '); 
 } 

 var template3 = "你的工资是:${0:salaryFormatter}"; 
 alert(dojo.string.substitute(template3, ["30000"]));

代码清单 8所示,其中包含了 4 个 dojo.string.substitute()调用。第一个使用 JavaScript 对象作为占位符真实值数据的来源;第二个使用数组来包含真实值数据;第三个使用了变换方法对属性 email作了额外的处理;最后一个使用了全局的 salaryFormatter方法来格式化数据。变换方法和格式化处理方法在作用上比较类似,不同的是格式化处理方法的使用是在模板中声明的,具体的 JavaScript 方法包含在 thisObject对象中。格式化处理方法是先于变换方法被调用的。

数字处理

Dojo 核心库提供了对数字进行格式化和解析的支持,可以满足应用的一般需求。对数字进行格式化的时候,使用的是 Unicode 的标准模式。在这个模式中,“0”和“#”都表示数字,不过“0”的情况下会对数字做填充处理,而“#”则不会。“.”表示小数点。“,”表示数字之间的分组符。“%” 表示百分数符号。“¤ (\u00A4)”表示货币符号。关于 Unicode 中对数字的格式化模式的更多信息,见 参考资料。Dojo 核心库中包含的方法如下:

  • dojo.number.format(value, options):该方法用来格式化数字。参数 value表示的是一个数字,options表示的是一个包含配置项的 JavaScript 对象。其中的配置项包括:pattern表示格式化使用的模式;type表示格式化的类型,可选的值有 decimalpercentcurrency,分别表示十进制、百分数和货币形式;places表示小数点所在的位置;round表示取整时的精确度;locale表示区域设置;fractional的值为 false时不显示小数点。代码清单 9 中给出了该方法的使用示例。
  • dojo.number.parse(expression, options):该方法用来把格式化之后的字符串中解析成数字。参数 expression表示的是一个字符串,options表示的是包含解析配置项的 JavaScript 对象。其中的配置项 patterntypelocale的含义与 dojo.number.format()方法相同。另外的配置项 strict用来设置是否采用严格模式。严格模式下,该方法只接受 dojo.number.format()方法的输出作为解析时的输入。
清单 9. dojo.number.format 使用示例
 var number = 123.4; 
 dojo.number.format(number, { 
    pattern : "#.000"
 }); // 结果是 123.400 
 dojo.number.format(number, { 
    pattern : "0000.00"
 }); // 结果是 0123.40

日期时间处理

JavaScript 语言内置了 Date对象来表示日期时间,在 Ajax 应用开发中经常会用到。不过 Date对象提供的方法比较简单,并不能很好的满足应用的需要。Dojo 核心库提供了一些处理 Date对象的实用方法来满足开发的需要。

  • dojo.date.getDaysInMonth(dateObject):该方法用来获取日期当月的天数。参数 dateObject是表示日期的 Date对象。
  • dojo.date.isLeapYear(dateObject):该方法用来判断日期所在年份是否为闰年。
  • dojo.date.compare(date1, date2, portion):该方法用来比较两个日期的大小。参数 date1date2是待比较的两个 Date对象,portion表示参与比较的部分,可选的值有 datetimedatetime,分别表示只比较日期、只比较时间和日期时间都比较。
  • dojo.date.add(date, interval, amount):该方法用来得到一个日期之后的新日期。参数 date表示基本的日期;interval表示日期增加的单位,可以是 yearmonthdayhourminutesecondmillisecondquarterweekweekday,分别表示年、月、日、小时、分钟、秒、毫秒、季度、星期和工作日;amount表示 interval单位增加的数量。
  • dojo.date.difference(date1, date2, interval):该方法用来给出两个日期之间的间隔。参数 date1date2是用来计算的两个 Date对象;interval的含义与 dojo.date.add()方法中的相同。

在介绍完 Dojo 核心库提供的对常用数据类型处理的支持之后,下面介绍如何支持浏览器后退按钮和书签功能。

浏览器后退按钮支持

目前很多 Ajax 应用都是单页面应用(Single Page Application)。用户在使用过程中,浏览器加载的页面并不会发生变化,而页面的内容会随着用户的使用而动态变化。传统的 Web 应用中则存在不同页面之间的跳转。单页面程序所带来的一个问题是浏览器的后退和前进按钮不再起作用,用户无法回退到上一步的操作。另外一个问题是如果用户将该应用的地址添加到浏览器收藏夹中,下次通过收藏夹直接访问的时候,应用总是在最初的状态。这是 Ajax 应用需要解决的两个重要问题,因为它们的行为与用户浏览网页的使用习惯相冲突,会影响用户体验。Dojo 核心库中提供了 dojo.back模块用来解决这两个问题。下面通过一个示例来具体说明。

该示例是一个简单的图书展示网站。用户访问此网站的时候,首先看到的是图书列表的界面,列出来所有的图书的标题。当用户点击某个图书标题的时候,界面会显示该图书的详细信息。当用户希望回到上一个图书列表界面时,他会习惯性的使用浏览器的后退按钮。但是该网站是一个单页面应用,图书列表界面和图书详细信息界面的切换是通过 DOM 操作来完成的,浏览器访问的页面地址并没有发生变化。因此用户无法后退到上一步操作的结果页面上。通过 dojo.back模块就可以比较容易的让这个网站支持浏览器的后退和前进按钮。

使用 dojo.back模块的时候,需要首先通过 dojo.back.init()方法来进行初始化。该方法的调用需要放在 <body> 元素中的一个 <script> 元素中。dojo.back会维护一个历史状态的列表,每个历史状态代表一个可以通过浏览器的后退按钮来回退的动作。比如在上面的示例中,显示图书详细信息就是一个可以回退的动作。dojo.back模块的使用都是围绕历史状态展开。应用根据需要在执行某些操作之后,添加对应的历史状态。这个时候浏览器的后退按钮变为可用的状态。当用户点击了后退按钮之后,dojo.back会负责执行与该历史状态对应的逻辑,应用就可以回退到之前的状态。历史状态是一个 JavaScript 对象,其中需要包含两组方法和一个属性。这两组方法分别是浏览器的后退和前进按钮被按下时的处理方法,其中 back()backButton()方法在后退的时候被调用;forward()forwardButton()在前进的时候被调用;handle()方法则后退和前进的时候都会被调用。后退的时候被调用的是 handle("back"),前进的时候被调用的是 handle("forward")。应用只需要实现相应的处理方法即可,dojo.back会负责调用这些方法。历史状态中的属性 changeUrl表示切换历史状态的时候,是否改变浏览器地址栏的地址,即添加片段标识符(fragment identifier)。该属性的值就是片段标识符的值。应用需要在适当的时候通过 dojo.back.addToHistory()来添加新的历史状态,以便用户后退。在应用启动的时候,还需要通过 dojo.back.setInitialState()来设置初始的历史状态。 代码清单 10中给出了使用 dojo.back的该图书展示网站的部分代码。

清单 10. 图书展示网站示例
 function AppState(isbn) { 
    this.isbn = isbn; 
    this.changeUrl = isbn; 
 } 
 dojo.extend(AppState, { 
    handle : function() { 
        if (this.isbn != null) { 
            displayBookDetail(this.isbn, false); 
        } 
        else { 
            displayBooks(false); 
        } 
    } 
 }); 

 function displayBooks(addHistory) { 
    // 显示图书列表
    
    if (addHistory) { 
        var state = new AppState(null); 
        dojo.back.addToHistory(state); 
    } 
 }   

 function displayBookDetail(isbn, addHistory) { 
    // 显示图书详细信息
    
    if (addHistory) { 
        var state = new AppState(isbn); 
        dojo.back.addToHistory(state); 
    } 
 } 

 dojo.addOnLoad(function() { 
    var isbn = dojo.hash(); 
    var state = null; 
    if (isbn) { 
        state = new AppState(isbn); 
        dojo.back.setInitialState(state); 
        displayBookDetail(isbn, false); 
    } 
    else { 
        state = new AppState(null); 
        dojo.back.setInitialState(state); 
        displayBooks(false); 
    } 
 });

代码清单 10 所示,AppState表示的是该图书展示网站中的历史状态,其中包含图书的 ISBN 编号 isbn。历史状态的 changeUrl属性也是 ISBN 编号,handle()方法会判断属性 isbn的值,如果此值为 null的话,则说明是显示图书列表;否则的话就显示对应图书的详细信息。当从图书列表界面跳转到某本图书的详细信息界面的时候,会创建一个 AppState对象,并通过 dojo.back.addToHistory()来添加此历史状态,同时浏览器的地址也会改变。这样的话,用户可以把某本书的详细信息界面加入自己的收藏夹中。当应用页面加载的时候,首先会检查浏览器地址的片段标识符。如果有的话,则认为是图书的 ISBN 编号,应用会去显示该图书的详细信息,并通过 dojo.back.setInitialState()设置正确的初始状态。这样就完成了对浏览器后退和前进按钮以及收藏夹的支持。

在介绍完 dojo.back模块之后,下面介绍用 Dojo 核心库实现高级动画效果。

高级动画效果

Dojo 基本库中提供了动画效果的实现基础(dojo.Animation)和简易的实现。Dojo 核心库则在此基础上提供了更加丰富的动画效果。

动画效果

Dojo 核心库提供的动画效果有划入、划出、滑动和切换显示。下面分别进行具体的说明。

  • 划入:dojo.fx.wipeIn()用来实现划入效果,把一个节点从其当前高度伸展到其自然的高度。比如 dojo.fx.wipeIn({node : "myDiv"})用来实现 myDiv节点的划入效果。
  • 划出:dojo.fx.wipeOut()用来实现划出效果,把一个节点从其当前高度缩小到 1 像素然后隐藏起来。
  • 滑动:dojo.fx.slideTo()用来实现滑动效果,把一个节点从其当前位置移动到新的位置上。新的位置由参数 topleft来指定。如 dojo.fx.slideTo({node : "myDiv", top : "100", left : "100"})把节点 myDiv移动到 (100,100) 的位置。
  • 切换:dojo.fx.Toggler用来实现切换效果,控制一个节点的显示或隐藏的状态。它的方法 show(delay)hide(delay)分别用来显示和隐藏节点,参数 delay表示的是延迟时间。 在创建 dojo.fx.Toggler的时候,可以选择显示和隐藏节点时使用的动画效果。通过参数 JavaScript 对象的 showFunchideFunc可以指定两个 JavaScript 方法,方法的返回值应该是一个 dojo.Animation对象。dojo.fx.Toggler在显示或隐藏节点的时候会使用此 dojo.Animation对象定义的动画效果。默认情况下使用的是 dojo.fadeIndojo.fadeOut

动画效果组合

有些情况下可能会需要把多个动画效果组合起来使用。Dojo 核心库提供了 dojo.fx.chain(animations)dojo.fx.combine(animations)两个方法来满足这样的需求。它们的参数 animationsdojo.Animation对象的数组,表示一组动画效果。这两个方法的返回值都是一个新的 dojo.Animation对象。dojo.fx.chain()方法是按照顺序依次播放一组动画效果,而 dojo.fx.combine()则是并行的同时播放一组动画效果。

介绍完高级动画效果之后,下面介绍一些其它内容。

其它内容

除了上面章节中介绍的内容之外,Dojo 核心库中还包含了一些重要的其它内容。下面简单介绍一下这些内容。

dojo.DeferredList

dojo.Deferred提供了对异步操作的抽象,不过只适合于单个异步操作。而在有些时候则需要处理多个异步操作。比如同时发出多个 XMLHTTPRequest 请求,并等待它们完成。dojo.DeferredList可以满足这种需求。它的构造方法是 dojo.DeferredList(list, fireOnOneCallback, fireOnOneErrback, consumeErrors, canceller),其中参数 list表示的是包含多个 dojo.Deferred对象的数组;fireOnOneCallbacktrue的话表示只要有一个 dojo.Deferred操作完成,就触发正确完成的回调方法;fireOnOneErrbacktrue的话表示只要有一个 dojo.Deferred操作出现错误,就触发出现错误时的回调方法;consumeErrorstrue的话,单个 dojo.Deferred产生的错误将不会被抛出;canceller是取消这些异步操作时所调用的方法。dojo.DeferredList继承自 dojo.Deferred,也就是说可以把多个异步操作抽象成一个来使用。通过设置参数 fireOnOneCallback的值,可以在多个异步操作全部完成之后,或是有一个完成之后就调用相应的逻辑。比如说通过 dojo.xhrGet()发出多个 XMLHTTPRequest 请求,等它们都完成之后再执行某些操作。这种情况下就可以用 new dojo.DeferredList(list, false, true, false)来创建一个 dojo.DeferredList对象。又或者某个操作有多种可选方案,只要一种方案成功,就可以继续进行。这种情况下就可以用 new dojo.DeferredList(list, true, false, true)来创建一个 dojo.DeferredList

dojo.html.set

当需要设置一个 DOM 节点所包含的内容的时候,一般的做法是直接设置 innerHTML属性。不过这种方式在处理某些元素的时候可能出现问题。Dojo 基本库提供的 dojo.place()也可以完成这样的功能。Dojo 核心库包含的 dojo.html.set(node, cont, params)方法提供了更加丰富的设置,其参数 node表示 DOM 节点;cont表示要设置的内容,可以是字符串、DOM 节点、NodeList等;params表示的是配置项的 JavaScript 对象,其中的属性有 cleanContentextractContentparseContent,分别表示是否从字符串中删除文档类型声明和 title元素、是否从 <body>元素中抽取内容以及是否通过 dojo.parser.parse()来进行解析。其中比较实用的配置项是 parseContent,通过它可以很方便的以声明式的方式在 DOM 节点中创建 Dojo 用户界面组件(dijit)并自动实例化出来。

总结

在开发自己的 Ajax 应用的时候,免不了会开发一些公用的组件。而 Dojo 核心库已经提供了比较多的组件来简化应用的开发工作。了解并掌握这些组件的使用,可以减少开发中的代码量,提高开发效率。如果确实需要自己开发一些公用组件,也最好以 Dojo 核心库为基础。本文详细介绍了 Dojo 核心库中比较重要的部分,包括数据模型、I/O 请求、拖放、常用数据类型处理、浏览器后退按钮支持和高级动画效果等,可以帮助提升对 Dojo 核心库的理解。

声明

本人所发表的内容仅为个人观点,不代表 IBM 公司立场、战略和观点。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=524524
ArticleTitle=使用 Dojo 的 Ajax 应用开发进阶教程,第 7 部分: Dojo 核心库深入介绍
publish-date=09202010