简体   繁体   English

敲除分页绑定

[英]Knockout paging binding

Sorry if this is a really basic question but I'm in the process of learning Knockout and trying to wire up paging to my dataset. 抱歉,如果这是一个非常基本的问题,但是我正在学习Knockout,并尝试将分页连接到我的数据集。

In my code below, you will see that I am retrieving the dataset, and the page size dropdown affects the results appropriately. 在下面的代码中,您将看到我正在检索数据集,并且页面大小下拉列表会适当地影响结果。
And when I change the page number (#'d links in footer of table), nothing happens. 而且,当我更改页码(表格页脚中的#个链接)时,什么也没发生。
Could someone tell me what I'm missing? 有人可以告诉我我所缺少的吗?

 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> 

Thanks to f_martinez for helping with my issue, here is the working example if anyone ends up here looking for how to do paging. 感谢f_martinez帮助解决我的问题,这是工作示例,如果有人最终在这里寻找如何进行分页的话。 jsfiddle jsfiddle

I will keep this open for now in case f_martinez would like to post an answer to accept. 我暂时将其保持打开状态,以防f_martinez发布要接受的答案。

 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 is about giving solution to common problems, and the answer solution is valid for OP ,but it is not very re-usable for other cases, Here is re-usable solution to this common problem (Knockout paging) 堆栈溢出是为常见问题提供解决方案,答案解决方案对OP有效,但在其他情况下不是很可重用,这是此常见问题的可重用解决方案(敲除分页)


I am working on website, which has a lot of tables (most of them need paging), so actually I needed some reusable-component for paging to use it in all the cases which I need paging. 我正在网站上工作,该网站上有很多表(其中大多数表需要分页),因此实际上我需要一些reusable-component来进行分页,以便在需要分页的所有情况下使用它。
So I developed my own component of the this issue, here it is. 因此,我在这个问题上开发了自己的组件。

Now on Github 现在在Github上

JsFiddle JsFiddle

And for more details, continue reading 有关更多详细信息,请继续阅读

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

Features 特征

  1. Show on need 根据需要显示
    When there is no need for paging at all (for example the items which need to display less than the page size) then the HTML component will disappear. 如果根本不需要分页(例如,需要显示的页面尺寸小于页面大小的项目),则HTML组件将消失。
    This will be established by statement data-bind="visible: NeedPaging" . 这将通过语句data-bind="visible: NeedPaging"

  2. Disable on need 根据需要禁用
    for example, if you are already selected the last page, why the last page or the Next button should be available to press? 例如,如果您已经选择了最后一页,为什么应该按下last page或“ Next按钮?
    I am handling this and in that case I am disabling those buttons by applying the following binding data-bind="css: { disabled: !PreviousPageActive() }" 我正在处理此问题,在那种情况下,我通过应用以下绑定 data-bind="css: { disabled: !PreviousPageActive() }" 禁用这些按钮 data-bind="css: { disabled: !PreviousPageActive() }"

  3. Distinguish the Selected page 区分所选页面
    a special class (in this case called active class) is applied on the selected page, to make the user know in which page he/she is right now. 在所选页面上会应用一个特殊的类(在这种情况下称为active类),以使用户知道他/她现在在哪个页面中。
    This is established by the binding data-bind="css: { active: $parent.CurrentPage() === $data }" 这是通过绑定data-bind="css: { active: $parent.CurrentPage() === $data }"

  4. Last & First 最后和第一个
    going to the first and last page is also available by simple buttons dedicated to this. 也可以通过专用于此的简单按钮来转到首页和最后一页。

  5. Limits for displayed buttons 显示按钮的限制
    suppose you have a lot of pages, for example 1000 pages, then what will happened? 假设您有很多页面,例如1000页,那么会发生什么? would you display them all for the user ? 您会为用户显示所有这些内容吗? absolutely not you have to display just a few of them according to the current page. 绝对不需要,您不必根据当前页面仅显示其中一些。 for example showing 3 pages before the page page and other 3 pages after the selected page. 例如,在页面页面之前显示3个页面,在所选页面之后显示其他3个页面。
    This case has been handled here <!-- ko foreach: GetPages() --> 这种情况已在这里处理<!-- ko foreach: GetPages() -->
    the GetPages function applying a simple algorithm to determine if we need to show all the pages (the page count is under the threshold, which could be determined easily), or to show just some of the buttons. GetPages函数采用一种简单的算法来确定我们是否需要显示所有页面(页面数在阈值以下,可以轻松确定),或者仅显示某些按钮。
    you can determine the threshold by changing the value of the maxPageCount variable 您可以通过更改maxPageCount变量的值来确定阈值
    Right now I assigned it as the following var maxPageCount = 7; 现在,我将其分配为以下var maxPageCount = 7; which mean that no more than 7 buttons could be displayed for the user (3 before the SelectedPage, and 3 after the Selected Page) and the Selected Page itself. 这意味着最多只能为用户显示7个按钮(SelectedPage之前的3个按钮,Selected Page后面的3个按钮)和Selected Page本身。

    You may wonder, what if there was not enough pages after OR before the current page to display? 您可能想知道,如果在当前页面之前之后没有足够的页面显示呢? do not worry I am handling this in the algorithm 不用担心我在算法中处理这个
    for example, if you have 11 pages and you have maxPageCount = 7 and the current selected page is 10 , Then the following pages will be shown 例如,如果您有11 pages并且maxPageCount = 7并且当前selected page is 10 ,那么将显示以下页面

    5,6,7,8,9,10(selected page),11

    so we always stratifying the maxPageCount , in the previous example showing 5 pages before the selected page and just 1 page after the selected page. 因此,我们总是对maxPageCount进行分层,在上一个示例中,该页面显示所选页面之前的5页,而所选页面之后的仅1页。

  6. Selected Page Validation 所选页面验证
    All set operation for the CurrentPage observable which determine the selected page by the user , is go through the function SetCurrentPage . 可观察到的CurrentPage所有设置操作( 由用户确定选择的页面 )都通过功能SetCurrentPage In only this function we set this observable, and as you can see from the code, before setting the value we make validation operations to make sure that we will not go beyond the available page of the pages. 仅在此函数中,我们将其设置为可观察的,并且如您从代码中所见,在设置该值之前,我们进行验证操作以确保不会超出页面的可用页面。

  7. Already clean 已经干净了
    I use only pureComputed not computed properties, which means you do not need to bother yourself with cleaning and disposing those properties. 我仅使用pureComputed而不是computed属性,这意味着您无需打扰清理和处置这些属性。 Although ,as you will see in example below, you need to dispose some other subscriptions which are outside of the component itself 尽管,如您在下面的示例中看到的,您需要处理组件本身之外的一些其他订阅

NOTE 1 注1
You may noticed that I am using some bootstrap classes in this component, This is suitable for me, but of course you can use your own classes instead of the bootstrap classes. 您可能会注意到,我在此组件中使用了一些bootstrap类,这对我来说很合适,但是您当然可以使用自己的类而不是引导程序类。
The bootstrap classes which I used here are pagination , pagination-sm , active and disabled 我在这里使用的引导程序类是paginationpagination-smactivedisabled
Feel free to change them as you need. 随时根据需要更改它们。

NOTE 2 笔记2
So I introduced the component for you, It is time to see how it could work. 因此,我为您介绍了该组件,现在该看看它如何工作。
You would integrate this component in your main ViewModel as like this. 您可以像这样在主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();
    }
}

Last but not least , Sure do not forget to change the binding in the html component according to you special viewModel, or wrap all the component with the with binding like this 最后但并非最不重要的一点是 ,请确保不要忘记根据您的特殊viewModel更改html组件中的绑定,或像这样用with binding包装所有组件

<div data-bind="with: PagingComponent()">
    <!-- put the component here -->
</div>

Cheers 干杯

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM