天天看点

MVVM(Knockout.js)的新尝试:多个Page,一个ViewModel一、MVVM模式二、类似的UI结构和操作行为三、共享的ViewModel四、Controller的定义五、View的定义六、_Layout.cshtml定义

目录 一、MVVM模式 二、类似的UI结构和操作行为 三、共享的ViewModel 四、Controller的定义 五、View的定义 六、_Layout.cshtml定义
MVVM(Knockout.js)的新尝试:多个Page,一个ViewModel一、MVVM模式二、类似的UI结构和操作行为三、共享的ViewModel四、Controller的定义五、View的定义六、_Layout.cshtml定义

MVVM可以看成是MVC模式的一个变体,Controller被ViewModel取代,但两者具有不同的职能,三元素之间的交互也相同。以通过KO实现的MVVM为例,其核心是“绑定”,我个人又将其分为两类,即“数据的绑定”和“行为的绑定”。所谓数据的绑定,就是将ViewModel定义的数据绑定到View中的UI元素(HTML元素)上,双向/单向绑定同时被支持,而我们通常使用的是双向绑定。而行为绑定体现为事件注册,即View中UI元素的事件(比如某个<button>的click事件)与ViewModel定义的方法(function)进行绑定。

如右图所示,用户行为(比如某个用户点击了页面上的某个Button)触发View的某个事件,与之绑定的定义在ViewModel中的EventHandler(ViewModel的某个方法成员)被自动执行。它可以执行Model,并修改自身维护的数据,由于View和ViewModel的数据绑定是双向的,用户在界面上输入的数据可以被ViewModel捕获,而ViewModel对数据的更新可以自动反映在View上。这样的好出显而易见——我们在通过JS定义UI处理逻辑的时候,无需关注View的细节(View上的HTML),只需要对自身的数据进行操作即可。

MVVM(Knockout.js)的新尝试:多个Page,一个ViewModel一、MVVM模式二、类似的UI结构和操作行为三、共享的ViewModel四、Controller的定义五、View的定义六、_Layout.cshtml定义

通过上面针对MVVM的介绍我们知道ViewModel是三者核心,ViewModel不但定义了绑定在View上的数据,同时也定义了响应View事件的操作。在实际Web应用开发中(尤其是我从事的企业应用开发),往往存在着很多类似的页面。它们不但具有相同的UI结构,对应的操作行为也大同小异,这意味着ViewModel的数据成员和方法成员(实际上KO中用于双向绑定的数据也是方法)也基本上类似,那么出用重用的目的,我们可以考虑为这些相似的页面定义相应的ViewModel。

MVVM(Knockout.js)的新尝试:多个Page,一个ViewModel一、MVVM模式二、类似的UI结构和操作行为三、共享的ViewModel四、Controller的定义五、View的定义六、_Layout.cshtml定义

企业应用很多情况下是在进行数据的维护,即对数据进行基本的CRUD操作。举个实际的例子,假设一个Web应用都采用左图所示的页面和操作行为进行针对不同数据的维护:用户输入查询条件点击“Search”按钮筛选需要操作的数据,获取的数据以表格的形式显示出来;考虑到数据量可能比较大,分页获取往往是必须的;表格的Titile为可点击的链接,用于根据当前列进行排序。

用户可以点击数据行右侧的链接(Update和Delete)修改或者删除当前记录,也可以点击上边的Add按钮添加一条新的数据。数据添加和修改的数据均通过弹出的对话框(如右图所示)的形式进行编辑。

那么现在我们希望定义一个公用的“类型”来作为这种页面的ViewModel,并且将相应的数据和行为操作定义其中。虽然这个页面结构比较简单,但是包含的功能还是挺多的,不仅仅具有基本的CRUD操作,还具有排序和分页的功能,所以为这样的页面定义一个公共的ViewMode还是要定义不少的成员。如下所示的就是这个ViewModel的定义,由于我为每个成员加上了注释,所以每个成员的作用和实现逻辑还是比较清晰的,在这里我就不一一解释了。补充一点的是,演示实例的样式和对话框功能是通过Bootstrap实现的。

目前我们公共的View已经定义好了,我们来看看在具体的页面中的绑定如何定义,以及ViewModel如何初始化。我们同样采用一个ASP.NET MVC应用作为例子,模式的场景就是上图中演示的“联系人管理”,如下所示的是表示联系人的Contact类型的定义:

如下所示的是Controller的定义,联系人管理页面通过默认的Action方法Index呈现出来,在View中实现CRUD操作的Ajax请求的目标Action方法也定义其中。用于获取数据的GetContacts方法不仅仅在用户点击“Search”按钮时被调用,实际上用户点击页码获取当前页数据,以及点击表格标头针对某个字段进行排序的时候调用的也是这个方法。该方法返回一个JSON对象,其Data属性返回具体的数据(针对指定的页码),而用于客户端重置页码的TotalPages属性表示总页数,在这里每页记录数设置为2。

针对HTTP-GET请求的Add和Update方法返回的是一个ViewResult,换句话说客户端通过Ajax请求最终得到的结果是相应的HTML。客户端最终将HTML作为对话框的内容显示出来,就是我们看到的“联系人编辑”对话框。两个方法呈现的都是一个名为ContactPartial的分部View,从如下定义可以看出这是一个Model类型为Contact的强类型View,Contact对象以编辑模式呈现在一个以Ajax方式提交的表单中。由于数据添加和数据更新操作针对不同的目标Action,而且提交之后回调的JavaScript函数也不一样,两者以ViewBag的形式(ViewBag.Action和ViewBag.OnSuccess)来动态设置。

我们最终来看看作为“联系人管理”页面的Index.cshtml的定义,由于大部分内容都可以与ViewModel的成员进行绑定,所以我们可以将它们通通定义在Layout之中,所以Index.cshtml的定义是非常少的。如下面的代码片断所示,HTML部分只包含针对Contact对象4个属性的绑定而已,因为ViewModel不包括具体数据类型相关的属性定义。对于JS部分,我们指定相应的options创建了一个具体的ViewModel对象并调用ko的applyBindings方法应用到当前页中。options指定的内容包括具体的title、searchCriteria、headers、defaultOrderBy和四个用于获取CRUD操作地址的函数。

所有能够共享的内容都被定义在如下所示的布局文件中,我们简单地分析一下每个部分具体和ViewModel的哪些成员绑定:

作为查询条件的标签和文本框(简单起见,这里只考虑了这一种输入元素类型)与ViewModel的searchCriteria进行绑定,集合元素包含标签(displayText)和对应的值(value)。

Search、Reset和Add按钮的Click事件则和ViewModel的search、reset和onDataAdding方法进行绑定。

与表格头部链接绑定的是ViewModel的headers,headers集合的元素包含显示文字(displayText)、对应的排序字段名(value)和宽度(width)。

对于表格头部的每一列,我们还通过KO的visible绑定设置了表示当前排序列和排序方向的图标(<i class="icon-circle-arrow-up" >和<i class="icon-circle-arrow-down" >)。

表示获取数据的表格主体部分与ViewModel的recordSet绑定。

每个记录后的Update和Delete链接的Click事件与ViewModel的onDataUpdating和onDataDeleting方法绑定。

页码列表和ViewModel的pageNumbers绑定,当前页的CSS(.selected)利用ViewModel的pageIndex来设置。

表示弹出对话框<div>的内容和ViewModel的dialogContent绑定。

作者:蒋金楠

微信公众账号:大内老A

如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

<a href="http://www.cnblogs.com/artech/archive/2013/01/06/shared-view-model.html" target="_blank">原文链接</a>

继续阅读