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

前端之家收集整理的这篇文章主要介绍了深入了解 Dojo 的数据访问和绑定工具包前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

本文首发于: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,

{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"});

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"},property: "a"});

trigger.transferArgument(source);

t.assertEqual(source.a,target.a);

target = {};

dojox.wire.connect({topic: "transferArgument"},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,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,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,

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",

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,value[0]);

t.assertEqual(source.b,value[1]);

new dojox.wire.CompositeWire({object: target,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.y);

new dojox.wire.CompositeWire({children: children}).setValue(value,target);

t.assertEqual(source.a,target.b);

target = {};

value = new dojox.wire.CompositeWire({children: children}).getValue(source);

t.assertEqual(source.a,value[1]);

new dojox.wire.CompositeWire({children: children}).setValue(value,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,attribute: "y"}).getValue();

t.assertEqual("Y",value);

// 嵌套属性

new dojox.wire.DataWire({dataStore: store,attribute: "y.z"}).setValue("Z");

value = new dojox.wire.DataWire(

{dataStore: store,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,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",{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",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"),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"),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 应用。

猜你在找的Dojo相关文章