江湖中那場異常慘烈的廝殺,現在都快被人遺忘了。當年,全部的武林同道爲了同一個敵人都拼盡了全力,爲數很少的倖存者心灰意冷,隱姓埋名,遠赴他鄉,他們將惟一的但願寄託給時間。少年子弟江湖老,紅顏少女的鬢邊也有了白髮。多年之後,聽聞那個魔頭也不久於人世,他們欣欣然回鄉,卻發現當初殫精竭慮研究出來對付敵人的招數全無用處,曾經受人尊敬的大俠如今被稱爲——新手 or 菜鳥。月下小酌,孤獨的他們對着夜空舉起酒杯,吼一聲:「走你,IE6!」css
-----------------------------------------------------割--------------------------------------------------------------------html
Bootstrap是一個前端框架,解放Web開發者的好東東,展示出的UI很是高端大氣上檔次,理論上能夠不用寫一行css。只要在標籤中加上合適的屬性便可。請參看Bootstrap中文文檔,這是3.0版本。前端
KnockoutJS是一個JavaScript實現的MVVM框架。很是棒。好比列表數據項增減後,不須要從新刷新整個控件片斷或本身寫JS增刪節點,只要預先定義模板和符合其語法定義的屬性便可。簡單的說,咱們只須要關注數據的存取。官網文檔。ajax
Bootstrap負責UI,KnockoutJS負責數據綁定,二者相得益彰,Web前端必備利器。json
咱們來作一個簡單的例子展現一下它們的威力。bootstrap
要擱之前,實現相似功能,能夠有兩個選擇:a)直接操做DOM,夠喝一壺,通常喜歡展示技術同窗的首選;b)藉助各類拉風的重量級JS框架,好比extjs,使用它們的API以減小工做量,不過這些框架的學習曲線也挺扭曲。固然本文所說的兩個框架也涉及到各自的JS類庫,but,提供給開發人員的使用方式是徹底不一樣的,後者更鬆散(廢話,兩個固然比一個鬆散)、靈活,且是基於特性聲明的方式,我的表示至關不錯。下面就讓咱們開始碼吧。c#
首先搭個初步的框架:後端
<div id="divAuthManage" class="row" style="margin-top: 30px;"> <div class="col-md-4 col-sm-4 col-xs-6"> <div> <div class="input-group"> <span class="input-group-addon">用戶名</span> <input id="inputUserName" type="text" class="form-control" /> <span class="btn btn-primary input-group-btn">添加</span> </div> <div id="divWaring" class="alert alert-warning" style="display: none;"></div> </div> <table class="table table-bordered table-hover" style="margin-top: 20px;"> <thead> <tr> <th>用戶ID</th> <th>用戶名</th> <th style="text-align: center;">刪除</th> </tr> </thead> <tbody> </tbody> </table> </div> <div class="col-md-8 col-sm-8 col-xs-12"> @foreach (AreaElement area in Model.Areas) { <div class="panel panel-default"> <div class="panel-heading"> @{if (area.Sites.Count == 0) { <label class="checkbox"> <input type="checkbox" value="@area.Code" /> @area.Name </label> } else { @area.Name } } </div> @if (area.Sites.Count > 0) { <div class="panel-body"> @foreach (SiteElement site in area.Sites) { <label class="checkbox-inline"> <input type="checkbox" value="@site.Code" />@site.Name </label> } </div> } </div> } <p class="text-right"> <button type="button" class="btn btn-default">保存</button> </p> </div> </div>
這裏就用到了bootstrap,若是一個元素使用了相應的class,它就會呈現bootstrap中預約義的樣式。bootstrap還提供了data-xxx屬性,這是用來以聲明方式使用組件,這裏沒有涉及。now,界面以下:api
圖中標註了須要改進的兩個地方,此時先不考慮。咱們如今要先把數據從後臺取出,以及其它的一些操做,因而引進KnockoutJS。接觸過WPF的都知道ViewModel的概念,說白了就是將前端分爲UI和交互邏輯,ViewModel就負責交互邏輯,knockoutJS也有這個東西。結合例子具體來看:前端框架
window.adApp.authManageViewModel = (function (ko) { var userList = ko.observableArray(), error = ko.observable(), addUser = function (username) { this.clearError(); if (!username) { error("請輸入用戶名."); } else { this._ajaxRequest("post", '/api/UserAuthority/AddUser', { "": username }, function (data) { if (!data.IsSucceed) this.error(data.Message); else { var user = new User(data.Data); this.userList.unshift(user); } }); } }, deleteUser = function (user) { this._ajaxRequest("delete", '/api/UserAuthority/DeleteUser', { "": user.userid }, function (data) { if (!data.IsSucceed) this.error(data.Message); else { this.userList.remove(user); } }); }, getUsers = function () { this._ajaxRequest("get", '/api/UserAuthority/GetUsers', null, function (data) { this.userList.removeAll(); for (var i = 0; i < data.length; i++) { userList.push(new User(data[i])); } }); }, selectUser = function (user) { for (var i = 0; i < userList().length; i++) { userList()[i].selected(false); } user.selected(true); this._ajaxRequest("get", '/api/UserAuthority/GetAccessNavItems', { userid: user.userid }, function (data) { user.navitems.removeAll(); for (var i = 0; i < data.length; i++) { user.navitems.push(data[i]); } }); }, clearError = function () { error(""); }; var viewmodel = { userList: userList, error: error, _ajaxRequest: ajaxRequest, addUser: addUser, deleteUser: deleteUser, clearError: clearError, _getUsers: getUsers, selectUser: selectUser, currentUser: ko.computed(function () { for (var i = 0; i < userList().length; i++) { if (userList()[i].selected()) { return userList()[i]; } } return null; }) }; viewmodel._getUsers(); return viewmodel; function ajaxRequest(type, url, data, callback) { // Ajax helper this.clearError(); $.ajax({ url: url, data: data, type: type, dataType: "json", context: this,//指定this爲當前對象viewmodel success: callback, error: function () { this.error("服務器錯誤."); } }); } })(ko); // Initiate the Knockout bindings ko.applyBindings(window.adApp.authManageViewModel);
window.adApp.authManageViewModel就是ViewModel,它包含了兩個屬性(UserList爲用戶集合,error爲提示信息,準確的命名應該相似msg,懶得改了)和若干函數(和服務端交互)。ko.applyBindings將該ViewModel綁定到頁面。上述代碼還涉及到兩個類型:
function NavItem(data) { var self = this; data = data || {}; // Persisted properties self.code = data.code; self.name = data.name; } function User(data) { var self = this; data = data || {}; // Persisted properties self.userid = data.userid; self.username = data.username; data.navitems = data.navitems || []; self.navitems = ko.observableArray(data.navitems); self.selected = ko.observable(false); } User.prototype.updateNavs = function () { var user = this; window.adApp.authManageViewModel._ajaxRequest( "put", '/api/UserAuthority/UpdateNavItems?userid=' + user.userid, { "": user.navitems()}, function (data) { if (!data.IsSucceed) this.error(data.Message); else { this.error("保存成功!"); } }); }
如今頁面代碼以下:
<div id="divAuthManage" class="row" style="margin-top: 30px;"> <div class="col-md-4 col-sm-4 col-xs-6"> <div> <div class="input-group"> <span class="input-group-addon">用戶名</span> @*data-bind="input: clearError" 不支持input綁定,so換用自定義綁定,or採用event綁定以下*@ <input id="inputUserName" type="text" class="form-control" data-bind="event: { input: clearError }" /> <span class="btn btn-primary input-group-btn" data-bind="click: function (data, event) { addUser(inputUserName.value) }">添加</span> </div> <div id="divWaring" class="alert alert-warning" style="display: none;" data-bind="animVisible: error"></div> </div> @*若是userList集合有項,才顯示該表格,注意if、ifnot的做用範圍不包括table標記自己,而是從thead開始*@ <table data-bind="if: userList().length > 0" class="table table-bordered table-hover" style="margin-top: 20px;"> <thead> <tr> <th>用戶ID</th> <th>用戶名</th> <th style="text-align: center;">刪除</th> </tr> </thead> <tbody data-bind="foreach: userList"> <tr data-bind="css: { success: selected }, click: function (data, event) { $parent.selectUser($data) }"> <td><span data-bind="text: userid"></span></td> <td><span data-bind="text: username"></span></td> <td style="text-align: center;"> <button type="button" class="btn btn-default btn-xs" data-bind="click: function (data, event) { $parent.deleteUser($data) }, clickBubble: false"> <span class="glyphicon glyphicon-remove"></span> </button> </td> </tr> </tbody> </table> </div> @*將下面div的綁定對象設爲currentUser,若是currentUser爲空,則該div中的內容不會顯示*@ <div class="col-md-8 col-sm-8 col-xs-12" data-bind="with: currentUser"> @foreach (AreaElement area in Model.Areas) { <div class="panel panel-default"> <div class="panel-heading"> @{if (area.Sites.Count == 0) { <label class="checkbox"> <input type="checkbox" value="@area.Code" data-bind="checked: navitems" /> @area.Name </label> } else { @area.Name } } </div> @if (area.Sites.Count > 0) { <div class="panel-body"> @foreach (SiteElement site in area.Sites) { <label class="checkbox-inline"> <input type="checkbox" value="@site.Code" data-bind="checked: navitems" />@site.Name </label> } </div> } </div> } <p class="text-right"> <button type="button" class="btn btn-default" data-bind="click: updateNavs">保存</button> </p> </div> </div>
代碼中加了不少data-bind屬性,做用不言自明。須要注意的是所謂自定義綁定。當綁定的值變更了,但願執行額外的邏輯(和c#中的事件類似),就會用到。這裏,當error的值有變更,爲空時提示面板隱藏,不然顯示:
<div id="divWaring" class="alert alert-warning" style="display: none;" data-bind="animVisible: error"></div>
animVisible就是一個自定義綁定,定義以下:
ko.bindingHandlers.animVisible = { update: function (elem, valueAccessor) { var error = ko.unwrap(valueAccessor()); if (error) { elem.innerText = error; $(elem).show(300); } else { $(elem).hide(300); elem.innerText = ""; } } };
OK,接下來,當點擊表格的每一行,currentUser就會自動計算獲得(ko.computed),並反饋到界面,綁定了該字段的div內部的相應節點的狀態也會相應變化(checkbox)。保存啥的就不說了。綜上所述,除了必要的與後臺交互的代碼,涉及到操做UI和DOM節點,咱們不須要寫一行JS,很爽很舒服。
ps:原本想寫詳細點,結果發現寫一大堆還不如幾行代碼來的清楚。文中如有錯誤之處,請及時告知,你們交流,共同進步。
knockoutjs補充: