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:

A Simple Example – Backbone.js Tutorial Part 2 – Creating an App View

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

In my previous entry I set up a model, a collection, and a view related to a “profile” – essentially bios for the people who work with me at Fortified Studio. The collection grabbed some JSON, used that data to create corresponding models, then displayed the data in a view. I’m still not sure I’m doing things in the best backbone.js way, but it’s working and I don’t think I’m doing anything outrageously against the rules.

Ultimately though, my page needs to have several views, each of which grab their data from JSON files. So I’d like to have a master view that render all the sub-views. I can render my profiles view now, then add other views as I build them.

I’ve seen app views created before, but none of them have struck me as the definitive way to do things. I’m sure I’ve tried this before, but I am once again going to look to the todos.js example for some guidance. Their AppView is somewhat lengthy, but I’ll start slowly and see what I can glean from it.

Firstly, I’ll follow the todos.js lead and bind the view to an existing DOM element. In my case I will bind it to the <body> tag. Is there anything wrong with that? Should I be binding it to a tag within the body? I’ll also use the app view’s initialize method to call fetch on the collection of profiles. Here then, is the very simple first iteration of the app view:

var AppView = Backbone.View.extend({
   el: "body",
   initialize: function() {
      profiles.fetch();
   }
});

Simple and it works. Next I’d like to trigger the rendering of subviews within the app view’s render function. Then again, I guess I don’t need to do this because I’ve already bound the rendering of the profiles to the ‘reset’ event triggered by the profiles collection.

How about if I instead insert all profiles-related instantiations into the app view’s initialize method. This doesn’t affect the functionality, but I suppose it cleans things up a bit.

var AppView = Backbone.View.extend({
   el: "body",
   initialize: function() {
      var profiles = new ProfileList(); 
      var profilesView = new ProfileView({
         model: profiles
      });

      // When profiles have been successfully grabbed,
      // display them using profile template
      profiles.bind('reset', function () {
         profilesView.render();
      });

      profiles.fetch();
   }
 });

Now I only instantiate the app view and the rest takes care of itself.

var App = new AppView;

This seems ok for now. Tomorrow I’ll see what happens when I try to add a second, more complicated subview. Here’s the code from today: backbone.blog.2.zip

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

A Simple Example – Backbone.js Tutorial Part 1 – Collection based on JSON + View

I still don’t feel like I understand Backbone at all. I decided to scrap what I had previously built – despite the fact that it’s working – because I don’t truly understand why it’s working. The result is this simple example presented as a backbone.js tutorial.

So … starting from nothing. Here we go. It won’t take long before I am completely stuck.

I want to create a simple list of user profiles. The profiles will be brought in via a JSON file and displayed as a list. Seems so simple!

I’ll start by creating the model:

var Profile = Backbone.Model.extend();

I’ve seen it done this way in many examples. Sometimes default values are added, but usually it’s just this simple declaration. According to the backbone.js documentation, it’s also possible to add an initialize function and to add validation. I’m going to keep things as simple as possible for now though.

Next, I know I want a collection of Profiles, so I will create that in its most simple form:

var ProfileList = Backbone.Collection.extend({
   model: Profile
});

That looks to me to be a collection of individual Profiles.

I know I want to generate the models based on the values in an external JSON file. According to backbone.js: “Set the url property (or function) on a collection to reference its location on the server. Models within the collection will use url to construct URLs of their own.” As a result, I have this:

var ProfileList = Backbone.Collection.extend({
   model: Profile,
   url: '/json/profiles.json'
});

Does the collection automatically go out and grab the JSON? I don’t think I should need to explicitly call fetch() because the documentation says: “Note that fetch should not be used to populate collections on page load — all models needed at load time should already be bootstrapped in to place.” And further: “fetch is intended for lazily-loading models for interfaces that are not needed immediately.” So if I create an instance of ProfileList the models should be created automatically, correct? I’ll try it:

var profiles = new ProfileList();
console.log(profiles);

This results in an empty object. Am I not giving it enough time to grab the JSON? If I could trigger the call to console.log on a “success” event or something similar, that would be a good way to know the collection has grabbed the data.

Changing my code to this produces different results:

var profiles = new ProfileList(); 
profiles.fetch();
console.log(profiles);

But I still think I need to trigger console.log with an event. When I look at the results using Firebug, it still shows me an empty object, but now when I click through to see the empty object in more detail I can see that the data is there. It had showed me more emptiness when I clicked through it before adding the explicit call to fetch().

Here is my attempt to bind the call to console to when the collection grabs the data (note that this at least partially derived from this StackOverflow example):

var profiles = new ProfileList(); 
profiles.fetch();
profiles.bind('reset', function () { console.log(profiles); });

And indeed this works. The backbone.js documentation says I shouldn’t call fetch explicitly on page load though. So how do I get the data without calling fetch()?

I found a StackOverflow thread that addresses this exact issue. The answers are not crystal clear to me though. I think the conclusion is that you can either send in a static reference to an initial set of data, then call fetch() as needed to grab more, or you call fetch explicitly like I am doing.

