English Version: http://dflying.dflying.net/1/archive/109_atlas_unleashed-bindings.html
Atlas架构提供了一种比ASP.NET中数据绑定(data binding)强大得多的客户端绑定模型。这种模型异常灵活,甚至有些类似WPF(Windows Presentation Foundation)中的绑定模型。Atlas提供的绑定模型允许您将某对象的任意一个属性绑定到另外一个对象的任意一个属性上。它不单单可以应用于数据绑定,甚至可以将某个控件的样式绑定到另外一个控件上。这样使得在Atlas中将一切关联起来变成可能。
在这个帖子中,我将尝试分析一些Atlas实现代码来解释Atlas是如何完成Binding的。
首先让我们察看一小段应用Atlas Binding的代码。这里将一个textbox的text属性和一个select list的selectedValue属性绑定起来。无论你改变其中的哪个,在另一个上面都会有立刻得到体现。
HTML和ASPX,定义textbox和select list。(注意必须声明一个ScriptManager服务器端对象,以引入Atlas必须的JavaScript文件。)
<atlas:ScriptManager ID="ScriptManager1" runat="server" />
<div>
Input an integer from 1 to 5.<br />
<input id="myTextBox" type="text" /><br />
Select an item.<br />
<select id="mySelect">
<option value="1">value 1</option>
<option value="2">value 2</option>
<option value="3">value 3</option>
<option value="4">value 4</option>
<option value="5">value 5</option>
</select>
</div>
Atlas脚本,将上面两个HTML控件“升级”成Atlas控件。
<page xmlns:script="http://schemas.microsoft.com/xml-script/2005">
<references>
</references>
<components>
<textBox id="myTextBox">
<bindings>
<binding dataContext="mySelect" dataPath="selectedValue" property="text" direction="InOut" />
</bindings>
</textBox>
<select id="mySelect" />
</components>
</page>
如上所示,我们只需要书写一小段简单的代码即可实现需要的绑定功能。
Atlas是如何实现这些的呢?首先,Atlas需要有一种途径来监听绑定控件的绑定属性的变化(除非你不需要Atlas提供的自动绑定功能)。在Atlas.js中定义了一个名为Sys.INotifyPropertyChanged的接口,类似.NET中提供的一样。对象可以实现这个接口以期让别的对象监听到自己的属性值的变化。Atlas中所有组件的基类,Sys.Component,实现了这个接口。Sys.Component同样提供一个方法raisePropertyChanged(propertyName),这个方法应该在每个属性的setter中被调用以发出INotifyPropertyChanged.propertyChanged事件。
目前为止,我们可以看一下Atlas控件中textbox的具体实现。看看textbox中是如何在相应的HTML事件发出时同样发出propertyChanged事件的。
var _text;
var _changeHandler;
this.get_text = function()
{
return this.element.value;
}
this.set_text = function(value)
{
if (this.element.value != value)
{
this.element.value = value;
this.raisePropertyChanged('text');
}
}
this.initialize = function()
{
Sys.UI.TextBox.callBaseMethod(this, 'initialize');
_text = this.element.value;
_changeHandler = Function.createDelegate(this, this._onChanged);
this.element.attachEvent('onchange', _changeHandler);
_keyPressHandler = Function.createDelegate(this, this._onKeyPress);
this.element.attachEvent('onkeypress', _keyPressHandler);
}
this._onChanged = function()
{
if (this.element.value != _text)
{
_text = this.element.value;
this.raisePropertyChanged('text');
}
}
可以看到,当text属性改变时,Atlas发出了propertyChanged事件,这就使绑定到这个属性成为可能。
而后Atlas的绑定模型捕获到了这个事件,再根据binding声明查找出与其相关的目的对象以及相应的属性,并调用这个属性的Setter来实现目的对象属性的变化。
如果源对象(source object)实现了INotifyPropertyChanged接口,并且改变的属性就是dataPath 中指定的属性,同时direction 设定为In或者InOut,Atlas绑定将通过分析引入(incoming)的binding来处理propertyChanged事件(参考下面将要介绍的evaluateIn()方法)。
类似的,如果目的对象(target object)实现了INotifyPropertyChanged接口,并且改变的属性就是property中指定的属性,同时direction 设定为Out或者InOut,Atlas绑定将通过分析流出(outgoing)的binding来处理propertyChanged事件(参考下面将要介绍的evaluateOut()方法)。
接下来让我们察看binding实现代码中的的公有方法和属性来分析一下Atlas绑定的核心实现。在这里没有必要列出涉及绑定的全部代码,如果您感兴趣,可以用关键词Sys.BindingBase和Sys.Binding 在Atlas.js文件中进行搜索。首先是Sys.BindingBase提供的方法和属性。
- 属性automatic:指定当源对象的相应属性变化时(对于In和InOut),或者目的对象的相应属性变化时(对于Out和InOut),绑定是否将被自动执行。这个属性默认会被置为true。当然如果你需要完全控制绑定的开始时机时也可以设定为false。例如,某些情况下你决定在一个AJAX请求成功返回的时候才开始绑定数据源与显示控件,以确保显示控件真正绑定到了一些数据,这时你需要显示的调用binding的evaluate()方法以开始绑定。
- 属性dataContext:指定拥有待绑定属性的对象。如果不指定的话,Atlas binding将调用包含它的父控件的dataContext属性代替。控件可以通过返回设定的dataContext或是按照默认返回其父控件的dataContext来实现这个属性。例如,某个ListView控件可以在其创建ListView Item时设定它的dataContext为一个DataRow对象,以实现绑定。
- 属性dataPath:指定需要绑定的源对象的属性。Atlas binding还允许绑定一个嵌套的属性,类似:sourceObjectProperty.nestedProperty.nestedNestedProperty。源代码中可以看出Atlas能自动为你转化并运行这些代码。dataPath属性的默认值为空,也就是Atlas会绑定这个对象本身。
- 属性property:指定需要绑定的目标对象的属性。你应该总是指定这个属性,否则这个绑定就没有任何意义。
- 属性propertyKey:有时候我们可能需要绑定到某个对象的嵌套属性上。比如,如果需要绑定到style的属性color,我们可以指定property属性为style,并指定propertyKey属性为color。
- 属性transformerArgument:传递给Atlas transformer的参数,只有设定transform时才会用到。
- 事件transform:这个事件允许在绑定时指定一个transformer。当你需要在绑定的时候对数据做以处理时,transformer将会是个很好的选择。例如,如果你希望显示一个布尔值为Yes/No而不是默认的true/false,那么就应该使用一个自定义的transformer。Atlas同时提供了一些内建的transformer,例如Add,Multiply以及Compare等。
- 方法evaluateIn:处理引入的binding。如果direction属性设置成为In或者InOut,该方法将取得源对象的属性的值(根据binding中设定的dataContext以及dataPath属性),并调用目标对象相应属性的Setter。这也就是Atlas中实现binding的核心部分。
Sys.Binding(也在Atlas.js中)中也有一些重要的方法/属性:
- 属性direction:指定希望监听的属性变化的方向。可以设定为In,Out或者InOut。默认值为In。
- 方法evaluateOut:与基类中的方法evaluateIn类似,但是以相反的方向执行。当然,需要将directiton属性设定为Out或者InOut。
希望这些解释能够让您对Atlas的“神奇的”绑定有一些更深入的理解。欢迎留言探讨。
PS:这篇文章中有些英文不知道应该如何用中文表示,希望能得到一些建议以及指正:
- evaluate,我翻译成“处理”,但文中还有“计算求值”的意思,这一点没有体现出来。
- incoming和outgoing,我翻译成“引入”和“流出”……觉得不是一般的别扭。
- transformer,没有翻译(实在不知道怎么说了)。
- getter和setter,没有翻译(一直都不知道怎么翻译)。