Building Applications with Backbone and Laravel: JST

13th April 2014 | Tags:

You might well be used to devel­op­ing Back­bone views like this:

MyApp.Views.SomeView = Backbone.View.extend({    
  template: _.template('<ul><% _.each(items, function(item){ %><li><%= item %></li><% }); %></ul>'),    
  el: '#some-element',

This is all fine and well for sim­ple struc­tures, but can quickly get unwieldly if you’re build­ing markup of any sig­nif­i­cant size or com­plex­ity. It can also lead to all sorts of issues around readability.

One alter­na­tive is to put more com­plex tem­plates into sep­a­rate HTML files:

// file: folder/template.html

<ul>
 <% _.each(items, function(item){ %>
 <li> <%= item %> </li>
 <% }); %>
</ul>

Each time you want to ren­der a tem­plate, you can load in the tem­plate via AJAX or use RequireJS’s text plu­gin. Great for devel­op­ment, but pretty atro­cious for per­for­mance, as it’ll fire off untold num­bers of HTTP requests.

Using JST allows you to develop tem­plates in this way — spread­ing tem­plates across sep­a­rate files, organ­ised into a log­i­cal hier­ar­chy — but “col­lapse” them into vari­ables which are avail­able via a sin­gle JS file. This file can then be mini­fied, or incor­po­rated into a larger Javascript file using, for exam­ple, the RequireJS opti­miser or Google Clo­sure Compiler.

Such a file looks a lit­tle like this:

// file: jst.js

var JST = JST || {};
JST['app/templates/folder/template.html'] = _.template('<ul><% _.each(items, function(item){ %><li><%= item %></li><% }); %></ul>');
JST['app/templates/folder/template2.html'] = _.template('...');
JST['app/templates/folder2/template.html'] = _.template('...');
// etc...

JST and Laravel

If your Back­bone application’s back-​end uses Lar­avel, I’ve writ­ten a pack­age to gen­er­ate such a file.

Usage

Gen­er­ate your JST file by enter­ing the fol­low­ing com­mand in your terminal:

php artisan jst:generate

Or alter­na­tively, you can have it “watch” your tem­plates direc­tory (and any sub-​directories) for changes, and recom­pile the file for you:

php artisan jst:watch

Back to your Back­bone application:

// file: namespace.js

define([
    // Libs
    "jquery",
    "use!underscore",
    "use!backbone"
],

function($, _, Backbone) {
 // Put application wide code here

 return {
  // This is useful when developing if you don't want to use a
  // build process every time you change a template.
  //
  // Delete if you are using a different template loading method.
  fetchTemplate: function(path, done) {
   var JST = window.JST = window.JST || {};
   var def = new $.Deferred();

   // Should be an instant synchronous way of getting the template, if it
   // exists in the JST object.
   if (JST[path]) {        
    if (_.isFunction(done)) {
     done(JST[path]);
    }

    return def.resolve(JST[path]);
   }

   // Fetch it asynchronously if not available from JST, ensure that
   // template requests are never cached and prevent global ajax event
   // handlers from firing.
   $.ajax({
    url: path,
    type: "get",
    dataType: "text",
    cache: false,
    global: false,

    success: function(contents) {
     JST[path] = _.template(contents);

     // Set the global JST cache and return the template
     if (_.isFunction(done)) {
      done(JST[path]);
     }

     // Resolve the template deferred
     def.resolve(JST[path]);
    }
   });

   // Ensure a normalized return value (Promise)
   return def.promise();
  },

 };
});

As you can see, this pro­vides a method which tries to load a tem­plate from a global vari­able called JST, but if that’s not avail­able — i.e., dur­ing devel­op­ment — then it loads it in as an HTML file using AJAX.

Now you can cre­ate a view like this, to take advan­tage of it:

MyApp.Views.SomeView = Backbone.View.extend({
 template: "app/templates/folder/someview.html",        
    render: function(done) {      
  namespace.fetchTemplate(this.template, function(tmpl) {        
   view.el.innerHTML = tmpl({model: view.model.toJSON() });
   if (_.isFunction(done)) {
    done(view.el);
   }  
  });
 }
// …

Using it Out­side of Laravel

There’s no rea­son why this code can’t be re-​purposed to work out­side of Lar­avel — per­haps I’ll get a chance at some point, but feel free to have a stab at it. All the depen­den­cies are man­aged via Composer.

Comments

No comments yet.

Links and images are allowed, but please note that rel="nofollow" will be automactically appended to any links.