javascript - KnockoutJS 或 Javascript 未正确跟踪显示的数组

标签 javascript jquery css arrays knockout.js

我正在写一个分页表,底部有一个页面选择器,显示不同的页码

enter image description here

我正在使用 knockout 。这些数字来自 ko.computed 数组 (self.pages),该数组根据每页的结果数/结果数计算有多少页。我遇到的问题是,如果数据数组很长并且每页的结果设置得有点低,我会得到这样的结果:

enter image description here

我想做的是将菜单项的数量限制为三个,因此如果选择第 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.pagesself.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 ),

三种可能的结果:

  1. 我们的选择适合
  2. 我们太接近开始了
  3. 我们离结束太近了

如果我们离开始太近了:

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/

相关文章:

javascript - 在 Rails 应用程序中隐藏标签云

javascript - 在加载新内容之前淡出

javascript - 如果可拖动的 div 到达页面顶部或底部,则转到 URL

css - 在调整大小和居中的背景图像上溢出?

javascript - 使用 ng-class 和 ng-change 在元素中动态设置类

css - 不要为有序或无序列表显示数字/元素符号

javascript - 按值对数据集数组排序[1]

javascript - 通过代码捕获 Firefox/IE 错误控制台的内容?

javascript - 选项卡委托(delegate)不工作

javascript - 如何在webAPP中的firebase中添加新条目