Dojo 作为最著名的 Ajax 开源项目之一,不仅让 Web 程序员可以免费获得和使用其框架进行 Web 应用的开发,更吸引了大量的开发者对其不断的扩充,开发新的组件。DojoX 就是在这样的开发社区中产生的。DojoX 是一组基于 dojo 的开源项目的集合,这些开源项目具有很好的创意和很高的实用性。这些 DojoX 项目有可能成长为一个稳定的版本保留在 DojoX 中,也有些可能会迁移到 Dojo Core 或者 Dijit 中。本文将对 DojoX 中的项目进行一个总体的概述,并结合实例介绍其中较为有特色的项目,本文主要会介绍 DataGrid,Charting,Gfx/Gfx 3D 和 DojoX Widget。
目前 DojoX 项目主要扩展了数据结构与算法、数据处理与通信、实用工具、图形 API 以及 Web UI 等。
涉及到数据结构与算法的项目包括了 DojoX Collections、DojoX Encoding 等。Collections 定义了很多非常有用的数据集合,包括了数组(ArrayList)、二叉树(BinaryTree)、字典(Dictionary)、迭代器(Iterator)、队列(Queue)、有序列表(SortedList)、堆栈(Stack)。这些集合的使用将大大提高程序开发的效率以及程序的质量。Encoding 不仅提供了字符串与字符编码的转换,还提供了对称算法河豚(Blowfish)和 MD5 数字摘要算法等。
DojoX Data、Embed、I/O、JSON、XML、RPC 等扩展了 Dojo 的数据处理与通信能力。其中,Data 项目提供了对更多数据格式的支持,包括了对 csv 文件以及 Google、Picasa 等提供的 API 的支持等等。
DojoX 的图形 API 扩展了 Dojo 的动画效果,并提供了 2D、3D 绘图的支持。DojoX Fx 通过对 dojo core 以及 dojo fx 的扩展提供了多种动画效果;gfx 提供了一系列矢量绘图的方法;而 gfx3d 则提供了一些简单的 3D 绘图 API。
而更加丰富的 Web UI 以及 Web 小部件也是 DojoX 的一大亮点。功能强大的 Grid、实用的 Charting、以及 DojoX Image 和 DojoX Layout 使得基于 dojo 开发的 Web UI 更加丰富。DojoX Widgets 中还提供了更加丰富的小部件可以满足大部分应用开发的需求。
除以上介绍的项目外,DojoX 还收集了很多实用工具,读者可以在 dojo API 网站上获得更多的信息。http://api.dojotoolkit.org/
接下来我们就来体验一下 DojoX 给我们带来的精彩吧
注意:本教程使用的 dojo 版本为 1.2.1,由于 1.2.x 版本里 dojo 以及 dojoX 的部分组件有较大变化,因此本文仅适用于 dojo1.2.x,对于 dojo1.0 的开发者本文仅供参考,部分代码不能正确运行
Grid 可能是 DojoX 中最受欢迎的部件,比起普通的 Web 表格部件,Grid 更像一个基于 Web 的 Excel 组件。这使得 Grid 足可以应付较为复杂的数据展示及数据操作。在 dojox1.2 中,dojox.grid 包中新增了 DataGrid 类,该类是对原 Grid 类的强化和替代,之所以叫做 DataGrid,是由于该类与 dojo 的数据操作类 store 无缝整合在一起。而之前的 Grid 需要将 store 对象包装为 model 对象才能使用。下文如果没有特殊声明,所有 Gird 或是 DataGrid 均指新版 DataGrid,而不是 Grid1.0。
我们为什么需要 Grid 呢?下面列出了 Grid 的特性:
除此之外,Grid 还有具有其他很多特性,例如,非常实用的偶数行上色、灵活的选取功能、自动调整列宽、数据的展开 / 合闭等。
DataGrid 基础
要创建一个 DojoX DataGrid,就需要对 DataGrid 的基本工作过程有一个大致的了解。一个 DataGrid 实例的组成结构如下图所示,DojoX DataGrid 是使用 DataGrid 的基础,因此在使用 Grid 的时候需要加载相关的 dojox 包;一个小部件通常由框架和样式组成,因此,我们需要指定 DataGrid 的样式表并且声明 DataGrid 实例。DataGrid 实例会组合一个 Structure 和一个 Store。Structure 是一个表头及数据模型的定义,而 Store 用于承载数据。
下面开始我们的第一个 DataGrid 应用为了在 Web 页面上创建一个 DataGrid 小部件,我们从最基本的二维表格开始。首先我们需要加载一些样式,来保证 DataGrid 能够正常显示,清单 1。
此处的 css 文件路径为相对于测试页面的相对路径。
在开始创建 Grid 之前,我们还要引入 Dojo 的基础包 dojo.js,以用来加载其他需要的 dojo 类,并加载 dojo.data.ItemFileReadStore 类以及 dojox.grid.DataGrid 类。接下来我们就可以着手开发第一个 DataGrid 了。首先是布局的定义,如 清单 2
var layout = [ {field: 'pro_no', name: 'Product Number' }, {field: 'pro', name: 'Product' }, {field: 'min_amount', name: 'Minimum Amount' }, {field: 'max_amount', name: 'Maximum Amount' }, {field: 'avg_amount', name: 'Average Amount' } ];
这里定义了一个数组 layout,其中每一个成员表示一个列的定义,其中 field 指定了使用的数据项,该取值需要遵循 javascript 变量定义规则;name 为该列显示的名称。接下来是 store 的开发,代码如 清单 3
var sampleData = { identifier: 'pro_no', label: 'pro_no', items: [ {pro_no:'2100', pro:'A Series', min_amount:346, max_amount:931, avg_amount:647}, {pro_no:'2200', pro:'B Series', min_amount:301, max_amount:894, avg_amount:608}, {pro_no:'2300', pro:'C Series', min_amount:456, max_amount:791, avg_amount:532}, {pro_no:'2400', pro:'D Series', min_amount:859, max_amount:2433, avg_amount:1840}, {pro_no:'2500', pro:'E Series', min_amount:459, max_amount:1433, avg_amount:1040} ] }; var jsonStore = new dojo.data.ItemFileReadStore({ data: sampleData });
在这里,我们首先定义一个 JSON 数据 sampleData,这里 identifier 是对于整行的唯一标识,因此在数据中不能出现重复;数组 items 是这个表格所显示的数据,其中数据必须完全符合 JSON 的语法,字符串两端必须使用引号,否则会出现语法错误,保险的办法是所有的值均用引号括住。
接下来,我们就要在网页的 Body 元素中定义 DataGrid 实例了,如 清单 4
dojoType 指定了该 Web 部件为 dojox.grid.DataGrid,数据使用 jsonStore,结构为 layout,自动调整宽度。到此,第一个 Grid 就已开发完毕,完整代码如 清单 5
first Grid First Grid
在浏览器中运行,效果如下:
DataGrid 开发详解
DataGrid 的创建
在 DataGrid 的开发中,有三种方法创建 DataGrid 实例,第一种是 javascript 创建结构,html 代码创建实例,我们第一个例子就是使用这种方式实现的;
第二种是由 html 代码创建结构及实例,在这种方法中,我们使用 table 标签,定义 Grid 的结构,而省去了在 javascript 中定义 structure 的部分。具体定义方式与标准的 html 书写方式非常类似,定义方式如 清单 6
Product Number | Product | Minimum Amount | Maximum Amount | Average Amount |
---|
第三种方式就是采用纯 javascript 的方式定义 DataGrid 实例,清单 7声明网页加载完成后就在 id 为 gridNode 的页面结点上创建一个 DataGrid 实例。
dojo.addOnLoad(function(){ // 指定页面加载完毕后执行 var grid = new dojox.grid.DataGrid({ query: { pro_no: '*' }, id: 'grid2', store: jsonStore, structure: [ {field: 'pro_no', name: 'Product Number' }, {field: 'pro', name: 'Product' }, {field: 'min_amount', name: 'Minimum Amount' }, {field: 'max_amount', name: 'Maximum Amount' }, {field: 'avg_amount', name: 'Average Amount' } ],rowsPerPage: 20 }, 'gridNode'); // 设置 grid 显示在 id 为 gridNode 的节点下 grid.startup(); // 启动 grid });
Grid1.2 可以通过这种方式很方便的与 dojo 容器结合在一起,动态创建页面布局。
Structure 详解
DataGrid 不仅可以创建简单的二维表格,还可以通过对 structure 的设计创建复杂的表格应用,同时还可以为每一列进行格式化或是取值。我们将 First Grid 进行简单的修改,得到 清单 8的代码。
function formatAmount(value){ return '$ ' + value; } function getRange(rowIndex, item){ if(!item){return '--';} var grid = dijit.byId('grid'); var max = grid.store.getValue(item, "max_amount"); var min = grid.store.getValue(item, "min_amount"); return max - min; } var subrow1 = [ {field: 'pro_no', name: 'Product Number', rowSpan: 2 }, {field: 'pro', name: 'Product', rowSpan: 2 }, {field: 'min_amount', name: 'Min. Amount',formatter: formatAmount,width: '80px' }, {field: 'avg_amount', name: 'Average Amount',formatter: formatAmount, rowSpan: 2 }, {field: 'range', name: 'Range',get:getRange, rowSpan: 2 } ]; var subrow2 = [ {field: 'max_amount', name: 'Max. Amount',formatter: formatAmount}, ]; var layout = [subrow1,subrow2];
这里,我们从新定义了 layout,将 layout 分为两个子行,其中子行 1 包含了五个字段,其中 pro_no、pro、avg_amount、range 具有值为 2 的 rowSpan 属性,也就表明这三列跨越了两行。第二行仅有 max_amount 一个字段。同时,我们为三个 amount 字段指定了 formatter 函数,在其数值前添加美元符号。为 range 字段指定了 get 方法来自动获取最大值与最小值的差。
显示效果如下:
除了 rowSpan 属性外我们还可以使用 colSpan 属性,这两个属性的用法与 html 中的用法一致,并且可以在 html 定义表结构中使用,我们再看这个表头的例子来理解一下 colSpan 的用法。
var structure = [[ {field: 'type', name: 'Type', rowSpan: 2}, {field: 'pro', name: 'Product', rowSpan: 2}, {field: 'Q20071', name: 'Q1',formatter: formatAmount }, {field: 'Q20072', name: 'Q2',formatter: formatAmount }, {field: 'Q20073', name: 'Q3',formatter: formatAmount }, {field: 'Q20074', name: 'Q4',formatter: formatAmount } ],[ {field: 'Y2007', name: 'Year 2007',formatter: formatAmount, colSpan: 4 } ]];
清单 9的显示效果如下:
Store 的使用
DataGrid 使用了 Store 作为数据源,在以上的例子中,我们都是将数据写在 javascript 中然后作为 data 参数值传给 Store 的构造方法。但是在大多数情况下,数据是要动态的通过 Ajax 请求从服务器端获取的,这同样可以通过 Store 来实现。我们仅需要将声明 Store 对象时传入请求的 url 地址即可,如:new dojo.data.ItemFileReadStore({url: 'jsondata.txt' }) 。Store 包括 dojo.data.ItemFileReadStore 和 dojo.data.ItemFileWriteStore 两个类。我们在使用 DataGrid 的编辑功能时需要使用 ItemFileWriteStore 来作为数据源。下面就演示了一个多功能的 DataGrid,这个 Grid 使用外部数据源,可以对单元格进行编辑,并且可以通过右击表头弹出列选菜单。为了页面能够正确,清单 10载入了所需的 CSS。
清单 11的代码引入了所需的 dojo 包,并创建了可编辑的 DataGrid,将其添加到了 id 为 gridNode 的页面节点中。为了使列具有编辑功能只需要在 structure 定义中的表示该列的 JSON 定义中添加值为 true 的 editable 属性。
清单 12定义了菜单 gridMenu 以及承载 DataGrid 的 DIV。
Data Grid
本例使用了外部数据源 dataGrid.txt,该文件的内容类似于 清单 13
{ identifier: 'emp_no', label: 'emp_no', items: [ {emp_no:'2100', name:'Matt', gender:'M', dept_no:730, bonus:647}, {emp_no:'2200', name:'Lisa', gender:'F', dept_no:731, bonus:608}, {emp_no:'2300', name:'Mick', gender:'M', dept_no:732, bonus:532}, {emp_no:'2400', name:'John', gender:'M', dept_no:733, bonus:1840}, {emp_no:'2500', name:'Jan', gender:'M', dept_no:734, bonus:1040}, {emp_no:'2101', name:'Jeff', gender:'M', dept_no:730, bonus:647}, {emp_no:'2202', name:'Frank', gender:'M', dept_no:731, bonus:608}, {emp_no:'2303', name:'Fred', gender:'M', dept_no:732, bonus:532} ]}
运行,结果如下图所示。
回页首
Charting 是基于 DojoX 绘图包的数据可视化组件,包括了 Chart2D 和 Chart3D 来分别绘制 2D 和 3D 的图表。Chart2D 提供多种样式的饼图、柱状图、折线图、面积图、网格等图表。Chart3D 目前仅提供了 3D 柱状图和 3D 圆柱图,并且从社区获取的信息表明由于 IE 上的性能问题导致 Chart3D 的开发暂时搁置。Charting 的应用主要分为如下几个步骤:
下面我们来看几个应用实例。
2D 饼图
清单 14的代码为 2D 饼图,我们可以看到该实例加载了类 Chart2D 和 themes.PlotKit.blue 对象。chart1 对象声明在 ID 为 char1 的元素下,并被添加了一个 Pie 部件作为默认部件,数据为 3、2、5、1、6、4。
……
运行结果如下:
带网格的 2D 面积图
这个例子中我们为 Chart2D 对象添加了两个部件,网格和面积图,如 清单 15。值得注意的是,我们为 chart 添加了两个部件,Plot1 和 Plot2,其中 Plot1 的类型为 Areas,Plot2 的类型为 Grid,我们给 Plot1 添加了三组数据,并没有给 Plot2 添加数据。
chart = new dojox.charting.Chart2D("chart2"); chart.setTheme(dojox.charting.themes.PlotKit.orange); chart.addAxis("x", {origin:"max"}); chart.addAxis("y", {vertical: true, leftBottom: true, min: 5000, max: 8000, majorTickStep: 500, minorTickStep: 100}); chart.addPlot("plot1", {type: "Areas", hAxis:"x", vAxis:"y"}); chart.addPlot("plot2", {type: "Grid", hAxis:"x", vAxis:"y"}); data1 = [{x:10,y:7200}, {x:20,y:6800}, {x:30,y:7000}, {x:40,y:6600}, {x:50,y:7000}, {x:60,y:6800}, {x:70,y:7200}, {x:80,y:6600}, {x:90,y:6800}, {x:100,y:7000}]; data2 = [{x:10,y:6800}, {x:20,y:5800}, {x:30,y:6400}, {x:40,y:5600}, {x:50,y:6000}, {x:60,y:6200}, {x:70,y:6600}, {x:80,y:7200}, {x:90,y:6300}, {x:100,y:6000}]; data3 = [{x:10,y:6000}, {x:20,y:6300}, {x:30,y:6800}, {x:40,y:6200}, {x:50,y:6200}, {x:60,y:6600}, {x:70,y:6300}, {x:80,y:6200}, {x:90,y:6000}, {x:100,y:5900}]; chart.addSeries("series B", data2, {plot: "plot1"}); chart.addSeries("series C", data3, {plot: "plot1"}); chart.addSeries("series A", data1, {plot: "plot1"}); chart.render();
运行结果如下:
巧用折线图进行函数图像的绘制
我们可以使用折线图或者面积图完成函数图像的绘制,原理就是按照一定的步长循环将定义域中将 x,y 值计算出来组成一组数据添加的 Chart2D 对象中,下面这个例子就是使用了折线图绘制了正弦余弦曲线。
清单16绘制正弦余弦曲线
dojo.require("dojox.charting.Chart2D"); dojo.require("dojox.charting.themes.PlotKit.blue"); dojo.addOnLoad(function() { var period = 2 * Math.PI; var tick = Math.PI / 180.0; var step = 5*Math.PI / 180.0; var chart = new dojox.charting.Chart2D('chart_area'); chart.setTheme(dojox.charting.themes.PlotKit.blue); chart.addAxis("x", {min: 0, max: period, majorTickStep: tick*30, minorTickStep: tick*10, minorLabels: false, font: '40px bold'}); chart.addAxis("y", {vertical: true, min: -1.01, max: 1, majorTickStep: 0.5, minorTickStep: 0.1, minorLabels: false, font: '40px bold'}); chart.addPlot("default", {type: 'Lines'}); chart.addPlot("grid", {type: "Grid", vMinorLines: true}); var series = {'sin' : [], 'cos' : []}; for(var i = 0; i < period; i+=step) { series.sin.push({'x' : i, 'y' : Math.sin(i)}); series.cos.push({'x' : i, 'y' : Math.cos(i)}); } chart.addSeries('sin', series.sin); chart.addSeries('cos', series.cos); chart.render(); });
运行结果如下:
3D 柱状图的绘制
使用 Chart3D 与 Chart2D 略有不同,这主要是因为 3D 绘图比 2D 绘图要复杂一些,3D 绘图一个很重要的过程就是坐标变换,以及光照和渲染都是很重要的考虑要点。不过 Chart3D 已经对 gfx3D 进行了封装,我们只需要通过对简单的几个参数的设置就可以完成一个 3D 图表。我们可以从 清单 17中看出 Chart3D 对象的声明比 Chart2D 多了一些参数,这主要是两类,光照与摄像机。光照指的是 3D 物体的周围的光环境,其中 lights 是光源数组,也就是说 3D 物体可以接受多个光源的光照;ambient 为环境光,影响物体的各个立体面;specular 是镜面反射光,这是光源照射物体特定位置发生镜面反射所产生的高光。当然了,你甚至可以不用理解这些参数的意义,在实际使用中多次调整参数以满足自己的使用需求。
dojo.require("dojox.charting.Chart3D"); dojo.require("dojox.charting.plot3d.Bars"); dojo.require("dojox.charting.plot3d.Cylinders"); makeObjects = function(){ var m = dojox.gfx3d.matrix; var chart = new dojox.charting.Chart3D("test", {lights: [{direction: {x: 5, y: 5, z: -5}, color: "white"}], ambient: {color:"white", intensity: 2}, specular: "white"}, [m.cameraRotateXg(10), m.cameraRotateYg(-10), m.scale(0.8), m.cameraTranslate(-50, -50, 0)] ); var plot1 = new dojox.charting.plot3d.Bars(500, 500, {gap: 10, material: "yellow"}); plot1.setData([2,1,2,1,1,1,2,3,5]); chart.addPlot(plot1); var plot2 = new dojox.charting.plot3d.Bars(500, 500, {gap: 10, material: "red"}); plot2.setData([1,2,3,2,1,2,3,4,5]); chart.addPlot(plot2); var plot3 = new dojox.charting.plot3d.Cylinders (500, 500, {gap: 10, material: "#66F"}); plot3.setData([2,3,4,3,2,3,4,5,5]); chart.addPlot(plot3); var plot4 = new dojox.charting.plot3d.Cylinders (500, 500, {gap: 10, material: "#E6F"}); plot4.setData([3,4,5,4,3,4,5,5,5]); chart.addPlot(plot4); chart.generate().render(); }; dojo.addOnLoad(makeObjects);
运行这段代码,显示效果如下:
这个 3D 柱状图结合了长方体和圆柱体,dojox.charting.plot3d.Bars 是长方体的声明类,new dojox.charting.plot3d. Cylinders 是圆柱体的声明类。
回页首
DojoX Gfx 和 Gfx3D 是 DojoX 中进行绘图的两个包,分别提供了 2D 和 3D 的绘图 API。前面介绍的 DojoX Charting 就是在这两个包的基础上开发的。Gfx 以及 Gfx 3D 是一组矢量绘图 API。对于原生的矢量图形,Gfx 能够支持 SVG、Canvas 和 VML,新版本中又增加了对 Silverlight 的支持。首先,我们来看看 DojoX Gfx 可以做什么。在 dojo 官方网站公布的开发包中有这么几个 Gfx 例子,见下图。可以看出,Gfx 对于简单的 2D 绘图已经绰绰有余了。
下面我们就来使用 Gfx 开始绘图。Gfx 绘图的基本步骤可以简单的归为两步:打开一个画布(surface),然后“画”。在绘画之前,我们要例行公事,引入 dojox.gfx 包。然后通过 dojox.gfx 对象的 createSurface 方法建立画布。Surface 提供了很多画笔来进行作画,这里我们介绍几个比较常用的。
除以上介绍的几种外还有点、折线、文字等画笔,并且还提供了组功能,可以使一组图形一起响应事件等。下面就看个简单的例子来加深理解。
dojo.require("dojox.gfx"); surface = dojox.gfx.createSurface(dojo.byId("gfx_holder"), 700, 700); surface.createRect({x: 260, y: 260, width: 50, height: 50}).setFill("#AAF"); surface.createLine({x1: 100, y1: 400, x2: 400, y2: 350}) .setStroke({color:"#9F3",width:5}); surface.createCircle({cx: 200, cy: 200, r: 50}) .setFill("#FEF").setStroke({color: "#F9A", width: 3}); var path="M153 334 " + "C153 334 151 334 151 334 C151 339 153 344 156 344 " + "C164 344 171 339 171 334 C171 322 164 314 156 314 " + "C142 314 131 322 131 334 C131 350 142 364 156 364 " + "C175 364 191 350 191 334 C191 311 175 294 156 294 " + "C131 294 111 311 111 334 C111 361 131 384 156 384 " + "C186 384 211 361 211 334 C211 300 186 274 156 274" surface.createPath(path).setFill("rgb(FF,FF,FF)").setStroke({color:"red",width:3});
清单 18中的代码将在 ID 为 gfx_holder 的 html 标记内添加这个绘图。这段代码中的 setFill 方法和 setStroke 方法分别用来设置填充效果和笔触效果。代码运行结果如下图所示。
Gfx3D 的工作原理是采用计算机图形学的原理将三维空间中的物体按照透视规则从三维坐标系转换成二维的坐标系然后通过 SVG 等矢量图显示出来。限于浏览器和 javascript 的性能,目前 Gfx3D 仅能绘制较为简单的 3D 物体和空间曲线,还不能绘制复杂的空间曲面以及进行纹理等渲染工作,但是 Gfx3D 足以满足大部分 Web 应用的需要了。Gfx3D 的使用大致分三个步骤:建立画布,在画布上建立视图,在视图上建立 3D 物体。视图上必不可少的要设置光源以及摄像机方位。设置光源可以通过 setLights 方法来设定,摄像机方位则需要使用 dojox.gf3d.maxtrix 对象的 cameraRotateX、cameraRotateY、cameraRotateZ、cameraTranslate 等方法来确定。清单 19在画布上绘制了一个立方体和一个圆柱体。
dojo.require("dojox.gfx3d"); makeObjects = function(){ var surface = dojox.gfx.createSurface("test", 500, 500); var view = surface.createViewport(); // 建立视图 view.setLights([{direction: {x: 0, y: 0, z: -10}, color: "white"}, {direction: {x: 10, y: 0, z: -10}, color: "#444"}], {color: "white", intensity: 2}, "white"); var m = dojox.gfx3d.matrix; // 建立空间 var l = view.createCube({bottom: {x: 0, y: 0, z: 0}, top: {x: 100, y: 100, z: 100}}) .setFill({type: "plastic", finish: "dull", color: "lime"}); // 绘制立方体 view.createCylinder({}) // 绘制圆柱体 .setTransform([m.translate(200, 100,200), m.rotateZg(60), m.rotateXg(-60)]) .setStroke("black") .setFill({type: "plastic", finish: "glossy", color: "red"}); var camera = [m.cameraRotateXg(20), m.cameraRotateYg(20), m.cameraTranslate(-200, -200, 0)]; // 设置摄像机方位 view.applyCameraTransform(camera); view.render(); }; dojo.addOnLoad(makeObjects);
运行,结果如下图所示。
其他 DojoX Widget
DojoX 的 widget 包中还提供了更多的小部件,非常的方便易用,本文的最后再来两个餐后甜点,拾色器和鱼眼。拾色器是我们经常会用到的小部件,用 DojoX 生成拾色器非常简单,仅需要 清单 20这一小段代码即可。
除了这段 javascript 代码,为了能正确的显示拾色器的样式,我们还需要导入它的 css 文件:dojox/widget/ColorPicker/ColorPicker.css。代码中,属性 animatePoint 来确定指针是否有滑动动画,默认为 true;属性 showHsv 表示是否显示 HSV 颜色模式数值;属性 showRgb 表示是否显示 RGB 颜色模式的数值;webSafe 表示是否显示 Web 安全色。清单 20显示效果如下:
鱼眼的实现也很简单,我们需要制作一组图标,命名为 fe1.gif、fe2.gif、fe3.gif 一直到 fe7.gif。我们将它们和页面放在同一文件夹下。然后在网页中书写如 清单 21所示代码。
运行,鼠标移上去来看看效果。
回页首
JavaScript 并不是万能的,同样,使用 JavaScript 开发的 Dojo 也不是万能的,但是为了满足众多开发者的需要,DojoX 提供了非常丰富的选择,并且提供了对 Flash、SilverLight、Google Gear 等组件的支持,使得 Dojo 爱好者们可以开发出更好更强大的 Web 应用。随着 DojoX 项目的不断完善和成熟,Dojo 将给开发者们带来更多的惊喜。