typescript 如何使用 Knockout 处理分页

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17668491/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-11 01:06:47  来源:igfitidea点击:

How to handle pagination with Knockout

javascriptknockout.jspaginationtypescript

提问by Ben Nelson

I have a div that is setup to bind to a observeableArray,but I only want to show at most 50 items from that observeableArrayat any given time. I want to handle this with pagination with a previous and next button along with indices on the page to allow users to cycle through pages of items from the collection.
I know I could probably do this with a computedObservableand a custom data binding but I'm not sure how to do it (I'm still a Knockout neophyte).
Can anyone point me in the right direction?

我有一个 div 设置为绑定到observeableArray,但我只想observeableArray在任何给定时间最多显示 50 个项目。我想通过带有上一个和下一个按钮以及页面上的索引的分页来处理这个问题,以允许用户循环浏览集合中的项目页面。
我知道我可能可以使用 acomputedObservable和自定义数据绑定来做到这一点,但我不知道该怎么做(我仍然是一个淘汰赛新手)。
任何人都可以指出我正确的方向吗?

Here is my code (the JS is in TypeScript):

这是我的代码(JS 在 TypeScript 中):

<div class="container-fluid">
    <div class="row-fluid">
        <div class="span12">
            <%=
            if params[:q]
              render 'active_search.html.erb'
            else
              render 'passive_search.html.erb'
            end
            %>
            <%= form_tag("/search", method: "get", :class => "form-search form-inline") do %>
            <%= label_tag(:q, "Search for:") %>
            <%= text_field_tag(:q, nil, class:"input-medium search-query") %>
            <%= submit_tag("Search", :class=>"btn") %>
            <% end %>

            <div class="media" data-bind="foreach: tweetsArray">
                <%= image_tag('twitter-icon.svg', :class=>"tweet_img", :style=>"display:inline;") %>
                <div class="media-body" style="display:inline;">
                    <h4 class="media-heading" data-bind="text: user.screen_name" style="display:inline;"></h4>
                    <span data-bind="text:text" style="display:inline;"></span> <br />
                    <span data-bind="text:'Created at '+created_at"></span> <br />
                </div>
            </div>

            <div class="pagination pagination-centered">
                <ul>
                    <li>
                        <a href="#">Prev</a>
                    </li>
                    <li>
                        <a href="#">1</a>
                    </li>
                    <li>
                        <a href="#">Next</a>
                    </li>
                </ul>
            </div>

        </div>
    </div>
</div>

<script>
    var viewModel = new twitterResearch.TweetViewModel();
    ko.applyBindings(viewModel);

    //TODO: notes to self, use custom binding for pagination along with a computed observable to determine where at in the list you are

    //document.onReady callback function
    $(function() {
        $.getJSON('twitter', {}, function(data) {
            viewModel.pushTweet(data);
            console.log(data.user);
        });
    });
</script>

declare var $: any;
declare var ko: any;

module twitterResearch {
    class Tweet {
        text: string;
        created_at: string;
        coordinates: string;
        user: string;
        entities: string;
        id: number;
        id_str: string;

        constructor(_text: string, _created_at: string, _coordinates: any, _user: any,
                    _entities: any, _id_str: string, _id: number){

            this.text = _text;
            this.created_at = _created_at;
            this.coordinates = _coordinates;
            this.user = _user;
            this.entities = _entities;
            this.id_str = _id_str;
            this.id = _id;
        }
    }

    export class TweetViewModel{

        tweetsArray: any;
        constructor()
        {
            this.tweetsArray = ko.observableArray([]);
        }

        //tweet is going to be the JSON tweet we return
        //from the server
        pushTweet(tweet)
        {
            var _tweet = new Tweet(tweet.text, tweet.created_at, tweet.coordinates,
                                    tweet.user, tweet.entities, tweet.id_str, tweet.id);
            this.tweetsArray.push(_tweet);
            this.tweetsArray.valueHasMutated();
        }
    }
}

回答by mael

Pagination is quite simple with Knockout. I would personally achieve it this way:

使用 Knockout 进行分页非常简单。我个人会通过这种方式实现它:

  • Have an observableArray containing all your elements
  • Have an observable containing the current page (initialized to 0)
  • Have a variable declaring the number of elements per page
  • Have a computed that returns the number of pages, calculated thanks to the number of elements per page and the total number of elements.
  • Finally, add a computed that slices the array containing all the elements.
  • 有一个包含所有元素的 observableArray
  • 有一个包含当前页面的 observable(初始化为 0)
  • 有一个变量来声明每页的元素数
  • 有一个计算返回页数,这要归功于每页的元素数和元素总数。
  • 最后,添加一个对包含所有元素的数组进行切片的计算。

Given that, you can now add a function that increments (next) or decrements (previous) the current page.

鉴于此,您现在可以添加一个增加(下一个)或减少(上一个)当前页面的函数。

Here is a quick example:

这是一个快速示例:

var Model = function() {
    var self = this;
    this.all = ko.observableArray([]);
    this.pageNumber = ko.observable(0);
    this.nbPerPage = 25;
    this.totalPages = ko.computed(function() {
        var div = Math.floor(self.all().length / self.nbPerPage);
        div += self.all().length % self.nbPerPage > 0 ? 1 : 0;
        return div - 1;
    });

    this.paginated = ko.computed(function() {
        var first = self.pageNumber() * self.nbPerPage;
        return self.all.slice(first, first + self.nbPerPage);
    });

    this.hasPrevious = ko.computed(function() {
        return self.pageNumber() !== 0;
    });

    this.hasNext = ko.computed(function() {
        return self.pageNumber() !== self.totalPages();
    });

    this.next = function() {
        if(self.pageNumber() < self.totalPages()) {
            self.pageNumber(self.pageNumber() + 1);
        }
    }

    this.previous = function() {
        if(self.pageNumber() != 0) {
            self.pageNumber(self.pageNumber() - 1);
        }
    }
}

You'll find a simple and complete example here: http://jsfiddle.net/LAbCv/(might be a bit buggy, but the idea is there).

你会在这里找到一个简单而完整的例子:http: //jsfiddle.net/LAbCv/(可能有点问题,但想法就在那里)。

回答by Hakan F?st?k

Actually I am working on a website, which has a lot of tables (most of them need paging).
So actually, I needed some reusable-componentfor paging to use it in all the cases which I need paging.
Also, I needed more advanced features than which provided in the accepted answer to this question.

实际上我正在一个网站上工作,该网站有很多表格(其中大多数需要分页)。
所以实际上,我需要一些reusable-component分页来在我需要分页的所有情况下使用它。
此外,我需要比此问题的公认答案中提供的更高级的功能。

So I developed my own component to solving this issue, here it is.

所以我开发了自己的组件来解决这个问题,就在这里。

Now on Github

现在在 Github

JsFiddle

提琴手

And for more details, continue reading (Please consider to take the code from GitHub, not from here, as the GitHub code was updated and enhanced since I put it here)

