ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## WeX5 & BeX5 页面框架核心之数据绑定 ### 视频地址:[http://pan.baidu.com/s/1hqlBoC8](http://pan.baidu.com/s/1hqlBoC8) # 目录 * [1、引言:《外卖》案例的代码不完整吗???](http://wex5.com/cn/?p=4253#1) * [2、概述](http://wex5.com/cn/?p=4253#2) * [2.1、WeX5页面组成](http://wex5.com/cn/?p=4253#2.1) * [2.2、数据绑定](http://wex5.com/cn/?p=4253#2.2) * [3、规则表达式](http://wex5.com/cn/?p=4253#3) * [3.1、数据规则](http://wex5.com/cn/?p=4253#3.1) * [3.2、环境变量](http://wex5.com/cn/?p=4253#3.2) * [3.3、上下文对象](http://wex5.com/cn/?p=4253#3.3) * [3.4、ref、val和label的区别](http://wex5.com/cn/?p=4253#3.4) * [4、绑定表达式](http://wex5.com/cn/?p=4253#4) * [4.1、概述](http://wex5.com/cn/?p=4253#4.1) * [4.2、绑定表达式的环境变量和上下文对象](http://wex5.com/cn/?p=4253#4.2) * [4.3、特殊的ref绑定(bind-ref、bind-labelRef、bind-extRef)](http://wex5.com/cn/?p=4253#4.3) * [4.4、数据相关的绑定表达式(bind-value、bind-checked、bind-text)](http://wex5.com/cn/?p=4253#4.4) * [4.5、状态相关的绑定表达式(bind-visible、bind-enabled、bind-disabled、bind-hasFocus)](http://wex5.com/cn/?p=4253#4.5) * [4.6、样式相关的绑定表达式(bind-style、bind-css)](http://wex5.com/cn/?p=4253#4.6) * [4.7、其他HTML属性的绑定表达式(bind-attr-src、bind-attr-href、bind-attr-title、bind-attr-xxx)](http://wex5.com/cn/?p=4253#4.7) * [4.8、HTML片段相关的绑定表达式(bind-if、binf-ifnot、bind-with、bind-foreach)](http://wex5.com/cn/?p=4253#4.8) * [4.9、其他绑定表达式(bind-html、bind-uniqueName)](http://wex5.com/cn/?p=4253#4.9) * [5、List组件的过滤表达式](http://wex5.com/cn/?p=4253#5) * [6、表达式中使用JS对象和函数](http://wex5.com/cn/?p=4253#6) * [附录、表达式环境变量及上下文对象一览表](http://wex5.com/cn/?p=4253#appendix) # 1、引言:《外卖》案例的代码不完整吗??? WeX5自带了一个经典的《外卖》案例,实现了常见的购物车需求。但是初学者在第一次去看它的源码时,会发现它的JS代码是“不完整的”。JS代码中只有一些核心的与数据操作相关的代码片段,很多页面上的动态计算和显示都找不到相应的代码实现。 那么这些动态计算和显示是如何实现的呢?不需要写代码吗?仔细观察相关组件的属性定义,会发现它们原来都是通过一些支持表达式定义的属性来实现的。这就是现在要给大家介绍的WeX5的数据绑定了。数据绑定让很多原本需要大量的事件监控和代码编程才能完成的数据运算、规则校验和动态显示等页面逻辑,现在通过简单的表达式定义就可以轻松实现了。 * 购物车中菜品增加后,下面的购物车按钮会自动变成黄色,并显示项目数 [![bind-1-01](https://box.kancloud.cn/2015-09-23_560187d79de06.png)](http://wex5.com/cn/wp-content/uploads/2015/04/bind-1-01.png)[![bind-1-02](https://box.kancloud.cn/2015-09-23_560187dd98506.png)](https://box.kancloud.cn/2015-09-23_560187dd98506.png) * 购物车中菜品数量改变时,合计金额会自动计算 [![bind-1-03](https://box.kancloud.cn/2015-09-23_560187e301682.png)](http://wex5.com/cn/wp-content/uploads/2015/04/bind-1-03.png) [![bind-1-04](https://box.kancloud.cn/2015-09-23_560187e863e32.png)](https://box.kancloud.cn/2015-09-23_560187e863e32.png) * 购物车中所有菜品数量为零时,下单按钮会自动禁用 [![bind-1-05](https://box.kancloud.cn/2015-09-23_560187e8c3909.png)](http://wex5.com/cn/wp-content/uploads/2015/04/bind-1-05.png) [![bind-1-06](https://box.kancloud.cn/2015-09-23_560187ee75397.png)](https://box.kancloud.cn/2015-09-23_560187ee75397.png) # 2、概述 ## 2.1、WeX5页面组成 [![bind-2-01](https://box.kancloud.cn/2015-09-23_560187eed0cd1.png)](https://box.kancloud.cn/2015-09-23_560187eed0cd1.png) 首先,我们来看一下WeX5的页面组成。WeX5的页面主要由数据(Data)、视图(View)和代码(Code)三个部分组成,体现了**数据与视图分离、页面描述与代码逻辑分离**的核心设计思想。 * 数据部分包含若干个数据组件,是整个页面的数据核心; * 视图部分包含布局组件、显示组件和编辑组件等各种视图组件,构成用户交互界面; * 代码部分包含当前页面的JS代码,WeX5页面采用基于组件对象的事件驱动程模式,数据和视图部分的每个组件对象都提供了大量的方法和事件,让开发者可以编程实现任意复杂的个性化逻辑。 ## 2.2、数据绑定 WeX5在数据和视图两个层面的组件上,都提供了大量可通过表达式来定义的动态属性。当表达式依赖的数据项改变时,表达式会自动重新计算,并通过属性动态影响组件的显示和行为。我们把页面上的这种由数据驱动界面动态交互的能力统称为“**数据绑定**”。 由于在数据和视图两个层面,表达式属性的作用有所不同,我们把表达式属性分为两类: * **规则表达式**:用于定义数据组件上的只读、必填、计算和约束等数据规则; * **绑定表达式**:用于定义视图组件上的数据、状态、样式等各种动态交互属性。 “**规则表达式**”和 “**绑定表达式**”都采用JS表达式语法,但不同的表达式属性有不同的作用和描述规则。下面我们会对WeX5中提供的各种表达式属性,逐个展开进行讲解。 **本文所用到案例参见WeX5模型资源中的“数据绑定”(/UI2/demo/misc/bind/index.w)。启动WeX5自带的Tomcat服务后,在浏览器中访问([http://localhost:8080/x5/UI2/demo/misc/bind/index.w](http://localhost:8080/x5/UI2/demo/misc/bind/index.w))即可运行。** # 3、规则表达式 ## 3.1、数据规则 在数据组件上支持表达式定义的数据规则有: * 对整个数据集生效的只读规则; * 对数据列生效的只读、计算、必填和约束规则。 [![bind-3-01](https://box.kancloud.cn/2015-09-23_560187f295c03.png)](https://box.kancloud.cn/2015-09-23_560187f295c03.png) 上图来自案例中foodData的数据规则(在foodData组件的右键菜单中选择编辑规则),我们现在就从图中的规则表达式开始讲起。 * **foodData只读:$model.tempData.val(‘foodDataReadonly’)** 规则含义:当tempData的foodDataReadonly列值为true时,foodData全部只读。 运行演示:点击“只读”按钮触发事件JS代码,将tempData的foodDataReadonly列取非并赋值,从而导致所依赖的foodData只读规则表达式重新计算,并最终影响界面相关组件的只读状态改变。 [![bind-3-02](https://box.kancloud.cn/2015-09-23_560187f305045.png)](https://box.kancloud.cn/2015-09-23_560187f305045.png) [![bind-3-03](https://box.kancloud.cn/2015-09-23_560187f367e3d.png)](https://box.kancloud.cn/2015-09-23_560187f367e3d.png) * **fDate只读:$row.val(“fPrice”)>30** 规则含义:当前行的价格大于30时,日期字段只读。 运行演示:选择价格大于30的数据,右边的日期字段自动只读。 [![bind-3-04](https://box.kancloud.cn/2015-09-23_560187f3c830f.png)](https://box.kancloud.cn/2015-09-23_560187f3c830f.png) * **fDate约束:!$val || $val < ‘2020-01-01′** 规则含义:当前列的值不为空时必须小于’2020-01-01’。 运行演示:选择大于’2020-01-01’的日期,会看到约束警告。 [![bind-3-05](https://box.kancloud.cn/2015-09-23_560187fe372c4.png)](https://box.kancloud.cn/2015-09-23_560187fe372c4.png) 从上面的几个示例中我们看到,数据规则表达式能力强大,但是使用简单。当数据规则表达式中依赖的“**数据项**”改变后,表达式会自动重新计算,并且最终通过数据规则影响所关联视图组件的显示和状态,反映给最终用户。 * **并不是任何一个组件都能感知数据规则**:在前面的foodData只读规则示例中,我们可以观察到当foodData只读时,并不是所有关联的视图组件都只读了。这是因为只有通过ref属性绑定到数据的视图组件才能感知数据规则,有关ref属性绑定的知识在后面的绑定表达式部分进行讲解。 [![bind-3-06](https://box.kancloud.cn/2015-09-23_560187fe958e7.png)](https://box.kancloud.cn/2015-09-23_560187fe958e7.png) * **并不是任何一个数据变量的改变都会自动触发表达式的运算**:这里的数据特指“可观察(observable)”数据。“可观察”数据不是一个简单变量,而是一个特殊的数据对象,当它的数据域改变时会自动刺激所有依赖它的表达式重新计算。Data组件中的数据项都是“可观察”数据,所以前面的示例中所有的表达式都可以自动计算。WeX5的数据绑定体系基于knockoutJS(这是一个开源的数据感知框架),更多相关知识,大家可以参见[http://knockoutjs.com](http://knockoutjs.com/)。 ## 3.2、环境变量 我们在前面的几个表达式例子中,已经看到$model、$row、$val等以$开头的特殊变量,这些就是在数据规则表达式中可以使用的环境变量。 * **$model**:当前页面的模型对象(也就是当前页面.js文件中Model对象的运行时实例) * **$data**:当前数据对象 * **$row**:当前计算行对象 * **$col**:当前列名 * **$val**:当前数据值 = $row.val($col) * **$rowID**:当前行的ID值 = $row.getID() 注意:$model.tempData是一种特殊写法,页面上只有数据组件可以这样写,获取其他页面组件都必须通过$model.comp(xid)方法。 注意:下面两个表达式的不同,如果有疑问的话建议自己写个例子实际观察一下: $data.val(‘fName’) — 当前选中行的fName列的值 $row.val(‘fName’) — 当前计算行的fName列的值 ## 3.3、上下文对象 在数据规则表达式中,除了有上面所列的环境变量以外,还有一个默认的上下文对象,在表达式里是可以忽略的。例如:fDate的只读规则$row.val(“fPrice”)>30,可以简写为val(“fPrice”)>30。 * 对于定义在数据集上的数据规则,默认上下文对象是$data * 对于定义在数据列上的数据规则,默认上下文对象是$row ## 3.4、ref、val和label的区别 [![bind-3-07](https://box.kancloud.cn/2015-09-23_560187ff08abe.png)](https://box.kancloud.cn/2015-09-23_560187ff08abe.png) 我们在表达式对话框里,每次都会看到每个列都有ref、val和label三个函数,它们有什么区别呢? * **val**:返回列的数据值,用于表达式计算(由于这些数据本身是“可观察”数据,所以当数据值改变时,会自动触发所依赖的表达式规则重新计算)。 * **ref**:返回列的“可观察”数据对象,用于视图组件的ref绑定。 * **label**:返回列的名称,一般用于页面显示列名标签。 # 4、绑定表达式 ## 4.1、概述 前面我们讲解了可以在数据上定义的数据规则表达式,接下来我们要讲解的是在视图组件上定义的动态表达式—“绑定表达式”。 虽然我们从设计器上来看,绑定表达式是视图组件的属性,但其实绑定表达式可以作用于任何一个HTML标签。当然,我们也可以认为每个HTML标签本身就是最简单的组件,而其他复杂组件都可以看做是基本HTML标签的组合。 绑定表达式的能力极其强大,可以动态定义HTML标签的所有属性,甚至可以通过表达式定义动态创建HTML片段,可以实现各种复杂的界面动态交互。**没有做不到,只有想不到!** 我们下面首先通过一个简单的示例,来看一下绑定表达式是如何作用于HTML的。参照案例中的bind-visible(>30),input组件的bind-visible属性值为foodData.val(‘fPrice’) > 30。我们首先通过选择不同的数据行,可以观察到如下现象: [![bind-4-01](https://box.kancloud.cn/2015-09-23_560187ff75b2a.png)](https://box.kancloud.cn/2015-09-23_560187ff75b2a.png) 这时我们用F12打开“开发者工具”,监控这个input标签的属性,会发现: [![bind-4-02](https://box.kancloud.cn/2015-09-23_560187ffda7ad.png)](https://box.kancloud.cn/2015-09-23_560187ffda7ad.png) [![bind-4-03](https://box.kancloud.cn/2015-09-23_560188005344c.png)](https://box.kancloud.cn/2015-09-23_560188005344c.png) 通过上面的示例观察我们可以知道,当绑定表达式依赖的数据项改变时会重新计算,并动态更新对应HTML标签的DOM结构,从而影响页面的显示,而这一切仅需要定义一个表达式就搞定! ## 4.2、绑定表达式的环境变量和上下文对象 **环境变量:** * **$model**:当前页面模型对象。 * **$element**:当前HTML标签的JS对象。 * **$index**:当前行索引,仅当表达式处于bind-foreach所作用的内容片段时有效,见后“HTML片段相关的绑定表达式”。 * **$object**:当前HTML标签所处环境的上下文对象,一般情况下就是$model;但是如果表达式处于bind-with和bind-foreach所作用的内容片段时,$object的值由bind-with和bind-foreach赋予,见后“HTML片段相关的绑定表达式”。 **上下文对象:** * **$object**:见上,上下文对象在表达式中是可以忽略的,所以在案例的绑定表达式中经常可以看到foodData.val(‘fPrice’)这样的写法,这里其实相当于$object.foodData.val(‘fPrice’)和$model.foodData.val(‘fPrice’)。 ## 4.3、特殊的ref绑定(bind-ref、bind-labelRef、bind-extRef) 在前面的数据规则表达式中我们提到过,视图组件必须通过ref绑定到数据才能感知数据规则。ref绑定是一种高级的复合数据绑定,它可以为组件同时带来多种数据感知能力: * **双向数据感知**:数据改变会自动更新组件显示,组件编辑后也会自动更新数据。从界面交互效果上来看,就是多个关联到同一个数据的组件,一个数据编辑后其它组件都会自动更新。 [![bind-4-04](https://box.kancloud.cn/2015-09-23_56018800b4dd3.png)](https://box.kancloud.cn/2015-09-23_56018800b4dd3.png) * **数据规则感知**:组件可以通过HTML状态和样式的改变将只读、必填、约束等规则提示给用户。 [![bind-4-05](https://box.kancloud.cn/2015-09-23_5601880122844.png)](https://box.kancloud.cn/2015-09-23_5601880122844.png) * **数据类型感知**:这里特指input组件通过ref绑定数据列后,可以感知列的数据类型。例如:当列是日期类型时,input自动变成日期编辑模式;当列是整型数值时,input只能输入数值。 [![bind-4-06](https://box.kancloud.cn/2015-09-23_560188042115c.png)](https://box.kancloud.cn/2015-09-23_560188042115c.png) 注意:并不是所有的组件都支持ref绑定,只有属性中包含bind-ref(bind-labelRef、bind-extRef)的组件才能支持ref绑定。目前支持ref绑定的组件有:input、output、textArea、select、gridSelect、radio、checkbox、radioGroup、checkboxGroup、toggle、attachment、blobImage等,基本上常用的表单编辑组件都是支持ref绑定的。 最后要注意,ref绑定的属性值只能是数据列的ref对象,不支持表达式。 ## 4.4、数据相关的绑定表达式(bind-value、bind-checked、bind-text) * **bind-value**:适用于input、password、select、textarea * **bind-checked**:适用于checkbox、radio * **bind-text**:适用于span、label、em…… 这三个表达式理解上很简单,就是通过表达式来动态定义HTML标签对应的数据属性。 [![bind-4-07](https://box.kancloud.cn/2015-09-23_5601880482338.png)](https://box.kancloud.cn/2015-09-23_5601880482338.png) bind-value="foodData.ref('fPrice')" bind-value="foodData.val('fPrice')" bind-text="foodData.ref('fPrice')" bind-checked="foodData.ref('fStatus')" bind-value和bind-checked也可以支持双向数据感知,但是要求表达式的值只能是“可观察”数据对象(即列的ref值)。 [![bind-4-08](https://box.kancloud.cn/2015-09-23_56018804dc2c7.png)](https://box.kancloud.cn/2015-09-23_56018804dc2c7.png) ## 4.5、状态相关的绑定表达式(bind-visible、bind-enabled、bind-disabled、bind-hasFocus) * **bind-visible**:组件是否显示 * **bind-enabled**:组件是否可用 * **bind-disabled**:组件是否禁用(优先级高于bind-enabled) * **bind-hasFocus**:组件是否获得输入焦点 与状态相关的四个表达式都要求必须是**布尔表达式**。 [![bind-4-09](https://box.kancloud.cn/2015-09-23_5601880542e75.png)](https://box.kancloud.cn/2015-09-23_5601880542e75.png) bind-visible="foodData.val('fPrice') > 30" bind-hasFocus="foodData.val('fPrice') > 50" bind-enable="foodData.val('fStatus') == 1" bind-disable="foodData.val('fStatus') != 1" ## 4.6、样式相关的绑定表达式(bind-style、bind-css) * **bind-style**:用表达式定义style中的属性值 * **bind-css**:动态为html标签增加或删除class 这两个绑定表达式都是用于动态定义HTML标签的样式,由于HTML样式的能力包罗万象、无所不能,所以对于这两个表达式的使用场景,大家可以尽情发挥想象力。 这两个表达式在写法上与前面的简单属性表达式有所不同,采用JSON数据格式: * bind-style:{“style属性”: 表达式动态定义此style属性的值} * bind-css:{“class名称”: 布尔表达式动态定义增加或删除class} [![bind-4-10](https://box.kancloud.cn/2015-09-23_560188059fe7a.png)](https://box.kancloud.cn/2015-09-23_560188059fe7a.png) bind-style(>30)的表达式: bind-style=”{ 'backgroundColor': foodData.val('fPrice') > 30 ? 'red' : null, 'color' : foodData.val('fPrice') > 30 ? 'yellow' : null }” 含义:背景色当价格大于30时为红色,前景色当价格大于30时为黄色 * bind-css(>30)的表达式: bind-css=”{ 'text-danger': foodData.val('fPrice') > 30 }” 含义:当价格大于30时,为class属性动态增加text-danger 由于WeX5的页面组件大多基于bootstrap框架,为了今后能统一的换肤,在大家定义bind-css表达式时,推荐大家尽量使用bootstrap已有的样式。下面列出一些bootstrap常用样式: * 字体:.h1 .h2 .h3 .h4 .h5 .h6 * 颜色:.text-muted .text-primary .text-success .text-info .text-warning .text-danger * 背景:.bg-primary .bg-success .bg-info .bg-warning .bg-danger Bootstrap相关的资料请参见:[http://v3.bootcss.com/](http://v3.bootcss.com/)、[http://getbootstrap.com/](http://getbootstrap.com/) ## 4.7、其他HTML属性的绑定表达式(bind-attr-src、bind-attr-href、bind-attr-title、bind-attr-xxx) * **bind-attr-src**: 标签的src属性 * **bind-attr-href**:标签的href属性 * **bind-attr-title**:HTML标签的title属性 * **bind-attr-xxx**:xxx可以是HTML标签的任意属性,甚至是自定义属性 bind-attr-src、bind-attr-href、bind-attr-title这三个表达式比较简单,分别用于动态定义几个特定的HTML属性。 除了前面讲的这些HTML属性对应的特定绑定表达式,还可用bind-attr-xxx的动态定义任何其他HTML属性。 [![bind-4-11](https://box.kancloud.cn/2015-09-23_560188060582a.png)](https://box.kancloud.cn/2015-09-23_560188060582a.png) ## 4.8、HTML片段相关的绑定表达式(bind-if、binf-ifnot、bind-with、bind-foreach) 前面介绍的绑定表达式都是与HTML属性相关的,下面介绍几个与HTML片段相关的绑定表达式: * **bind-if**:用布尔表达式定义HTML标签的内容片段(innerHTML)是否创建 * **bind-ifnot**:同上但逻辑相反 注意:上面两个表达式只是定义HTML标签的内容片段是否**创建**,并不是隐藏和显示,而且当前HTML标签是不变的。 [![bind-4-12](https://box.kancloud.cn/2015-09-23_560188066dbfc.png)](https://box.kancloud.cn/2015-09-23_560188066dbfc.png) <div class="x-col" xid="col82" bind-if="foodData.val('fPrice') &gt; 30"> <input type="text" value="" xid="input25" bind-value="foodData.val('fPrice')" style="width:100%;"/> </div> * **bind-with**:为HTML标签的内容片段指定表达式的上下文对象$object [![bind-4-13](https://box.kancloud.cn/2015-09-23_56018806caa35.png)](https://box.kancloud.cn/2015-09-23_56018806caa35.png) <div class="x-col" xid="col16" bind-with="foodData"> <input type="text" value="" xid="input2" bind-value="xid+ '的数量:' + getCount()" style="width:100%;"/> </div> * **bind-foreach**:按照表达式定义的数组项,动态创建多份内容片段(innerHTML),并且每份内容片段的上下文对象$object分别等于数组中的每一项。 [![bind-4-14](https://box.kancloud.cn/2015-09-23_560188072fcc4.png)](https://box.kancloud.cn/2015-09-23_560188072fcc4.png) <div class="x-col" xid="col17" bind-foreach="foodData.datas"> <input type="text" value="" xid="input4" style="width:100%;" bind-value="$index() + val('fName')"/> </div> ## 4.9、其他绑定表达式(bind-html、bind-uniqueName) * **bind-html**:动态创建HTML标签的innerHTML,要求表达式返回html片段。这个能力很强大吧,呵呵,这个算是留个课后作业,就不给例子了,自己DIY看看吧。 * **bind-uniqueName**:为HTML标签动态创建唯一name,这个很少用。 # 5、List组件的过滤表达式 List组件的filter属性可以用表达式过滤可显示的数据项,List组件的filter表达式支持的环境变量有: * **$model**:当前页面模型对象。 * **$row**:当前计算行对象。 * **$object**:当前HTML标签所处环境的上下文对象,同视图组件。 修改案例左边的List组件,filter=”$row.val(‘fStatus’) == 1”,页面刷新后只显示启用的数据。 [![bind-5-01](https://box.kancloud.cn/2015-09-23_560188078ed3b.png)](https://box.kancloud.cn/2015-09-23_560188078ed3b.png) # 6、表达式中使用JS对象和函数 在数据表达式中除了我们前面提到的那些环境变量,其实我们可以使用任何“可以访问的JS对象和函数”: * Javasrcipt的全局函数和对象,例如:parseInt、parseFloat等。 * window对象,这个不用讲了吧。 * jQuery,$可以直接用,例如:$.each、$(‘div’)等。 * justep工具类,例如:justep.Date.fromString。 * $model、$data、$row……等环境变量。 获取页面组件: * 获取当前页面上的X5组件对象: $model.comp(xid) * 获取当前页面上的HTML原生对象:$model.getElementByXid(xid) 基于$model自定义函数和函数库: * 自定义函数:在页面的JS文件中为Model增加自定义函数,表达式中通过$model调用。 * 自定义函数库:在页面的JS文件中引入JS函数库,将函数库对象设置为Model的变量,表达式中通过$model访问函数库对象。 # 附录、表达式环境变量及上下文对象一览表 | **表达式** | **上下文对象** | **环境变量** | | Data规则表达式 | 数据集表达式 | $data | $model、$data | | 字段表达式 | $row | $model、$data、$row、$col、$rowID | | View绑定表达式 | 一般场景 | $object=$model | $model、$element、$object | | 在bind-with环境内 | $object=with的对象 | $model、$element、$object | | 在list组件内 | $object=当前行对象row | $model、$element、$object、$index | | 在bind-foreach环境内 | $object=当前行数据项 | $model、$element、$object、$index | | 过滤表达式 | list的filter属性 | $object(同上) | $model、$object、$row(当前计算行) |