如何使用Knockout处理分页

问题描述:

我有一个设置为绑定到observeableArray的div,但我只想在任何给定时间显示最多来自该observeableArray的50个项目。我想要通过页面上的前一个和下一个按钮以及索引进行分页处理,以便用户循环浏览集合中的项目页面。
我知道我可以使用computedObservable和自定义数据绑定来做到这一点,但我不知道如何去做(我仍然是一名Knockout初学者)。
任何人都可以指向正确的方向吗?如何使用Knockout处理分页

这里是我的代码(JS是打字稿):

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

分页是淘汰赛很简单。我会亲自实现这种方式:

  • 有一个包含所有元素的observableArray
  • 有可观察到的包含当前页面(初始化为0)
  • 具有可变宣布每
  • 页面元素的数量
  • 有一个计算结果返回页数,由于每页元素的数量和元素的总数计算得出。
  • 最后,添加一个计算器,对包含所有元素的数组进行切片。

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

下面是一个简单的例子:

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/(可能是一个有点马车,但这个想法是存在的)。

+5

这太好了..谢谢!我已经扩展它以显示每个页码,并允许用户从第5页点击以说出第1页。http://jsfiddle.net/LAbCv/31/ –

+5

将最后一页丢到右边? 'Math.ceil'应该在页计数而不是'Math.floor'中?编辑:啊,你加上剩余部分/模量,我只是把它放在一边并完成。 – MrYellow

+0

如果您正在处理可能具有相当大结果集的任何内容,请小心此方法。加载结果客户端的整个列表,然后在javascript中进行页面加载并不是一个好主意。对于大型结果集,您需要某种仅加载当前页面所需数据的AJAX解决方案。 – Mir

我已经创建了一篇博文,详细介绍了如何使用小JQuery插件(here)的帮助来创建分页。

基本上,我使用了正常的敲除数据绑定与AJAX和数据已从服务器中检索后,我打电话插件。你可以找到插件here。它被称为简单分页

其实我正在一个网站,它有很多的表(大多数需要分页)。
所以实际上,我需要一些reusable-component进行分页,以便在需要分页的所有情况下使用它。
此外,我需要比接受的问题中提供的更高级的功能。

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

Now on 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组件将dis出现。
    这将通过声明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个按钮(选定页面前3个,以及后3个按钮选定页面)和选定页面本身。

    你可能想知道,如果没有足够的页面后的当前页面显示之前? 不用担心我处理这
    例如,如果你有11 pages,你有maxPageCount = 7和当前selected page is 10,然后下面的页面将显示

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

    所以我们总是分层的maxPageCount算法,在前面的示例中,在选定页面之前显示5页面,在选定页面之后显示1页面。

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

  7. 已经干净
    我只用pureComputedcomputed性质,这意味着你不必费心自己与清洁和这些属性的处置。 虽然,你会在下面的例子中看到的,你需要配置哪些是外部组件本身

注意的一些其他的订阅1
您可能注意到,我使用了一些bootstrap类此组件, 这是适合我的,但,当然,您可以使用自己的类,而不是自举类。
我在这里使用的引导程序类是pagination,pagination-sm,activedisabled
随时根据需要更改它们。

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

最后但并非最不重要的,当然不要忘了用with binding这样

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

干杯根据您的特殊视图模型来更改HTML组件的结合,或包裹所有组件

这个问题仍然是“淘汰赛分页”的热门搜索之一,所以淘汰赛延期knockout-paginggit)值得一提。
它通过扩展ko.observableArray提供分页。它是有据可查的,易于使用。
使用示例为here