标签:
Knockoutjs 以下简称 ko, 是一个 javascript 的 MVVM 框架.
在 MVC 4 的项目模板中, 默认会引用 knockoutjs, 我正式接触它也不过4个月的时间.
在没有使用它之前, 要做一个主从关系的维护, 大概要写一堆 CloneNode / Append, js 水平一般的同学, 要写这样一个业务页面, 大概要写几百几千行 js 才行.
本示例不是介绍 ko 的用法, 而是 以 我在项目中 处理过的 稍有些复杂的 业务 为原型, 做深入一步的理解如何用KO带你装B带你飞. 所以, 如果你不了解 ko , 可以先看看 ko 的文档:
http://knockoutjs.com/documentation/introduction.html
示例下载地址 (VS2013 MVC5):
http://files.cnblogs.com/xling/KOExamples.7z
一分钱难倒英雄汉, 曾经,有位兄弟, 有个处理5级联动 的业务 的任务, 涉及到9张表, 在同一个页面. 总之搞的昏天暗地, 也没有最终做完.
本示例中用到的: foreach / click / bind / attr / $parent/ $parentContext / visible / $data / $root / textInput / $index
在 click 绑定说明文档中的第二节, Note 2: Accessing the event object, or passing more parameters.
如果事件绑定不指定参数, 默认的参数就是当前绑定到Html 对象上的 模型数据.
$parent 指 当前 绑定的模型数据 的 父模型数据.
$parentContext 指 当前 绑定的模型数据 的父模型数据的上下文.
主从关系的经典场景就是: 点击主记录,显示对应的从数据列表.
在 html 中, 能达到这种效果的最简单的途径就是 显示与隐藏, 或动态生成.
如果是基于 AJAX 的数据提交, 动态生成无所谓, 最终的数据都记录在 JS对象中. 但是如果是基于 Form 表单提交, 动态生成子列表就不行了, 所以本示例用的是 显示/隐藏.
在VS 中, 按 F5 Debug , 打开 IE 后, 在解决方案中可以看到一个 Function Code 项, 可以对其进行调试:
序号使用的是 $index , $Index 只存在于 foreach 中, 是 $context 的子对象.
即数组的长度
因为 Companys 的定义如下:
this.Companys = ko.observableArray();
ko.observableArray() 返回的是 Function 对象, 该对象也有一个 length 属性, 但是其值一直都是 0
Companys() 返回的即上图中的 _lastValue 数组, 所以, 在绑定的时候, 是写的:
text: Companys().length , 而不是 text: Companys.length
跟据你的 Companys 定义而定. 如果 this.Companys = […] 的话, 绑定就要写成 text: Compansys.length 了.
在点击某一行后, 要把子级列表显示出来, 这里是通过修改模型数据中的 IsCurrent 来实现的.
<tr data-bind="click:SetCurrent.bind(null,$data, $root),attr:{class:IsCurrent() ? ‘success‘ : ‘‘}">
3 var BaseObj = function () { 5 /// <summary>可设置当前活动对象的基类</summary> 9 //是否当前对象 11 this.IsCurrent = ko.observable(false); 13 this.SetCurrent = SetCurrent; 15 }; 16 19 var SetCurrent = function (o, oc) { 21 /// <summary>设置当前活动对象</summary> 23 /// <param name="o" type="object">要设置为活动的对象</param> 25 /// <param name="oc" type="object">活动对象的记录者</param> 26 27 if (o == null) 29 return; 30 33 //将原来的活动对象变为非活动对象. 35 if (oc.Current != null) 37 oc.Current.IsCurrent(false); 38 41 oc.Current = o; 43 oc.Current.IsCurrent(true); 45 }
子级列表的 visible 也绑定到了 IsCurrent 上:
<!-- ko foreach:Companys --> <div data-bind="foreach:Products, visible:IsCurrent()"> Companys 的作用范围 …products 的作用范围 …
这样, 当主记录的 IsCurrent 变化后, 子列表显示/隐藏就会随着变化.
本示例中使用拖动插件是 jquery ui 提供的 sortable
一开始我是取 拖动调整后 的 单元格(TD)中的港口代码 来重组模型数据.
但是该列表中, 同一港口代码允许出现两次. 这样就出现问题了.
其实跟本就不用那么复杂.
ko 有个 dataFor API , 直接拿来用就是了.
1 var sortRegPortsByUISeq = function () { 3 var ports = []; 5 $("tbody tr").each(function (i, tr) { 7 //获取绑定到行上的数据对象 9 var port = ko.dataFor(tr); 11 ports.push(port); 13 }); 17 vm.RegPorts(ports); 19 }
该业务的条件如下:
如果为一口价, 只允许有一条数据. 可以没有数据.
自动把上一条的 "小于" 带到 下一条的 "大于" 中. 因为 "大于"不能手工输入.
控制新增按钮显示与否用以下语法:
visible: (FAK_TEU_FORMULA_MODE() == 1 && $data.FAK().length < 1) || (FAK_TEU_FORMULA_MODE() != 1)
FAK_TEU_FORMULA_MODE() == 1 即 一口价
$data.FAK().length < 1 即 当前还没有数据
自动带入上一条的值到下一条的实现:
//RANGE_TO的更改之前的值 this._RangeTo = 0; //当 RANGE_TO 改变的时候, 执行 vm.RangeToChanged 方法 this.RANGE_TO.subscribe(doFunction(vm.RangeToChanged, this, type)); this.RANGE_TO.subscribe(function (o) { /// <summary>change 之前, 将 RANGE_TO 的值保存,用于后面比较</summary> self._RangeTo = self.RANGE_TO.peek() || 0; //.peek() 返回可观察对象的最后值 }, null, "beforeChange")
这里定义了一个 RANGE_TO 和 一个 _RANGE_TO. _RANGE_TO 用于保存旧值.
还定义了两个针对 RANGE_TO 的 订阅, 一个无事件, 一个指定事件 beforeChange
当可观察对象发生了变化, 就会执行订阅(subscribe)方法.
beforeChange , 即在改变值之前.
具体的如何将值传入到下一条记录, 在 RangeToChanged 方法中定义.
参于计算的各数据项, 必须是可观察的. 其它无特殊说明.
大量的表单项, 展现在页面上, 不是随便输入, 就可以提交的, 这就需要有验证规则.
MVC 结合 jQuery.Validate 对客户端的验证做的很完美, 基本不用写一句JS代码就可以处理绝大部分的数据校验. 这是因为 MVC输出HTML表单项的时候, 会把模型的 DataAnnoation 转化为 data-val-xxx .
想使用 KO, 又不想丢掉MVC提供的这个功能, 怎么办呢?
自定义 helper
@helper A(string prop) { @Html.TextBox(prop, null, new {data_bind = string.Format("value:{0},attr:{{name:‘routes[‘ + $parentContext.$index() + ‘].Ports[‘ + $index() + ‘].{0}‘}}", prop),@class = "form-control input-sm"}) } @helper B(string prop) {
@Html.Hidden(prop, null, new {data_bind = string.Format("value:$index,attr:{{name:‘routes[‘ + $parentContext.$index() + ‘].Ports[‘ + $index() + ‘].{0}‘}}", prop)})
} @helper C(string prop) {
@Html.CheckBox(prop, false, new {data_bind = string.Format("checked:{0},attr:{{name:‘routes[‘ + $parentContext.$index() + ‘].Ports[‘ + $index() + ‘].{0}‘}}", prop)})
}
使用
<td>@A(Helper.GetPropertyName(Html, m => m.PortCode))</td> <td>@C(Helper.GetPropertyName(Html, m => m.IsBack))</td>
这样就使 KO 和 MVC 无缝的结合在一起了.
标签:
原文地址:http://www.cnblogs.com/xling/p/4244828.html