本文首发于: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 应用。