Book Image

Backbone.js Patterns and Best Practices

By : Swarnendu De
Book Image

Backbone.js Patterns and Best Practices

By: Swarnendu De

Overview of this book

Table of Contents (19 chapters)
Backbone.js Patterns and Best Practices
Credits
About the Author
Acknowledgments
About the Reviewers
www.PacktPub.com
Preface
Precompiling Templates on the Server Side
Index

Re-using code with extensions


Backbone is quite a small library in comparison with other libraries. Any complex application can be structured and developed with Backbone, but the framework itself doesn't come with prebuilt widgets or reusable UI components. In this section, we will talk about some Backbone and JavaScript techniques that will help you build a reusable interface library.

For simple and small applications, code reusability doesn't always seem much of a necessity. But as you proceed to create an application with multiple views, models, and collections, you find that a certain portion of your code gets repeated several times. Creating reusable extensions and plugins in such cases improves the performance of the application by enhancing modularity and reducing the code size. Let's create a simple Backbone view to understand how we can create an extension, shown in the following code snippet:

var User = Backbone.Model.extend({
  defaults: {
    name: 'John Doe'
  }
});

var UserItemView = Backbone.View.extend({
  template: '<span><%= name %></span>',
  render: function () {
    var tpl = _.template(this.template),
      html = tpl(this.model.toJSON());

    this.$el.html(html);
    return this;
  }
});

// Create a view instance passing a new model instance
var userItem = new UserItemView({
  model: new User
});

$(document.body).append(userItem.render().el);

The view named UserItemView is a simple Backbone view where we want to display our model data inside a template and append this view element to the DOM. This is a fundamental functionality of Backbone where the primary requirement is to display a model's data as a view. If we have another similar view with a model, and this has the same functionality, the render() function will also be identical. That said, won't it be beneficial if we move the common code to a base class and extend that class to inherit the functionality? The answer is yes. Let's see how we can do that in the example in the following section.

Creating a base class

We create a BaseView class where common functionality such as the render() method is added. Then all other view classes can extend from this base class, and eventually inherit the rendering functionality. The following is the BaseView class with minimal rendering functionality:

// Parent view which has the render function
var BaseView = Backbone.View.extend({
  render: function () {
    var tpl = _.template(this.template),
      data = (this.model) ? this.model.toJSON() : {},
      html = tpl(data);

    this.$el.html(html);
    return this;
  }
});

Now, UserItemView will look much better. We will extend the BaseView class and will provide only the template as follows:

// A simpler view class
var UserItemView = BaseView.extend({
  template: '<span><%= name %></span>'
});

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

If you wish to add some extra functionality such as calling another function in your view's render() method, you can override the render method of the base class. Check the following example:

var UserItemView = BaseView.extend({
  tagName: 'div',
  template: '<span><%= name %></span>',
  render: function () {
    // Call the parent view's render function
    BaseView.prototype.render.apply(this, arguments);

    // Add your code here
    this.anotherFn();
    return this;
  },

  anotherFn: function () {}
});

There are a number of functionalities that you can move to your base class depending on your requirements. For example, in a non-trivial application, we often need to replace a view with another, destroy the old view by removing it from DOM, and clean up other dependencies. So, we can add a close() method to BaseView (as shown in the following code) that can take care of every view removal mechanism.

var BaseView = Backbone.View.extend({
  render: function () {
    var tpl = _.template(this.template),
      data = (this.model) ? this.model.toJSON() : {},
      html = tpl(data);

    this.$el.html(html);
    return this;
  },

  close: function () {
    // Extra stuff goes here

    // Remove the view
    this.remove();
  }
});
// This is not production-ready code, but it clearly gives you the concept of using custom widgets to reduce boilerplate in your code. It will not always be necessary to extend a Backbone class to create a plugin.

Developing plugins without extending base classes

Sometimes we find that creating a constructor function and adding methods to its prototype can be a better choice than extending the Backbone base classes. For example, in the Pagination plugin in the following code, instead of creating a PaginationCollection class by extending Backbone.Collection, we will prefer to go for a simpler class—a constructor function that accepts two arguments: a collection and the number of the items to be shown in a page.

// Pagination constructor function
var Pagination = function (collection, noOfItemsInPage) {
  if (!collection) {
    throw "No collection is passed";
  }
  this.currentPage = 1;
  this.noOfItemsInPage = noOfItemsInPage || 10;
  this.collection = collection;
}

// Use Underscore's extend method to add properties to your plugin
_.extend(Pagination.prototype, {
  nextPage: function () {},
  prevPage: function () {}
});

var User = Backbone.Model.extend({
  defaults: {
    name: 'John Doe'
  }
});

var Users = Backbone.Collection.extend({
  model: User
});

var paging1 = new Pagination(10, new Users());
var paging2 = new Pagination(20, new Users());

We didn't add the actual functionality, but just showed a skeleton of how the Pagination class may look. The benefit can be observed when you already have a collection and you want to implement pagination without extending a parent collection class. We added the member variables in constructor function so that individual instances of this class can have their own set of variables. On the other hand, the methods are added to the prototype of the class so that they are shared by all instances of the class.

This mechanism can be useful when you need a custom plugin that is not a type of Backbone view, model, or collection.