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文件。)
<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控件。
<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 _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。
PS:這篇文章中有些英文不知道應該如何用中文表示,希望能得到一些建議以及指正:
- evaluate,我翻譯成“處理”,但文中還有“計算求值”的意思,這一點沒有體現出來。
- incoming和outgoing,我翻譯成“引入”和“流出”……覺得不是一般的彆扭。
- transformer,沒有翻譯(實在不知道怎麼說了)。
- getter和setter,沒有翻譯(一直都不知道怎麼翻譯)。