在使用knockout过程中 发现jquery tmpl 在循环上性能很差。经过多方查询得知 knockout 其实有 自己的 无容器绑定。
那么废话少说现在开始。
1、后台模型展示
为了构建更生动的数据源我们先声明一个类,起名叫 User 这个类的接口一眼就看穿了,需要注意的地方就是 每个User 都有一个 UserFriends的集合。
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace WebSite.ViewModels { public class User { public Guid UserId { get; set; } public string UserName { get; set; } public List<User> UserFriends { get; set; } } }
2、构建数据源。。。此处省略吧。自己new个数组就行了,我直接粘贴代码。
这里面比较生僻的 地方 是 SerializeHelper这个类是我们自己封装的,而这句话 ser.Serializer(users) 其实返回的就是一个json串。
这里在啰嗦一句,我们都知道json传 为string类型。那么为什么 return View(ser.Serializer(users)); 不这么写呢。
这个源于 mvc View(string ViewName)有个重载方法。你直接写string 他就当成 视图名称了。
当然还有个重载 叫 View(object obj); 这个 就是 页面要的model了。
public ActionResult NoWarp() { DotNet.Common.Json.SerializeHelper ser = new DotNet.Common.Json.SerializeHelper(); List<User> users = new List<User>(); for (int i =1; i <= 10; i++) { users.Add(new User() { UserId = Guid.NewGuid(), UserName = "崔" + i, UserFriends = new List<User>() { new User(){ UserId=Guid.NewGuid(), UserName="蚊子"+i }, new User(){ UserId=Guid.NewGuid(), UserName="#总"+i }, new User(){ UserId=Guid.NewGuid(), UserName="老郭"+i }, new User(){ UserId=Guid.NewGuid(), UserName="DK"+i }, } }); } return View(new MvcHtmlString(ser.Serializer(users))); }
3、页面
上面我们 new MvcHtmlString 那么页面上就需要通过这个类型把他接过来。 注意代码第一行 @model MvcHtmlString 记得一定要用mvchtmlstring 要不然会被直接转义。
到这里准备工作就做完了。
相信用knockout的对上面都不陌生。
knockout 本身提供了 无容器绑定。
具体实现 大家直接看就行了。
值得注意的是,在循环中 如何获取 item1 item2 的索引
在当前循环中获取 索引 $index
获取上一层循环索引 $parentContext.$index
在上一次的索引 $parentContext.$parentContext.$index
无限级 $parentContext....$parentContext.$index
以下是完整页面代码。
@model MvcHtmlString @{ ViewBag.Title = "NoWarp"; } @*<script src="~/Assets/plugins/knockout.js"></script>*@ @*layout中已经引用jquery和knockout这里就不在提了*@ <h2>NoWarp</h2> <div id="pageUsers"> <table class="table table-striped table-advance"> <thead> <tr> <th style="width:300px;"> ID </th> <th style="width:150px"> 姓名 </th> <th> 朋友们 </th> </tr> </thead> <tbody> <!--ko foreach:{data:Users,as:'iuser'}--> <tr> <td> <!--ko text:iuser.UserId--> <!--/ko--> </td> <td> <!--ko text:iuser.UserName--> <!--/ko--> </td> <td data-bind="foreach:{data:iuser.UserFriends,as:'ifriend'}"> <!--ko text:ifriend.UserName--> <!--/ko--> <!--ko if:$index()<iuser.UserFriends.length-1 -->,<!--/ko--> <!--ko if:$index()==iuser.UserFriends.length-1&&$parentContext.$index()==3--> <span style="color:red">*</span> <!--/ko--> </td> </tr> <!--/ko--> </tbody> </table> </div> <script> var ViewModel = { Users: ko.observableArray(eval('(' + '@Model' + ')')) }; $(function () { ko.applyBindings(ViewModel, $("#pageUsers")[0]); }); </script>
进阶篇
下面开始增加难度,来个删除功能。
我们都知道 数组中 删除 是这么写的 array.splice(index,length);
上面我们可以获取索引 那么删除 就可以写了。上代码
先来正常的逻辑
<td data-bind="foreach:{data:iuser.UserFriends,as:'ifriend'}"> <!--ko text:ifriend.UserName--> <!--/ko--> <!--ko if:$index()<iuser.UserFriends.length-1 -->,<!--/ko--> <a href="javascript:;" data-bind="click:function(){iuser.UserFriends.splice($index(),1);}">删除此朋友</a> <br/> <!--ko if:$index()==iuser.UserFriends.length-1&&$parentContext.$index()==3--> <span style="color:red">*</span> <!--/ko--> </td>
页面生成如下
我点击了删除 发现 居然无效。好的 那我们加个debugger 看看 发生了什么。。
好吧 从最开始就入坑了,大家都知道knockout 绑定 想要实现页面联动 必须声明依赖属性。我们直接把json对象拿过来用。当然不好使了。
好的那我们开始构建一个 user模型。
<script> function User(model) { this.UserId = ko.observable(model ? model.UserId : '空Id'); this.UserName = ko.observable(model ? model.UserName : '空Name'); var tmpF = []; if (model && model.UserFriends) { $(model.UserFriends).each(function (i, item) { tmpF.push(item); }); } this.UserFriends = ko.observableArray(tmpF); } var ViewModel = { Users: ko.observableArray([]) }; $(function () { $(eval('(' + '@Model' + ')')).each(function (i, item) { ViewModel.Users.push(new User(item)); }); ko.applyBindings(ViewModel, $("#pageUsers")[0]); }); </script>
好的模型转换之后我们看一下对象结构
好的,结构变成绑定数组了。ok 这个删除也好使了。
还有在knockout绑定中
有 $parent 这个对象代表当前对象的父级。
$parents 代表当前对象绑定的 父级集合 $parents[0] 最近 $parents[n]。。无限级。
$element 代表当前绑定的元素。(无容器获取不到。)
当官方文档是 还有很多内置对象,我们常用大概也就是这些。
对象详细请参考:http://knockoutjs.com/documentation/binding-context.html
具体可以参考:http://knockoutjs.com/
注意事项,在knockout 无容器绑定模版时 ,如果需要写循环
可以这么写。
我就手懒 直接在官方api上截图了。
值得注意的是:模版中不能使用 foreach bind 和 if bind 目前发现这2个不能用。
如果模版套模版实现循环时可以这么写。
我就不一一截图吧,路子都在代码里。
直接上完整代码
@model MvcHtmlString @{ ViewBag.Title = "NoWarp"; } @*<script src="~/Assets/plugins/knockout.js"></script>*@ @*layout中已经引用jquery和knockout这里就不在提了*@ <h2>NoWarp</h2> <div id="pageUsers"> <table class="table table-striped table-advance"> <thead> <tr> <th style="width:300px;"> ID </th> <th style="width:150px"> 姓名 </th> <th> 朋友们 </th> <th> 操作 </th> </tr> </thead> <tbody> <!--ko template:{name:'tempate1',foreach:Users}--> <!--/ko--> <tr> <td colspan="4"> —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— </td> </tr> <!--ko foreach:{data:Users,as:'iuser'}--> <tr> <td> <!--ko text:iuser.UserId--> <!--/ko--> </td> <td> <!--ko text:iuser.UserName--> <!--/ko--> </td> <td data-bind="foreach:{data:iuser.UserFriends,as:'ifriend'}"> <!--ko text:ifriend.UserName--> <!--/ko--> <!--ko if:$index()<iuser.UserFriends.length-1 -->,<!--/ko--> <a href="javascript:;" data-bind="click:function(){iuser.UserFriends.splice($index(),1);}">删除此朋友</a> <br /> <!--ko if:$index()==iuser.UserFriends.length-1&&$parentContext.$index()==3--> <span style="color:red">*</span> <!--/ko--> </td> <td> <a href="javascript:;" data-bind="click:function(){Users.splice($index(),1);}">删除用户</a> </td> </tr> <!--/ko--> </tbody> </table> </div> <script> function User(model) { this.UserId = ko.observable(model ? model.UserId : '空Id'); this.UserName = ko.observable(model ? model.UserName : '空Name'); var tmpF = []; if (model && model.UserFriends) { $(model.UserFriends).each(function (i, item) { tmpF.push(item); }); } this.UserFriends = ko.observableArray(tmpF); } var ViewModel = { Users: ko.observableArray([]) }; $(function () { $(eval('(' + '@Model' + ')')).each(function (i, item) { ViewModel.Users.push(new User(item)); }); ko.applyBindings(ViewModel, $("#pageUsers")[0]); }); </script> <script type="text/html" id="tempate1"> <tr> <td> <!--ko text:$data.UserId--> <!--/ko--> </td> <td > <!--ko text:$data.UserName--> <!--/ko--> </td> <td data-bind="template:{templateOptions:{itemuser:$data,itemIndex:$index() },foreach:$data.UserFriends,name:'tempate2',as:'itemFriend'}"> </td> <td> <a href="javascript:;" data-bind="click:function(){$parent.Users.splice($index(),1);}">删除用户</a> </td> </tr> </script> <script type="text/html" id="tempate2"> @*这里如果想过去 上级的 item 就需要用 templateOptions 作为传入参数。*@ <!--ko text:$item.itemuser.UserName--> <!--/ko--> 的朋友 <!--ko text:itemFriend.UserName--> <!--/ko--> <br/> </script>
结果是这样的。