深入了解 Dojo 的数据访问和绑定工具包

IBMdW 发布于 2012/03/08 22:46
阅读 648
收藏 1

在我们的日常开发中经常碰到这样一些需求:我们经常需要访问数据的某个属性,或者经常修改复杂数据的某一部分,或者绑定两个数据使它们能够同步变化,或者 绑定两个动作 & 事件使得它们能够同步响应等等。针对这些需求,如果用最原始的代码来解决,往往都需要一定的代码量,同时可能伴随着或多或少的代码可读性和效率问题。 Dojo 提供了一种新式的数据访问和绑定方式工具包 dojox.wire,它让我们能更加方便且高效地访问、修改我们的数据。作为 Dojo 这样一个强大且全面的 web 开发控件库中的一部分,它提供了很多简单且功能完善的 API。通过这些 API,我们能够很方便的实现数据的绑定,包括简单数据的绑定、复杂数据的绑定、绑定转换器、动作或者事件的绑定以及各种数据绑定适配器,如:文本型适配 器、表格型适配器、树型适配器等等。它不仅支持脚本类型的接口 API:“dojox.wire.ml”,还支持声明式的 API,即:以 Dojo 的 Widget 的方式直接在 HTML 中声明来进行数据绑定的操作。这篇文章将重点介绍 Dojo 的这种数据访问和绑定的功能,以及我们如何基于 Dojo 的 Wire 工具包实现自己的数据访问和绑定。

Dojox 的 Wire 工具包简介

Dojox 的 Wire 工具包是 Dojo 中用于数据操作的一组工具集合,它主要用于数据绑定和服务调用。它提供了一组非常强大的 API 让用户能十分方便地访问、更新复杂数据以及向后台服务传值和取值。它也提供了一组声明式绑定数据用法,用于完善先前的只支持编程方式 API 的不足。

Dojox 的 Wire 工具包的基础接口

我们先来看看 Wire 包里面的 create 接口:


清单 1. 接口“dojox.wire.create”
				 
 var wire = dojox.wire.create({}); 
 t.assertTrue(wire instanceof dojox.wire.Wire); 

 wire = dojox.wire.create({property: "a"}); 
 t.assertTrue(wire instanceof dojox.wire.Wire); 

 wire = dojox.wire.create({attribute: "a"}); 
 t.assertTrue(wire instanceof dojox.wire.DataWire); 

 wire = dojox.wire.create({path: "a"}); 
 t.assertTrue(wire instanceof dojox.wire.XmlWire); 

 wire = dojox.wire.create({children: "a"}); 
 t.assertTrue(wire instanceof dojox.wire.CompositeWire); 

 wire = dojox.wire.create({columns: "a"}); 
 t.assertTrue(wire instanceof dojox.wire.TableAdapter); 

 wire = dojox.wire.create({nodes: "a"}); 
 t.assertTrue(wire instanceof dojox.wire.TreeAdapter); 

 wire = dojox.wire.create({segments: "a"}); 
 t.assertTrue(wire instanceof dojox.wire.TextAdapter); 

 wire = dojox.wire.create({wireClass: "dojox.wire.DataWire"}); 
 t.assertTrue(wire instanceof dojox.wire.DataWire); 

可见,通过 Create 接口,我们可以创建出我们所需要的相应 Wire 对象。通过以上实例,我们可以了解一下 Dojo 所支持的所有类型的 Wire 对象:Wire、DataWire、XmlWire、CompositeWire、TableAdapter、TreeAdapter、 TextAdapter、DataWire。我们会在接下来的章节中一一介绍这些对象的用途,用法和使用技巧。

接下来我们再来看看另一个基础接口“transfer”:


清单 2. 接口 transfer
				 
 var source = {a: "A"}; 
 var target = {}; 
 dojox.wire.transfer( 
 {object: source, property: "a"}, 
 {object: target, property: "a"}); 
 t.assertEqual(source.a, target.a); 

这里的 transfer 接口其实起到了一个数据复制的作用,即将“source”的属性“a”的值复制到“target”的属性“a”上。

再来看看“connect”接口:


清单 3. 接口 connect
				 
 var trigger = {transfer: function() {}, transferArgument: function() {}}; 
 var source = {a: "A"}; 
 var target = {}; 
 dojox.wire.connect({scope: trigger, event: "transfer"},
 {object: source, property: "a"}, 
 {object: target, property: "a"});   trigger.transfer();  t.assertEqual(source.a, target.a); 

其实“connect”的接口功能与“transfer”基本一样,但是,它是更深层次的数据复制,它定义了触发这个数据复制行为的方法,即:只有调用该方法才会触发数据复制的行为。这里的“trigger”对象的“transfer”方法就是这种方法。

还可以通过注册 topic 的方式来使用 connect 接口:


清单 4. 接口 connect 基于 topic
				 
 target = {}; 
 dojox.wire.connect({topic: "transfer"}, 
 {object: source, property: "a"}, 
 {object: target, property: "a"}); 
 dojo.publish("transfer"); 
 t.assertEqual(source.a, target.a); 

可以看到,这里可以基于 Dojo 的 publish 方法来使用 connect 接口。

还有,connect 接口还支持参数传递:


清单 5. 接口 connect 使用参数
				 
 target = {}; 
 dojox.wire.connect({scope: trigger, event: "transferArgument"}, 
 {property: "[0].a"}, 
 {object: target, property: "a"}); 
 trigger.transferArgument(source); 
 t.assertEqual(source.a, target.a); 

 target = {}; 
 dojox.wire.connect({topic: "transferArgument"}, 
 {property: "[0].a"}, 
 {object: target, property: "a"}); 
 dojo.publish("transferArgument", [source]); 
 t.assertEqual(source.a, target.a); 

可见,不论是基于对象方法的 connect 接口,还是基于 topic 的 connect 接口,都可以传递参数。这里的“[0]”便是指代的第一个传入参数。

