当前位置 : 主页 > 网页制作 > Dojo >

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

来源:互联网 收集:自由互联 发布时间:2021-06-15
本文首发于:http://www.ibm.com/developerworks/cn/web/1203_zhouxiang_dojoxwire/ 作为 Dojo 这样一个强大且全面的 web 开发控件库中的一部分,它提供了很多简单且功能完善的 API。通过这些 API,我们能

本文首发于:http://www.ibm.com/developerworks/cn/web/1203_zhouxiang_dojoxwire/


作为 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();

 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. 同步输入框(actionchaining)
 

由图可见,在编辑第一个输入框时,第二个和第三个输入框随即变化,这是一个动作链,在 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 应用。

网友评论