敲除分页绑定

时间:2016-09-02 19:16:44

标签: javascript knockout.js knockout-3.0

很抱歉,如果这是一个非常基本的问题,但我正在学习Knockout并尝试将分页连接到我的数据集。

在下面的代码中,您将看到我正在检索数据集,并且页面大小下拉列表会相应地影响结果。
当我更改页码时(表格页脚中的#'d链接),什么都没发生。
有人能告诉我我错过了什么吗?

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>

2 个答案:

答案 0 :(得分:1)

感谢 f_martinez 帮助解决我的问题,如果有人最终在这里寻找如何进行分页,这是一个有效的示例。 jsfiddle

如果 f_martinez 希望发布接受答案,我会暂时保持此状态。

&#13;
&#13;
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);
&#13;
.pagination > li > a:focus,
.pagination > li > a:hover,
.pagination > li > span:focus,
.page-link.active {
  background-color: rgb(238, 238, 238);
}
&#13;
<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>
&#13;
&#13;
&#13;

答案 1 :(得分:0)

堆栈溢出是为了解决常见问题,而答案解决方案对OP有效,但对于其他情况它并不是非常可重用,这是这个常见问题的可重用解决方案(Knockout分页) )

我正在网站上工作,它有很多表(大多数都需要分页),所以实际上我需要一些reusable-component来进行分页,以便在我需要分页的所有情况下使用它。
所以我在这个问题上开发了自己的组件。

<强> Now on Github

<强> JsFiddle

有关详细信息,请继续阅读

<强>的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>

功能

  1. 显示需要
    当根本不需要分页时(例如需要显示小于页面大小的项目),{{1} }组件将消失。
    这将由声明HTML确定。

  2. 根据需要禁用
    例如,如果您已经选择了最后一页,为什么要按data-bind="visible: NeedPaging"last page按钮?
    我正在处理此问题,在这种情况下,我通过应用以下绑定来禁用这些按钮 Next

  3. 区分所选页面
    在所选页面上应用一个特殊类(在本例中称为data-bind="css: { disabled: !PreviousPageActive() }"类),以使用户知道他/她现在在哪个页面中。
    这是通过绑定active

  4. 建立的
  5. 最后&amp;首先
    通过专门用于此的简单按钮也可以访问第一页和最后一页。

  6. 显示按钮的限制
    假设你有很多页面,例如1000页,那会发生什么?你会为用户显示它们吗? 绝对不你必须根据当前页面只显示其中一些。例如,在页面页面之前显示3页,在所选页面之后显示其他3页。
    此案件已在此处理data-bind="css: { active: $parent.CurrentPage() === $data }"
    <!-- ko foreach: GetPages() -->函数应用一个简单的算法来确定我们是否需要显示所有页面(页面数量低于阈值,可以很容易地确定),或者只显示一些按钮。
    您可以通过更改GetPages变量的值来确定阈值 现在我将它指定为以下maxPageCount,这意味着可以为用户显示不超过7个按钮(在SelectedPage之前为3,在Selected Page之后为3)和Selected Page本身。

    您可能想知道,如果在当前页面显示之前 OR 之后没有足够的页面会怎么样? 不要担心我在算法中处理此问题例如,如果您有var maxPageCount = 7;并且您有11 pages和当前maxPageCount = 7,那么以下页面将显示

    selected page is 10

    所以我们总是将5,6,7,8,9,10(selected page),11分层,在上一个示例中,在所选页面之前显示maxPageCount页面,在所选页面之后只显示5页面。

  7. 所选页面验证
    1可观察的所有设置操作(用户确定所选页面)都通过函数CurrentPage。在这个函数中,我们设置了这个可观察的,从代码中可以看出,在设置值之前,我们进行验证操作,以确保我们不会超出页面的可用页面。

  8. 已经清洁
    我只使用SetCurrentPage而非pureComputed属性,这意味着您无需为清理和处理这些属性而烦恼。 虽然,正如您将在下面的示例中看到的那样,您需要处理组件本身之外的一些其他订阅

  9. 注1
    您可能会注意到我在此组件中使用了一些computed类, 这对我来说很合适,但当然你可以使用自己的类而不是bootstrap类。
    我在这里使用的引导类是bootstrappaginationpagination-smactive
    随意根据需要更改它们。

    注2
    所以我为你介绍了这个组件,是时候看看它是如何工作的。
    您可以将此组件集成到主ViewModel中,如下所示。

    disabled

    最后但并非最不重要的,当然不要忘记根据您的特殊viewModel更改html组件中的绑定,或者使用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(); } } 这样包装所有组件

    with binding

    干杯