另外,如果需要取消这种关联,使用“disconnect”接口即可:


清单 6. 接口 disconnect
				 
 dojox.wire.disconnect(connection) 

Wire 工具进阶

这一章我们主要来介绍一下稍微复杂一点的 Wire 接口,Dojo 主要将其封装在“dojox.wire.Wire”这个对象里。


清单 7. 数据的关联 Wire
				 
 var source = {a: "A", b: {c: "B.C"}}; 
 var sourceWire = new dojox.wire.Wire({object: source, property: "a"}); 

 source.a = "B"; 
 t.assertEqual("B", sourceWire.getValue()); 

 sourceWire.setValue("C"); 
 t.assertEqual("C", source.a); 

可以看到,“sourceWire”这个对象就像一根电线一样,连接着“source”对象的“a”属性,随时可以取得和设置该属性的 值:“sourceWire.getValue()”和“sourceWire.setValue()”。这种功能很适合用于复杂的对象,我们不用每次都 深入该复杂对象的内部,去访问或修改其内部某个属性的值,而只需要建立一个外部变量与该内部属性的关联(就好象牵了一根线锁定了该属性)就可以了,以后对 该属性的所有操作都可以通过这个外部变量来完成。

再来看看稍微复杂一点的例子:


清单 8. 复杂数据关联 Wire
				 
 target = {}; 
 value = new dojox.wire.Wire({object: source, property: "b.c"}).getValue(); 
 new dojox.wire.Wire({object: target, property: "b.c"}).setValue(value); 
 t.assertEqual(source.b.c, target.b.c); 

 source = {a: ["A"]}; 
 target = {}; 
 value = new dojox.wire.Wire({object: source, property: "a[0]"}).getValue(); 
 new dojox.wire.Wire({object: target, property: "a[0]"}).setValue(value); 
 t.assertEqual(source.a[0], target.a[0]); 

这里我们主要想说明的是,关联的属性值可以是 2 级甚至更多(“b.c”或“b.c.d.e.x.x.x.x.x”),同时,对于数组类型的属性值,同样可以关联 (dojox.wire.Wire({object: source, property: "a[0]"}))。

再来看一组更为新颖的例子:


清单 9. 复杂数据关联 Wire 基于函数
				 
 // by getter/setter 
 source = {getA: function() { return this._a; }, _a: "A"}; 
 target = {setA: function(a) { this._a = a; }}; 
 value = new dojox.wire.Wire({object: source, property: "a"}).getValue(); 
 new dojox.wire.Wire({object: target, property: "a"}).setValue(value); 
 t.assertEqual(source._a, target._a); 

 // by get/setPropertyValue 
 source = {getPropertyValue: function(p) { return this["_" + p]; }, _a: "A"}; 
 target = {setPropertyValue: function(p, v) { this["_" + p] = v; }}; 
 value = new dojox.wire.Wire({object: source, property: "a"}).getValue(); 
 new dojox.wire.Wire({object: target, property: "a"}).setValue(value); 
 t.assertEqual(source._a, target._a); 

从例子中可以看出,这里不论是 source 还是 target 都没有属性“a”,取而代之的是属性“_a”。但是这里我们关联的属性还是“a”,因为“setA”和“getA”方法的存在。同 样,“getPropertyValue”,“setPropertyValue”也有同样的功能。所以这里我们可以看出,数据的关联还可以通过方法来完 成,这种方式也会促使我们的代码变得更加规范。

让我们再深入一点,其实 Dojo 的 wire 还支持直接的类型转换:


清单 10. 复杂数据关联 Wire 类型转换
				 
 //例 1 
 var source = {a: "1"}; 
 var string = new dojox.wire.Wire({object: source, property: "a"}).getValue(); 
 t.assertEqual("11", string + 1); 
 var number = new dojox.wire.Wire( 
   {object: source, property: "a", type: "number"}).getValue(); 
 t.assertEqual(2, number + 1); 

 //例 2 
 var source = {a: "1"}; 
 var converter = {convert: function(v) { return v + 1; }}; 
 var string = new dojox.wire.Wire( 
 {object: source, property: "a", converter: converter}).getValue(); 
 t.assertEqual("11", string); 

 //例 3 
 var source = {a: "1"}; 
 var converter = {convert: function(v) { return v + 1; }}; 
 var number = new dojox.wire.Wire( 
 {object: source,property: "a", type: "number", converter: converter.convert}).getValue(); |-------10--------20--------30--------40--------50--------60--------70--------80--------9| |-------- XML error:  The previous line is longer than the max of 90 characters ---------|  t.assertEqual(2, number); 

 //例 4 
 dojo.declare("dojox.wire.tests.programmatic.Wire.Converter", null, { 
 convert: function(v){ 
 return v + 1; 
 } 
 }); 
 var source = {a: "1"}; 
 var number = new dojox.wire.Wire({ 
 object: source, 
 property: "a", 
 type: "number", 
 converter: "dojox.wire.tests.programmatic.Wire.Converter"}).getValue(); 
 t.assertEqual(2, number); 

例 1:直接将字符串转换为数字类型。

例 2,例 3:通过转换函数“converter.convert”进行转换,这里可以通过函数对关联的值作任何操作,事实上已经超出了类型转换的范畴了。

例 4:可以声明专门的 Dojo 类来完成数据转换功能,这种做法比较符合面向对象的设计思路。

混合类型的 Wire

混合类型的 Wire 其实和基本的 Wire 类似,不同的是它支持一次性关联多个属性。我们先来看看如下示例代码:


清单 11. 混合型的 Wire(对象模式)
				 
 var source = {a: "A", b: "B"}; 
 var target = {}; 
 var children = {x: {property: "a"}, y: {property: "b"}}; 
 var value = new dojox.wire.CompositeWire( 
   {object: source, children: children}).getValue(); 
 t.assertEqual(source.a, value.x); 
 t.assertEqual(source.b, value.y); 
 new dojox.wire.CompositeWire({object: target, children: children}).setValue(value); 
 t.assertEqual(source.a, target.a); 
 t.assertEqual(source.b, target.b); 

