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