KnockoutJS是一個JavaScript實現的MVVM框架。非常棒。比如列表數據項增減後,不需要重新刷新整個控件片段或自己寫JS增刪節點,只要預先定義模板和符合其語法定義的屬性即可。簡單的說,我們只需要關注數據的存取。
一、引言
由於最近公司的系統需要改版,改版的新系統我打算使用KnockoutJs來制作Web前端。在做的過程中,遇到一個問題——如何使用KnockoutJs來完成分頁的功能。在前一篇文章中並沒有介紹使用KnockoutJs來實現分頁,所以在這篇文章中,將補充用KnockoutJs+Bootstrap來實現數據的分頁顯示。
二、使用KnockoutJs實現分頁
這裡采用了兩種方式來實現分頁,第一種是將所有數據加載出來,然後再將所有數據分頁顯示;第二種是每次都只加載部分數據,每次請求都重新加載後面的數據。
對於這兩種方式,使用Razor方式實現的分頁一般都會采用第二種方式來實現分頁,但是對於單頁面程序來說,第一種實現方式也有其好處,對於不是非常大量的數據完全可以采用第一種實現方式,因為這樣的話,後面的數據的加載,用戶體驗非常的流暢。所以這裡將分別介紹這兩種實現方式。
2.1 每次加載部分數據的實現
這裡的後端代碼采用的是前一篇文章的代碼,只是多加了一些示例數據而已。具體的後端實現代碼為:
/// <summary> /// Web API 服務,為Web前端提供數據服務 /// </summary> public class TaskController : ApiController { private readonly TaskRepository _taskRepository = TaskRepository.Current; public IEnumerable<Task> GetAll() { return _taskRepository.GetAll().OrderBy(a => a.Id); } [Route("api/task/GetByPaged")] public PagedModel GetAll([FromUri]int pageIndex) { const int pageSize = 3; int totalCount; var tasks = _taskRepository.GetAll(pageIndex, pageSize, out totalCount).OrderBy(a => a.Id); var pageData = new PagedModel() { PageIndex = pageIndex, PagedData = tasks.ToList(), TotalCount = totalCount, PageCount = (totalCount+ pageSize -1) / pageSize }; //返回數據 return pageData; } } /// <summary> /// 任務倉儲,封裝了所有關於數據庫的操作 /// </summary> public class TaskRepository { #region Static Filed private static Lazy<TaskRepository> _taskRepository = new Lazy<TaskRepository>(() => new TaskRepository()); public static TaskRepository Current { get { return _taskRepository.Value; } } #endregion #region Fields private readonly List<Task> _tasks = new List<Task>() { new Task { Id =1, Name = "創建一個SPA程序", Description = "SPA(single page web application),SPA的優勢就是少量帶寬,平滑體驗", Owner = "Learning hard", FinishTime = DateTime.Parse(DateTime.Now.AddDays(1).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =2, Name = "學習KnockoutJs", Description = "KnockoutJs是一個MVVM類庫,支持雙向綁定", Owner = "Tommy Li", FinishTime = DateTime.Parse(DateTime.Now.AddDays(2).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =3, Name = "學習AngularJS", Description = "AngularJs是MVVM框架,集MVVM和MVC與一體。", Owner = "李志", FinishTime = DateTime.Parse(DateTime.Now.AddDays(3).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =4, Name = "學習ASP.NET MVC網站", Description = "Glimpse是一款.NET下的性能測試工具,支持asp.net 、asp.net mvc, EF等等,優勢在於,不需要修改原項目任何代碼,且能輸出代碼執行各個環節的執行時間", Owner = "Tonny Li", FinishTime = DateTime.Parse(DateTime.Now.AddDays(4).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =5, Name = "測試任務1", Description = "測試任務1", Owner = "李志", FinishTime = DateTime.Parse(DateTime.Now.AddDays(5).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =6, Name = "測試任務2", Description = "測試任務2", Owner = "李志", FinishTime = DateTime.Parse(DateTime.Now.AddDays(6).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =7, Name = "測試任務3", Description = "測試任務3", Owner = "李志", FinishTime = DateTime.Parse(DateTime.Now.AddDays(7).ToString(CultureInfo.InvariantCulture)) }, }; #endregion #region Public Methods public IEnumerable<Task> GetAll() { return _tasks; } public IEnumerable<Task> GetAll(int pageNumber, int pageSize, out int totalCount) { var skip = (pageNumber - 1) * pageSize; var take = pageSize; totalCount = _tasks.Count; return _tasks.Skip(skip).Take(take); } public Task Get(int id) { return _tasks.Find(p => p.Id == id); } public Task Add(Task item) { if (item == null) { throw new ArgumentNullException("item"); } item.Id = _tasks.Count + 1; _tasks.Add(item); return item; } public void Remove(int id) { _tasks.RemoveAll(p => p.Id == id); } public bool Update(Task item) { if (item == null) { throw new ArgumentNullException("item"); } var taskItem = Get(item.Id); if (taskItem == null) { return false; } _tasks.Remove(taskItem); _tasks.Add(item); return true; } #endregion }
Web前端的實現代碼:
@{ ViewBag.Title = "Index2"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="list2"> <h2>分頁第二種實現方式——任務列表</h2> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>編號</th> <th>名稱</th> <th>描述</th> <th>負責人</th> <th>創建時間</th> <th>完成時間</th> <th>狀態</th> </tr> </thead> <tbody data-bind="foreach:pagedList"> <tr> <td data-bind="text: id"></td> <td><a data-bind="text: name"></a></td> <td data-bind="text: description"></td> <td data-bind="text: owner"></td> <td data-bind="text: creationTime"></td> <td data-bind="text: finishTime"></td> <td data-bind="text: state"></td> </tr> </tbody> <tbody data-bind="if: loadingState"> <tr> <td colspan="8" class="text-center"> <img width="60" src="/images/loading.gif" /> </td> </tr> </tbody> <tfoot data-bind="ifnot:loadingState"> <tr> <td colspan="8"> <div class="pull-right"> <div>總共有<span data-bind="text: totalCount"></span>條記錄, 每頁顯示:<span data-bind="text: pageSize"></span>條</div> <div> <ul class="pagination"> <li data-bind="css: { disabled: pageIndex() === 1 }"><a href="#" data-bind="click: previous">«</a></li> </ul> <ul data-bind="foreach: allPages" class="pagination"> <li data-bind="css: { active: $data.pageNumber === ($root.pageIndex()) }"><a href="#" data-bind="text: $data.pageNumber, click: function() { $root.gotoPage($data.pageNumber); }"></a></li> </ul> <ul class="pagination"><li data-bind="css: { disabled: pageIndex() === pageCount }"><a href="#" data-bind="click: next">»</a></li></ul> </div> </div> </td> </tr> </tfoot> </table> </div> </div>
對應的Js實現為:
// 實現分頁的第二種方式 var ListViewModel2 = function() { //viewModel本身。用來防止直接使用this的時候作用域混亂 var self = this; self.loadingState = ko.observable(true); self.pageSize = ko.observable(3); //數據 this.pagedList = ko.observableArray(); //要訪問的頁碼 this.pageIndex = ko.observable(1); //總頁數 this.pageCount = ko.observable(1); //頁碼數 this.allPages = ko.observableArray(); //當前頁 this.currengePage = ko.observable(1); self.totalCount = ko.observable(1); this.refresh = function() { //限制請求頁碼在該數據頁碼范圍內 if (self.pageIndex() < 1) self.pageIndex(1); if (self.pageIndex() > self.pageCount()) { self.pageIndex(self.pageCount()); } //post異步加載數據 sendAjaxRequest("GET", function (data) { // 加載新的數據前,先移除原先的數據 self.pagedList.removeAll(); self.allPages.removeAll(); self.totalCount(data.totalCount); self.pageCount(data.pageCount); self.loadingState(false); for (var i = 1; i <= data.pageCount; i++) { //裝填頁碼 self.allPages.push({ pageNumber: i }); } //for...in 語句用於對數組或者對象的屬性進行循環操作。 //for ... in 循環中的代碼每執行一次,就會對數組的元素或者對象的屬性進行一次操作。 for (var i in data.pagedData) { //裝填數據 self.pagedList.push(data.pagedData[i]); } }, 'GetByPaged', { 'pageIndex': self.pageIndex() }); }; //請求第一頁數據 this.first = function() { self.pageIndex(1); self.refresh(); }; //請求下一頁數據 this.next = function() { self.pageIndex(this.pageIndex() + 1); self.refresh(); }; //請求先前一頁數據 this.previous = function() { self.pageIndex(this.pageIndex() - 1); self.refresh(); }; //請求最後一頁數據 this.last = function() { self.pageIndex(this.pageCount() - 1); self.refresh(); }; //跳轉到某頁 this.gotoPage = function (data, event) { self.pageIndex(data); self.refresh(); }; }; function sendAjaxRequest(httpMethod, callback, url, reqData) { $.ajax("/api/task" + (url ? "/" + url : ""), { type: httpMethod, success: callback, data: reqData }); } $(document).ready(function () { var viewModel = new ListViewModel2(); viewModel.refresh(); if ($('#list2').length) ko.applyBindings(viewModel, $('#list2').get(0)); });
這裡介紹了下使用KnockoutJs實現分頁功能的實現思路:
1.頁面加載完成之後,發起Ajax請求去異步調用REST 服務來請求部分數據。
2.然後將請求的數據通過KnockoutJs綁定顯示。
3.將對應的分頁信息綁定到Bootstrap分頁中
4.當用戶點擊翻頁時,再發起一個Ajax請求去異步調用Rest服務請求數據,再將請求的數據顯示出來。
這上面是描述的代碼的調用邏輯關系,你可以參考對應的JS代碼來理解上面的描述。到此我們第二種實現方式就實現完成了。
2.2 第一次加載所有數據,然後將所有數據分頁顯示
接下來就介紹了第一種實現方式,這樣的實現方式,用戶只會在第一次的時候才會感覺到數據加載中,翻頁過程中感覺不到頁面的加載,這樣對於一些本身數據了不是太多的情況下,對於用戶的感覺也是更加流暢的。
其具體的實現思路,也就是將請求的數據不要全部顯示在頁面上,因為數據太多,一下子顯示到頁面中,用戶可能會眼花缭亂。將數據分頁顯示將使得用戶查看更加清晰。
具體的Web前端Js的實現代碼為:
var ListViewModel = function () { var self = this; window.viewModel = self; self.list = ko.observableArray(); self.pageSize = ko.observable(3); self.pageIndex = ko.observable(0); //要訪問的頁碼 self.totalCount = ko.observable(1); //總記錄數 self.loadingState = ko.observable(true); self.pagedList = ko.dependentObservable(function () { var size = self.pageSize(); var start = self.pageIndex() * size; return self.list.slice(start, start + size); }); self.maxPageIndex = ko.dependentObservable(function () { return Math.ceil(self.list().length / self.pageSize()) - 1; }); self.previousPage = function () { if (self.pageIndex() > 0) { self.pageIndex(self.pageIndex() - 1); } }; self.nextPage = function () { if (self.pageIndex() < self.maxPageIndex()) { self.pageIndex(self.pageIndex() + 1); } }; self.allPages = ko.dependentObservable(function () { var pages = []; for (var i = 0; i <= self.maxPageIndex() ; i++) { pages.push({ pageNumber: (i + 1) }); } return pages; }); self.moveToPage = function (index) { self.pageIndex(index); }; }; var listViewModel = new ListViewModel(); function bindViewModel() { sendAjaxRequest("GET", function (data) { listViewModel.loadingState(false); listViewModel.list(data); listViewModel.totalCount(data.length); if ($('#list').length) ko.applyBindings(listViewModel, $('#list').get(0)); }, null, null); } $(document).ready(function () { bindViewModel(); });
其前端頁面的實現與前面的實現類似。具體頁面代碼如下:
@{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="list"> <h2>任務列表</h2> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>編號</th> <th>名稱</th> <th>描述</th> <th>負責人</th> <th>創建時間</th> <th>完成時間</th> <th>狀態</th> </tr> </thead> <tbody data-bind="foreach:pagedList"> <tr> <td data-bind="text: id"></td> <td><a data-bind="text: name"></a></td> <td data-bind="text: description"></td> <td data-bind="text: owner"></td> <td data-bind="text: creationTime"></td> <td data-bind="text: finishTime"></td> <td data-bind="text: state"></td> </tr> </tbody> <tbody data-bind="if:loadingState"> <tr> <td colspan="8" class="text-center"> <img width="60" src="/images/loading.gif" /> </td> </tr> </tbody> <tfoot data-bind="ifnot:loadingState"> <tr> <td colspan="8"> <div class="pull-right"> <div>總共有<span data-bind="text: totalCount"></span>條記錄, 每頁顯示:<span data-bind="text: pageSize"></span>條</div> <div> <ul class="pagination"> <li data-bind="css: { disabled: pageIndex() === 0 }"><a href="#" data-bind="click: previousPage">«</a></li> </ul> <ul data-bind="foreach: allPages" class="pagination"> <li data-bind="css: { active: $data.pageNumber === ($root.pageIndex() + 1) }"><a href="#" data-bind="text: $data.pageNumber, click: function() { $root.moveToPage($data.pageNumber-1); }"></a></li> </ul> <ul class="pagination"><li data-bind="css: { disabled: pageIndex() === maxPageIndex() }"><a href="#" data-bind="click: nextPage">»</a></li></ul> </div> </div> </td> </tr> </tfoot> </table> </div> </div>
三、運行效果
接下來,讓我們看看,使用KnockoutJs實現的分頁效果:
四、總結
到這裡,本文要介紹的內容就結束,盡管本文實現的內容相對比較簡單,但是對於一些剛接觸KnockoutJs的朋友來說,相信本文的實現會是一個很多的指導。接下來,我將會為大家分享下AngularJs的相關內容。
以上所述是小編給大家介紹的Bootstrap與KnockoutJs相結合實現分頁效果實例詳解,希望對大家有所幫助!