I suppose I could change my JSON file to create a variable holding all my data, then pass it directly to the collection. That avoids a server call, but it doesn’t seem right. For one thing it will be a little cumbersome to switch to a dynamic data source (e.g. a db) down the line. I guess it just seems less flexible in general.

I’ll stick with explicitly calling fetch() for now.

Now I need to display my data. I need to create a view to display the profiles. The view should use an underscore template I have created and the rendering should be triggered by the ‘reset’ event of the profiles collection. I have a div already on the page and I’d like the newly created DOM elements to be inserted into that div.

The ID of the element into which I want the new DOM elements inserted is “profiles,” so the first line of my view is :

el: "#profiles"

My understanding is that by setting the ‘el’ property I am telling backbone there is an existing DOM element I want to use. If instead I were to set values for “tagName”, “className”, “id” or “attributes” properties, backbone would create a new element with those attributes.

I know that to specify the template to be used by the view I do this:

template: _.template($('#profileTemplate').html()),

Where the template with the id ‘profileTemplate’ looks like this:

<script id="profileTemplate" type="text/template">
   <div class="profile">
      <div class="info">
        <div class="name"><%= name %></div>
        <div class="title"><%= title %></div>
        <div class="background"><%= background %></div>
      </div>
  </div>
</script>

I think my view can have a simple render function that looks like this:

render: function(eventName) {
   _.each(this.model.models, function(profile){
      var profileTemplate = this.template(profile.toJSON());
      $(this.el).append(profileTemplate);
   }, this);

   return this;
}

So the complete view looks like this:

var ProfileView = Backbone.View.extend({
   el: "#profiles",
   template: _.template($('#profileTemplate').html()),
   render: function(eventName) {
      _.each(this.model.models, function(profile){
         var profileTemplate = this.template(profile.toJSON());
         $(this.el).append(profileTemplate);
      }, this);
      return this;
   }
});

Indeed, this works as I had hoped.

Here’s the code from today’s complicated look at a simple backbone application:
backbone.blog.1.zip

Tomorrow I’m going to jump to a slightly more complicated example. I’d also like to incorporate the above code into a more general app view that can handle multiple sub views.

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

More with Backbone

In my last entry I had made a small change to an existing backbone.js application. Instead of loading all projects in my company’s portfolio, I adjusted the view to only load a subset of them. I still need, however, to create a “load more” button to allow the user load the remainder of the projects as desired. I left my previous entry wondering how to do this.

I think I know how to add the event that the button will trigger at least. It should look like this:

 events:{
    "click .more" : "renderNextProjectGroup"
 }

And renderNextProjectGroup should figure out how many projects are currently being displayed n, then load n + 1 through n + 1 + projectsPerLoad. How do I determine how projects are currently being displayed?

I know I can do this using basic jQuery:

renderNextProjectGroup: function () {
   var that = this;

   var start = $(".project").length;
   var end = start + (that.projectsPerLoad - 1);
   that.renderProjectGroup(start, end);
},

start grabs the number of projects currently displayed. end simply adds the number of additional projects to show.

This works but it feels wrong. Should a view (or model?) be keeping track of how many projects are currently displayed, or is ok to grab that info as-needed like I’m doing?

I think a bigger offense, however, is using “.project” instead of grabbing this value from the ProjectView, which look like this:

var ProjectView = Backbone.View.extend({
   className: "project coda-slider-wrapper",
   template: $("#projectTemplate").html(),

   render: function () {
      var tmpl = _.template(this.template);
      this.$el.html(tmpl(this.model.toJSON()));
      return this;
   }
});

How do I use backbone/underscore to determine how many elements there are with ProjectView.className?

I tried this but it doesn’t work:

var Portfolio = Backbone.Collection.extend({
   model: Project,
   url: '/json/projects.json',
   projectClass: "project2 coda-slider-wrapper"
});

var ProjectView = Backbone.View.extend({
   className: this.Portfolio.projectClass,
   ....
});

Hm. I’ve decided I’m never going to figure out how this is typically done within backbone until I see it done using backbone. I need to find an existing example of a similar effort.

< … googling “backbone pagination” … >

I found a backbone plugin (or component, if you will) that seems to do exactly what I’m trying to do: http://addyosmani.com/blog/backbone-paginator-new-pagination-components-for-backbone-js/

< … frustrating efforts to use addy osmani’s pagination components … >

I can’t get Addy Osmani’s pagination components to work. Despite the ability to customize the query parameters, I can’t figure out how to get the paginator to work with my data. It calls my data but I don’t know if the data is being successfully set up in a array somewhere or what has happened to it.

Maybe there is something more simple out there. Maybe I can just do this myself after all. All I really need is the load more button and a better way to keep track of the total number of projects. Maybe I can use the pagination component for ideas, but just code it myself.

One thing I haven’t been sure of is whether I should simply hard code the HTML for the load more button or use a template. Osmani’s pagination component uses a template and that seems reasonable, so I will start there. The code for the button will look like this (adapted from Osmani’s):

<script id="projectPagination" type="text/html">
   <% if (page < totalPages) { %>
      <a href="#" class="btn project_next">Show More</a>
   <% } %>
</script>

