【问题标题】:KnockoutJS or Javascript not keeping proper track of displayed arrayKnockoutJS 或 Javascript 没有正确跟踪显示的数组
【发布时间】:2015-06-07 20:30:04
【问题描述】:

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

我正在使用淘汰赛。这些数字来自 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);
  }
});

现在所有这一切似乎都运行良好,但有一个错误我无法消除。使用敲除 css 数据绑定,我告诉它为任何与 self.pageNumber 具有相同值的元素分配一个 'selected' 类(参见下面的代码)。

如果选择的元素不需要self.availablePages改变(即选择2,而1是之前的选择),没有问题; 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 observable 添加到菜单中的所有项目,然后当self.availablePages 被重新计算时,该函数检查 pageNumber 是什么,然后找到数组中的哪个项目匹配并设置self.isSelected(true),然后尝试将css绑定键控到那个。

不幸的是,这不起作用;它仍然有完全相同的错误。我一直在疯狂地调试脚本,似乎没有问题;一切似乎都知道应该选择 3,但实际选择的是 4。

我猜淘汰赛绑定不够聪明,无法跟上这一点。有什么我可以做的或一些有助于淘汰赛跟踪应该选择哪个元素的模式吗?我什至尝试将其完全淘汰,并在脚本中手动删除/添加'selected'类,只要self.pageNumber 更改和/或self.availablePages 更改但我仍然遇到了同样的问题,所以也许这不是淘汰赛问题,而是 javascript 的问题。

我已经尝试了我能想到的所有其他方法;订阅各种 observables、promise,但就像我说的那样,一切都已经知道应该选择什么,因此额外的检查和回调不会改变任何东西,也不会消除错误。

我希望有人会知道错误的原因/解决方案,或者知道更聪明的方法来完成任务。这是self.availablePages 关闭的self.pages,以防万一:

    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>


【问题讨论】:

  • 能否请您发布一个完整的、可运行的当前分页代码示例,最好在您的问题中使用Stackoverflow snippet 或JSFiddle?
  • 我不知道如何获取数据,所以不确定如何使其可运行,但我会添加整个模型和表格

标签: javascript jquery css arrays knockout.js


【解决方案1】:

我继续构建了一个分页器。我没有像你那样使用数组,而是只使用了可用页数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);

让我们一步一步来。我们希望当前页面位于所有显示的分页按钮的中间,一些在左侧,一些在右侧。但是有多少?

如果我们使用奇数,例如三,那很简单:数字减 1(选定的数字)除以 2。 (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>

【讨论】:

  • 感谢您的详细帮助。我实施了更改并且它起作用了;希望我能更好地理解问题所在;我在使用您的模型时遇到了第 0 页的问题,并尝试通过将所有内容(包括可见页面)从 currentPage + 1 的新计算中键入所有内容来解决导致相同问题的最小更改。因此,我更改了 data-bind = text: $data + 1 并且有效......当涉及另一个计算时,整个事情变得混乱似乎很奇怪。两种情况都将接受的最低值设置为 0,并首先设置为 0……奇怪。
  • 所有比较都假定第 1 页是第一个。我将快速适应从零开始的索引,请稍等。
  • 实际上,从头开始:如果你反过来,代码仍然更容易理解,保持分页不变并简单地计算 currentPage() - 1 用于你需要的任何其他目的(AJAX 调用? )。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-10-19
  • 1970-01-01
  • 1970-01-01
  • 2023-03-21
  • 2013-09-20
  • 2011-08-12
  • 1970-01-01
相关资源
最近更新 更多