A Simple Example – Backbone.js Tutorial Part 3 – Creating a Paginated View

Thanks for checking out part 3 of my Backbone.js tutorial.

I have previously covered my efforts to use backbone.js to create a paginated view. They were somewhat successful, but in light of my efforts to revamp my work I am going to tackle this task again. I think I will simply defer to those blog entries in some areas, but it’s a useful and necessary exercise to do this again – this time in the context of the overarching app view I have created.

First off, I should clarify something: I’m not trying to build pagination that allows the user to skip between pages, go to previous and next pages, etc. I’m trying to load details about projects Fortified Studio has worked on. I want to load an initial set of projects, then allow the user to click a “load more” button to view the next set (or page, if you will).

I think I can create the most basic structure by merely cloning what I have done for Fortified’s employee profiles.

< … Cloning, then doing a replacing all mentions of “profile” with “project” … >

Indeed that works well, although it loads all the projects. Hopefully we’ll fix that next.

The code – including the underscore template for each project – is getting a little lengthy at this point, so I’m not going to copy and paste it here. If you’ve looked through the previous entry where I created the model, collection, and view for profiles, you’ve seen what the projects model, collection, and view look like – they’re practically identical. You can, however, download the source code if you want to see what everything looks like at this point: backbone.blog.3a.zip. Note that I am not including images in the my source code zips, nor am I including any styling. I’m just demonstrating the loading of data at this point. Perhaps I will walk though adding styling in a future entry.

Now I’m going to try to limit the initial rendering to only show five projects. Basically I just need to split this _.each loop so it only renders a subset of projects:

_.each(this.model.models, function(project){
   var projectTemplate = this.template(project.toJSON());
      $(this.el).append(projectTemplate);
}, this);

As I mentioned, I’ve already done this, so I’m going to cheat a bit. First I’m going to add some properties to the view so I know how to split up the projects:

var ProjectView = Backbone.View.extend({
   el: "#projects",
   template: _.template($('#projectTemplate').html()),
   page: 0,
   perPage: 5,
   totalPages: 0,
   ...
});

Then, instead of rendering all the projects as I had previously done, I will render the initial page of five projects:

render: function (eventName) {
   this.totalPages = Math.ceil(_.size(this.model.models) / this.perPage);
   return this.renderProjectGroup(0, this.perPage - 1);
},
renderProjectGroup : function(start, end) {
   var that = this;
   var subset = _.filter(this.model.models, function(num, index){
      return (index >= start) && (index <= end);
   });

   _.each(subset, function (project) {
      var projectTemplate = this.template(project.toJSON());
      $(this.el).append(projectTemplate);
   }, this); 

   return this;
}

renderProjectGroup is a new method that filters out a range of projects then uses an _.each loop to render each of them. renderProjectGroup is used to render the initial group of five projects, but more importantly can be called on again to render additional subsets of project models.

To trigger the loading of additional projects, we’ll need a button. In our case, this button is a little tricky because it needs to be determined if it should be hidden or visible. If it’s going to be visible, it should be placed at the end of the list of projects.

I tried to imagine a way of doing this that didn’t involve simply injecting some jQuery into the ProjectView that will hide or show the load more button. Here’s what I came up with. You can tell me if it makes sense.

var LoadMoreProjectsView = Backbone.View.extend({
   el: $("#load-more-projects")
});

LoadMoreProjectsView is a view that simply creates a reference to an existing DOM element. It may seem pointless to do this, but I like that I can now refer to the view’s el property instead of the DOM element directly. It also allows the option of expanding the button into something more complex later on. UPDATE (7/25/12): This turned out to be a better idea than I had imaged. See below for details.

I need to create an instance of LoadMoreProjectView, but I’m not sure whether to do it in ProjectView or AppView. AppView makes sense because, so far anyway, I’m creating each view there. ProjectView makes sense because I’ll need to hide and show the load more button based on the properties of the ProjectView. It will be a lot easier to grab them from within the ProjectView. Here’s how I have created the LoadMoreProjectView instance:

First I create a property within ProjectView that will refer to the LoadMoreProjectView instance, then I set the property in ProjectView’s initialize method:

var ProjectView = Backbone.View.extend({
   ... 
   loadMore: null,

   initialize: function() {
      this.loadMore = new LoadMoreView();
   },
   ...
});

Each time the load more button is clicked, we’ll want to load the next group of projects, so I’ll bind a call to load the next group to the click:

events: {
   "click .load-more-projects": "renderNextProjectGroup"
},

I’d really like that to be something like “click this.loadMore” instead of referring to the CSS class of the load more button, but I can’t figure out how to do it.

UPDATE (7/25/12): I figured out how to avoid referring to the CSS class of the load more button. I removed the events property above and augmented ProjectView’s initialize function to look like this:

initialize: function() {
   var that = this;

   // Create a reference to the load more button
   this.loadMore = new LoadMoreProjectsView();

   // Bind the load more button
   (this.loadMore.$el).on("click", function() {
      that.renderNextProjectGroup();
   })
},

Turns out it was a better idea than I had thought to use LoadMoreProjectsView, despite its simplicity. Now back to the pre-update info ….

Now I need to define the renderNextProjectGroup function. It should determine what “page” we’re on and try to render the next page. I can use the properties I defined for ProjectView to figure out what page we’re on and the start and end positions of the next group of projects. Here’s what it looks like:

renderNextProjectGroup: function () {
   if(this.page < this.totalPages) {
      this.page++;
      var start = this.page * this.perPage;
      var end = start + (this.perPage - 1);
      this.renderProjectGroup(start, end);
   }
}

Finally, each time renderProjectGroup() is called, it should determine whether or not the load more button should still be displayed. I’ll call the function to determine the visibility of the button renderLoadMoreButton:

renderLoadMoreButton: function() {
   // If we're on the last page, hide the load more button
   if(this.page >= (this.totalPages - 1)) {
      this.loadMore.$el.hide();
   }
   // Otherwise we need to push the load more to the bottom
   // of the portfolio view
   else {
      this.$el.append( this.loadMore.$el.detach().show() );
   }
}

That’s all looking good. I definitely missed some detail above, but you can check out the source code here: backbone.blog.3b.zip. I added just a tiny bit of CSS to make things a little more wieldy, but this is still all about data for the time being.

Here are some handy links to parts 1 and 2 of this tutorial:

2 thoughts on “A Simple Example – Backbone.js Tutorial Part 3 – Creating a Paginated View

  1. Pingback: Simple Backbone.js Example Part 1 - Collection based on JSON + View - Blog de CodeBlog de Code

  2. Pingback: A Simple Example - Backbone.js Tutorial Part 1 - Collection based on JSON + View | Blog de CodeBlog de Code

Leave a Reply