注意这里的“children”变量,对于混合类型的 Wire(CompositeWire),它的“getValue”方法拿到的是一个关联数据的对象,这里为“value”,value 的“x”和“y”值分别对应着关联数据(source)的“a”和“b”属性值。

这里我们是通过对象来做数据关联,同样也可以通过数组,参考如下代码:


清单 12. 混合型的 Wire(数组模式)
 
 target = {}; 
 children = [{property: "a"}, {property: "b"}]; 
 value = new dojox.wire.CompositeWire({object: source, children: children}).getValue(); 
 t.assertEqual(source.a, value[0]); 
 t.assertEqual(source.b, value[1]); 
 new dojox.wire.CompositeWire({object: target, children: children}).setValue(value); 
 t.assertEqual(source.a, target.a); 
 t.assertEqual(source.b, target.b); 
			

大家可以看到,这里的“children”为数组,与之前对象方式不同,其关联的数据也通过数组的下标运算符取得:“value[0]”,“value[1]”。

这两种类型的混合型 Wire 均可以通过另一种方式来使用:


清单 13. 混合型的 Wire 新模式
				 
 target = {}; 
 value = new dojox.wire.CompositeWire({children: children}).getValue(source); 
 t.assertEqual(source.a, value.x); 
 t.assertEqual(source.b, value.y); 
 new dojox.wire.CompositeWire({children: children}).setValue(value, target); 
 t.assertEqual(source.a, target.a); 
 t.assertEqual(source.b, target.b); 
 target = {}; 
 value = new dojox.wire.CompositeWire({children: children}).getValue(source); 
 t.assertEqual(source.a, value[0]); 
 t.assertEqual(source.b, value[1]); 
 new dojox.wire.CompositeWire({children: children}).setValue(value, target); 
 t.assertEqual(source.a, target.a); 
 t.assertEqual(source.b, target.b); 			
			

不知道大家是否注意到,这里的混合型 Wire 对象已经变成一个纯的 Wire 对象了,在它的构造器里面我们找不到数据源(source)了,数据源在这里是通过参数传递给 Wire 对象:“(new dojox.wire.CompositeWire({children: children}).getValue(source))”

由此可见,其实 Wire 并不是依赖于数据源而存在的,它可以随时切换数据源对象以便即时取得相应数据源的内部值。就好象一只蚊子,可以随时扎在不同的人身上的不同位置。

基于复杂数据源的 DataWire

之前我们所看到的 Wire 都是针对一些简单的对象类型,接下来我们会看到建立在复杂数据源上面的 Wire,即 DataWire:


清单 14. 基于复杂数据源的 DataWire
				 
 var store = new dojox.data.XmlStore(); 
 var item = store.newItem({tagName: "x"}); 
 new dojox.wire.DataWire({dataStore: store, object: item, attribute: "y"}).setValue("Y"); 
 var value = new dojox.wire.DataWire( 
     {dataStore: store, object: item, attribute: "y"}).getValue(); 
 t.assertEqual("Y", value); 

 // 嵌套属性
 new dojox.wire.DataWire({dataStore: store,object: item,attribute: "y.z"}).setValue("Z"); 
 value = new dojox.wire.DataWire( 
     {dataStore: store, object: item, attribute: "y.z"}).getValue(); 
 t.assertEqual("Z", value); 

通过这个例子我们可以看到,其实它的用法与之前介绍的 Wire 和 CompositeWire 基本类似,只是它是建立在一个 store 对象的某个 item 上面。这里它对 tagName 为“x”的 item 加入了“y”属性和“z”属性,并分别赋予其“Y”值和“Z”值。

这种功能非常实用,我们不用再每次去深入到 store 内部查找和获取某个内部属性的值,直接通过 DataWire 来操作即可。

同样,它也能直接设置和获取嵌套属性,见后半段代码,这里对 item 的“y.z”属性赋值和取值,其实就是“item.y.z”的取值和赋值。Wire 支持这种嵌套属性的直接生成和赋 / 取值。

这里需要强调的是,对数据关联到 DataWire 对象所做出的任何修改都是永久性的,也就是说数据源也会被改变,Wire 和 CompositeWire 也都是如此,所以大家一定要慎用。如果你仅仅是想克隆一份数据,请千万别用 Wire 的方式。

基于 Path 的 XMLWire

我们的 Wire 还支持通过 Path(类似于 XPATH)的方式来定位和操作数据,这是一个非常强大的功能,尤其是针对复杂数据源。


清单 15. XMLWire 基本用法
				 
 var object = {}; 
 var wire = dojox.wire.create({object: object, property: "element"}); 
 new dojox.wire.XmlWire({object: wire, path: "/x/y/text()"}).setValue("Y"); 
 var value = 
 new dojox.wire.XmlWire({object:object,property: "element",path: "y/text()"}).getValue(); 
 t.assertEqual("Y", value); 

从上上述示例中我们可以看到一个熟悉的标识:“path: "/x/y/text()"”,是不是很像 XPATH ?不错,这里就是 Path 定位。我们给 object 这个对象加上了“x”元素,同时在 x 元素下面加上了“y”元素,然后给它赋值“Y”。取值的时候直接通过 Path:“"y/text()"”即可取得刚刚设定的值。完全符合 XPATH 的操作模式。这种方式很适用于复杂数据的快速定位。

XMLWire 不仅支持赋值,还支持对于属性的操作和基于索引(index)的定位,见如下示例:


清单 16. XMLWire 基本用法进阶
				 
 // attribute 
 new dojox.wire.XmlWire( 
 {object: object, property: "element", path: "y/@z"}).setValue("Z"); 
 value = new dojox.wire.XmlWire({object: wire, path: "/x/y/@z"}).getValue(); 
 t.assertEqual("Z", value); 

 // with index 
 var document = object.element.ownerDocument; 
 var element = document.createElement("y"); 
 element.appendChild(document.createTextNode("Y2")); 
 object.element.appendChild(element); 
 value = new dojox.wire.XmlWire( 
 {object: object.element, path: "y[2]/text()"}).getValue(); 
 t.assertEqual("Y2", value); 

与之前 Path 的操作方式一样,加上“@”即可实现对于属性的关联:“path: "y/@z"”。

同样,通过数组下标运算符,可以实现通过索引(index)的定位:“"y[2]/text()"”。

数据适配器 Adapter

适配器其实也有点类似于之前介绍的数据关联(Wire),它们都是 CompositeWire 的一种扩展。但是它们不再是简单的关联,而是会把关联的数据转换成一种新的形势展现出来。Dojo 提供三种适配器:TextAdapter,TableAdapter,TreeAdapter。我们先来看看 TextAdapter。


清单 17. 简单的 TextAdapter
				 
 var source = {a: "a", b: "b", c: "c"}; 
 var segments = [{property: "a"}, {property: "b"}, {property: "c"}]; 
 var value = new dojox.wire.TextAdapter({object: source, segments: segments}).getValue(); 
 t.assertEqual("abc", value); 

可以看到,这里的 TextAdapter 将所有关联的属性“a”,“b”,“c”做了一个连接并最终变为“abc”。它主要适用于取值,对于赋值“setValue”它是不支持的。

当然,它还可以做一些连接上的设置:


清单 18. 简单的 TextAdapter(连接符)
				 
 var source = {a: "a", b: "b", c: "c"}; 
 var segments = [{property: "a"}, {property: "b"}, {property: "c"}]; 
 var value = new dojox.wire.TextAdapter( 
   {object: source, segments: segments, delimiter: "/"}).getValue(); 
 t.assertEqual("a/b/c", value); 

这里我们给它连接符(分隔符)“delimiter: "/"”,所得到的结果就是“"a/b/c"”。

再来看看 TableAdapter:


清单 19. TableAdapter 示例
				 
 var source = [ 
 {a: "A1", b: "B1", c: "C1"}, 
 {a: "A2", b: "B2", c: "C2"}, 
 {a: "A3", b: "B3", c: "C3"} 
 ]; 
 var columns = {x: {property: "a"}, y: {property: "b"}, z: {property: "c"}}; 
 var value = new dojox.wire.TableAdapter({object: source, columns: columns}).getValue(); 
 t.assertEqual(source[0].a, value[0].x); 
 t.assertEqual(source[1].b, value[1].y); 
 t.assertEqual(source[2].c, value[2].z); 

还记得我们之前的 CompositeWire 的例子吗? CompositeWire 支持对象和数组两种模式的数据关联,而这里是两种同时存在。它有点类似于表格模式的匹配,所以被称为“TableAdapter”。注意其访问方式 (“value[1].y”等等)与 source 一致。注意:这里的 TableAdapter 也不支持修改操作“setValue”。

最后,我们来看看 TreeAdapter,它也是最为复杂的一种适配器(adapter):


清单 20. TreeAdapter 示例
				 
 var source = [ 
 {a: "A1", b: "B1", c: "C1"}, 
 {a: "A2", b: "B2", c: "C2"}, 
 {a: "A3", b: "B3", c: "C3"} 
 ]; 
 var nodes = [ 
 {title: {property: "a"}, children: [ 
 {node: {property: "b"}}, 
 {title: {property: "c"}} 
 ]} 
 ]; 
 var value = new dojox.wire.TreeAdapter({object: source, nodes: nodes}).getValue(); 
 t.assertEqual(source[0].a, value[0].title); 
 t.assertEqual(source[1].b, value[1].children[0].title); 
 t.assertEqual(source[2].c, value[2].children[1].title); 

这里我们还是使用 TableAdapter 的数据源,但是关联之后对该数据源的访问方式变 了:“value[0].title”,“value[1].children[0].title”,“value[2].children[1].title”。 这里我们可以看出,对原始表格型数据的访问已经变成对树型数据源的访问。其实从这个例子我们可以看出,对于很多种数据格式,我们都可以通过 Wire 或者 Adapter 将其转换为我们想要的格式。注意:这里的 TableAdapter 同样不支持修改操作“setValue”。

声明性的数据绑定

之前介绍的都是通过脚本的方式来创建关联,接下来我们来看看如何通过纯声明的方式来建立关联。Dojo 将这些接口都包装在“dojox.wire.ml”包内。

基础接口

先来看一个简单的接口 Action:“dojox.wire.ml.Action”:


清单 21. Action 接口示例
				 
 dojox.wire.ml.tests.markup.Action = { 
 transfer: function(){}, 
 source: {a: "A", b: "B"} 
 }; 

 <div dojoType="dojox.wire.ml.Action"
 trigger="dojox.wire.ml.tests.markup.Action"
 triggerEvent="transfer"> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="dojox.wire.ml.tests.markup.Action.source.a"
 target="dojox.wire.ml.tests.markup.Action.target.a"></div> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="dojox.wire.ml.tests.markup.Action.source.b"
 target="dojox.wire.ml.tests.markup.Action.target.b"></div> 
 </div> 


 dojox.wire.ml.tests.markup.Action.target = {}; 
 dojox.wire.ml.tests.markup.Action.transfer(); 
 t.assertEqual(dojox.wire.ml.tests.markup.Action.source.a, 
     dojox.wire.ml.tests.markup.Action.target.a); 
 t.assertEqual(dojox.wire.ml.tests.markup.Action.source.b, 
     dojox.wire.ml.tests.markup.Action.target.b); 

