KnockoutJS is an MVVM framework implemented in JavaScript. Very cool. For example, after the list data items are added or deleted, there is no need to refresh the entire control fragment or write JS to add or delete nodes by yourself. You only need to pre-define the template and attributes that conform to its syntax definition. Simply put, we only need to focus on data access.
1. Introduction
Since the company’s system needs to be revised recently, I plan to use KnockoutJs to make the web front-end for the new system. In the process of doing it, I encountered a problem - how to use KnockoutJs to complete the paging function. In the previous article, we did not introduce the use of KnockoutJs to implement paging, so in this article, we will supplement the use of KnockoutJs+Bootstrap to implement paging display of data.
2. Use KnockoutJs to implement paging
There are two ways to implement paging. The first is to load all the data, and then display all the data in pages; the second is to only load part of the data each time, and reload the following for each request. data.
For these two methods, paging implemented using the Razor method generally uses the second method to implement paging. However, for single-page programs, the first implementation method also has its benefits. It is completely suitable for not very large amounts of data. The first implementation method can be used, because in this case, the user experience of subsequent data loading will be very smooth. So these two implementation methods will be introduced here.
2.1 Implementation of loading partial data each time
The backend code here uses the code from the previous article, with just some additional sample data added. The specific backend implementation code is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
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;
}
}
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
}
|
Copy after login
Web front-end implementation code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | @{
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>
|
Copy after login
The corresponding Js implementation is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | var ListViewModel2 = function () {
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());
}
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 ( 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));
});
|
Copy after login
Here is an introduction to the implementation idea of using KnockoutJs to implement the paging function:
1. After the page is loaded, initiate an Ajax request to asynchronously call the REST service to request some data.
2. Then display the requested data through KnockoutJs binding.
3. Bind the corresponding paging information to Bootstrap paging
4. When the user clicks to turn the page, initiate an Ajax request to asynchronously call the Rest service to request data, and then display the requested data.
The above is the calling logic relationship of the 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 in pages
Next, we introduce the first implementation method. With this implementation method, the user will only feel that the data is loading for the first time, and will not feel that the page is loading during the page turning process. In this way, for some When the data itself is not too much, the user experience is smoother.
The specific implementation idea is not to display all the requested data on the page, because there is too much data and the user may be dazzled if it is displayed on the page all at once. Displaying the data in pages will make it clearer for users.
The specific implementation code of Web front-end Js is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | 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();
});
|
Copy after login
The implementation of its front-end page is similar to the previous implementation. The specific page code is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | @{
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>
|
Copy after login
3. Operation effect
Next, let’s take a look at the paging effect achieved using KnockoutJs:

4. Summary
At this point, the content to be introduced in this article ends. Although the content implemented in this article is relatively simple, for some friends who are new to KnockoutJs, I believe 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 the detailed explanation of the example of combining Bootstrap and KnockoutJs to achieve the paging effect introduced by the editor. I hope it will be helpful to everyone!