我正在写一个分页表,底部有一个页面选择器,显示不同的页码
我正在使用 knockout 。这些数字来自 ko.computed 数组 (self.pages),该数组根据每页的结果数/结果数计算有多少页。我遇到的问题是,如果数据数组很长并且每页的结果设置得有点低,我会得到这样的结果:
我想做的是将菜单项的数量限制为三个,因此如果选择第 4 页,则只有元素 3、4、5 可见。目前我正在实现第二个 ko.computed ,它首先检索 self.pages
的值, 然后获取当前页码的值 ( self.pageNumber
), 并对数组进行切片, 以便只返回 3 个元素:
self.availablePages = ko.computed(function() {
var pages = self.pages();
var current = self.pageNumber();
if (current === 0) {
return pages.slice(current, current + 3);
} else {
return pages.slice(current - 1, current + 2);
}
});
现在所有这一切似乎工作正常,但有一个错误我无法消除。使用 knockout css
数据绑定(bind),我告诉它为与 self.pageNumber
具有相同值的元素分配一个类“selected” (见下面的代码)。
如果选择的元素不需要self.availablePages
更改(即在之前选择 1 时选择 2),没有问题; 2 个变为选中状态,1 个变为未选中状态。
但是,如果选择确实需要self.availablePages
更改(即 1、2、3 可见,选择 3 将变为 2、3、4 可见),显示正确的数字,但选择的不是 3,而是 4。我假设这是因为 3 曾经位于(最后)的数组索引现在被 4 占用了。
这是菜单:
<ul data-bind="foreach: availablePages">
<li data-bind="if: $index() < 1">
<a data-bind="click: $parent.toFirstPage">First</a>
</li>
<li>
<a data-bind="text: displayValue, click: $parent.goToPage(iterator), css: { selected: $parent.pageNumber() === iterator }"></a>
</li>
<li data-bind="if: $parent.isLastIteration($index)">
<a data-bind="click: $parent.toLastPage">Last</a>
</li>
</ul>
被迭代的数组最初只是一个数字数组,但为了修复这个错误,我将其更改为以下对象的数组:
available.MenuModel = function(iterator) {
var self = this;
self.displayValue = iterator + 1;
self.iterator = iterator;
self.isSelected = ko.observable(false);
}
我尝试做的一件事是添加 self.isSelected
可观察到菜单中的所有元素,然后当self.availablePages
重新计算,该函数检查 pageNumber 是什么,然后找到数组中的哪个元素匹配并设置 self.isSelected(true)
, 然后尝试将 css 绑定(bind)到它。
不幸的是,这没有用;它仍然有完全相同的错误。我一直在疯狂地调试脚本,但似乎没有问题;一切似乎都知道应该选择3,但实际选择的是4。
我猜 knockout 绑定(bind)不够智能,跟不上这个。有什么我可以做的事情或某种模式可以帮助 knockout 跟踪应该选择哪个元素吗?我什至尝试完全删除它,并且在脚本中有一个函数在 self.pageNumber
时手动删除/添加 'selected' 类被改变和/或每当self.availablePages
改变了,但我仍然遇到同样的问题,所以也许这不是 knockout 问题,而是 javascript 问题。
我已经尝试了所有我能想到的;订阅各种可观察对象、 promise ,但就像我说的,一切都已经知道应该选择什么,所以额外的检查和回调不会改变任何东西,也不会消除错误。
我希望有人会知道错误的原因/解决方案或更聪明的方法来完成任务。这是self.pages
那self.availablePages
关掉 key ,以防有帮助:
self.pages = ko.computed(function() {
var start = self.totalPages();
var pages = [];
for (var i = 0; i < start + 1; ++i)
pages.push(new available.MenuModel(i));
return pages;
});
这是整个 javascript 模型(使用 requireJs):
define(['underscore', 'knockout'], function(_, ko) {
var available = available || {};
available.DynamicResponsiveModel = function(isDataObservable, isPaginated) {
var self = this;
self.workingArray = ko.observableArray([]);
self.backgroundArray = ko.observableArray([]);
self.pageNumber = ko.observable(0);
self.count = function () {
return 15;
}
self.resultsPerPage = ko.observable(self.count());
self.selectResultsPerPage = [25, 50, 100, 200, 500];
self.resultsPerPageOptions = ko.computed(function () {
return self.selectResultsPerPage;
});
self.activeSortFunction = isDataObservable ? available.sortAlphaNumericObservable : available.sortAlphaNumeric;
self.resetPageNumber = function() {
self.pageNumber(0);
}
self.initialize = function(data) {
var sortedList = data.sort(function(obj1, obj2) {
return obj2.NumberOfServices - obj1.NumberOfServices;
});
self.workingArray(sortedList);
self.backgroundArray(sortedList);
self.pageNumber(0);
}
self.intializeWithoutSort = function(data) {
self.workingArray(data);
self.backgroundArray(data);
self.pageNumber(0);
}
self.totalPages = ko.computed(function() {
var num = Math.floor(self.workingArray().length / self.resultsPerPage());
num += self.workingArray().length % self.resultsPerPage() > 0 ? 1 : 0;
return num - 1;
});
self.paginated = ko.computed(function () {
if (isPaginated) {
var first = self.pageNumber() * self.resultsPerPage();
return self.workingArray.slice(first, first + self.resultsPerPage());
} else {
return self.workingArray();
}
});
self.pages = ko.computed(function() {
var start = self.totalPages();
var pages = [];
for (var i = 0; i < start + 1; ++i)
pages.push(new available.MenuModel(i));
return pages;
});
self.availablePages = ko.computed(function() {
var pages = self.pages();
var current = self.pageNumber();
if (current === 0) {
return pages.slice(current, current + 3);
} else {
return pages.slice(current - 1, current + 2);
}
});
self.pageNumDisplay = ko.computed(function() {
return self.pageNumber() + 1;
});
self.hasPrevious = ko.computed(function() {
return self.pageNumber() !== 0;
});
self.hasNext = ko.computed(function() {
return self.pageNumber() !== self.totalPages();
});
self.next = function() {
if (self.pageNumber() < self.totalPages()) {
self.pageNumber(self.pageNumber() + 1);
}
}
self.previous = function() {
if (self.pageNumber() != 0) {
self.pageNumber(self.pageNumber() - 1);
}
}
self.toFirstPage = function() {
self.pageNumber(0);
}
self.toLastPage = function() {
self.pageNumber(self.totalPages());
}
self.setPage = function(data) {
return new Promise(function(resolve, reject) {
self.pageNumber(data);
});
}
self.goToPage = function(data) {
self.pageNumber(data);
}
self.isLastIteration = function (index) {
var currentIndex = index();
var count = self.pages().length;
return currentIndex === count - 1;
}
self.resultsPerPage.subscribe(function() {
self.pageNumber(0);
});
self.filterResults = function (filterFunction) {
self.resetPageNumber();
self.workingArray(filterFunction(self.backgroundArray()));
}
self.resetDisplayData = function() {
self.workingArray(self.backgroundArray());
}
self.updateVisibleResults = function(data) {
self.workingArray(data);
}
}
available.sortAlphaNumericObservable = function () {
//...
}
available.sortAlphaNumeric = function () {
//...
}
return available;
});
这是整个表格:
<div data-bind="visible: showListOfEquipment, with: availableEquipmentModel">
<section class="panel panel-default table-dynamic">
<table class="primary-table table-bordered">
<thead>
<tr>
<th>
<div class="th">
Part Number
<span class="fa fa-angle-up" data-bind="click: function () { sortByFirstColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortByFirstColumn(true); }"></span>
</div>
</th>
<th>
<div class="th">
Serial Number
<span class="fa fa-angle-up" data-bind="click: function () { sortBySecondColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortBySecondColumn(true); }"></span>
</div>
</th>
<th>
<div class="th">
Type
<span class="fa fa-angle-up" data-bind="click: function () { sortByThirdColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortByThirdColumn(true); }"></span>
</div>
</th>
<th>
<div class="th">
Equipment Group
<span class="fa fa-angle-up" data-bind="click: function () { sortByFourthColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortByFourthColumn(true); }"></span>
</div>
</th>
<th>
<div class="th">
Operational
<span class="fa fa-angle-up" data-bind="click: function () { sortByFifthColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortByFifthColumn(true); }"></span>
</div>
</th>
<th>
<div class="th">
Valid
<span class="fa fa-angle-up" data-bind="click: function () { sortBySixthColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortBySixthColumn(true); }"></span>
</div>
</th>
</tr>
</thead>
<tbody data-bind="foreach: paginated">
<tr>
<td data-bind="text: $data.PartNumber"></td>
<td><a target="_blank" data-bind="text: $data.SerialNumber, click: function () { $root.setSerialNumberAndFindEquipment(SerialNumber) }" style="color:royalblue"></a></td>
<td data-bind="text: $data.Type"></td>
<td data-bind="text: $data.EquipmentGroup"></td>
<td>
<span data-bind="css: $root.operationalCss($data), text: $root.getOpStatus($data)"></span>
</td>
<td data-bind="text: $data.Validity"></td>
</tr>
</tbody>
</table>
<footer class="table-footer">
<div class="row">
<div class="col-md-6 page-num-info">
<span>Show <select style="min-width: 40px; max-width: 50px;" data-bind="options: selectResultsPerPage, value: resultsPerPage"></select> entries per page</span>
</div>
<div class="col-md-6 text-right pagination-container">
<ul class="pagination-sm pagination" data-bind="foreach: pages">
<li data-bind="if: $index() < 1"><a data-bind="click: $parent.toFirstPage">First</a> </li>
<li class="paginationLi"><a data-bind="text: displayValue, click: $parent.goToPage(iterator), css: { selected: isSelected }"></a></li>
<li data-bind="if: $parent.isLastIteration($index)"> <a data-bind="click: $parent.toLastPage">Last</a> </li>
</ul>
</div>
</div>
</footer>
</section>
最佳答案
我继续构建了一个分页器。我没有像您那样使用数组,而是只使用了可用页面的数量 pageCount
。
可能唯一值得更详细研究的是计算要显示的页面:
this.visiblePages = ko.computed(function() {
var previousHalf = Math.floor( (this.visiblePageCount() - 1) / 2 ),
nextHalf = Math.ceil( (this.visiblePageCount() - 1) / 2 ),
visiblePages = [],
firstPage,
lastPage;
// too close to the beginning
if ( this.currentPage() - previousHalf < 1 ) {
firstPage = 1;
lastPage = this.visiblePageCount();
if ( lastPage > this.pageCount() ) {
lastPage = this.pageCount();
}
// too close to the end
} else if ( this.currentPage() + nextHalf > this.pageCount() ) {
lastPage = this.pageCount();
firstPage = this.pageCount() - this.visiblePageCount() + 1;
if (firstPage < 1) {
firstPage = 1;
}
// just right
} else {
firstPage = this.currentPage() - previousHalf;
lastPage = this.currentPage() + nextHalf;
}
for (var i = firstPage; i <= lastPage; i++) {
visiblePages.push(i);
}
return visiblePages;
}, this);
让我们一 block 一 block 地过一遍。我们希望当前页面位于所有显示的分页按钮的中间,一些在其左侧,一些在其右侧。但是有多少呢?
如果我们使用奇数,例如三,这很简单:数字减去 1(选中的数字)除以二。 (3 - 1)/2 = 1
,或每边一个。
要显示偶数个分页按钮,这是行不通的,因此我们分别计算每一侧并将一个结果向上舍入,一个结果向下舍入:
var previousHalf = Math.floor( (this.visiblePageCount() - 1) / 2 ),
nextHalf = Math.ceil( (this.visiblePageCount() - 1) / 2 ),
三种可能的结果:
- 我们的选择适合
- 我们太接近开始了
- 我们离结束太近了
如果我们离开始太近了:
if ( this.currentPage() - previousHalf < 1 ) {
firstPage = 1;
lastPage = this.visiblePageCount();
if ( lastPage > this.pageCount() ) {
lastPage = this.pageCount();
}
}
我们从 1 开始并尝试显示第 1 页,直到 visiblePageCount
。如果这也不起作用,因为我们没有足够的页面,我们只显示我们所有的。
如果我们离结束太近了:
} else if ( this.currentPage() + nextHalf > this.pageCount() ) {
lastPage = this.pageCount();
firstPage = this.pageCount() - this.visiblePageCount() + 1;
if (firstPage < 1) {
firstPage = 1;
}
}
我们以最后一页结束,并尝试在左侧显示尽可能多的页面。如果这不起作用,因为我们没有足够的页面,我们只显示我们所有的。
这是完整的例子:
var ViewModel;
ViewModel = function ViewModel() {
var that = this;
this.pageCount = ko.observable(20);
this.currentPage = ko.observable(1);
this.visiblePageCount = ko.observable(3);
this.gotoPage = function gotoPage(page) {
that.currentPage(page);
};
this.visiblePages = ko.computed(function() {
var previousHalf = Math.floor( (this.visiblePageCount() - 1) / 2 ),
nextHalf = Math.ceil( (this.visiblePageCount() - 1) / 2 ),
visiblePages = [],
firstPage,
lastPage;
if ( this.currentPage() - previousHalf < 1 ) {
firstPage = 1;
lastPage = this.visiblePageCount();
if ( lastPage > this.pageCount() ) {
lastPage = this.pageCount();
}
} else if ( this.currentPage() + nextHalf > this.pageCount() ) {
lastPage = this.pageCount();
firstPage = this.pageCount() - this.visiblePageCount() + 1;
if (firstPage < 1) {
firstPage = 1;
}
} else {
firstPage = this.currentPage() - previousHalf;
lastPage = this.currentPage() + nextHalf;
}
for (var i = firstPage; i <= lastPage; i++) {
visiblePages.push(i);
}
return visiblePages;
}, this);
};
ko.applyBindings( new ViewModel() );
ul {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
margin: 0;
padding: 0;
list-style-type: none;
}
ul li {
-webkit-box-flex: 0;
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
}
button {
margin-right: 0.5rem;
padding: 0.5rem;
background-color: lightgrey;
border: none;
}
button.selected {
background-color: lightblue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul>
<li><button data-bind="click: gotoPage.bind($data, 1)">First</button></li>
<!-- ko foreach: visiblePages -->
<li>
<button data-bind="text: $data,
click: $parent.gotoPage,
css: { selected: $parent.currentPage() === $data }"></button>
</li>
<!-- /ko -->
<li><button data-bind="click: gotoPage.bind($data, pageCount())">Last</button></li>
</ul>
关于javascript - KnockoutJS 或 Javascript 未正确跟踪显示的数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29420509/