最近开始用KnockoutJS 作为client的主要框架,结合Bootstrap, BootstrapValidator, 直接调用Web api,绕过MVC的controller那层来交互数据,效果非常不错,项目做到现在进度一半,knockout的优点很多,所以这里小结一下:
Q: 什么是KnockoutJS?
A: 我们都叫它KO, 一个基于MVVM架构的轻量级JavaScript框架,最大的特点就是支持界面Dom对象和JS 数据对象的双向绑定。
Q:KO的优点?
- 免费,使用MIT开源软件许可协议
- 纯JavaScript, 可以和任何其他框架一起工作,没有依赖
- 轻量级框架, 相对于AugularJS, jQuery,压缩后只有20kb
- 已经比较成熟,支持IE6+, Firefox 3.5+, Chrome, Opera, Safari等浏览器
- KO做的事情j比如Query也能做,但是比较麻烦,有很多代码需要写,而且不够直观; AugularJS也能做,但是比较复杂,学习成本较高。
接下来,我们来了解下如何完成一个KO的页面:
- 数据
KO提供了灵活的方式支持对JS的对象定义在View Model,然后绑定到HTML元素上,View Model里面的值和 HTML上面的元素相匹配。
- 声明普通JS 对象来定义View Model对象
varmyViewModel = { personName:'Bob', personAge: ko.observable(123), jobs:ko.observableArray("Engineer", "Tester") }; varContactsModel = function (contacts) { var self = this; self.FirstName = "King"; self.LastName =ko.observable(""); self.Id = ""; }
Note:
- 普通的赋值适用于值不变的对象,适用于Lable等
- Observable代表双向绑定,也就是界面的DOM对象更新,这个JS变量也会跟着更新, 一般的input需要定义成这种对象。
- ObservableArray对应数组的双向绑定,适用对Checkbox等多选的对象。
b. 声明JSON为View Model对象
var initialData = [ { firstName: "Danny",lastName: "LaRusso",id:"C6A2D391-6B68-42EA-9EE2-3E25756143C2", phones: [ { type: "Mobile",number: "(555) 121-2121", id:"C6A2D391-6B68-42EA-9EE2-3E25756143C1" }, { type: "Home", number:"(555) 123-4567", id:"C6A2D391-6B68-42EA-9EE2-3E25756143C5" }] }, { firstName: "Sensei",lastName: "Miyagi", id:"C6A2D391-6B68-42EA-9EE2-3E2575614334", phones: [ { type: "Mobile",number: "(555) 444-2222", id:"C6A2D391-6B68-42EA-9EE2-3E25756143C20" }, { type: "Home", number:"(555) 999-1212", id:"C6A2D391-6B67-42EA-9EE2-3E25756143C2" }] } ]; var ContactsModel = function (contacts) { var self = this; self.contacts =ko.observableArray(ko.utils.arrayMap(contacts, function (contact) { return { firstName:ko.observable(contact.firstName), lastName:ko.observable(contact.lastName), id: contact.id, phones:ko.observableArray(ko.utils.arrayMap(contact.phones, function (phone) { return { type2:ko.observable(phone.type), number2:ko.observable(phone.number), id: phone.id } })) }; })); }
KO提供了Ko.utils类库来支持一些基本的操作,比如ko.utils.arrayMap相当于$.each,可以对数组里面的对象逐个绑,在多层结构的JSON或数据非常有用。这种方式相当于手动绑定JSON对象里面的元素,可以过滤掉不用的,也可以重命名。如果整个JSON都需要用,不需要过滤,而且不需要重命名,可以用KO的Mapping插件,非常方便。一开始不知道有这个Mapping插件,写了很多手动匹配的代码。。。
- 数据绑定
定义好数据,接下来就是绑定到HTML作用域了。使用applyBindings方法,第一个参数是ViewModel,第二个参数是HTML的节点,这里注意下,如果第二个参数不指定或指定的DOM对象不存在,都会绑定为全局的作用域。
ko.applyBindings(newContactsModel(initialData), document.getElementById("contactsList"));
- HTML View层
这也是KO的核心部分之一,KO定义了很多常用的Bindings,针对HTML 标签,属性,CSS样式,事件,所有的绑定行为写在Data-bind属性里,也可以写在注释<!-- -->里(这个非常有用)。有了View层的binding, 所有的逻辑都能够很显式的在HTML里找到,enable/disable,visible/invisible, click/textInput, if/ifnot等等,比起jQuery把所有的行为写在js里,HTML完全看不出来有哪些逻辑直观很多。
<divid='contactsList'> <table class='contactsEditor'> <thead> <tr> <td>First name</td> <td>Last name</td> <td>Operation</td> </thead> <tbodydata-bind="foreach: contacts"> <!-- foreach 绑定--> <tr> <td><inputdata-bind="value: firstName" class="form-control" /> <!-- value 绑定--> </td> <td><inputdata-bind='value: lastName' class="form-control" /></td> <td> <buttonclass="btn btn-warning" data-toggle="modal"data-target="#updateContact" data-bind="click:$root.updateContactInit.bind(this,firstName,lastName,id)">Update acontact</button> <!-- click事件绑定--> <buttondata-bind='click: $root.removeContact' class="btnbtn-warning">Delete Contact</button> </td> </tr> </tbody> </table> </div>
Binding的使用在KO的官网都有解释 http://knockoutjs.com/documentation/introduction.html, 个人觉得比Tom大叔讲的要明白很多。
Note:
- click: $root.removeContact, 这个绑定会把当前层的Ko对象作为参数传递给removeContact方法,如果想要传递多个参数,可以使用bind方法,例如$root.updateContactInit.bind(this,firstName,lastName,id)
- checked绑定对于checkbox非常有用,只需声明个数组 this.checkedList= ko.observableArray(); 然后在HTML绑定时指定value和checked就能实现多选,不需要很复杂的代码:databind="value: Id, checked: checkedList", checkedList取到的就是选中的Id列表。
- Input标签使用textInput 绑定而不是value, textInput在值改变的时候就能触发, value要等到keydown才行。
最后就是KO的一个带2层数据的CRUD例子,官网上的例子是直接在input里修改,实际项目很少这么做,一般都是弹出框修改,所以我把它改成了bootstrap的popupmodel:http://jsbin.com/bahofu/ ,里面的updatePhone方法留给大家思考。
Reference:
http://blog.nebithi.com/knockoutjs-vs-angularjs/