數據綁定是連接用戶界面 \(UI\) 到一個數據對象 \(code\) 的方法。它可以讓在代碼里修改的變化通過反射傳遞給UI,反之亦然。
> 后面的章節里, **source** 代表代碼里的任何對象, **target** 代表任何UI控件(如 TextField )。
## [**Data flow direction**](http://docs.nativescript.org/core-concepts/data-binding#data-flow-direction)數據流動方向
數據綁定的部分設置是數據流動的方向。 NativeScript 數據綁定支持如下的數據傳輸:
* **單向**:這是默認設置,這使得當源屬性改變時目標屬性更新。然而,UI修改不會更新代碼并且終止綁定鏈接。
* **雙向**:該設置使得改變的反射在兩個方向——從源到目標和從目標到源。當你要處理用戶輸入的時候,可以使用雙向數據綁定。
## [**Basic binding concepts**](http://docs.nativescript.org/core-concepts/data-binding#basic-binding-concepts)綁定的基本概念
通常,幾乎每個UI控件都能綁定到一個數據對象(所有的 NativeScript 控件都是經由數據綁定創造的)。在你的代碼與后面的要求匹配之后,你可以跳出盒子使用數據綁定。
* 目標對象必須是 **Bindable** 類的繼承者。所有的 NativeScript 控件已經從該類繼承。
* 對于雙向數據綁定,目標屬性應該是一個依賴屬性。
* 對于單向綁定,使用一個普通屬性就足夠了。
* 數據對象要為其屬性值里的每個變化聲明一個 **propertyChange** 事件,來通知所有對變化有興趣的監聽。
## **[How to create a binding](http://docs.nativescript.org/core-concepts/data-binding#how-to-create-a-binding)**如何創建綁定
### **[Two-way binding in code](http://docs.nativescript.org/core-concepts/data-binding#two-way-binding-in-code)**在代碼里雙向綁定
下面的例子包含一個 `Label`,`TextField` 和綁定UI控件的source屬性。這樣的目的是為了,當用戶在 `TextField` 輸入的時候,更新代碼里的屬性和 `Label` 的文本。
首先, `source` 對象創建時帶有一個 `textSource` 屬性。從 `source` 屬性向 `Label` 傳遞變動的固定流向是必要的。因此,代碼里的屬性需要聲明一個 `propertyChange` 事件來通知 `Label` 改變。要聲明這個事件,使用了一個內置的類,它提供這個功能—— `Observable` 。
> ### JS
>
> ---
>
> `var observableModule = require("data/observable");`
>
> `var source = new observableModule.Observable();`
>
> `source.textSource = "Text set via twoWay binding";`
然后,創建目標對象以便綁定到 `source` 屬性。在這里,它們是 一個 `Label` 和一個 `TextField` ,都是繼承了 `Bindable` 類的(所有的UI控件都繼承了該類)。
> ### JS
>
> ---
>
> `//create the TextField`
>
> `var textFieldModule = require("ui/text-field");`
>
> `var targetTextField = new textFieldModule.TextField();`
>
> `//create the Label`
>
> `var labelModule = require("ui/label");`
>
> `var targetLabel = new labelModule.Label();`
接著,目標對象綁定到 source 對象。 `TextField` 使用了一個雙向綁定,所以用戶輸入能夠改變代碼里的屬性。同時 `Label` 的綁定是單向的,用來只從代碼向UI傳遞改變。
### **[Example 1: Binding label text property.](http://docs.nativescript.org/core-concepts/data-binding#example-1-binding-label-text-property)**綁定lable的文本屬性
> ### JS
>
> ---
>
> `//binding the TextField`
>
> `var textFieldBindingOptions = {`
>
> `sourceProperty: "textSource",`
>
> `targetProperty: "text",`
>
> `twoWay: true`
>
> `};`
>
> `targetTextField.bind(textFieldBindingOptions, source);`
>
> `//binding the Label`
>
> `var labelBindingOptions = {`
>
> `sourceProperty: "textSource",`
>
> `targetProperty: "text",`
>
> `twoWay: false };`
>
> `targetLabel.bind(labelBindingOptions, source);`
### **[Binding in XML](http://docs.nativescript.org/core-concepts/data-binding#binding-in-xml)**在XML里綁定
要在XML里綁定,需要一個源對象,用同樣的方式創建,像上面的例子那樣(_在代碼里雙向綁定_)。然后在XML里描述這個綁定(使用一個小胡子語法)。在XML聲明里,只設置屬性名——目標:text,源: textSource 。有趣的是該綁定源不是顯示地指定。關于這個主題更多的討論會在 [Binding source](http://docs.nativescript.org/core-concepts/data-binding#binding-source) 文章里。
> `<Page>`
>
> `<StackLayout>`
>
> `<TextField text="{{ textSource }}" />`
>
> `</StackLayout>`
>
> `</Page>`
>
> ### **注意:**
>
> ---
>
> **當通過XML聲明創建UI元素時,數據綁定默認是雙向的。**
## [**Binding source**](http://docs.nativescript.org/core-concepts/data-binding#binding-source)綁定源
### [**Binding to a property**](http://docs.nativescript.org/core-concepts/data-binding#binding-to-a-property)綁定到一個屬性
設置源對象是數據綁定的一個重要部分。對于一個持續的數據流變動,目標屬性需要發出一個 **propertyChange** 事件。 NativeScript 數據綁定可以和任何發出該事件的對象綁定。添加一個綁定源主要通過把它作為 **bind\(bindingOptions, source\)** 方法的第二個參數實現。當源被當作 `Bindable` 類的名為 **bindingContext** 的屬性情況下,這個參數是可選和可以省略的。該屬性特別之處在于它在整個視覺樹是可繼承的。這意味著一個UI控件可以使用它第一個父元素的 **bindingContext** ,該父元素顯示地設置了 **bindingContext。**在 [Two-Way Binding in Code](http://docs.nativescript.org/core-concepts/data-binding#two-way-binding-in-code) 例子里, `bindingContext`can 可以設置到一個 `Page` 或 `StackLayout` 實例并且 `TextField` 將繼承它作為一個應有的源來綁定其 "text" 屬性。
> JS
>
> ---
>
> `page.bindingContext = source; `
>
> `//or `
>
> `stackLayout.bindingContext = source; `
### [**Binding to an event in XML**](http://docs.nativescript.org/core-concepts/data-binding#binding-to-an-event-in-xml)綁定到一個XML里的事件
此處有個選項來綁定一個函數在特定事件上執行(像MVVM命令)。這個選項只能通過XML聲明。要實現這個功能,源對象就需要有個事件處理器函數。
### [**Example 2: Binding function on button tap event.**](http://docs.nativescript.org/core-concepts/data-binding#example-2-binding-function-on-button-tap-event)綁定函數到按鈕點擊事件
> XML
>
> ---
>
> `<Page> `
>
> `<StackLayout> `
>
> `<Button text="Test Button For Binding" tap="{{ onTap }}" /> `
>
> `</StackLayout> `
>
> `</Page> `
>
> JS
>
> ---
>
> `source.set("onTap", function(eventData) { `
>
> `console.log("button is tapped!"); `
>
> `}); `
>
> `page.bindingContext = source;`
> ### **注意:**
>
> ---
>
> 要知道如果有一個按鈕在后臺代碼頁面里具有一個事件處理器函數 **onTap** ,并且 **onTap** 函數在 **bindingContext** 對象里面,那么就不能有兩個事件處理器掛載到該按鈕上。要在執行后臺代碼里的該函數,在XML里應該使用這樣的語法 - **tap="onTap"** ,而對于來自 bindingContext 的該函數則使用 **tap="{{ onTap }}"** 。
### [**Binding to a plain object**](http://docs.nativescript.org/core-concepts/data-binding#binding-to-a-plain-object)綁定到一個普通對象
通常的情況是提供一個普通元素(數字,日期,字符串)的列表(數組)到一個`ListView` 項目集合。上面的所有示例證明了如何綁定一個UI元素到 bindingContext 的屬性。如果現在只有普通數據,這里就沒有屬性可以綁定,所以就要綁定到整個對象。這里 NativeScript 綁定的一個內容就出馬了——對象或值綁定。要指向整個對象,即示例里的 Date\(\) ,就要使用關鍵詞 `$value` 。
### [**Example 3: Bind ListView to a property of the bindingContext .**](http://docs.nativescript.org/core-concepts/data-binding#example-3-bind-listview-to-a-property-of-the-bindingcontext-)綁定 [**ListView**](http://docs.nativescript.org/core-concepts/data-binding#example-3-bind-listview-to-a-property-of-the-bindingcontext-) 到 **bindingContext** 的屬性
> XML
>
> ---
>
> `<Page> `
>
> `<StackLayout> `
>
> `<ListView items="{{ items }}" height="200"> `
>
> `<ListView.itemTemplate> `
>
> `<Label text="{{ $value }}" /> `
>
> `</ListView.itemTemplate> `
>
> `</ListView> `
>
> `</StackLayout> `
>
> `</Page> `
> JS
>
> ---
>
> `var appModule = require("application"); `
>
> `var list = []; `
>
> `var i; for(i = 0; i < 5; i++) { `
>
> `list.push(new Date()); `
>
> `} `
>
> `source.set("items", list);`
### [**Binding to a parent binding context**](http://docs.nativescript.org/core-concepts/data-binding#binding-to-a-parent-binding-context)綁定到父綁定上
綁定操作的另一個常見情況是請求訪問父元素的綁定上下文。這是因為它可能不同于子節點的綁定上下文并且包含了子節點可能使用的信息。通常,綁定上下文是可繼承的,但當元素\(items\)是基于某些數據源而動態創建時不是。例如,`ListView`基于一個`itemТemplate`創建它的子項目,`itemТemplate`描述`ListView`元素的外觀。當這個元素添加到視覺樹時,它從一個ListView `items` 數組\(通過相應的index\)獲取這個元素的綁定上下文。這個進程為子項目和它的內含UI元素創建了一個新的綁定上下文鏈。所以,內含UI元素不能訪問'ListView'的綁定上下文。為了解決這個問題,NativeScript綁定的基礎架構里有兩個特別的關鍵詞:`$parent` 和 `$parents` 。第一個表示直接父視覺元素的綁定上下文,第二個可以當作數組來使用\(通過數字或字符串index\)。這就給你了選擇來選擇`N`級UI嵌套或是得到一個給定類型的父UI元素。我們來看看這在一個現實的例子里是如何工作的。
[Example 4:基于模板創建ListView子項目](http://docs.nativescript.org/core-concepts/data-binding#example-4-creating-listview-child-items-based-on-the-itemtemplate)
### ** XML**
---
>`<Page loaded="pageLoaded">
<GridLayout rows="*" >
<ListView items="{{ items }}">
<!--Describing how the element will look like-->
<ListView.itemTemplate>
<GridLayout columns="auto, *">
<Label text="{{ $value }}" col="0"/>
<!--The TextField has a different bindingCotnext from the ListView, but has to use its properties. Thus the parents['ListView'] has to be used.-->
<TextField text="{{ $parents['ListView'].test, $parents['ListView'].test }}" col="1"/>
</GridLayout>
</ListView.itemTemplate>
</ListView>
</GridLayout>
</Page>`
### **js**
---
> `var observable = require("data/observable"); `
> `var pageModule = require("ui/page"); `
> `var viewModel = new observable.Observable();
`
> `function pageLoaded(args) { `
> `var page = args> .object;`
> `viewModel.set("items", [1, 2, 3]); `
> `viewModel.set("test", "Test for parent binding!");`
> `page.bindingContext = viewModel; `
> `} `
> `exports.pageLoaded = pageLoaded;`
#### [**使用表達式綁定**](http://docs.nativescript.org/core-concepts/data-binding#using-expressions-for-bindings)
The result should be a TextFieldelement that will display the value of the sourceProperty followed by " some static text" string.
你可以創建一個自定義表達式來綁定。自定義表達式能夠在需要對UI實現某些邏輯的時候幫到你,同時保持基本的業務數據和邏輯清晰。為了更具體,我們來看一個基本的綁定表達式示例。它的結果應該是一個TextField元素,將“some static text”字符串合并計算sourceProperty的值。
`<Page>`
` <StackLayout>`
` <TextField text="{{ sourceProperty, sourceProperty + ' some static text' }}" />`
` </StackLayout>`
`</Page>`
### **注意:**
> **
可以使用沒有顯式命名源屬性的綁定表達式(`TextField text=""`)。在此情況下,$value作為源屬性使用。然而,當一個嵌套的屬性本應觀察到變化(比如item.nestedProp)時,這可能導致問題。代表綁定上下文,并且當綁定上下文的任何屬性發生變化時,表達式的值會重新計算。既然nestedProp在項目里不是綁定上下文的屬性,那么它就沒有就沒有連接屬性變化監聽器,且nestedProp的改變就不會填充到UI。因此,為了消除這些問題,指定哪些屬性應該被用作源屬性是一個很好的做法。**
完整的綁定語法包含三個參數——首先是源屬性,它會被監聽到變動。第二個參數是會被計算的表達式。第三個參數聲明是否是雙向綁定。早前提到過,XML聲明默認創建一個雙向綁定,所以在該例里,第三個參數可以省略。保留其余兩個參數意味著只有當源屬性發生變化時自定義表達式才會重新計算。第一個表達式也可以省略;你這樣做的話,那么每次綁定上下文變化時自定義表達式都會計算。因此,建議的語法是在XML聲明里包含兩個參數,就像我們例子里的——關注的屬性和要被計算的表達式。
#### [Supported expressions](http://docs.nativescript.org/core-concepts/data-binding#supported-expressions)支持的表達式
NativeScript 支持不同種類的表達式,包括:
#### [Using converters in bindings](http://docs.nativescript.org/core-concepts/data-binding#using-converters-in-bindings)在綁定里使用轉換
講到雙向綁定,這里有個常規的問題——有不同的方法存儲和顯示數據。或許這里最好的例子就是日期和時間對象了。日期和時間信息是以數字或數字序列(對于索引、搜索和其他數據庫操作相當有用)存儲的,但這對于應用使用者來說不是顯示日期的最可能的選項。同時當用戶輸入日期時,這里有個另外的問題(在下面的例子里,用戶在TextField里輸入)。用戶輸入的結果會是個字符串,它將按照用戶的喜好格式化。這個字符串應該轉換成正確的日期對象。我們來看看用NativeScript 綁定如何處理。
#### [Example 5: 處理 textField 日期輸入并按照偏好格式化](http://docs.nativescript.org/core-concepts/data-binding#example-5-handle-textfield-date-input-and-formatted-in-accordance-preferences)
**XML**
> **`<Page> `**
> **`<StackLayout> `**
> **`<TextField text="{{ testDate, testDate | dateConverter('DD.MM.YYYY') }}" /> `**
> **`</StackLayout>` **
> **`</Page> `**
**JS**
>**`var dateConverter = { `
>` toView: function (value, format) { `
>` var result = format; `
>` var day = value.getDate(); `
>` result = result.replace("DD", day < 10 ? "0" + day : day);`
>` var month = value.getMonth() + 1; `
>` result = result.replace("MM", month < 10 ? "0" + month : month); `
>` result = result.replace("YYYY", value.getFullYear()); `
>` return result; `
>` }, `
>` toModel: function (value, format) { `
>` var ddIndex = format.indexOf("DD"); `
>` var day = parseInt(value.substr(ddIndex, 2)); `
>` var mmIndex = format.indexOf("MM"); `
>` var month = parseInt(value.substr(mmIndex, 2)); `
>` var yyyyIndex = format.indexOf("YYYY"); `
>` var year = parseInt(value.substr(yyyyIndex, 4));`
>` var result = new Date(year, month - 1, day); `
>` return result; `
>` } `
>`} `
>`source.set("dateConverter", dateConverter); `
>`source.set("testDate", new Date()); `
>`page.bindingContext = source; `**
注意表達式里這個特殊的操作符(|)。上面的代碼片段(XML 和 JavaScript)會以日日.月月.年年年年格式(toView函數)顯示日期,并且當以相同格式輸入一個新的日期時,它會被轉換成有效的日期對象(toModel函數)。當一個數據要被轉換時,轉換對象應該有一個或兩個函數(toView 和 toModel)執行。當數據作為任何UI視圖的值要顯示給最終用戶時toView 函數被調用;當這里存在可編輯元素(比如TextField)且用戶輸入一個新的值時toModel函數會被調用。在單向綁定情況下,該轉換對象可能只有一個toView函數或者它就是一個函數。所有的轉換器函數都有一個參數數組,其中第一個參數是將被轉換的值,所有其他參數都是在轉換器定義中定義的自定義參數。
>####**評論:**
>任何轉換器方法里的運行時錯誤都會被自動處理且應用不會暫停,但視圖模型中的數據將不會被改變(在錯誤的情況下)并且更多錯誤信息將被記錄到控制臺。要啟用它,通過一個Error 目錄使用內置的trace模塊。一個簡化的日期轉換器只是為了淺嘗下例子,它不應該被用來作為一個從日期到字符串的全功能的轉換器,反之亦然。
轉換器不但能接收靜態自定義參數,而且能從綁定上下文接受任何值。例如:
####[Example 6: 轉換新日期為有效的日期對象](http://docs.nativescript.org/core-concepts/data-binding#example-6-converting-the-new-date-input-to-a-valid-date-object)
**XML**
>**`<Page> `**
>**`<StackLayout>`**
>**`<TextField text="{{ testDate, testDate | dateConverter(dateFormat) }}" /> `**
>**`</StackLayout> `**
>**`</Page> `**
**JS**
>**`... `**
>**`source.set("dateFormat", "DD.MM.YYYY"); `**
>**`page.bindingContext = source; `**
設定轉換器函數和一個位于綁定上下文內部的參數,對于保證數據正確轉換是很有用的。然而,這不是listview項目綁定時的情況。問題來自于listview子項目的綁定上下文是一個數據子項目,它是任何集合(數組)的一部分,并請求轉換——轉換器和它的參數應當添加到該數據子項目,它將導致多個轉換器實例。用NativeScript處理這類問題相當簡單。綁定基礎設施尋求應用級的資源,以找到一個適當的轉換器和參數。因此,您可以在應用程序模塊中的資源中添加轉換器(app目錄的resources目錄中)。為了更清楚,查看下面的例子(XML和JS):
####[Example 7: 在App模塊的resource目錄中添加轉換器](http://docs.nativescript.org/core-concepts/data-binding#example-7-adding-converters-in-the-application-module-resources)
####**XML**
>**`<Page>`**
>**` <StackLayout>`**
>**` <ListView items="{{ items }}" height="200">`**
>**` <ListView.itemTemplate>`**
>**` <Label text="{{ itemDate | dateConverter(dateFormat) }}" />`**
>**` </ListView.itemTemplate>`**
>**` </ListView>`**
>**` </StackLayout>`**
>**`</Page>`**
####**JS**
>**`var appModule = require("application"); var list = []; var i; for(i = 0; i < 5; i++) { list.push({ itemDate: new Date()}); } source.set("items", list); var dateConverter = function(value, format) { var result = format; var day = value.getDate(); result = result.replace("DD", day < 10 ? "0" + day : day); var month = value.getMonth() + 1; result = result.replace("MM", month < 10 ? "0" + month : month); result = result.replace("YYYY", value.getFullYear()); return result; }; appModule.resources["dateConverter"] = dateConverter; appModule.resources["dateFormat"] = "DD.MM.YYYY";`**
####**注意:**
>應用模塊是靜態的并可以在整個應用內部達到,它只需require。另一個不同之處是日期轉換器是一個函數,取代了擁有toView 和 toModel兩個方法的對象。由于通常的操作是將數據從模型轉換到視圖,它就作為一個toView函數。
####[終止綁定](http://docs.nativescript.org/core-concepts/data-binding#stop-binding)
一般來說沒有必要明確地終止綁定,因為綁定對象使用弱引用,從而防止任何內存泄漏。然而,有一些情況下綁定必須終止。為了終止現有的數據綁定,只需用目標屬性的名稱作為參數調用unbind方法。
####**JS**
>**`targetTextField.unbind("text");`**
你可以在[API參考](http://docs.nativescript.org/api-reference/classes/_ui_core_bindable_.bindable.html)里找到更多關于綁定的信息。