如何使用Knockout处理分页

How to handle pagination with Knockout

本文关键字:分页 处理 Knockout 何使用      更新时间:2023-09-26

我有一个div,它被设置为绑定到observeableArray,但我只想在任何给定时间显示来自该observeableArray的最多50个项目。我想用页面上的上一个和下一个按钮以及索引进行分页,以允许用户在集合中的项目页面中循环
我知道使用computedObservable和自定义数据绑定可能可以做到这一点,但我不确定如何做到(我还是一个Knockout新手)
有人能给我指正确的方向吗?

这是我的代码(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();
        }
    }
}

使用Knockout分页非常简单。我个人会这样实现:

  • 拥有一个包含所有元素的observableArray
  • 具有包含当前页面的可观察项(初始化为0)
  • 有一个变量声明每页的元素数
  • 具有一个返回页数的computed,该页数是根据每页的元素数和元素总数计算得出的
  • 最后,添加一个computed,它对包含所有元素的数组进行切片

考虑到这一点,您现在可以添加一个函数来增加(下一页)或减少(上一页)当前页面。

这里有一个快速的例子:

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);
        }
    }
}

您可以在这里找到一个简单完整的示例:http://jsfiddle.net/LAbCv/(可能有点麻烦,但想法是存在的)。

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

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

现在在Github上

JsFiddle

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

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

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

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

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

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

    您可能会想,如果当前页面之前之后没有足够的页面显示,该怎么办别担心,我正在算法中处理这个问题
    例如,如果您有11 pagesmaxPageCount = 7和当前的selected page is 10,则会显示以下页面

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

    所以我们总是对maxPageCount进行分层,在前面的例子中,显示了所选页面之前的5页面和所选页面之后的1页面。

  6. 所选页面验证
    CurrentPage可观察的所有设置操作都通过函数SetCurrentPage来确定用户选择的页面。只有在这个函数中,我们设置了这个可观察的值,正如你从代码中看到的,在设置值之前,我们会进行验证操作,以确保我们不会超出页面的可用页面。

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

注意1
您可能会注意到,我在这个组件中使用了一些CCD_ 27类,这对我来说很合适,但,当然您可以使用自己的类而不是引导程序类
我在这里使用的引导程序类是paginationpagination-smactivedisabled
您可以根据需要随意更改它们。

注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();
    }
}

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

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

干杯

我创建了一篇博客文章,详细解释了如何在JQuery插件的帮助下创建分页(此处)。

基本上,我使用了AJAX的普通敲除数据绑定,在从服务器检索到数据后,我调用插件。你可以在这里找到插件。它被称为简单分页

这个问题仍然是"淘汰分页"的热门搜索之一,因此淘汰扩展淘汰分页(git)值得一提
它通过扩展ko.observableArray提供分页。它有很好的文档记录并且易于使用
下面是使用示例。