Handlebars, loading external template files

CaptSaltyJack picture CaptSaltyJack · Oct 30, 2014 · Viewed 64.6k times · Source

My goal is to put all my Handlebars templates in a single folder, as so:

templates/products.hbs
templates/comments.hbs

I found this snippet in a few places via a cursory Google search, which apparently will load in Handlebar templates in external files, which makes much more sense than putting a bunch of templates in a single index file.

(function getTemplateAjax(path) {
    var source;
    var template;

    $.ajax({
        url: path, //ex. js/templates/mytemplate.handlebars
        cache: true,
        success: function(data) {
            source    = data;
            template  = Handlebars.compile(source);
            $('#target').html(template);
        }               
    });         
})()

The problem is, I don't understand this function or how to use it. Why is the whole function wrapped in parentheses and then made a function call? e.g. (function x() { ... })() I don't know what this is doing.

And if I'm not mistaken, looks like $('#target') is hardcoded when it shouldn't be. Furthermore, isn't this supposed to set a data variable somewhere so the variables referenced in the template will work?? Seems the correct function should be:

function getTemplateAjax(path, target, jsonData) {
  var source;
  var template;

  $.ajax({
    url: path, //ex. js/templates/mytemplate.handlebars
    cache: true,
    success: function(data) {
      source    = data;
      template  = Handlebars.compile(source);
      $(target).html(template(jsonData));
    }               
  });         
}

Side note: if someone could point me to a better template engine, one that actually natively supports external template files, and is better organized than Handlebars, I'd be eternally grateful.

Another issue: I can't actually name my files mytemplate.hbs, because when the Ajax call happens, it sees it as a binary file and it comes through as binary. I suppose this is an issue of setting the server's mime type for .hbs to text/html or text/plain, but the issue is that this is a Grunt server and I'm not sure how to change its mime types.

Answer

istos picture istos · Oct 30, 2014

The code is wrapped in an IIFE (Immediately Invoked Function Expression), which means the function is executed immediately. That's what the following means:

(function x() {
  console.log('hello');
})();

You can also do:

(function() {
  console.log('hello');
}());

IIFEs are commonly used to create a "private" scope for a bit of code so that it plays nice (doesn't conflict) with anything else.


The second function you provided makes more sense and perhaps the first one must have been just an example.


Handlebars allows you to precompile your templates so you don't have to compile them at run-time. Also this way you don't have to make extra HTTP requests just to load the template(s).

For example if I have the following project structure - (note that my models, collections, and views are all within main.js just for this example and all my .js files are in my root directory):

├── Gruntfile.js
├── handlebars-v2.0.0.js
├── index.html
├── main.js
├── package.json
└── templates
    └── todo.handlebars


My todo.handlebars looks like so - just html with Handlebars syntax:

<h3>{{title}}</h3>
<p>Created by: {{author}}</p>


To precompile my template I would do the following in the command line (you have to install the handlebars precompile script first with: npm install -g handlebars):

> handlebars templates/todo.handlebars -f todo.tpl.js

Now my project structure looks like so:

├── Gruntfile.js
├── handlebars-v2.0.0.js
├── index.html
├── main.js
├── package.json
├── templates
│   └── todo.handlebars
└── todo.tpl.js

You'll see that a todo.tpl.js file has been added to my root directory. I could have named it something different if I wanted to as long as the extension is a .js because the file contains valid JavaScript code. Also I could have specified a different directory to output it to. Remember that the todo.tpl.js file is the actual template that your Backbone View will use. You write your HTML in todo.handlebars and compile it as todo.tpl.js.


Now that I have the todo.tpl.js file I can use Grunt to maybe concat all my JS template files into a all_templates.js file or I can reference each file directly in my HTML like so:

  <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
  <script src="http://documentcloud.github.io/underscore/underscore-min.js"></script>
  <script src="http://documentcloud.github.io/backbone/backbone-min.js"></script>
  <script src="handlebars-v2.0.0.js"></script>
  <script src="todo.tpl.js"></script> <!-- My Template for a Todo item -->
  <script src="main.js"></script>


In my Backbone View, which in my case lives inside my main.js file, I would get the template like so:

var TodoView = Backbone.View.extend({
  tagName: 'li',  
  className: 'todo-item',
  events: {

  },

  // You can grab your template function with the name you chose when
  // you precompiled it from: `Handlebars.templates`
  template: Handlebars.templates.todo,

  initialize: function(options) {
    this.listenTo(this.model, 'change', this.render);
  },

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


And you're done! More info here: