目錄 一、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的介紹我們知道ViewModel是三者核心,ViewModel不但定義了綁定在View上的資料,同時也定義了響應View事件的操作。在實際Web應用開發中(尤其是我從事的企業應用開發),往往存在着很多類似的頁面。它們不但具有相同的UI結構,對應的操作行為也大同小異,這意味着ViewModel的資料成員和方法成員(實際上KO中用于雙向綁定的資料也是方法)也基本上類似,那麼出用重用的目的,我們可以考慮為這些相似的頁面定義相應的ViewModel。
企業應用很多情況下是在進行資料的維護,即對資料進行基本的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>