敲除分页绑定
Knockout paging binding
抱歉,如果这是一个非常基本的问题,但我正在学习 Knockout 并尝试将分页连接到我的数据集。
在我下面的代码中,您会看到我正在检索数据集,页面大小下拉列表会适当地影响结果。
当我更改页码时(#'链接在页脚中table), 没有任何反应。
谁能告诉我我错过了什么?
function ViewModel(){
var vm = this;
// variables
vm.drinks = ko.observableArray();
vm.pageSizes = [15,25,35,50];
vm.pageSize = ko.observable(pageSizes[0]);
vm.currentPage = ko.observable(0);
// computed variables
// returns number of pages required for number of results selected
vm.PageCount = ko.computed(function(){
if(vm.pageSize()){
return Math.ceil(vm.drinks().length / vm.pageSize());
}else{
return 1;
}
});
// returns items from the array for the current page
vm.PagedResults = ko.computed(function(){
//return vm.drinks().slice(vm.currentPage, vm.pageSize());
return vm.drinks().slice(vm.currentPage() * vm.pageSize(), (vm.currentPage() * vm.pageSize()) + vm.pageSize());
});
// returns a list of numbers for all pages
vm.PageList = ko.computed(function(){
if(vm.PageCount() > 1){
return Array.apply(null, {length: vm.PageCount()}).map(Number.call, Number);
}
});
// methods
vm.ResetCurrentPage = function(){
vm.currentPage(0);
}
// go to page number
vm.GoToPage = function(page){
vm.currentPage(page);
}
// populate drink list
vm.GetDrinks = function(){
// get data
$(function () {
$.ajax({
type: "GET",
url: 'https://mysafeinfo.com/api/data?list=alcoholicbeverages&format=json',
dataType: "json",
success: function (data) {
vm.drinks(data);
}
});
});
}
// populate drinks
vm.GetDrinks();
}
// apply bindings
ko.applyBindings(ViewModel);
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="row">
<div class="col-sm-3 pull-right form-horizontal">
<label class="control-label col-sm-4">
Results:
</label>
<div class="col-sm-8">
<select data-bind="value: pageSize,
optionsCaption: 'Page Size',
options: pageSizes, event:{ change: ResetCurrentPage }"
class="form-control"></select>
</div>
</div>
</div>
<table class="table table-striped table-condensed">
<thead>
<tr>
<th style="width: 25%">Name</th>
<th>Category</th>
<th style="width: 50%">Description</th>
</tr>
</thead>
<tbody data-bind="foreach: PagedResults">
<tr>
<td data-bind="text: nm"></td>
<td data-bind="text: cat"></td>
<td data-bind="text: dsc"></td>
</tr>
</tbody>
<tfooter>
<tr>
<td colspan="3">
Current Page: <label data-bind="text: currentPage"></label><br />
<ul data-bind="foreach: PageList" class="pagination">
<li class="page-item"><a class="page-link" href="#" data-bind="text: $data + 1, click: GoToPage">1</a></li>
</ul>
</td>
</tr>
</tfooter>
</table>
感谢 f_martinez 帮助解决我的问题,如果有人最终在这里寻找如何进行分页,这里是工作示例。 jsfiddle
如果 f_martinez 想要 post 接受答案,我会暂时保持此状态。
function ViewModel() {
var vm = this;
// variables
vm.drinks = ko.observableArray();
vm.pageSizes = [15, 25, 35, 50];
vm.pageSize = ko.observable(pageSizes[0]);
vm.currentPage = ko.observable(0);
// computed variables
// returns number of pages required for number of results selected
vm.PageCount = ko.computed(function() {
if (vm.pageSize()) {
return Math.ceil(vm.drinks().length / vm.pageSize());
} else {
return 1;
}
});
// returns items from the array for the current page
vm.PagedResults = ko.computed(function() {
if (vm.PageCount() > 1) {
//return vm.drinks().slice(vm.currentPage, vm.pageSize());
return vm.drinks().slice(vm.currentPage() * vm.pageSize(), (vm.currentPage() * vm.pageSize()) + vm.pageSize());
} else {
return vm.drinks();
}
});
// returns a list of numbers for all pages
vm.PageList = ko.computed(function() {
if (vm.PageCount() > 1) {
return Array.apply(null, {
length: vm.PageCount()
}).map(Number.call, Number);
}
});
// methods
// reset to first page
vm.ResetCurrentPage = function() {
vm.currentPage(0);
}
// go to page number
vm.GoToPage = function(page) {
vm.currentPage(page);
}
// determines if page # is active returns active class
vm.GetClass = function(page) {
return (page == vm.currentPage()) ? "active" : "";
}
// populate drink list
vm.GetDrinks = function() {
// get data
$(function() {
$.ajax({
type: "GET",
url: 'https://mysafeinfo.com/api/data?list=alcoholicbeverages&format=json',
dataType: "json",
success: function(data) {
vm.drinks(data);
}
});
});
}
// populate drinks
vm.GetDrinks();
}
// apply bindings
ko.applyBindings(ViewModel);
.pagination > li > a:focus,
.pagination > li > a:hover,
.pagination > li > span:focus,
.page-link.active {
background-color: rgb(238, 238, 238);
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="row">
<div class="col-sm-3 pull-right form-horizontal">
<label class="control-label col-sm-4">
Results:
</label>
<div class="col-sm-8">
<select data-bind="value: pageSize,
optionsCaption: 'All Results',
options: pageSizes, event:{ change: ResetCurrentPage }" class="form-control"></select>
</div>
</div>
</div>
<table class="table table-striped table-condensed">
<thead>
<tr>
<th style="width: 25%">Name</th>
<th>Category</th>
<th style="width: 50%">Description</th>
</tr>
</thead>
<tbody data-bind="foreach: PagedResults">
<tr>
<td data-bind="text: nm"></td>
<td data-bind="text: cat"></td>
<td data-bind="text: dsc"></td>
</tr>
</tbody>
<tfooter>
<tr>
<td colspan="3" class="text-center">
<ul data-bind="foreach: PageList" class="pagination">
<li class="page-item">
<a href="#" class="page-link" data-bind="text: $data + 1, click: GoToPage, css: GetClass($data)"></a>
</li>
</ul>
</td>
</tr>
</tfooter>
</table>
Stack overflow 是关于给出常见问题的解决方案,答案解决方案对 OP 有效,但对于其他情况不是很可重用,这里是这个常见问题的可重用解决方案问题(淘汰分页)
我在网站上工作,它有很多表(其中大部分需要分页),所以实际上我需要一些 reusable-component
用于分页以便在所有需要分页的情况下使用它。
所以我自己开发了这个问题的组件,在这里。
更多详情,请继续阅读
JavaScript
function PagingVM(options) {
var self = this;
self.PageSize = ko.observable(options.pageSize);
self.CurrentPage = ko.observable(1);
self.TotalCount = ko.observable(options.totalCount);
self.PageCount = ko.pureComputed(function () {
return Math.ceil(self.TotalCount() / self.PageSize());
});
self.SetCurrentPage = function (page) {
if (page < self.FirstPage)
page = self.FirstPage;
if (page > self.LastPage())
page = self.LastPage();
self.CurrentPage(page);
};
self.FirstPage = 1;
self.LastPage = ko.pureComputed(function () {
return self.PageCount();
});
self.NextPage = ko.pureComputed(function () {
var next = self.CurrentPage() + 1;
if (next > self.LastPage())
return null;
return next;
});
self.PreviousPage = ko.pureComputed(function () {
var previous = self.CurrentPage() - 1;
if (previous < self.FirstPage)
return null;
return previous;
});
self.NeedPaging = ko.pureComputed(function () {
return self.PageCount() > 1;
});
self.NextPageActive = ko.pureComputed(function () {
return self.NextPage() != null;
});
self.PreviousPageActive = ko.pureComputed(function () {
return self.PreviousPage() != null;
});
self.LastPageActive = ko.pureComputed(function () {
return (self.LastPage() != self.CurrentPage());
});
self.FirstPageActive = ko.pureComputed(function () {
return (self.FirstPage != self.CurrentPage());
});
// this should be odd number always
var maxPageCount = 7;
self.generateAllPages = function () {
var pages = [];
for (var i = self.FirstPage; i <= self.LastPage() ; i++)
pages.push(i);
return pages;
};
self.generateMaxPage = function () {
var current = self.CurrentPage();
var pageCount = self.PageCount();
var first = self.FirstPage;
var upperLimit = current + parseInt((maxPageCount - 1) / 2);
var downLimit = current - parseInt((maxPageCount - 1) / 2);
while (upperLimit > pageCount) {
upperLimit--;
if (downLimit > first)
downLimit--;
}
while (downLimit < first) {
downLimit++;
if (upperLimit < pageCount)
upperLimit++;
}
var pages = [];
for (var i = downLimit; i <= upperLimit; i++) {
pages.push(i);
}
return pages;
};
self.GetPages = ko.pureComputed(function () {
self.CurrentPage();
self.TotalCount();
if (self.PageCount() <= maxPageCount) {
return ko.observableArray(self.generateAllPages());
} else {
return ko.observableArray(self.generateMaxPage());
}
});
self.Update = function (e) {
self.TotalCount(e.TotalCount);
self.PageSize(e.PageSize);
self.SetCurrentPage(e.CurrentPage);
};
self.GoToPage = function (page) {
if (page >= self.FirstPage && page <= self.LastPage())
self.SetCurrentPage(page);
}
self.GoToFirst = function () {
self.SetCurrentPage(self.FirstPage);
};
self.GoToPrevious = function () {
var previous = self.PreviousPage();
if (previous != null)
self.SetCurrentPage(previous);
};
self.GoToNext = function () {
var next = self.NextPage();
if (next != null)
self.SetCurrentPage(next);
};
self.GoToLast = function () {
self.SetCurrentPage(self.LastPage());
};
}
HTML
<ul data-bind="visible: NeedPaging" class="pagination pagination-sm">
<li data-bind="css: { disabled: !FirstPageActive() }">
<a data-bind="click: GoToFirst">First</a>
</li>
<li data-bind="css: { disabled: !PreviousPageActive() }">
<a data-bind="click: GoToPrevious">Previous</a>
</li>
<!-- ko foreach: GetPages() -->
<li data-bind="css: { active: $parent.CurrentPage() === $data }">
<a data-bind="click: $parent.GoToPage, text: $data"></a>
</li>
<!-- /ko -->
<li data-bind="css: { disabled: !NextPageActive() }">
<a data-bind="click: GoToNext">Next</a>
</li>
<li data-bind="css: { disabled: !LastPageActive() }">
<a data-bind="click: GoToLast">Last</a>
</li>
</ul>
特征
按需要显示
完全不需要分页的时候(比如需要显示小于页面的项目) size) 然后 HTML
组件将消失。
这将由语句 data-bind="visible: NeedPaging"
.
建立
根据需要禁用
例如,如果您已经选择了最后一页,为什么 last page
或 Next
按钮应该可以按下?
我正在处理这个问题,在那种情况下,我将通过应用以下绑定来禁用这些按钮 data-bind="css: { disabled: !PreviousPageActive() }"
区分选中的页面
一个特殊的 class(在本例中称为 active
class)被应用到所选页面,让用户知道 he/she 现在在哪个页面。
这是通过绑定建立的 data-bind="css: { active: $parent.CurrentPage() === $data }"
姓和名
也可以通过专用于此的简单按钮转到第一页和最后一页。
显示按钮的限制
假设你有很多页,比如 1000 页,那么会发生什么?你会为用户显示它们吗? 绝对不行你只需要根据当前页面显示其中的几个即可。例如,在页面页面之前显示 3 页,在所选页面之后显示其他 3 页。
此案例已在此处处理 <!-- ko foreach: GetPages() -->
GetPages
函数应用一个简单的算法来确定我们是否需要显示所有页面(页数低于阈值,这很容易确定),或者只显示一些按钮。
您可以通过更改 maxPageCount
变量
的值来确定阈值
现在我将其分配为以下 var maxPageCount = 7;
,这意味着不能为用户显示超过 7 个按钮(3 个在 SelectedPage 之前,3 个在 Selected Page 之后)和 Selected Page 本身。
您可能会想,如果当前页面之前 或 之后的页面不够显示怎么办? 别担心我在算法中处理这个
例如,如果你有 11 pages
并且你有 maxPageCount = 7
和当前 selected page is 10
, 则显示以下页面
5,6,7,8,9,10(selected page),11
所以我们总是对 maxPageCount
进行分层,在前面的示例中显示所选页面之前的 5
页和所选页面之后的 1
页。
选定页面验证
CurrentPage
observable 确定用户 选择的页面的所有设置操作都是通过函数 SetCurrentPage
进行的。只有在这个函数中我们设置了这个observable,从代码中可以看出,在设置值之前我们做了校验操作,确保我们不会超出页面的可用页面。
已经干净了
我只使用 pureComputed
而不是 computed
属性,这意味着您无需为清理和处置这些属性而烦恼。 虽然,正如您将在下面的示例中看到的那样,您需要处理组件本身之外的一些其他订阅
注 1
你可能注意到我在这个组件中使用了一些 bootstrap
classes,
这很适合我,但是 当然 您可以使用自己的 classes 而不是 bootstrap classes。
我在这里使用的 bootstrap class 是 pagination
、pagination-sm
、active
和 disabled
您可以根据需要随意更改它们。
注 2
所以我为你介绍了这个组件,是时候看看它是如何工作的了。
您可以像这样将此组件集成到您的主 ViewModel 中。
function MainVM() {
var self = this;
self.PagingComponent = ko.observable(new Paging({
pageSize: 10, // how many items you would show in one page
totalCount: 100, // how many ALL the items do you have.
}));
self.currentPageSubscription = self.PagingComponent().CurrentPage.subscribe(function (newPage) {
// here is the code which will be executed when the user change the page.
// you can handle this in the way you need.
// for example, in my case, I am requesting the data from the server again by making an ajax request
// and then updating the component
var data = /*bring data from server , for example*/
self.PagingComponent().Update({
// we need to set this again, why? because we could apply some other search criteria in the bringing data from the server,
// so the total count of all the items could change, and this will affect the paging
TotalCount: data.TotalCount,
// in most cases we will not change the PageSize after we bring data from the server
// but the component allow us to do that.
PageSize: self.PagingComponent().PageSize(),
// use this statement for now as it is, or you have to made some modifications on the 'Update' function.
CurrentPage: self.PagingComponent().CurrentPage(),
});
});
self.dispose = function () {
// you need to dispose the manual created subscription, you have created before.
self.currentPageSubscription.dispose();
}
}
最后但并非最不重要,当然不要忘记根据您的特殊viewModel更改html组件中的绑定,或者用[包裹所有组件=42=]像这样
<div data-bind="with: PagingComponent()">
<!-- put the component here -->
</div>
干杯
抱歉,如果这是一个非常基本的问题,但我正在学习 Knockout 并尝试将分页连接到我的数据集。
在我下面的代码中,您会看到我正在检索数据集,页面大小下拉列表会适当地影响结果。
当我更改页码时(#'链接在页脚中table), 没有任何反应。
谁能告诉我我错过了什么?
function ViewModel(){
var vm = this;
// variables
vm.drinks = ko.observableArray();
vm.pageSizes = [15,25,35,50];
vm.pageSize = ko.observable(pageSizes[0]);
vm.currentPage = ko.observable(0);
// computed variables
// returns number of pages required for number of results selected
vm.PageCount = ko.computed(function(){
if(vm.pageSize()){
return Math.ceil(vm.drinks().length / vm.pageSize());
}else{
return 1;
}
});
// returns items from the array for the current page
vm.PagedResults = ko.computed(function(){
//return vm.drinks().slice(vm.currentPage, vm.pageSize());
return vm.drinks().slice(vm.currentPage() * vm.pageSize(), (vm.currentPage() * vm.pageSize()) + vm.pageSize());
});
// returns a list of numbers for all pages
vm.PageList = ko.computed(function(){
if(vm.PageCount() > 1){
return Array.apply(null, {length: vm.PageCount()}).map(Number.call, Number);
}
});
// methods
vm.ResetCurrentPage = function(){
vm.currentPage(0);
}
// go to page number
vm.GoToPage = function(page){
vm.currentPage(page);
}
// populate drink list
vm.GetDrinks = function(){
// get data
$(function () {
$.ajax({
type: "GET",
url: 'https://mysafeinfo.com/api/data?list=alcoholicbeverages&format=json',
dataType: "json",
success: function (data) {
vm.drinks(data);
}
});
});
}
// populate drinks
vm.GetDrinks();
}
// apply bindings
ko.applyBindings(ViewModel);
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="row">
<div class="col-sm-3 pull-right form-horizontal">
<label class="control-label col-sm-4">
Results:
</label>
<div class="col-sm-8">
<select data-bind="value: pageSize,
optionsCaption: 'Page Size',
options: pageSizes, event:{ change: ResetCurrentPage }"
class="form-control"></select>
</div>
</div>
</div>
<table class="table table-striped table-condensed">
<thead>
<tr>
<th style="width: 25%">Name</th>
<th>Category</th>
<th style="width: 50%">Description</th>
</tr>
</thead>
<tbody data-bind="foreach: PagedResults">
<tr>
<td data-bind="text: nm"></td>
<td data-bind="text: cat"></td>
<td data-bind="text: dsc"></td>
</tr>
</tbody>
<tfooter>
<tr>
<td colspan="3">
Current Page: <label data-bind="text: currentPage"></label><br />
<ul data-bind="foreach: PageList" class="pagination">
<li class="page-item"><a class="page-link" href="#" data-bind="text: $data + 1, click: GoToPage">1</a></li>
</ul>
</td>
</tr>
</tfooter>
</table>
感谢 f_martinez 帮助解决我的问题,如果有人最终在这里寻找如何进行分页,这里是工作示例。 jsfiddle
如果 f_martinez 想要 post 接受答案,我会暂时保持此状态。
function ViewModel() {
var vm = this;
// variables
vm.drinks = ko.observableArray();
vm.pageSizes = [15, 25, 35, 50];
vm.pageSize = ko.observable(pageSizes[0]);
vm.currentPage = ko.observable(0);
// computed variables
// returns number of pages required for number of results selected
vm.PageCount = ko.computed(function() {
if (vm.pageSize()) {
return Math.ceil(vm.drinks().length / vm.pageSize());
} else {
return 1;
}
});
// returns items from the array for the current page
vm.PagedResults = ko.computed(function() {
if (vm.PageCount() > 1) {
//return vm.drinks().slice(vm.currentPage, vm.pageSize());
return vm.drinks().slice(vm.currentPage() * vm.pageSize(), (vm.currentPage() * vm.pageSize()) + vm.pageSize());
} else {
return vm.drinks();
}
});
// returns a list of numbers for all pages
vm.PageList = ko.computed(function() {
if (vm.PageCount() > 1) {
return Array.apply(null, {
length: vm.PageCount()
}).map(Number.call, Number);
}
});
// methods
// reset to first page
vm.ResetCurrentPage = function() {
vm.currentPage(0);
}
// go to page number
vm.GoToPage = function(page) {
vm.currentPage(page);
}
// determines if page # is active returns active class
vm.GetClass = function(page) {
return (page == vm.currentPage()) ? "active" : "";
}
// populate drink list
vm.GetDrinks = function() {
// get data
$(function() {
$.ajax({
type: "GET",
url: 'https://mysafeinfo.com/api/data?list=alcoholicbeverages&format=json',
dataType: "json",
success: function(data) {
vm.drinks(data);
}
});
});
}
// populate drinks
vm.GetDrinks();
}
// apply bindings
ko.applyBindings(ViewModel);
.pagination > li > a:focus,
.pagination > li > a:hover,
.pagination > li > span:focus,
.page-link.active {
background-color: rgb(238, 238, 238);
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="row">
<div class="col-sm-3 pull-right form-horizontal">
<label class="control-label col-sm-4">
Results:
</label>
<div class="col-sm-8">
<select data-bind="value: pageSize,
optionsCaption: 'All Results',
options: pageSizes, event:{ change: ResetCurrentPage }" class="form-control"></select>
</div>
</div>
</div>
<table class="table table-striped table-condensed">
<thead>
<tr>
<th style="width: 25%">Name</th>
<th>Category</th>
<th style="width: 50%">Description</th>
</tr>
</thead>
<tbody data-bind="foreach: PagedResults">
<tr>
<td data-bind="text: nm"></td>
<td data-bind="text: cat"></td>
<td data-bind="text: dsc"></td>
</tr>
</tbody>
<tfooter>
<tr>
<td colspan="3" class="text-center">
<ul data-bind="foreach: PageList" class="pagination">
<li class="page-item">
<a href="#" class="page-link" data-bind="text: $data + 1, click: GoToPage, css: GetClass($data)"></a>
</li>
</ul>
</td>
</tr>
</tfooter>
</table>
Stack overflow 是关于给出常见问题的解决方案,答案解决方案对 OP 有效,但对于其他情况不是很可重用,这里是这个常见问题的可重用解决方案问题(淘汰分页)
我在网站上工作,它有很多表(其中大部分需要分页),所以实际上我需要一些 reusable-component
用于分页以便在所有需要分页的情况下使用它。
所以我自己开发了这个问题的组件,在这里。
更多详情,请继续阅读
JavaScript
function PagingVM(options) {
var self = this;
self.PageSize = ko.observable(options.pageSize);
self.CurrentPage = ko.observable(1);
self.TotalCount = ko.observable(options.totalCount);
self.PageCount = ko.pureComputed(function () {
return Math.ceil(self.TotalCount() / self.PageSize());
});
self.SetCurrentPage = function (page) {
if (page < self.FirstPage)
page = self.FirstPage;
if (page > self.LastPage())
page = self.LastPage();
self.CurrentPage(page);
};
self.FirstPage = 1;
self.LastPage = ko.pureComputed(function () {
return self.PageCount();
});
self.NextPage = ko.pureComputed(function () {
var next = self.CurrentPage() + 1;
if (next > self.LastPage())
return null;
return next;
});
self.PreviousPage = ko.pureComputed(function () {
var previous = self.CurrentPage() - 1;
if (previous < self.FirstPage)
return null;
return previous;
});
self.NeedPaging = ko.pureComputed(function () {
return self.PageCount() > 1;
});
self.NextPageActive = ko.pureComputed(function () {
return self.NextPage() != null;
});
self.PreviousPageActive = ko.pureComputed(function () {
return self.PreviousPage() != null;
});
self.LastPageActive = ko.pureComputed(function () {
return (self.LastPage() != self.CurrentPage());
});
self.FirstPageActive = ko.pureComputed(function () {
return (self.FirstPage != self.CurrentPage());
});
// this should be odd number always
var maxPageCount = 7;
self.generateAllPages = function () {
var pages = [];
for (var i = self.FirstPage; i <= self.LastPage() ; i++)
pages.push(i);
return pages;
};
self.generateMaxPage = function () {
var current = self.CurrentPage();
var pageCount = self.PageCount();
var first = self.FirstPage;
var upperLimit = current + parseInt((maxPageCount - 1) / 2);
var downLimit = current - parseInt((maxPageCount - 1) / 2);
while (upperLimit > pageCount) {
upperLimit--;
if (downLimit > first)
downLimit--;
}
while (downLimit < first) {
downLimit++;
if (upperLimit < pageCount)
upperLimit++;
}
var pages = [];
for (var i = downLimit; i <= upperLimit; i++) {
pages.push(i);
}
return pages;
};
self.GetPages = ko.pureComputed(function () {
self.CurrentPage();
self.TotalCount();
if (self.PageCount() <= maxPageCount) {
return ko.observableArray(self.generateAllPages());
} else {
return ko.observableArray(self.generateMaxPage());
}
});
self.Update = function (e) {
self.TotalCount(e.TotalCount);
self.PageSize(e.PageSize);
self.SetCurrentPage(e.CurrentPage);
};
self.GoToPage = function (page) {
if (page >= self.FirstPage && page <= self.LastPage())
self.SetCurrentPage(page);
}
self.GoToFirst = function () {
self.SetCurrentPage(self.FirstPage);
};
self.GoToPrevious = function () {
var previous = self.PreviousPage();
if (previous != null)
self.SetCurrentPage(previous);
};
self.GoToNext = function () {
var next = self.NextPage();
if (next != null)
self.SetCurrentPage(next);
};
self.GoToLast = function () {
self.SetCurrentPage(self.LastPage());
};
}
HTML
<ul data-bind="visible: NeedPaging" class="pagination pagination-sm">
<li data-bind="css: { disabled: !FirstPageActive() }">
<a data-bind="click: GoToFirst">First</a>
</li>
<li data-bind="css: { disabled: !PreviousPageActive() }">
<a data-bind="click: GoToPrevious">Previous</a>
</li>
<!-- ko foreach: GetPages() -->
<li data-bind="css: { active: $parent.CurrentPage() === $data }">
<a data-bind="click: $parent.GoToPage, text: $data"></a>
</li>
<!-- /ko -->
<li data-bind="css: { disabled: !NextPageActive() }">
<a data-bind="click: GoToNext">Next</a>
</li>
<li data-bind="css: { disabled: !LastPageActive() }">
<a data-bind="click: GoToLast">Last</a>
</li>
</ul>
特征
按需要显示
完全不需要分页的时候(比如需要显示小于页面的项目) size) 然后HTML
组件将消失。
这将由语句data-bind="visible: NeedPaging"
. 建立
根据需要禁用
例如,如果您已经选择了最后一页,为什么last page
或Next
按钮应该可以按下?
我正在处理这个问题,在那种情况下,我将通过应用以下绑定来禁用这些按钮data-bind="css: { disabled: !PreviousPageActive() }"
区分选中的页面
一个特殊的 class(在本例中称为active
class)被应用到所选页面,让用户知道 he/she 现在在哪个页面。
这是通过绑定建立的data-bind="css: { active: $parent.CurrentPage() === $data }"
姓和名
也可以通过专用于此的简单按钮转到第一页和最后一页。显示按钮的限制
假设你有很多页,比如 1000 页,那么会发生什么?你会为用户显示它们吗? 绝对不行你只需要根据当前页面显示其中的几个即可。例如,在页面页面之前显示 3 页,在所选页面之后显示其他 3 页。
此案例已在此处处理<!-- ko foreach: GetPages() -->
GetPages
函数应用一个简单的算法来确定我们是否需要显示所有页面(页数低于阈值,这很容易确定),或者只显示一些按钮。
您可以通过更改maxPageCount
变量
的值来确定阈值 现在我将其分配为以下var maxPageCount = 7;
,这意味着不能为用户显示超过 7 个按钮(3 个在 SelectedPage 之前,3 个在 Selected Page 之后)和 Selected Page 本身。
您可能会想,如果当前页面之前 或 之后的页面不够显示怎么办? 别担心我在算法中处理这个
例如,如果你有11 pages
并且你有maxPageCount = 7
和当前selected page is 10
, 则显示以下页面
5,6,7,8,9,10(selected page),11
所以我们总是对maxPageCount
进行分层,在前面的示例中显示所选页面之前的5
页和所选页面之后的1
页。选定页面验证
CurrentPage
observable 确定用户 选择的页面的所有设置操作都是通过函数SetCurrentPage
进行的。只有在这个函数中我们设置了这个observable,从代码中可以看出,在设置值之前我们做了校验操作,确保我们不会超出页面的可用页面。已经干净了
我只使用pureComputed
而不是computed
属性,这意味着您无需为清理和处置这些属性而烦恼。 虽然,正如您将在下面的示例中看到的那样,您需要处理组件本身之外的一些其他订阅
注 1
你可能注意到我在这个组件中使用了一些 bootstrap
classes,
这很适合我,但是 当然 您可以使用自己的 classes 而不是 bootstrap classes。
我在这里使用的 bootstrap class 是 pagination
、pagination-sm
、active
和 disabled
您可以根据需要随意更改它们。
注 2
所以我为你介绍了这个组件,是时候看看它是如何工作的了。
您可以像这样将此组件集成到您的主 ViewModel 中。
function MainVM() {
var self = this;
self.PagingComponent = ko.observable(new Paging({
pageSize: 10, // how many items you would show in one page
totalCount: 100, // how many ALL the items do you have.
}));
self.currentPageSubscription = self.PagingComponent().CurrentPage.subscribe(function (newPage) {
// here is the code which will be executed when the user change the page.
// you can handle this in the way you need.
// for example, in my case, I am requesting the data from the server again by making an ajax request
// and then updating the component
var data = /*bring data from server , for example*/
self.PagingComponent().Update({
// we need to set this again, why? because we could apply some other search criteria in the bringing data from the server,
// so the total count of all the items could change, and this will affect the paging
TotalCount: data.TotalCount,
// in most cases we will not change the PageSize after we bring data from the server
// but the component allow us to do that.
PageSize: self.PagingComponent().PageSize(),
// use this statement for now as it is, or you have to made some modifications on the 'Update' function.
CurrentPage: self.PagingComponent().CurrentPage(),
});
});
self.dispose = function () {
// you need to dispose the manual created subscription, you have created before.
self.currentPageSubscription.dispose();
}
}
最后但并非最不重要,当然不要忘记根据您的特殊viewModel更改html组件中的绑定,或者用[包裹所有组件=42=]像这样
<div data-bind="with: PagingComponent()">
<!-- put the component here -->
</div>
干杯