Knockout中ViewModel与Model的互转
在我们平常的开发当中使用频率最多的就是CRUD(添加、更新、删除、查询)。 而“添加”和“编辑”操作又是整个数据源的入口,在整个CRUD中占有非常重要的地位。常规情况下我们做一个编辑操作时,首先需要将实体对象从数据库中提取出来并将其值展示在页面上供用户进行编辑。用户编辑完成之后点击提交按钮时我们需要再将实体对象的值从页面中提取出来,并组成一个完整的实体对象提后交到后台进行处理。
那么这种业务场景我们如何来使用knockout来完成呢?ko是一种MVVM(Model-View-ViewModel)模式的应用,本质上我们的实体对象是一个数据模型也就是model,如果我们想要它展示在View上边那么就需要将它转化为ViewModel。同理,如果我们想要取得页面的数据信息,那么也就需要再将ViewModel转换为model。这样我们需要解决的两个是:
1、展示数据时我们需要将model转换为ViewModel 。
2、提交数据之前,我们需要将ViewModel转换为model对象
Model转换为ViewModel
ko的监控机制是一个典型的“观查者”模式的实现。ko.observable()方法将对象转换为一个监控对象,即:“被观察者”,通过使用“被观察者”的subscribe方法来注册“观查者”。首先,我们定义一个实体对象(entity)并将它监控起来,然后我们订阅一个专门的用来将Model转换为ViewModel的Listener。当这个Listener接收到entity发生变化的通知时,将对entity的属性进行检测,如果它没有被监控,则将它转换为一个监控对象。ko.isObservable是ko官方提供的一个辅助API,它返回一个Boolean值,用来判断对象是否已经被ko监控。
//案例视图模型 function viewModel() { var model = this; //使用ko.observable将实体对象监控起来 this.entity = ko.observable() //注册一个用来将entity转换为viewModel的listener this.entity.subscribe(function (newValue) { //判断属性children是否已经被监控 if (newValue && !ko.isObservable(newValue.children)) { //将children属性转为监控对象 newValue.children = ko.observableArray(newValue.children || []); //将name属性转换为监控对象 newValue.name = ko.observable(newValue.name); } }); }
ViewModel转换为Model
将ViewModel转换为Model是一个让很多新手很痛苦的地方。常规情况下因为entity的属性因在转换为ViewModel时被转换为“被观察者”也就是由属性变成了一个函数,我们需要它再次将它转回为属性。并且因为ViewModel和页面显示是直接相关联的,我们这时候还不能直接修改entity对象,因为一旦发生修改,那么页面也跟随着发生变化。所以实际上我们需要clone一个entity对象,并将监控对象转换为属性。这个过程相对来讲比较麻烦,也是很多人抱怨的地方。
实质上knockout已经帮我们想到并处理了这个问题。ko.toJS方法就是官方提供的一个专门用来转换ViewModel的辅助API,它可以自动将监控对象转为属性对象。同时ko还提供了一个ko.toJSON方法,它会将ViewModel直接转换为model的json字符串形式。那么我们定义一个json对象,它的值就是viewModel转为json字符串展示在页面上,让我们可以即时的看到页面发生变化时实体对象同时跟随发生的变化。
//实体对象生成的字符串,并来在页面上同步展示View发生的变化 this.json = ko.computed(function () { return ko.toJSON(model.entity); });
页面ViewModel
<div id="case" data-bind="with:entity" style="padding-left:25px;"> <div> <label>名称:</label> <input data-bind="value: name" /> </div> <div> <label>子级:</label> <ul style="display:inline-block"> <!-- ko foreach:children --> <li data-bind="text:$data"></li> <!-- /ko --> <li> <input data-bind="value:$root.newChildName" /> <input data-bind="click:$root.addChildEventHandle" type="button" value="增加" /> </li> </ul> </div> <div style="margin-top:50px;"> <label>json:</label> <label data-bind="text:$root.json"></label> </div> </div>
绑定页面视图
var model = new viewModel(); model.entity({ name: '张三', children: [ '狗子', '二蛋' ] }); ko.applyBindings(model, document.getElementById('case'));