Chapter 4

Using JavaScript Templates

Templates are the best way to generate long strings of markup in JavaScript. Instead of creating markup piece by piece by appending individual elements to the DOM, you build a template that generates an entire chunk of markup at once. Templates eliminate the need to mix markup into your JavaScript, and make generating DOM content much more intuitive, because they provide a dedicated place for markup, which will compile according to the variables you pass into it. That means your JavaScript can still drive the content of the template, without having to mix with it.

In this chapter, you learn why you should be using templates and about some different libraries you can use for templating. Then you discover the basics of using Underscore templates: how to mix markup strings with variables, and how to include basic bits of JavaScript such as loops. You also find some best practices on how and where to include your templates. Finally, I'll tie in what you learned in Chapter 3 and teach you how to incorporate templates into Backbone using them to render views.

Introduction to Templates

JavaScript templates will revolutionize the way you inject markup into the DOM. They make it easy to generate long strings of markup that are driven by JavaScript variables, yet remain separate from the domain logic of your application.

Why Use Templates?

When it comes to code organization, JavaScript templates are one of the most important techniques you can use. Templates allow you to separate messy markup from your JavaScript. Nothing looks quite as ugly as JavaScript that is riddled with strings of markup—it's the epitome of spaghetti code.

Separation of Concerns

When you insert markup inline in your JavaScript, you're coupling the JavaScript very tightly to the DOM. Then, if you ever need to make a markup change for styling or content reasons, you have to modify your script. It's much better to keep the markup in a separate location—a template that you can modify independently from the rest of your JavaScript. That way you decouple the app's presentation layer from its domain logic.

Performance

Templates also tend to outperform other alternatives because the markup is compiled independently as a string, rather than inserted bit by bit into the DOM. That means you have to manipulate the DOM only once to render its initial state: at the point you finally insert the compiled template.

Altering the DOM remains one of the biggest performance sinkholes in front-end development. Every time you make changes to the visible page, the browser has to re-render it, which can trigger reflows. Reflows occur whenever the browser has to adjust the other content on the page to accommodate a geometry change in the DOM. For instance, if you increase the width of a floated element, it can push other floated elements down to the next line, and therefore push all the following content down as well.

Often, triggering reflow is unavoidable—after all, you've got to modify the page sometimes. But it's best to batch these changes so as to trigger a single reflow, rather than repeatedly appending new elements to the DOM. And that's exactly how most template engines handle it.

To get an idea of how reflow works, take a look at this visualization in a Gecko-based browser: http://youtu.be/ZTnIxIA5KGw.

Understanding the Different Template Libraries

Template libraries fall into two main categories: those with embedded JavaScript and those that are logic-less. The former group allows you to use JavaScript logic within the template, whereas the latter allows you only to pass in variables and use a few predefined functions.

The argument for logic-less templates is that they adhere to the general purpose of templates: separation of concerns. Advocates of this approach want their templates as “dumb” as possible, with all of the logic in the actual JavaScript. However in practice, logic-less template frameworks can actually reduce the separation of concerns because working with them often requires you to prepare the data in a way that is tightly coupled to the presentational layer. For instance, if you need JavaScript to reformat date strings, would you rather include that in your domain logic or in the template itself? Ultimately, the choice depends on your development style.

Underscore.js

Underscore is a utility belt library that provides a range of functions that solve common JavaScript tasks. It also has a basic JavaScript template engine that helps separate rendered markup from your core JavaScript files.

Although Underscore's templates aren't as fully featured as those in other template libraries, you're unlikely to need a richer functionality. And because Underscore is a prerequisite of Backbone, it's a very attractive option for any project that uses Backbone.

I use Backbone on just about every project that requires templates, so Underscore is my template library of choice. It doesn't add any extra weight to the app and allows you to embed JavaScript for any functionality it lacks.

To learn more about Underscore, visit http://underscorejs.org/.

The examples in this chapter all use Underscore, but the concepts can be extended to any library you prefer.

Handlebars.js

Handlebars.js is a very popular template solution that's basically an extension of Mustache.js —another “logic-less” template library.

Handlebars provides a few conveniences you won't find in Underscore:

• You can set up a context for variables to make it easier to step through large objects.

• There are a couple built-in loops you can use instead of JavaScript loops.

• You can use a special comment syntax in your template.

• You can define simple helper functions, for instance to combine two variables.

A lot of the features in Handlebars are pretty useful, but other than the comments, they are all things you can do in Underscore by mixing in a little JavaScript.

The question is more one of approach:

• Since templates are meant to separate markup from JavaScript, do you really want to be mixing a lot of JavaScript into your templates?

• On the other hand, you'd still be using Handlebars to do scripting tasks. So why would you want to learn new methods when you're already comfortable writing JavaScript? And do you really need that extra bloat?

To learn more about Handlebars, visit http://handlebarsjs.com.

Handlebars.js can precompile templates, which is often better for performance.

Transparency

Transparency is an interesting template solution that works a bit differently from the others. Transparency ties the data in your app in the form of JSON objects to actual DOM elements. Then rather than compiling a long markup string and inserting it, Transparency uses the cached DOM references to replace miniature strings one by one.

It's lauded by some for its substantial benefits in performance when compared to all the other options. However, the way Transparency binds data to the DOM defeats the purpose of using templates. Rather than decoupling your markup and JavaScript, it creates the tightest coupling possible between your app's data and presentation layer. That makes it difficult to implement and an even larger pain to maintain.

You can find out more about Transparency at http://leonidas.github.com/transparency.

Micro Templating

A while ago, John Resig posted a micro template solution on his blog that is extremely lightweight, weighing in at around 1K. Micro templating is by far the smallest template option, and it's also the most minimalist. But if you don't need anything extra, it can be a very attractive option. To learn more about micro templating, visit http://ejohn.org/blog/javascript-micro-templating.

The template engine in Underscore is actually based on the micro templating solution proposed by Resig.

Making the Right Choice

With all the templating solutions out there, it can be hard to choose the right one. Here are four things you need to consider:

Functionality: Does the solution provide everything you need? On the other hand, does it provide too much, and thereby bloat your codebase?

Performance: How fast do the templates compile? Can you precompile them? There's a pretty handy JSPerf comparing different options here: http://jsperf.com/dom-vs-innerhtml-based-templating/365.

Flexibility: How easy is it to use? Transparency tops out all the competitors in performance, but it strictly binds the template to the DOM, thereby defeating the purpose of templates.

Maturity: As tempting as it may be, using a templating engine that is still in its infancy is not often the smartest choice. For business critical applications, you may make a smarter choice by selecting a library that is tried and tested.

If you're still having trouble deciding which template library is right for you, head over to http://garann.github.com/template-chooser where you can get help in narrowing down your choices by the features you need.

Using Underscore Templates

This chapter uses Underscore templates, because they're easy to use and you're already including Underscore as a prerequisite for Backbone.

Underscore Template Basics

Using Underscore templates is actually pretty easy. Simply define the template, pass in your variables, and insert it into the DOM.

Using Templates

The first step is defining the template:

var myTemplate = _.template('Welcome, <%= name %>'),

This example uses Underscore's template method to define a new template. The text in the template works as follows:

• There's some static text that will be output as is (“Welcome, “).

• There's also a variable “name”, enclosed in <%= ... %>. The value of the variable is passed in when you compile the template.

In Underscore, code is interpolated using <% ... %>. Here the added equals sign (<%=) indicates that the template should output this code when it compiles.

The next step is compiling the template. To do this, you call the function you defined for the template and pass in an object whose properties map to the variable names you defined in the template string.

var compiled = myTemplate({name: 'Jon'});

When the template compiles, it passes in the variable you defined here. Now use console.log(compiled) to see the completed string:

Welcome, Jon

And that's all there is to it. Although this chapter goes through a lot of additional actions you can perform with templates, fundamentally none are much more complicated than passing in basic variables and rendering that text.

Interspersing Markup

You can also combine markup in your templates. After all, building simple strings isn't really the best use of templates. You can do that in your JavaScript. The main reason to use templates is to avoid putting markup in your JS. For instance, you can create a template for a small section of web content:

var myTemplate = _.template('<article>

<hgroup>

  <h1><%= title %></h1>

  <h2><%= subtitle %></h2>

</hgroup>

<p><%= description %></p>

</article>'),

Here a variety of markup elements are mixed with variables. Each line ends with a backslash, which allows you to build a multiline string in your JavaScript.

Working with backslashes can be pretty annoying, so later in this chapter in the Reviewing Template Best Practices section, I'll show you a better way.

Now you can compile this template with some variables:

var compiled = myTemplate({

  title: 'JavaScript Templates',

  subtitle: 'Are pretty awesome',

  description: 'They take the markup out of your JavaScript'

});

You have the template compiled, but you still need to get it on the page. You can inject this content into the DOM in several ways:

• Select an element by ID and insert it.

• Replace an element.

• Append it to the body.

To keep things simple, here's an example that appends it to the body using jQuery:

$('body').append(compiled);

Figure 4-1 shows this content is rendering on the page.

9781118524404-fg0401.tif

Figure 4-1: The page has been rendered using templates.

Just to recap, here's the code all together:

// build the template

var myTemplate = _.template('<article>

<hgroup>

  <h1><%= title %></h1>

  <h2><%= subtitle %></h2>

</hgroup>

<p><%= description %></p>

</article>'),

// compile the template with variables

var compiled = myTemplate({

  title: 'JavaScript Templates',

  subtitle: 'Are pretty awesome',

  description: 'They take the markup out of your JavaScript'

});

// append it to the DOM

$('body').append(compiled);

Using Different Interpolation Strings

By default, Underscore uses ERB-style delimiters—variables are set off in <% ... %>. But you can also set up your own delimiters. For instance, to use Handlebars.js style {{ ... }} interpolation, define it in _.templateSettings:

_.templateSettings = {

  interpolate : /{{(.+?)}}/g

};

As you can see here, you set the interpolate setting with a regex. Now you can use the new delimiters in your templates:

// build the template with the new delimeters

var myTemplate = _.template('Welcome, {{ name }}'),

// compile the template with variables

var compiled = myTemplate({name: 'Jon'});

Here, the template you defined earlier is being compiled with the new interpolation pattern.

Setting up a different interpolation setting isn't only about style. It is sometimes necessary, for example, when the default <% clashes with other tags reserved for other languages.

Reviewing Template Best Practices

The previous examples used backslashes to build multiline strings in JavaScript. Although that works, it can get pretty annoying. Additionally, I've been talking about how important it is to keep the markup out of your JavaScript. Sure the templates keep it all in one place, but so far it's still in the JS, which is a really bad practice. Fortunately, there's a much better way to handle templates.

Separating Your Templates

The best way to include JavaScript templates is to completely isolate them from the rest of your JavaScript. To do so, include the template in a separate <script> tag on the page, for example:

<script type="text/template">

<article>

  <hgroup>

    <h1><%= title %></h1>

    <h2><%= subtitle %></h2>

  </hgroup>

  <p><%= description %></p>

</article>

</script>

Notice here that you don't need the backslashes at the end of each line—that's because this isn't a string; it's a separate script.

Also, pay close attention to the type attribute of the script tag. Normally, a JavaScript script tag uses type=”text/javascript” (or no type at all for HTML5). However, here type=”text/template” is used to indicate that this is a template, not JavaScript. That's absolutely essential; otherwise, the browser will try to evaluate and run this as JavaScript (and undoubtedly throw errors).

Next you need to pull this into Underscore. To do so, first attach an ID to the script tag:

<script type="text/template" id="my-template">

...

</script>

Now you can pull in the text of this script using jQuery and use that to define the template:

var myTemplate = _.template( $('#my-template').text() );

And you're done—you can now use the template as normal, passing in variables to compile it.

It's worth noting that documents not using the HTML5 doctype don't validate as correct markup when you use this approach. Additionally, if you're worried about very old browsers and use the XHTML doctype, make sure to enclose your template in //<![CDATA[ ... //]]>. However, you'll also need to account for this when you pull in the template text.

Using External Templates

Alternatively, you can use external templates. The approach is similar: First use a <script> tag; however, this time add an external src:

<script type="text/template" src="my-template.html"

id="my-template"></script>

Now it gets a little more content, because you have to request the template content using Ajax:

$.ajax({

  // get the template url from the script tag

  url: $('#my-template').attr('src'),

  

  // what to do once it loads

  success: function(data) {

    // after it loads, define your template

    var myTemplate = _.template(data);

        

    // compile it

    var compiled = myTemplate({

      title: 'JavaScript Templates',

      subtitle: 'Are pretty awesome',

      description: 'They take the markup out of your JavaScript'

    });

    

    // append it to the DOM

    $('body').append(compiled);

  },

  

  // in case something goes wrong

  error: function() {

    console.log('Problem loading template'),

  }

});

As you can see, you're first using the src attribute from the script tag to define the URL for the Ajax request. Then you compile the template in the success callback, after Ajax returns the template text.

Considering that you'll probably be doing this for a number of templates, you should define a function to handle it for you:

// load an external template

var loadTemplate = function(src, callback) {

  $.ajax({

    url: src,

    

    // what to do once it loads

    success: function(data) {

      // after it loads, define your template

      var template = _.template(data);

      

      // call the callback, passing in the template

      callback(template);

    },

    

    // in case something goes wrong

    error: function() {

      console.log('Problem loading template: ' + src);

    }

  });

};

This function requests the template text using Ajax, creates the template function, and then passes that to a callback so you can compile it or do whatever else you need to do:

// example usage

loadTemplate( $('#my-template').attr('src'), function(template) {

  // compile it

  var compiled = template({

    title: 'JavaScript Templates',

    subtitle: 'Are pretty awesome',

    description: 'They take the markup out of your JavaScript'

  });

  

  // append it to the DOM

  $('body').append(compiled);

});

Here you call your template loading function, passing in two arguments:

• The template URL that you pull from the script tag

• A callback to handle the returned template

If you're not using the HTML5 doctype, leveraging external templates will avoid the HTML validation issues.

External Versus Inline

Although external templates have their merits, it's a good idea to keep them on the page itself for a couple reasons:

• Including an external file means an extra HTTP request. If you often use multiple templates on a page, the performance implications can be substantial.

• Markup is already included on the page, and the templates are essentially more markup. In most cases, it makes sense to keep the template in the HTML, because that's where the rest of the markup lives.

• It's just a lot easier to manage; as you can see, the Ajax script for the external template means having to navigate different callbacks for each template.

Using JavaScript in Templates

In addition to text and variables, you can also use JavaScript within your templates.

Basic If-Then Conditionals

One useful technique is to include basic conditionals statements. For instance, you can determine whether a check box is checked in your template:

<input type="checkbox" <%= checked ? 'checked' : '' %> />

Now you can pass in a Boolean for the checked variable; if it's true, the check box is checked when the template compiles. Additionally, you can check to see whether a variable exists at all. For instance, your content might require a title, but not always a subtitle. You can handle that with a simple if statement:

<hgroup>

  <h1><%= title %></h1>

  <% if ( typeof subtitle !== 'undefined' ) { %>

  <h2><%= subtitle %></h2>

  <% } %>

</hgroup>

Here the <h1> prints no matter what, but the <h2> prints only if subtitle has been defined.

Notice how the <% %> delimiter around the if statement doesn't include the equals sign. That's because you want to evaluate some JavaScript and not print it. The following <h2> on the other hand is not contained in a delimiter so it will print (and so will the variable it contains because that uses <%= %>).

It's important to check whether variables are undefined in Underscore templates. Unlike jQuery, Underscore throws errors when variables are undefined.

Loops

You can also use any of the loops available to you in JavaScript. Loops can be very useful in templates, for instance when you want to populate a list:

<h2><%= listTitle %></h2>

<ul>

  <% for ( var i = 0, len = listItems.length; i < len; i++ ) { %>

  <li><%= listItems[i] %></li>

  <% } %>

</ul>

Here a for loop is used to iterate over the items in the listItems array. Next, pass in this array when you compile the template:

var compiled = template({

  listTitle: 'Reasons I like templates',

  

  listItems: [

    'Keeping JavaScript clean',

    'Separating concerns',

    'Faster DOM insertion'

  ]

});

As you can see in Figure 4-2, the items in the list render properly.

9781118524404-fg0402.tif

Figure 4-2 This list has been populated using a for loop in the template.

And it doesn't stop with for loops; you can use while, switch, or any other JavaScript you want.

Each Loop

In addition to the standard JavaScript loops you already know, you can use an Underscore-specific loop: _.each().

You already know how to use a for loop to iterate across an array of list items in your template. Well, you can accomplish the same thing a little more easily using Underscore's each() function:

<ul>

  <% _.each( listItems, function(item) { %>

  <li><%= item %></li>

  <% }); %>

</ul>

This code accomplishes exactly the same thing as the for loop demonstrated earlier. However, Underscore's each loop works a little differently, because it's a function. In this example, you passed in two arguments:

• An array or object to iterate through

• A callback function that defines a variable for each new item

Pay attention to the closing parentheses after the each loop finishes. It's there because the <li> is output in the callback function, not in a proper for loop.

Realistically, the each loop doesn't make a big difference when you're handling something like this because you can accomplish the same thing with a simple for loop. However, it really starts to shine when you want to iterate over all the items in an object (which is harder to handle natively). For instance, you can print all the key-value pairs in an object:

<ul>

  <% _.each( myObject, function(value, key) { %>

  <li><%= key %> : <%= value %></li>

  <% }); %>

</ul>

The only difference here is that the callback is accepting two arguments: the value and then the associated key.

Now you can pass an object when compiling this template:

var compiled = template({

  myObject: {

    boolean1: true,

    boolean2: false,

    string1: 'Hello',

    string2: 'World'

  }

});

When this renders, all of these variables are printed out, as shown in Figure 4-3.

9781118524404-fg0403.tif

Figure 4-3 All of the key-value pairs in this object have been printed out with an _.each() loop.

Using Templates in Backbone

JavaScript templates and Backbone go hand in hand, because there's no better way to render the content in a Backbone view than with a JavaScript template. And since Backbone already depends on Underscore, there's no additional weight when using Underscore templates in your Backbone projects.

Setting Up the Model and View Without Templates

Before you get started, you need a model and view to work with. The basic idea is that you're going to set up a model and then pass the data in the model into a template to render the content in the view. Start by setting up the model. For this, I use a model from an example in Chapter 3:

// create the model for the user

var User = Backbone.Model.extend({});

var user = new User({

  username: 'jonraasch',

  displayName: 'Jon Raasch',

  bio: 'Some nerd'

});

Now, you take this content and render it on the page as a view. But before doing that with templates, review how this was handled in the previous chapter:

// create the view

var UserView = Backbone.View.extend({

  el: '#user-card',

  

  initialize: function() {

    this.render();

  },

  

  render: function() {

    // create a link to the user's profile as a wrapper

    var $card = $('<a href="/users/' + this.model.get('username') +

      '"/>'),

    

    // add the user's name

    var $name = $('<h1>' + this.model.get('displayName') +

      '</h1>').appendTo($card);

    

    // add the user's bio

    var $bio = $('<p>' + this.model.get('bio') +     '</p>').appendTo($card);

    // append this element to the DOM

    this.$el.empty().append($card);

    return this;

  }

});

// create a new instance of the view, tying it to the user model

var userView = new UserView({

  model: user

});

This code starts by associating the view with the DOM element #user-card. Then a render function uses various attributes from the model and adds them to the DOM using jQuery and inline markup.

You're probably noticing how sloppy all of this code is. There's a lot of ugly markup interspersed with separate calls to grab each individual bit of data from the model. Fortunately, you can clean this up a great deal by using templates.

Rendering a View with Templates

Converting this view to render with templates isn't too complicated. The first step is to convert the inline jQuery and markup into a template:

<script type="text/template" id="user-template">

<a href="/users/<%= username %>">

  <h1><%= displayName %></h1>

  

  <p><%= bio %></p>

</a>

</script>

The code already looks a lot cleaner, with simple markup interpolated with basic variables.

The next step is to define the template along the view:

// create the view

var UserView = Backbone.View.extend({

  el: '#user-card',

  

  template: _.template( $('#user-template').text() )

});

Here you define the template using the text from the script tag built for the template. You then assign that value along the view so that you can access it at any time with this.template().

Now set up the render function as before, except this time, render the content with the template:

// create the view

var UserView = Backbone.View.extend({

  el: '#user-card',

  

  template: _.template( $('#user-template').text() ),

  

  initialize: function() {

    this.render();

  },

  

  render: function() {

    // compile the template with the model

    var compiled = this.template( this.model.toJSON() );

    

    // append the compiled markup to the DOM

    this.$el.html( compiled );

    

    return this;

  }

});

The first thing that happens in this code is that it compiles the template. To make it compile, you convert the model to an object and then pass that object into the template function. Since the keys of the model line up with the template created earlier, there's no need to alter this data (or fetch each attribute individually). Then you append the compiled code into the DOM element that's tied to the view.

Finally, for good measure add a change function to re-render this view if the model changes:

// create the view

var UserView = Backbone.View.extend({

  el: '#user-card',

  

  template: _.template( $('#user-template').text() ),

  

  initialize: function() {

    // re-render the view if the model changes

    this.model.on('change', this.render, this)

    

    this.render();

  },

  

  render: function() {

    // compile the template with the model

    var compiled = this.template( this.model.toJSON() );

    

    // append the compiled markup to the DOM

    this.$el.html( compiled );

    

    return this;

  }

});

Now the view will update whenever the model changes.

Because the text of the template is cached on the view, the JavaScript engine doesn't have to redo that lookup every time the model changes, which speeds up performance. But it can't compile until you actually render the view, because you have to take into account the changed model. To wrap up, here's the completed code:

<script type="text/template" id="user-template">

<a href="/users/<%= username %>">

  <h1><%= displayName %></h1>

  

  <p><%= bio %></p>

</a>

</script>

<script type="text/javascript">

// create the model for the user

var User = Backbone.Model.extend({});

var user = new User({

  username: 'jonraasch',

  displayName: 'Jon Raasch',

  bio: 'Some nerd'

});

// create the view

var UserView = Backbone.View.extend({

  el: '#user-card',

  

  template: _.template( $('#user-template').text() ),

  

  initialize: function() {

    // re-render the view if the model changes

    this.model.on('change', this.render, this);

        

    this.render();

  },

  

  render: function() {

    // compile the template with the model

    var compiled = this.template( this.model.toJSON() );

    

    // append the compiled markup to the DOM

    this.$el.html( compiled );

    

    return this;

  }

});

// create a new instance of the view, tying it to the user model

var userView = new UserView({

  model: user

});

</script>

As you can see, the code is much cleaner and better organized than it was without the template.

Summary

Templates eliminate spaghetti code and allow you to build long strings of markup without having to intersperse HTML in your JavaScript. They make your codebase cleaner, and streamline the development process.

In this chapter, you learned how to use Underscore templates to generate markup that is compiled according to variables from your JavaScript. That way your markup can still be driven by JavaScript, without actually having to live alongside the JavaScript.

Finally, you found out how to use loops in your templates and also how to use them to render views in Backbone.

In the coming chapters, you extend your use of templates, combining them with a variety of other JavaScript techniques. Now that you understand Backbone and templates, you have the two pillars that make up the foundation of the rest of your app. It's time to dig in, and have some fun!

Additional Resources

Template Libraries

Underscore: http://underscorejs.org

Handlebars: http://handlebarsjs.com

Transparency: http://leonidas.github.com/transparency

Micro-Templating: http://ejohn.org/blog/javascript-micro-templating

Choosing a Template Library

Template Chooser: http://garann.github.com/template-chooser

Template Performance Comparison: http://jsperf.com/dom-vs-innerhtml-based-templating/365

Pros and Cons of Logic-Less Templates

Client-Side Templating Throwdown: http://engineering.linkedin.com/frontend/client-side-templating-throwdown-mustache-handlebars-dustjs-and-more

The Case Against Logic-Less Templates: http://www.ebaytechblog.com/2012/10/01/the-case-against-logic-less-templates/

Templates in Backbone

Backbone View Patterns: http://ricostacruz.com/backbone-patterns/#view_patterns

Backbone.js Lessons Learned and Improved Sample App: http://coenraets.org/blog/2012/01/backbone-js-lessons-learned-and-improved-sample-app/

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

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