Meteor pagination

I was intrigued by meteor  and continue to play with it. Remember my previous post Meteor js first impressions

Be ready that even for the most common things you won’t have existed tools. Or you’ll need to spend much more efforts in comparison with you favorite language/framework.

Today I will tell you about one of such examples – pagination in meteor js.

 I think everybody agree that pagination is the most common thing in any web site. And I expect to have existed pagination tool in any web framework. So I started to surf atmosphere to find pagination package. As you can see there are few quite popular and stared packages – alethes:pages (590 app installs) and mrt:pagination (204 app installs) all other packages have less then 27 installs (by January 2015).

So I have to choose which one is the best… (Again as a new developer I need to spend time to choose. That’s why I think that for now meteor js development is quite expensive at least in terms of time).

I started from mrt:pagionation it looked simpler. And actually that’s true it’s simple. And the biggest drawback – it doesn’t work with server at all. (Look at the code it’s really small and easy.)  What does it mean? That you need to subscribe and get ALL the data of you models on client.

    // on server adding publication
    Meteor.publish('allPosts', function() {
        return Posts.find({});   // there's no options limit and skip just we need to get everything
    });

And then the package itself will work with skip and limit options on the client. And YES then it becomes really easy to do.

Therefore such package is extremely inefficient. Although, of course it will work really fast after you get the data on the client, then you don’t need to get data from server for each page. I even thought that it make sense to set threshold for getting all the data and get data per page. For example if you know that you have less then N pages we will get all the data.

Anyway this package did not suit me. And I started to check out alethes:pages.  One of the features that they claim is

  • Incremental subscriptions. Downloads only what is needed, not the entire collection at once. Suitable for large datasets.

So all you have to do is


Pages = new Meteor.Pagination("posts")

Then the package will do all the magic. And your pagination template will have all the data. BUT where should I define this? server or client? The answer is you need both.  I must admit that I was confused with the usage of the package. You know I guess everybody now uses iron-router. If the route has some logic I put it into controller. And now tell me where should I put this code? Also there are so many settings that I don’t really need.

Finally I found example with iron router. (Don’t know why did I miss it at the beginning)


Pages = new Meteor.Pagination(Items, {
    router: "iron-router",
    homeRoute: ["/", "/items/"],
    route: "/items/",
    routerTemplate: "items",
    routerLayout: "layout"
});

If you look inside the Meteor.Pagination you will find that they will create a route for you. But…. why pagination class creates a route. It was breaking the mold for me. Moreover if you have some extra logic in waitOn I didn’t find the way how can I integrate it with Meteor.Pagination.

It was a weekend and I decided why not let’s reinvent the wheel.

the-wheel

I thought that pagination is quite easy to do and it was interesting for me to play with meteor.  Also I didn’t want to keep extra functionality. And make simple easy pagination for me. So I started to write it. I guess it took about a couple of hours when it started to work. At that moment I realized that I have almost the same that mrt:pagination does, i.e. I have only client pagination. I subscribe for all the data. OK let’s continue and improve our class.

And at this moment I realized that everything is not so easy :).

The first challenge is getting total number. For example we need to show first page of the users. And we have 2000 users. So we subscribe for the first page of the users.


waitOn: function() {
   return Meteor.subscribe("users", {limit: 10, skip: 0})
}

// server publications
Meteor.publish("users", function(options) {
    return Users.find({}, options);
})

// this will ask server to return first page of the users
User.find({/*some extra condition if needed*/}, {limit: 10, skip: 0})

// so when subscription will be ready and we do
var userCount = Users.find().count();
// yes we will have 10 users on the client.

What does it means – we also need to get total count of the users from the server.

I was trying to find an easy way to get the data from the server in controller. But I didn’t find the way how can I do that with iron router. In fact all controller methods like waitOn, data, on[Hook] methods work on the client. You cannot subscribe for scalar value. So the only way that I found is calling of meteor method. And here I understood why alethes:pages use this approach with declaring Meteor.Pagination on both client and server. I need to admit that I borrowed ideas from alethes :). I was also thinking about method like serverInit for the iron router controller. That will work on server start up and where Pagination class could add Meteor method to get total number of models. Well anyway for now I added new option for server method name to my pagination class.

Having this done I need to get buttons (I guess it was the easiest part of the job ha-ha) and pass pagination link/buttons to template.  And here I got another BUT. In order to build pagination links I need to make a Meteor call that is asynchronous. So I get the data only in callback. And now tell me how you can show the pagerButtons

</pre>
<pre><template name="pagination">
    <ul class="pagination">
        {{#each pagerButtons}}
            <li class="{{class}}"><a href="{{ path }}">{{label}}</a></li>
        {{/each}}
    </ul>
</template></pre>
<pre>

if this is a function and it doesn’t return anything. But will call callback when we have total count?

At this point I started to review Template helpers docs again and reactive var too. Eventually I can pass the data to template helper as reactive var like this.

  getItems: function() {
    var reactivePagerButtons;
    reactivePagerButtons = new ReactiveVar(null);
    this.getButtons(function(buttons) {
      return reactivePagerButtons.set(buttons);
    });
    Template[this.templateName].helpers({
      pagerButtons: function() {
        return reactivePagerButtons.get();
      }
    });
    return this.collection.find(this.selector);
  }

Finally here’s the full example what I eventually get – Meteor pagination. Perhaps will publish it as a package at some moment.

As a conclusion: it was really interesting to work on this class. Learned a lot new about Reactive Var, iron Router and Blaze Templates.

Code and have fun !

 

4 thoughts on “Meteor pagination

  1. Kamil

    Thanks for this post. Today is mine pagination day. I also stumbled upon alethes:pages but was interested if I can manipulate it in some nicer manner (I also heavily use wait on).

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *