KnockoutJS is a JavaScript-implemented MVVM framework. Very good. For example, after adding or decreasing the list data items, there is no need to refresh the entire control fragment or write JS addition and deletion nodes by yourself. Just define the template and attributes that meet its syntax definitions. Simply put, we only need to pay attention to the access to data.
1. Introduction
Since the company's system needs to be revamped recently, I plan to use KnockoutJs to create a web front-end for the revamped new system. During the process, I encountered a problem - how to use KnockoutJs to complete the pagination function. In the previous article, KnockoutJs is not introduced to implement pagination, so in this article, KnockoutJs + Bootstrap will be used to implement pagination display of data.
2. Use KnockoutJs to implement pagination
There are two ways to implement paging. The first is to load all data and then display all data on pages; the second is to load only part of the data each time, and reload the subsequent data every time.
For these two methods, pagination implemented using the Razor method generally adopts the second method to implement pagination, but for single-page programs, the first implementation also has its benefits. For not very large amounts of data, the first implementation method can be used, because in this way, the subsequent data loading will be very smooth. So here we will introduce these two implementation methods separately.
2.1 Implementation of partial data loading each time
The backend code here uses the code from the previous article, but adds some example data. The specific backend implementation code is:
/// <summary> /// Web API service, providing data services for the web front-end /// </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 data return pageData; } }/// <summary> /// Task Repository, encapsulating all operations about the database /// </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 = "Create a SPA program", Description = "SPA(single page web application), The advantage of SPA is a small amount of bandwidth and a smooth experience", Owner = "Learning hard", FinishTime = DateTime.Parse(DateTime.Now.AddDays(1).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =2, Name = "Learning KnockoutJs", Description = "KnockoutJs is an MVVM class library that supports two-way binding", Owner = "Tommy Li", FinishTime = DateTime.Parse(DateTime.Now.AddDays(2).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =3, Name = "Learn AngularJS", Description = "AngularJs is an MVVM framework that integrates MVVM and MVC with one.", Owner = "Li Zhi", FinishTime = DateTime.Parse(DateTime.Now.AddDays(3).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =4, Name = "Learn ASP.NET MVC website", Description = "Glimpse is a performance testing tool under .NET, which supports asp.net, asp.net mvc, EF, etc. The advantage is that it does not need to modify any code of the original project, and can output the execution time of each link of the code execution", Owner = "Tonny Li", FinishTime = DateTime.Parse(DateTime.Now.AddDays(4).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =5, Name = "Test Task 1", Description = "Test Task 1", Owner = "Li Zhi", FinishTime = DateTime.Parse(DateTime.Now.AddDays(5).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =6, Name = "Test Task 2", Description = "Test Task 2", Owner = "Li Zhi", FinishTime = DateTime.Parse(DateTime.Now.AddDays(6).ToString(CultureInfo.InvariantCulture)) }, new Task { Id =7, Name = "Test Task 3", Description = "Test Task 3", Owner = "Li Zhi", 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 front-end implementation code:
@{ViewBag.Title = "Index2";Layout = "~/Views/Shared/_Layout.cshtml";}<div id="list2"><h2>The second implementation method of pagination-task list</h2><div><table><thead><tr><th>number</th><th>name</th><th>description</th><th>person in charge</th><th>creation time</th><th>complete time</th><th>status</th></tr></treat><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"><img src="/images/loading.gif" /></td></tr></tbody><tfoot data-bind="ifnot:loadingState"><tr><td colspan="8"><div><div>There are a total of <span data-bind="text: totalCount"></span> records, and each page is displayed: <span data-bind="text: pageSize"></span> records</div><div><ul><li data-bind="css: { disabled: pageIndex() === 1 }"><a href="#" data-bind="click: previous">«</a></li></ul><ul data-bind="foreach: allPages"><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><li data-bind="css: { disabled: pageIndex() === pageCount }"><a href="#" data-bind="click: next">»</a></li></ul></div></div></td></tfoot></table></div></div>The corresponding Js implementation is:
// The second way to implement paging var ListViewModel2 = function() {//viewModel itself. Used to prevent scope confusion when using this directly var self = this; self.loadingState = ko.observable(true); self.pageSize = ko.observable(3);//Data this.pagedList = ko.observableArray();//The page number to be accessed this.pageIndex = ko.observable(1);//Total number of pages this.pageCount = ko.observable(1);//The number of pages this.allPages = ko.observableArray();//The current page this.currengePage = ko.observable(1); self.totalCount = ko.observable(1);this.refresh = function() {//Restrict the request page number within the data page number if (self.pageIndex() < 1)self.pageIndex(1);if (self.pageIndex() > self.pageCount()) {self.pageIndex(self.pageCount());}//Post asynchronously load data sendAjaxRequest("GET", function (data) {// Before loading new data, remove the original 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++) {//Reload page number self.allPages.push({ pageNumber: i });}//for...in statement is used to loop the properties of an array or object. //for ... in For ... in Every time the code in the loop is executed, it will operate on the elements of the array or object's properties. for (var i in data.pagedData) {//Recharge data self.pagedList.push(data.pagedData[i]);}}, 'GetByPaged', { 'pageIndex': self.pageIndex() });};//Request the first page of data this.first = function() {self.pageIndex(1);self.refresh();};//Request the next page of data this.next = function() {self.pageIndex(this.pageIndex() + 1);self.refresh();};//Request the previous page of data this.previous = function() {self.pageIndex(this.pageIndex() - 1);self.refresh();};//Request the last page of data this.last = function() {self.pageIndex(this.pageCount() - 1);self.refresh();};//Redirect to a certain page 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));});Here is an introduction to the implementation idea of using KnockoutJs to implement the pagination function:
1. After the page is loaded, initiate an Ajax request to call the REST service asynchronously to request part of the data.
2. Then display the requested data through KnockoutJs binding.
3. Bind the corresponding paging information to the Bootstrap paging
4. When the user clicks to turn the page, initiate an Ajax request to call the Rest service asynchronously and display the requested data.
This is the logical relationship of the calling code described above. You can refer to the corresponding JS code to understand the above description. At this point, our second implementation method is completed.
2.2 Load all data for the first time, and then display all data paging
Next, we will introduce the first implementation method. In this implementation method, users will only feel that the data is loading for the first time, and they will not feel the page loading during the page turning process. This will make users feel smoother when there is not too much data in some cases.
The specific implementation idea is to not display all the requested data on the page, because there is too much data and it will be displayed on the page at once, and users may be dazzled. Displaying data paging will make users view more clear.
The specific implementation code of Web front-end Js is:
var ListViewModel = function () {var self = this;window.viewModel = self;self.list = ko.observableArray();self.pageSize = ko.observable(3); self.pageIndex = ko.observable(0); //The page number to be accessed self.totalCount = ko.observable(1); //Total number of records 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();});The implementation of its front-end page is similar to the previous implementation. The specific page code is as follows:
@{ViewBag.Title = "Index";Layout = "~/Views/Shared/_Layout.cshtml";}<div id="list"><h2>Task List</h2><div><table><thead><tr><th>Number</th><th>Name</th><th>Description</th><th>People in charge</th><th>Creation time</th><th>Completion time</th><th>Statu</th></tr></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"><img src="/images/loading.gif" /></td></tr></tbody><tfoot data-bind="ifnot:loadingState"><tr><td colspan="8"><div><div>There are a total of <span data-bind="text: totalCount"></span> records, and each page is displayed: <span data-bind="text: pageSize"></span> records</div><div><ul><li data-bind="css: { disabled: pageIndex() === 0 }"><a href="#" data-bind="click: previousPage">«</a></li></ul><ul data-bind="foreach: allPages"><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><li data-bind="css: { disabled: pageIndex() === maxPageIndex() }"><a href="#" data-bind="click: nextPage">»</a></li></ul></div></div></td></tr></tfoot></table></tiv></div>3. Operation effect
Next, let's take a look at the pagination effect implemented using KnockoutJs:
4. Summary
At this point, the content to be introduced in this article is over. Although the content implemented in this article is relatively simple, for some friends who are new to KnockoutJs, I believe that the implementation of this article will be a lot of guidance. Next, I will share with you the relevant content of AngularJs.
The above is a detailed explanation of the examples of combining Bootstrap with KnockoutJs to achieve pagination effects that the editor introduced to you. I hope it will be helpful to you!