Rendering strategies

Now that we've covered all of View's capabilities, it's time to return to the question of how to render a View. Specifically, let's look at the main options available to you, which are explained in the sections that follow, when you overwrite the render method.

Simple templating

The first, and perhaps the most obvious, approach for rendering is to use a simple, logic-less templating system. The render method provided in the Backbone documentation is a perfect example of this, as it relies on the Underscore library's template method:

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

The template method takes a string, which contains one or more specially designated sections, and then combines this string with an object, filling in the designated sections with that object's values. This is best explained with the following example:

var author ={
    firstName: 'Isaac',
    lastName: 'Asimov',
    genre: 'science-fiction'
};
var templateString = '<%= firstName %> <%= lastName %> was a '+
                     'great <%= genre %> author.';
var template = _.template(templateString);
alert(template(author));
// will alert "Isaac Asimov was a great science-fiction author."

While the template function works with any string and data source, when used as part of a Backbone View, it is typically used with an HTML string and a Backbone.Model data source. Underscore's template function lets us combine the two to easily create a render method.

For instance, if you want to create a <div> tag with an <h1> tag inside containing an author's name and genre and then add an emphasis (in other words, an <em> tag) around the genre, you can create a template string with the desired HTML and placeholders for the first name, last name, and genre. We can then use the _. template to create a template function and then use this template function in a render method with an author Model's attributes.

Of course, as we mentioned in Chapter 3, Accessing Server Data with Models, it's safer if we don't access a Model's attributes directly; so, we'll want to use the Model's toJSON method instead. Putting all of this together, we get the following block of code:

var authorTemplate =  _.template(
    '<h1>' +
        '<%= firstName %> <%= lastName %> was a ' +
        '<em><%= genre %></em> author.' +
    '</h1>'
);
var AuthorView = Backbone.View.extend({
  template: authorTemplate,
  render: function() {
    this.$el.html(this.template(this.model.toJSON()));
    return this;
  }
});
var tolkien = new Backbone.Model({
    firstName: 'J.R.R.', // Tolkien's actual first name was John
    lastName: 'Tolkien',
    genre: 'fantasy'
});
var tolkienView = new AuthorView({model: tolkien});
var tolkientHtml = tolkienView.render().$el.html();
alert(tolkienHtml);
// alerts "<h1>J.R.R. Tolkien was a <em>fantasy</em> author.</h1>"

One major advantage of this approach is that because our HTML is completely separated into a string, we can optionally choose to move it out into a separate file and then bring it in using jQuery or a dependency library, such as Require.js. HTML that is stored separately like this can be easier for a designer to work with, if you have such a person in your team.

Advanced templating

Instead of using Underscore's template method, you can also employ one of the many quality third-party templating libraries available, such as Handlebars, Mustache, Hamljs, or Eco. All these libraries offer the same basic ability to combine a data object with a template string, but they also offer the possibility to include logic inside the template. For instance, here's an example of a Handlebars template string that uses an if statement, which is based on a provided isMale data property, to select the correct gender pronoun inside a template:

var heOrShe = '{{#if isMale }}he{{else}}she{{/if}}';

If we use Handlebars' compile method to turn that into a template, we can then use it just as we will use an Underscore template:

var heOrSheTemplate = Handlebars.compile(heOrShe);
alert(heOrSheTemplate({isMale: false})); // alerts "she"

We'll discuss more about Handlebars in Chapter 11, (Not) Re-inventing the Wheel: Utilizing Third-Party Libraries but the important thing to understand for now is that no matter which templating library you choose, you can easily incorporate it as part of your View's render method. The difficult part is deciding whether or not you want to allow logic inside your templates and if so, how much.

Logic-based

Instead of relying on templating libraries, another option is to use Backbone's View logic, jQuery methods, and/or string concatenation to power your render methods. For instance, you can reimplement the preceding AuthorView without using templates at all:

var AuthorView = Backbone.View.extend({
  render: function() {
    var h1View = new Backbone.View({tagName: 'h1'});
    var emView = new Backbone.View({tagName: 'em'});
    emView.text(this.model.get('genre'));
    h1View.$el.html(this.model.get('firstName') + ' ' +
                    this.model.get('lastName') + ' was a '),
    h1View.$el.append(emView.el, ' author.'),
    this.$el.html(h1View.el);
    return this;
  }
});

As you can see, there are both advantages and disadvantages to a purely logic-based approach. The main advantage, of course, is that we don't have to deal with a template at all. This means that we can see exactly what logic is being used, because nothing is hidden inside the template library's code. Also, there is no limit on this logic; you can do anything that you will normally do in JavaScript code.

However, we also lost a good deal of readability by not using a template, and if we had a designer on our team who wanted to edit the HTML, they would find that code very difficult to work with. In the first version, they will see a familiar HTML structure, but in the second version, they will have to work with the JavaScript code even though they (probably) aren't familiar with programming. Yet another downside is that because we've mixed the logic with the HTML code, there's no way to store the HTML in a separate file, the way we can if it were a template.

The combined approach

All three preceding approaches have advantages and disadvantages, and rather than settling for just one, you can choose to combine some of them instead. There's no reason why, for instance, you can't use a templating system (either Underscore's for simplicity or an external one for power) and then switch to using logic when you want to do something that doesn't fit neatly into a template.

For example, let's say you want to render a <ul> with a class HTML attribute derived from the attributes of a Model—this sounds like something that will be easier with JavaScript logic. However, let's say you also want this <ul> to contain <li> elements with text based on a template and filled in with the attributes of Models in a Collection; that sounds like something we can best handle with a template. Luckily, there is nothing stopping you from combining the two approaches, as shown here:

var ListItemView = Backbone.View({
    tagName: 'li',
    template: _.template('<%= value1 %> <%= value2 %> are both ' +
                         'values, just like <%= value3 %>'),
    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    }
});
var ListView = Backbone.View.extend({
  tagName: 'ul',
  render: function() {
    this.$el.toggleClass('boldList', this.model.get('isBold'));
    this.$el.toggleClass('largeFontList',
                         this.model.get('useLargeFont'));
    this.$el.empty();
    this.collection.each(function(model) {
        var itemView = new ListItemView({model: model});
        this.$el.append(itemView.render().el);
    }, this);
    this.$el.html(this.template(this.model.toJSON()));
    return this;
  }
});

Of course, mixing approaches means that you won't always get the full benefits of both. The most notable issue will be that any HTML that you render via Backbone's Views or other JavaScript logic won't be accessible to a non-programming designer. If you don't have such a role in your team, then this limitation won't bother you, but if you do, you should probably try to keep as much HTML inside templates as possible.

Other render considerations

In addition to deciding whether you can rely on templates and how much you can rely on them in your render methods, there are a few other things that you need to consider when writing them. None of these choices are completely binary, but you should strive for as consistent an approach as possible. This means that the real question isn't about which one you should choose, as it is about when you will choose one over the other. Ultimately, a consistent strategy will make it such that both you and your co-workers don't have to think in order to decide which approach to use when writing new render methods or editing existing ones.

Child views

Very often, in Backbone, you will want to create one View for a container element, such as a <ul> element, and another View for its child elements, such as its <li> element. When creating these child Views, you can either choose to have a child View create the child elements, or you can have the parent View create them and pass a jQuery selector as the child View's el element, as shown here:

var ListView1 = Backbone.View.extend({
    render: function() {
        this.$el.append(new ListItemView().render().el);
    }
});
var ListView2 = Backbone.View.extend({
    render: function() {
        this.$el.append('<li>'),
        new ListItemView({el: this.$('li')}).render();
    }
});

The primary advantage of generating the child element in the View is encapsulation. By taking this approach, you can keep all the logic related to the child element within the child View. This will make the code easier to work with, as you won't have to think about both the child and parent Views at the same time.

On the other hand, when you are using templates, it may be inconvenient to split the template into parts in order to render the overall container element. Also, rendering the child elements in the parent view provides a good separation between DOM-generating Views and purely event handling Views, which can help you to sort your logic better.

Neither approach has to be taken to the extreme. While you can have a single page View with a single template for all your content or you can have every single child View generate its DOM elements, you can also choose to have a mix of approaches in your Views. In fact, unless you want to have a single page template for a top-level render, which generates the entire DOM, you will probably want to take a combined approach, so the real question is how much do you want to rely on each technique? The more you rely on the parent View, the easier it is to see the big picture; but the more you rely on DOM generation in child Views, the easier it will be to encapsulate your logic.

Repeatable versus one-time renders

Another thing that you'll want to consider is whether to design your render methods to be rendered once (usually when the View is first created), or whether you want to be able to call your render method multiple times in response to changes. The render methods that are designed to render only once are the simplest to write and the simplest to work with; when something needs to change with your View's DOM element, you simply call the View's render method again and the DOM gets updated.

However, this approach can also lead to performance issues, particularly if the View involved contains a great number of child Views. To avoid this, you might instead want to write render methods that don't blindly replace their content but instead update them in response to changes. For instance, if you have a <ul> elements where you want some of the child <li> elements to appear or disappear in response to user actions, you might want to have your render method check whether the <li> elements already exist first and then simply apply changes to their display style rather than rebuilding the entire <ul> from scratch every time.

Again, you'll probably want to mix the two approaches based on the type of View you are writing. If performance is a high priority for you, then you'll likely want to think carefully about how you can reuse previously-generated DOM elements in your render methods. On the other hand, if performance is less important, designing your render method for one-time DOM generation will keep its logic a lot simpler. Also, if performance problems crop up, you can always change the render method in question to reuse existing elements.

Return value – this or this.$el

The sample render method from the Backbone documentation returns this when it completes. This approach allows you to easily access the el property of the render by simply calling it on the render method's return value, as follows:

var ViewThatReturnsThis = Backbone.View.extend({render: function() {
        // do the rendering
        return this;
    }
});
var childView = new ViewThatReturnsThis();
parentView.$el.append(childView.render().$el);

However, you can make it even easier to access the View's el by returning that el instead of this:

var ViewThatReturns$el = Backbone.View.extend({render: function() {
        // do the rendering
        return this.$el;
    }
});
var childView = new ViewThatReturns$el();
parentView.$el.append(childView.render());

The downside of returning the $el from a render method is that you can no longer chain other View methods from the return value of the render method:

parentView.$el.append(childView.render().nextMethod());// won't work

So, what this choice really comes down to is how much you plan to post-render your Views and how much you plan to call those post-rendering methods from outside the View itself (if you call them from within the render method, their return values are irrelevant). If you're not sure, the safest bet is to follow the render method in Backbone's documentation and return this, as it offers maximum flexibility (at the cost of a slightly less-concise code).

Tip

Whatever you choose, you'll likely want to keep it consistent across all your Views. If you don't, you will find yourself constantly having to look up a View's render method every time you want to use it.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.38.221