注意中间的一段 HTML 代码,这里我们通过 HTML 直接声明了数据的关联,它的触发器(trigger)为“dojox.wire.ml.tests.markup.Action”, 触发事件为“transfer”。然后通过“dojox.wire.ml.Transfer”类将 source 的“a”和“b”分别和 target 的“a”和“b”进行关联。一旦“dojox.wire.ml.tests.markup.Action”对象调用“transfer”方法,则 source 和 target 随即建立关联。其作用同基本的 Wire(dojox.wire.Wire)一样。

同样,声明方式的关联也支持通过 topic 方式建立关联,这里不做赘述。

接下来我们看看更为复杂的情况,Action 可以基于条件来选择是否实现关联。参见如下示例:


清单 22. Action 接口进阶(ActionFilter)
				 
 <div dojoType="dojox.wire.ml.Action"
 triggerTopic="transferFilter"> 
 <div dojoType="dojox.wire.ml.ActionFilter"
 required="dojox.wire.ml.tests.markup.Action.required"  message="no required"
 error="dojox.wire.ml.tests.markup.Action.error"></div> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="dojox.wire.ml.tests.markup.Action.source.a"
 target="dojox.wire.ml.tests.markup.Action.target.a"></div> 
 </div> 

 <div dojoType="dojox.wire.ml.Action"
 triggerTopic="transferFilterNumber"> 
 <div dojoType="dojox.wire.ml.ActionFilter"
 required="dojox.wire.ml.tests.markup.Action.value"  requiredValue="20"  type="number"> 
 </div> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="dojox.wire.ml.tests.markup.Action.source.a"
 target="dojox.wire.ml.tests.markup.Action.target.a"></div> 
 </div> 

 <div dojoType="dojox.wire.ml.Action"
 triggerTopic="transferFilterBoolean"> 
 <div dojoType="dojox.wire.ml.ActionFilter"
 required="dojox.wire.ml.tests.markup.Action.value"  requiredValue="true"  type="boolean"> 
 </div> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="dojox.wire.ml.tests.markup.Action.source.a"
 target="dojox.wire.ml.tests.markup.Action.target.a"></div> 
 </div> 

参考如上 3 段代码,均为有条件的关联:

1. “dojox.wire.ml.tests.markup.Action.required”必须赋值,否则不关联,错误消息(message)为“no required”。

2. “dojox.wire.ml.tests.markup.Action.value”值必须为数字 20。

3. “dojox.wire.ml.tests.markup.Action.value”值必须为布尔值 true。

我们可以看一个测试案例来了解一下有条件的关联是如何工作的:


清单 23. Action 接口进阶(ActionFilter)工作模式
				 
 <div dojoType="dojox.wire.ml.Action"
 triggerTopic="transferFilterString"> 
 <div dojoType="dojox.wire.ml.ActionFilter"
 required="dojox.wire.ml.tests.markup.Action.value"
 requiredValue="executeThis"> 
 </div> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="dojox.wire.ml.tests.markup.Action.source.a"
 target="dojox.wire.ml.tests.markup.Action.target.a"></div> 
 </div> 


 dojox.wire.ml.tests.markup.Action.target = {}; 
 dojox.wire.ml.tests.markup.Action.value = null; 
 dojo.publish("transferFilterString"); 

 t.assertEqual(undefined, dojox.wire.ml.tests.markup.Action.target.a); 

 dojox.wire.ml.tests.markup.Action.value = "executeThis"; 
 dojo.publish("transferFilterString"); 
 t.assertEqual(dojox.wire.ml.tests.markup.Action.source.a, 
       dojox.wire.ml.tests.markup.Action.target.a); 

可见,在我们没有设置“dojox.wire.ml.tests.markup.Action.value”的值为 “executeThis”的时候,即便是我们触发转换 topic 也无济于事,“dojox.wire.ml.tests.markup.Action.target.a”的值保持为“undefined”。只有当其被 赋值“executeThis”之后,关联才被建立起来。

再来看一个基于显示声明数据(HTML Data)的 Action:


清单 24. Action 接口与 Data 协同
				 
 dojox.wire.ml.tests.markup.Data = {}; 

 <div dojoType="dojox.wire.ml.Data"  id="Data1"> 
 <div dojoType="dojox.wire.ml.DataProperty"
 name="a"
 value="A"></div> 
 <div dojoType="dojox.wire.ml.DataProperty"
 name="b"
 type="number" value="1"></div> 
 <div dojoType="dojox.wire.ml.DataProperty"
 name="c"
 type="boolean" value="true"></div> 
 <div dojoType="dojox.wire.ml.DataProperty"
 name="d"
 type="object"> 
 <div dojoType="dojox.wire.ml.DataProperty"
 name="a"
 value="DA"></div> 
 <div dojoType="dojox.wire.ml.DataProperty"
 name="b"
 value="DB"></div> 
 </div> 
 <div dojoType="dojox.wire.ml.DataProperty"
 name="e"
 type="array"> 
 <div dojoType="dojox.wire.ml.DataProperty"
 value="E1"></div> 
 <div dojoType="dojox.wire.ml.DataProperty"
 value="E2"></div> 
 </div> 
 <div dojoType="dojox.wire.ml.DataProperty"
 name="f"
 type="element"
 value="x"> 
 <div dojoType="dojox.wire.ml.DataProperty"
 name="text()"
 value="F"></div> 
 <div dojoType="dojox.wire.ml.DataProperty"
 name="@y"
 value="G"></div> 
 </div> 
 </div> 
 <div dojoType="dojox.wire.ml.Action"
 triggerTopic="transfer"> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="Data1.a"
 target="dojox.wire.ml.tests.markup.Data.target.a"></div> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="Data1.b"
 target="dojox.wire.ml.tests.markup.Data.target.b"></div> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="Data1.c"
 target="dojox.wire.ml.tests.markup.Data.target.c"></div> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="Data1.d"
 target="dojox.wire.ml.tests.markup.Data.target.d"></div> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="Data1.e"
 target="dojox.wire.ml.tests.markup.Data.target.e"></div> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="Data1.f"
 target="dojox.wire.ml.tests.markup.Data.target.f"></div> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="Data1.f.@y"
 target="dojox.wire.ml.tests.markup.Data.target.g"></div> 
 </div> 

