How to handle pagination with Knockout

javascript, knockout.js, pagination, typescript

Solution

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

- 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.

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).

Problem

I have a div that is setup to bind to a `observeableArray` ,but I only want to show at most 50 items from that `observeableArray` at 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 `computedObservable` and 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? Here is my code (the JS is in 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(); } } } ```

Original source