有关更多详细信息,继续阅读(请考虑从 GitHub 获取代码,而不是从这里获取,因为 GitHub 代码自我放在这里以来已更新和增强)

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 HTMLcomponent will disappear.
    This will be established by statementdata-bind="visible: NeedPaging".

  2. Disable on need
    for example, if you are already selected the last page, why the last pageor the Nextbutton should be available to press?
    I am handling this and in that case I am disabling those buttons by applying the following bindingdata-bind="css: { disabled: !PreviousPageActive() }"

  3. Distinguish the Selected page
    a special class (in this case called activeclass) is applied on the selected page, to make the user know in which page he/she is right now.
    This is established by the binding 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 happen? would you display them all for the user? absolutely notyou have to display just a few of them according to the current page. for example, showing 3 pages before and other 3 pages after the selected page.
    This case has been handled here <!-- ko foreach: GetPages() -->
    the GetPagesfunction 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.
    you can determine the threshold by changing the value of the maxPageCountvariable
    Right now I assigned it as the following 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.

    You may wonder, what if there were not enough pages after ORbefore the current page to display? do not worry I am handling this in the algorithm
    for example, if you have 11 pagesand you have maxPageCount = 7and the current selected page is 10, Then the following pages will be shown

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

    so we always stratifying the maxPageCount, in the previous example showing 5pages before the selected page and just 1page after the selected page.

  6. Selected Page Validation
    All set operation for the CurrentPageobservable which determine the selected page by the user, is going through the function 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 pureComputednot computedproperties, which means you do not need to bother yourself with cleaning and disposing of those properties. Although, as you will see in the example below, you need to dispose of some other subscriptions which are outside of the component itself

  1. 按需显示
    当根本不需要分页(例如需要显示小于页面大小的项目)时,该HTML组件将消失。
    这将通过声明来确定data-bind="visible: NeedPaging"


  2. 例如,根据需要禁用,如果您已经选择了最后一页,为什么last pageNext按钮应该可以按下?
    我正在处理这个问题,在这种情况下,我通过应用以下绑定来禁用这些按钮data-bind="css: { disabled: !PreviousPageActive() }"

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

  4. Last & First
    也可以通过专门用于此的简单按钮访问第一页和最后一页。

  5. 显示按钮的限制
    假设您有很多页面,例如 1000 个页面,那么会发生什么?你会为用户显示它们吗?绝对不是你必须根据当前页面只显示其中的几个。例如,显示所选页面之前的 3 页和其他 3 页。
    这里已经处理了这种情况,<!-- ko foreach: GetPages() -->
    GetPages函数应用了一个简单的算法来确定我们是否需要显示所有页面(页面计数低于阈值,这很容易确定),或者只显示一些按钮。
    您可以通过更改maxPageCount变量的值来确定阈值
    现在我将其分配如下var maxPageCount = 7;这意味着不能为用户显示超过 7 个按钮(3 个在 SelectedPage 之前,3 个在 Selected Page 之后)和 Selected Page 本身。

    您可能想知道,如果当前页面之前OR之后没有足够的页面显示怎么办?不用担心,我正在算法中处理此问题,
    例如,如果您有11 pages并且您有maxPageCount = 7当前的selected page is 10,那么将显示以下页面,

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

    因此我们总是对maxPageCount,在前面的示例中显示5所选页面之前的页面和1之后的页面所选页面。

  6. Selected Page Validation用户确定所选页面
    CurrentPageobservable 的所有设置操作,都在通过该函数。仅在这个函数中我们设置了这个 observable,从代码中可以看出,在设置值之前,我们进行了验证操作,以确保我们不会超出页面的可用页面。SetCurrentPage

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

NOTE 1
You may notice that I am using some bootstrapclasses in this component, This is suitable for me, but , of course,you can use your own classes instead of the bootstrap classes.
The bootstrap classes which I used here are pagination, pagination-sm, activeand disabled
Feel free to change them as you need.

注意 1
你可能注意到我bootstrap在这个组件中使用了一些类,这很适合我,但是,当然,你可以使用你自己的类而不是引导类。
我这里使用的自举类是paginationpagination-smactivedisabled
随意,因为你需要改变它们。

NOTE 2
So I introduced the component for you, It is time to see how it could work.
You would integrate this component into your main ViewModel as like this.

NOTE 2
所以我为你介绍了这个组件,是时候看看它是如何工作的了。
您可以像这样将此组件集成到您的主 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 changes 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 allows 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 your special viewModel, or wrap all the component with the with bindinglike this

最后但并非最不重要的是,一定不要忘记根据您的特殊视图模型更改 HTML 组件中的绑定,或者用with binding这样的方式包装所有组件

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

Cheers

干杯

回答by Eli Algranti

This question is still one of the top searches for "knockout pagination", so the knockout extension knockout-paging(git) is worth mentioning.
It provides pagination by extending ko.observableArray. It is well documented and easy to use.
The usage example is here.

这个问题仍然是“knockout pagination”的热门搜索之一,因此knockout扩展knockout-paginggit)值得一提。
它通过扩展提供分页ko.observableArray。它有据可查且易于使用。
使用示例是here

回答by Romesh D. Niriella

I have created a blogpost with detailed explanation on how to create pagination with the help of a little JQuery plugin (here).

我创建了一篇博文,详细解释了如何在一个小 JQuery 插件的帮助下创建分页(这里)。

Basically, I have used normal knockout data binding with AJAX and after data has been retrieved from the server, I call the plugin. You can find the plugin here. It's called Simple Pagination.

基本上,我使用了 AJAX 的正常淘汰赛数据绑定,在从服务器检索数据后,我调用了插件。您可以在此处找到该插件。它被称为简单分页