这里的代码可能有点多,其实主要就是两个对象:

通过“dojox.wire.ml.Data”显示声明了一个数据源“Data1”,大家可以稍微浏览一下这个数据源,它是一个大型的对象类型数据,里面包含了布尔,字符串,对象,数组等等子类型。

通过“dojox.wire.ml.Action”显示关联“dojox.wire.ml.Data”数据源到“dojox.wire.ml.tests.markup.Data”变量上。

通过以上两步的操作,我们已经可以随时触发数据的关联并开始相应的数据操作了。参考使用方式如下:


清单 25. Action 接口与 Data 协同测试案例
				 
 dojox.wire.ml.tests.markup.Data.target = {}; 
 dojo.publish("transfer"); 
 t.assertEqual("A", dojox.wire.ml.tests.markup.Data.target.a); 
 t.assertEqual(1, dojox.wire.ml.tests.markup.Data.target.b); 
 t.assertEqual(true, dojox.wire.ml.tests.markup.Data.target.c); 
 t.assertEqual("DA", dojox.wire.ml.tests.markup.Data.target.d.a); 
 t.assertEqual("DB", dojox.wire.ml.tests.markup.Data.target.d.b); 
 t.assertEqual("E1", dojox.wire.ml.tests.markup.Data.target.e[0]); 
 t.assertEqual("E2", dojox.wire.ml.tests.markup.Data.target.e[1]); 
 t.assertEqual("F", dojox.wire.ml.tests.markup.Data.target.f); 
 t.assertEqual("G", dojox.wire.ml.tests.markup.Data.target.g); 

相信大家通过前面的例子也能够看到,声明式 Wire 中用于数据关联匹配的对象为“dojox.wire.ml.Transfer”。之前大家也看到了一些简单的 Transfer 对象,接下来我们再来看几个稍微复杂一点的 Transfer:


清单 26. 复杂 Transfer
				 
 <div dojoType="dojox.wire.ml.Action"
 triggerTopic="transferData"> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="dojox.wire.ml.tests.markup.Transfer.source.a"
 target="dojox.wire.ml.tests.markup.Transfer.item"
 targetStore="dojox.wire.ml.tests.markup.Transfer.store"
 targetAttribute="y"></div> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="dojox.wire.ml.tests.markup.Transfer.item"
 sourceStore="dojox.wire.ml.tests.markup.Transfer.store"
 sourceAttribute="y"
 target="dojox.wire.ml.tests.markup.Transfer.target.a"></div> 
 </div> 

 <div dojoType="dojox.wire.ml.Action"
 triggerTopic="transferXml"> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="dojox.wire.ml.tests.markup.Transfer.source.a"
 target="dojox.wire.ml.tests.markup.Transfer.element"
 targetPath="y/text()"></div> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="dojox.wire.ml.tests.markup.Transfer.element"
 sourcePath="y/text()"
 target="dojox.wire.ml.tests.markup.Transfer.target.a"></div> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="dojox.wire.ml.tests.markup.Transfer.source.b"
 target="dojox.wire.ml.tests.markup.Transfer.element"
 targetPath="y/@z"></div> 
 <div dojoType="dojox.wire.ml.Transfer"
 source="dojox.wire.ml.tests.markup.Transfer.element"
 sourcePath="y/@z"
 target="dojox.wire.ml.tests.markup.Transfer.target.b"></div> 
 </div> 

以上两段代码分别为 Data 数据类型的显示 Wire 和 XML 类型的显示 Wire,大家可以仔细看看里面的参数设置,是不是有一种似曾相识的感觉,不错,这里的设置与我们之前介绍的 DataWire 和 XMLWire 基本一致,不同的仅仅是这里通过 HTML 显示声明这种关联。

我们再来看看一下两个示例:


清单 27. 复杂 Transfer 进阶
				 
 <div dojoType="dojox.wire.ml.Transfer"
 triggerTopic="transferTable"
 source="dojox.wire.ml.tests.markup.Transfer.source.c"
 target="dojox.wire.ml.tests.markup.Transfer.target.a"> 
 <div dojoType="dojox.wire.ml.ColumnWire"
 column="b"
 property="d"></div> 
 <div dojoType="dojox.wire.ml.ColumnWire"
 column="c"
 property="e"></div> 
 </div> 
 <div dojoType="dojox.wire.ml.Transfer"
 triggerTopic="transferTree"
 source="dojox.wire.ml.tests.markup.Transfer.source.c"
 target="dojox.wire.ml.tests.markup.Transfer.target.a"> 
 <div dojoType="dojox.wire.ml.NodeWire"
 titleProperty="d"> 
 <div dojoType="dojox.wire.ml.NodeWire"
 titleProperty="e"></div> 
 </div> 
 </div> 

是不是又有一种似曾相识的感觉?不错,它就是我们之前介绍的 TableAdapter 和 TreeAdapter 的 HTML 版本。这里的“ColumnWire”和“NodeWire”对象正是对应着之前脚本模式下面的“columns”参数和“nodes”参数。注意:并非 只有 Action 可以定义起始触发事件,Transfer 本身也可以定义起始触发事件。

使用进阶

接下来我们会介绍一些更为新颖的用法,先从 Invocation 入手,Invocation(dojox.wire.ml.Invocation)是一个专门用来触发事件或者发布 topic 的 widget,来看看如下一组示例:


清单 28. Invocation 示例
				 
 dojox.wire.ml.tests.markup.Invocation = { 
 invoke: function(p1, p2){return p1 + p2;}, 
 invokeError: function(p){throw new Error(p);}, 
 parameters: {a: "A", b: "B", c: "C"} 
 }; 

  <div dojoType="dojox.wire.ml.Invocation" 
  triggerTopic="invokeMethod" 
  object="dojox.wire.ml.tests.markup.Invocation" 
  method="invoke" 
  parameters= 
"dojox.wire.ml.tests.markup.Invocation.parameters.a, 
 dojox.wire.ml.tests.markup.Invocation.parameters.b" 
  result="dojox.wire.ml.tests.markup.Invocation.result"></div> 

 dojo.publish("invokeMethod"); 
 t.assertEqual("AB", dojox.wire.ml.tests.markup.Invocation.result); 

看中间那段代码,这里定义了一个触发器,一旦我们发布 topic -- “invokeMethod”,系统便会调用“dojox.wire.ml.tests.markup.Invocation”对象的“invoke”方 法,并将“parameters”后面的参数当作函数实参传入,并将结构返回到 “dojox.wire.ml.tests.markup.Invocation.result”对象中。这种触发器在操作一些大型数据源时非常有用。

接下来我们来看一个使用 Invocation 的 DataStore 示例:


清单 29. 基于 Invocation 的 DataStore 的操作
				 
 //code block 1 
 dojox.wire.ml.tests.markup.DataStore = { 
 request: {onComplete: function(){}, onError: function(){}} 
 }; 

 //code block 2 
 <div dojoType="dojox.wire.ml.DataStore"
 id="DataStore1"
 storeClass="dojox.data.XmlStore"
 url="DataStore.xml"></div> 

 //code block 3 
 <div dojoType="dojox.wire.ml.Invocation"
 triggerTopic="invokeFetch"
 object="DataStore1"
 method="fetch"
 parameters="dojox.wire.ml.tests.markup.DataStore.request"> 
 </div> 

 //code block 4 
 <div dojoType="dojox.wire.ml.Transfer"
 trigger="dojox.wire.ml.tests.markup.DataStore.request"
 triggerEvent="onComplete"
 source="arguments[0]"
 sourceStore="DataStore1.store"
 target="dojox.wire.ml.tests.markup.DataStore.target"> 
 <div dojoType="dojox.wire.ml.ColumnWire"
 column="a" attribute="x"></div> 
 <div dojoType="dojox.wire.ml.ColumnWire"
 column="b" attribute="y"></div> 
 <div dojoType="dojox.wire.ml.ColumnWire"
 column="c" attribute="z"></div> 
 </div> 

可能这段代码不是很容易理解,我们来一一解读:

1. 先看第一段代码,我们定义了一个“DataStore ”对象,它包含“request”属性及其中的“onComplete”和“onError”方法。

2. 再来看第二段,这里是定义了一个 XML 数据源“DataStore.xml”,标识(id)为:DataStore1。

3. 第三段则定义了一个 Invocation,当我们触发“invokeFetch”的 topic 时,便会触发 DataStore1 通过“fetch”方法去有条件的去服务端去数据,并以“dojox.wire.ml.tests.markup.DataStore.request” 作为传入参数。当数据返回后,便会调用“dojox.wire.ml.tests.markup.DataStore.request”对象的 “onComplete”方法,并将返回值作为函数的实参传入。

4. 最后定义了一个 Transfer 对象,这个对象是当“dojox.wire.ml.tests.markup.DataStore.request”对象的“onComplete”方法 调用时便会触发关联,将“DataStore1.store”取值的返回值的第 0 个参数“arguments[0]”作为数据源,关联到“dojox.wire.ml.tests.markup.DataStore.target”对 象上,通过表格关联的方式。

所以,我们可以看到,看似非常复杂的逻辑流程,其实是可以通过清晰的 HTML 定义来实现的。

通过如下代码可以了解该关联的触发和验证过程:


清单 30. DataStore 操作的触发和验证
				 
 dojo.connect(dojox.wire.ml.tests.markup.DataStore.request, "onComplete", function(){ 
  t.assertEqual("X1", dojox.wire.ml.tests.markup.DataStore.target[0].a); 
  t.assertEqual("Y2", dojox.wire.ml.tests.markup.DataStore.target[1].b); 
  t.assertEqual("Z3", dojox.wire.ml.tests.markup.DataStore.target[2].c); 
 d.callback(true); 
 }); 

 dojo.connect(dojox.wire.ml.tests.markup.DataStore.request, "onError", function(error){ 
 d.errback(error); 
 }); 

 dojo.publish("invokeFetch"); 

最后,我们来介绍一下最为复杂的一种新用法:“Service”,它其实是 RPC 的一种应用(有兴趣的读者可以先去看看 Dojo 的 RPC 包“dojox.rpc”了解一下),来看一下在 Wire 是如何使用它的:


清单 31. Service 示例
				 
 //code block 1 
 dojox.wire.ml.tests.markup.Service = { 
 query: {name: "a"} 
 }; 

 //code block 2 
 <div dojoType="dojox.wire.ml.Service"
 id="Service1"
 url="Service/XML.smd"></div> 

 //code block 3 
 <div dojoType="dojox.wire.ml.Invocation"
 id="Invocation1"
 triggerTopic="invokeGetXml"
 object="Service1"
 method="get"
 parameters="dojox.wire.ml.tests.markup.Service.query"> 
 </div> 

 //code block 4 
 <div dojoType="dojox.wire.ml.Transfer"
 trigger="Invocation1"
 triggerEvent="onComplete"
 source="arguments[0].item.name"
 target="dojox.wire.ml.tests.markup.Service.target.a"></div> 

 //code block 5 
 dojo.connect(dijit.byId("Invocation1"), "onComplete", function(result){ 
 t.assertEqual("a", dojox.wire.ml.tests.markup.Service.target.a); 
 var o = result.toObject(); 
 t.assertEqual("a", o.item.name); // test XmlElement.toObject() 
 t.assertEqual("b", o.item.data); // test XmlElement.toObject() 

 d.callback(true); 
 }); 
 dojo.connect(dijit.byId("Invocation1"), "onError", function(error){ 
 d.errback(error); 
 }); 
 dojo.publish("invokeGetXml"); 

