Pagination

12

Percentage Translated

In this chapter, you will:

  • Learn more about Meteor's subscriptions, and how we can use them to control data.
  • Implement infinite-style pagination.
  • Use the `iron-router-progress` package to implement a nifty iOS-style progress bar.
  • Create a special subscription to deal with direct links to posts page.
  • ////

    ////

    ////

    ////

    Adding More Posts

    ////

    // Fixture data 
    if (Posts.find().count() === 0) {
    
      //...
    
      Posts.insert({
        title: 'The Meteor Book',
        userId: tom._id,
        author: tom.profile.name,
        url: 'http://themeteorbook.com',
        submitted: now - 12 * 3600 * 1000,
        commentsCount: 0
      });
    
      for (var i = 0; i < 10; i++) {
        Posts.insert({
          title: 'Test post #' + i,
          author: sacha.profile.name,
          userId: sacha._id,
          url: 'http://google.com/?q=test-' + i,
          submitted: now - i * 3600 * 1000,
          commentsCount: 0
        });
      }
    }
    
    server/fixtures.js

    ////

    Displaying dummy data.
    Displaying dummy data.

    Commit 12-1

    Added enough posts that pagination is necessary.

    Infinite Pagination

    ////

    ////

    ////

    ////

    ////

    ////

    Router.configure({
      layoutTemplate: 'layout',
      loadingTemplate: 'loading',
      waitOn: function() { 
        return [Meteor.subscribe('notifications')]
      }
    });
    
    lib/router.js

    ////

    Router.map(function() {
      //...
    
      this.route('postsList', {
        path: '/:postsLimit?'
      });
    });
    
    lib/router.js

    ////

    ////

    ////

    Router.map(function() {
      //..
    
      this.route('postsList', {
        path: '/:postsLimit?',
        waitOn: function() {
          var postsLimit = parseInt(this.params.postsLimit) || 5; 
          return Meteor.subscribe('posts', {sort: {submitted: -1}, limit: postsLimit});
        }
      });
    });
    
    lib/router.js

    ////

    Meteor.publish('posts', function(options) {
      return Posts.find({}, options);
    });
    
    Meteor.publish('comments', function(postId) {
      return Comments.find({postId: postId});
    });
    
    Meteor.publish('notifications', function() {
      return Notifications.find({userId: this.userId});
    });
    
    server/publications.js

    Passing Parameters

    ////

    ////

    ////

    ////

    Meteor.publish('posts', function(sort, limit) {
      return Posts.find({}, {sort: sort, limit: limit});
    });
    

    ////

    ////

    Router.map(function() {
      this.route('postsList', {
        path: '/:postsLimit?',
        waitOn: function() {
          var limit = parseInt(this.params.postsLimit) || 5; 
          return Meteor.subscribe('posts', {sort: {submitted: -1}, limit: limit});
        },
        data: function() {
          var limit = parseInt(this.params.postsLimit) || 5; 
          return {
            posts: Posts.find({}, {sort: {submitted: -1}, limit: limit})
          };
        }
      });
    
      //..
    });
    
    lib/router.js

    ////

    ////

    Router.configure({
      layoutTemplate: 'layout',
      loadingTemplate: 'loading',
      waitOn: function() { 
        return [Meteor.subscribe('notifications')]
      }
    });
    
    Router.map(function() {
      //...
    
      this.route('postsList', {
        path: '/:postsLimit?',
        waitOn: function() {
          var limit = parseInt(this.params.postsLimit) || 5; 
          return Meteor.subscribe('posts', {sort: {submitted: -1}, limit: limit});
        },
        data: function() {
          var limit = parseInt(this.params.postsLimit) || 5; 
          return {
            posts: Posts.find({}, {sort: {submitted: -1}, limit: limit})
          };
        }
      });
    });
    
    lib/router.js

    Commit 12-2

    Augmented the postsList route to take a limit.

    ////

    Controlling the number of posts on the homepage.
    Controlling the number of posts on the homepage.

    Why Not Pages?

    ////

    ////

    ////

    ////

    ////

    ////

    ////

    ////

    ////

    Creating a Route Controller

    ////

    ////

    PostsListController = RouteController.extend({
      template: 'postsList',
      increment: 5, 
      limit: function() { 
        return parseInt(this.params.postsLimit) || this.increment; 
      },
      findOptions: function() {
        return {sort: {submitted: -1}, limit: this.limit()};
      },
      waitOn: function() {
        return Meteor.subscribe('posts', this.findOptions());
      },
      data: function() {
        return {posts: Posts.find({}, this.findOptions())};
      }
    });
    
    Router.map(function() {
      //...
    
      this.route('postsList', {
        path: '/:postsLimit?',
        controller: PostsListController
      });
    });
    
    lib/router.js

    ////

    ////

    ////

    ////

    Commit 12-3

    Refactored postsLists route into a RouteController.

    Adding A Load More Link

    ////

    ////

    ////

    PostsListController = RouteController.extend({
      template: 'postsList',
      increment: 5, 
      limit: function() { 
        return parseInt(this.params.postsLimit) || this.increment; 
      },
      findOptions: function() {
        return {sort: {submitted: -1}, limit: this.limit()};
      },
      waitOn: function() {
        return Meteor.subscribe('posts', this.findOptions());
      },
      posts: function() {
        return Posts.find({}, this.findOptions());
      },
      data: function() {
        var hasMore = this.posts().fetch().length === this.limit();
        var nextPath = this.route.path({postsLimit: this.limit() + this.increment});
        return {
          posts: this.posts(),
          nextPath: hasMore ? nextPath : null
        };
      }
    });
    
    lib/router.js

    ////

    ////

    ////

    ////

    ////

    ////

    ////

    ////

    ////

    ////

    <template name="postsList">
      <div class="posts">
        {{#each posts}}
          {{> postItem}}
        {{/each}}
    
        {{#if nextPath}}
          <a class="load-more" href="{{nextPath}}">Load more</a>
        {{/if}}
      </div>
    </template>
    
    client/views/posts/posts_list.html

    ////

    The “load more” button.
    The “load more” button.

    Commit 12-4

    Added nextPath() to the controller and use it to step thr…

    Count vs Length

    ////

    A Better Progress Bar

    ////

    ////

    ////

    mrt add iron-router-progress
    
    bash console

    ////

    ////

    Router.map(function() {
    
      //...
    
      this.route('postSubmit', {
        path: '/submit',
        disableProgress: true
      });
    });
    
    lib/router.js

    Commit 12-5

    Use the iron-router-progress package to make pagination n…

    Accessing Any Post

    ////

    An empty template.
    An empty template.

    ////

    ////

    ////

    Meteor.publish('posts', function(options) {
      return Posts.find({}, options);
    });
    
    Meteor.publish('singlePost', function(id) {
      return id && Posts.find(id);
    });
    
    server/publications.js

    ////

    Router.map(function() {
    
      //...
    
      this.route('postPage', {
        path: '/posts/:_id',
        waitOn: function() {
          return [
            Meteor.subscribe('singlePost', this.params._id),
            Meteor.subscribe('comments', this.params._id)
          ];
        },
        data: function() { return Posts.findOne(this.params._id); }
      });
    
      this.route('postEdit', {
        path: '/posts/:_id/edit',
        waitOn: function() { 
          return Meteor.subscribe('singlePost', this.params._id);
        },
        data: function() { return Posts.findOne(this.params._id); }    
      });
    
      /...
    
    });
    
    lib/router.js

    Commit 12-6

    Use a single post subscription to ensure that we can alwa…

    ////