数据绑定是连接用户界面 \(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)里找到更多关于绑定的信息。