Obviously I need to put values to page and totalPages. Osmani’s pagination component declares those values in the collection, which makes sense. I can set the totalPages value when the collection finishes fetching the data. Here’s what I end up with:

var Portfolio = Backbone.Collection.extend({
   model: Project,
   url: '/json/projects.json',
   projectClass: "project2 coda-slider-wrapper",
   numProjects: 0,
   page: 0,
   perPage: 5,
   totalPages: 0,

   parse: function(response) {
      this.numProjects = _.size(response);
      this.totalPages = Math.ceil(this.numProjects / this.perPage);

      return response;
   }
 });

This all works as expected. But what about the page variable? Where and when should that be set? Beyond the initial declaration the page variable is nowhere to be found in the collection. Osmani’s collection does extend paginator.requestPager though. It’s all over the place there. Because our needs are so much more simple than what Osmani’s code does though, I think I will just do this myself.

First I’m going to set up the event that’s triggered when the load more button is clicked.

< … big time struggling with how to share a controller between multiple views … >

I can’t figure out how to have my PortfolioView, which displays all the projects, and my LoadMoreView, which is simply the button that will load more projects, both get values from the controller. PortfolioView needs the models from the controller to display each project, and LoadMoreView needs to know if it should continue to be displayed.

Finally I have found “Developing Backbone.js Applications” and the “Gotchyas” section of “A Conceptual Understanding of Backbone.js For The Everyman“. Both describe passing a model and the collection it belongs to to each view. I’m going to give that a try.

 

Backbone attempt #4080

I have neglected the blog for some time. I think it was due to the fact that I began working on a project in WordPress and it just wasn’t presenting me with the type of challenge that I want to focus on in this blog. Or maybe I just got lazy, one of the two.

I don’t mean to say WordPress isn’t challenging. I have worked with WP before but I’m far from an expert. I learned a lot about it and I learned as I built out the project – not always the best way to do things, but I think it came out pretty well. I created a (very simple) plugin, contributed to another, and built a child theme. I’m quite sure there are things I could have done better, but the site feels pretty solid and our client has a straightforward UI to work with to enter new content.

I’m now going to return to looking at backbone.js. It’s an MVC framework that I have looked at using previously, but I never got too far with it.

For one thing, is it just me or is the backbonejs.org home page daunting? The single-huge-scrolling-page thing is off-putting to me. I know the left hand navigation should alleviate my anxiety but it just doesn’t. I guess I’ll get used to it over time.

Fortunately I have a real-world project I get to dig into instead of needing to build something from scratch. The Fortified Studio Web site needs a small alteration, so I’m going to see what I can do about it.

Instead of listing all x Fortified projects, we want to list just y (where y = 5 for starters). I should be able to do this, right?

For starters, what is the present value of x? I have sadly never really used underscore.js, so I need to try it out. I see right away there is a method called size(). Let’s try it out.

Indeed it works like a charm. By running the following statement in the View’s render I find out Fortified is currently displaying 14 projects in its portfolio. Not too shabby.

console.log("# projects = " + _.size(this.collection.models));

Seems like it would be easy enough to limit the display to any number of projects. And providing a link to show the rest would also be easy to do (I think). But what about loading a specific number of additional projects? For example, what if we initially display five projects, then provide a load more button that will load up to five more and continue to display the load more button if more projects are available? We’d have to keep track of how many projects are currently displayed, but I can’t think of any other complications.

My View (“PortfolioView”) currently has a render method that triggers the rendering of all portfolio items, and a renderProject method that handles the rendering of each project. I’m going to add a renderProjectGroup method that will load, well, a group of projects. This will  replace render’s job of rendering all projects at once. Here’s the signature of the method I will create:

renderProjectGroup : function(start, begin) {}

My unfamiliarity with underscore.js flares up again. Is there a way to extract a subset from a collection like this – by providing a start and end index? None of the underscore methods are jumping out at me, so I’ll turn to Google.

< … googling but finding nothing … >

I thought for sure that would be a common issue. Stupid internet. I took a closer look at the underscore methods and I think I may have something.

The filter() method returns each element in a collection and returns a new collection comprised of the elements that pass a given test. The documentation doesn’t mention it, but if the second parameter passed to the filter is the index of the element being passed. We can then check for the value of the index in the filter, and cut the collection off after a given number. Like this:

var start = 0;
var end = 3;
var sublist = _.filter([1, 2, 3, 4, 5, 6], function(num, index){
     return (index >= start) && (index <= end);
});

The result is:

[1, 2, 3, 4]

Sweet. That seems to work. Here’s how it looks within the Fortified site:

renderProjectGroup : function(start, end) {
    var that = this;
    var subset = _.filter(this.collection.models, function(num, index){
        return (index >= start) && (index <= end);
    });

    _.each(subset, function (item) {
        that.renderProject(item);
    }, this); 
},

Still need to create a load more button that will call that renderProjectGroup. I’m not sure how to approach this in the best backbone way. Do I include the button in the HTML then add the functionality to a view or controller? Or do I use a view to generate the button then bind its events to a method in a view or controller? I’m going to stop here and continue tomorrow.