这段代码与之前的 DataStore 操作的是非常相似的,我们来一一解读:

1. 第一段代码我们声明了一个 Service 变量,用于之后的查询参数。

2. 第二段代码我们声明了一个 Service,指向“Service/XML.smd”数据源(smd 实为一段数据取值方式的描述),标识为“Service1”。

3. 声明一个 Invocation 用于调用“Service1”的“get”方法来取值,并以之前定义的变量“dojox.wire.ml.tests.markup.Service.query”作为传入参数。

4. 定义 Transfer 对象,以实现当“onComplete”被调用时关联数据源“arguments[0].item.name”到目标数据“dojox.wire.ml.tests.markup.Service.target.a”上。

5. 最后,第五段代码用来验证之前的声明式关联的正确与否。

Service 还支持参数的传递,你可以通过参数来决定使用哪个 smd 文件。

Wire 应用实例

最后,我们通过一个实例来看看 Wire 的用途,下面是一个输入框同步的案例,见下图:


图 1. 同步输入框(action chaining)
图 1. 同步输入框(action chaining)

由图可见,在编辑第一个输入框时,第二个和第三个输入框随即变化,这是一个动作链,在 Dojo 的 Wire 包的支持下,只需要做一些简单的配置即可实现。参考如下代码:


清单 32. 同步输入框代码示例
				 
 //code block 1 
 <div dojoType="dijit.form.TextBox" id="inputField" value="" 
     size="50" intermediateChanges="true"></div> 
 <div dojoType="dijit.form.TextBox" id="targetField1" value="" 
    disabled="true" size="50"></div> 
 <div dojoType="dijit.form.TextBox" id="targetField2" value="" 
    disabled="true" size="50"></div> 

 //code block 2 
 <div dojoType="dojox.wire.ml.Data"
 id="data"> 
 <div dojoType="dojox.wire.ml.DataProperty"
 name="tempData"
 value=""> 
 </div> 
 <div dojoType="dojox.wire.ml.DataProperty"
 name="value"
 value="value"> 
 </div> 
 </div> 

 //code block 3 
 <div dojoType="dojox.wire.ml.Action"
 id="action1"
 trigger="inputField"
 triggerEvent="onChange"> 
 <div dojoType="dojox.wire.ml.Transfer" source="inputField.value" 
     target="data.tempData"></div> 
 <div dojoType="dojox.wire.ml.Invocation" id="targetCopy" object="targetField1"  
 method="set" parameters="data.value, data.tempData"></div> 
 </div> 

 //code block 4 
 <div dojoType="dojox.wire.ml.Action"
 id="action2"
 trigger="targetCopy"
 triggerEvent="onComplete"> 
 <div dojoType="dojox.wire.ml.Transfer" source="targetField1.value" 
   target="targetField2.value"></div> 
 </div> 

我们来一一解读:

1. 第一段代码主要是三个输入框的声明:inputField,targetField1,targetField2。

2. 第二段是一个声明式数据,标识为“data”,有两个属性:“tempData”和“value”,其值分别为空值和“value”。

3. 第三段便开始声明动作链了,这里声明了动作链中的第一个动作,标识为“action1”。它的触发源为“inputField”,即第一个输入框。触发动 作为“onChange”,这个方法相信大家不陌生了。然后我们重点来看看它的 Transfer:在用户输字符入后,会触发输入框的“onChange”事件,进而实现这里的数据关联,即:输入值 “inputField.value”会立即写入临时数据“data.tempData”中。然后,“onChange”时间还会启动这里的 Invocation,使得“targetField1”这个 widget 调用“set”方法,第一个实参为“data.value”变量(其值为 value),第二个实参则为“data.tempData”,也就是刚才的输入值,相当于调用了“targetField1.set('value', data.tempData)”。所以通过这个关联,我们实现了第一个输入框和第二个输入框内容的同步。

4. 第四段声明了动作链中的第二个动作,即当之前的 Invocation 对象“targetCopy”的“onComplete”调用时,会触发第二个输入框(targetField1)和第三个输入框 (targetField2)的同步。“onComplete”会在 targetField1 的 set 方法调用完毕且返回后,立即调用。

至此,我们的动作链便构造完成。当然,Dojo 里面还有很多关于 Wire 的应用实例,这里由于篇幅的限制,不在进一步深入,有兴趣的读者可以自己深入了解一下 Dojo 的 Wire 包里面的案例,还有它的“demo”目录下的实例。

Dojo 的 Wire 工具包主要用在针对大型数据(如大型 JSON,XML 等等)的维护和管理上。在很多情况下,我们只需要操作这个大型数据的某一小块,比如我们只需要获取或修改产品数据里面的价格,而不需要访问任何其它产品信 息(产量,重量,体积等等),而这个时候我们又只能取得该大型产品数据,检索出我们要的价格信息并作相应的修改。如果我们用 Wire 事先绑定可能经常用到的产品价格数据,我们不就拥有了直接通往关键信息的钥匙吗?

结束语

这篇文章介绍了 Dojo 开发中关于 Wire 的工具包,从基本的 Wire 接口作为切入点,进而谈及到复杂的 Wire,混合类型的 Wire(CompositeWire),复杂数据源的 Wire 到基于 Path 的 XMLWire。然后有介绍了基于 CompositeWire 的 Adapter,用于格式化检索的数据。然后,深入的介绍了一下声明性的数据 Wire,从 Action,Transfer 两个方向说明了声明型的 Wire 的使用方式,并基于 Action 和 Transfer 阐述了一下 Invocation,DataStore 以及 Service 的使用方式,并和脚本方式的 Wire 做了比较。最后基于一个应用实例说明了 Action 链的构造和使用方式。这些内容我们可以在开发过程中多关注一下,以尽可能多的完善我们的 Web 应用。

文章出处: IBM developerWorks

加载中
返回顶部
顶部