Sails.js + Handlebars.js

I used sails.js to build my website and think it’s worth checking out if you’re looking for a decent, production ready web framework sitting on top of node.js (and express).

I’m not going to go into the details of why I think it’s good or how it differs from alternatives out there; there are many posts doing this already. Instead I’m going to talk about something I don’t really like - the default server side templating engine ejs. Below is a little bit of ejs:

<h1><%= img.title %></h1>
<time datetime="<%= img.dateTaken %>"><%= img.dateTaken %></time>
<img src="<%= img.imgsrc %>" alt="<%= img.alt %>" />
<p><%= img.description %></p>

This example could be used for displaying a picture gallery item. So far ejs is looking quite neat, similar to embedded PHP which may or may not be a good thing depending on personal taste. Okay, lets look at displaying an array of images.

<ul>
  <% for(var i = 0; i < galleryItems.length; i++) { %>
  	<li>
      <h2><%= galleryItems[i].title %></h2>
      <time datetime="<%= galleryItems[i].dateTaken %>"><%= galleryItems[i].dateTaken %></time>
      <img src="<%= galleryItems[i].src %>" alt="<%= galleryItems[i].alt %>" />
    </li>
<% } %>
</ul>

Now for some extra magic:

<ul>
  <% _.each(galleryItems, function(img) { %>
  	<li>
      <h2><%= img.title %></h2>
      <time datetime="<%= img.dateTaken %>"><%= img.dateTaken %></time>
      <img src="<%= img.src %>" alt="<%= img.alt %>" />
    </li>
<% } %>
</ul>

We can use lodash in our templates because sails passes it through for us, that’s pretty powerful. But in my mind being able to write functional code in our templates is an issue. The above example is just a loop, nothing unusual. However, with ejs you can do pretty much what you want and this can lead to hiding logic in views which arguably shouldn’t be there. Don’t worry, I’m not going to start a lecture about why to use MVC (or any of the many variants…), I don’t care. I do think it’s crucial to have a good separation of responsibilities though. Of course this is possible with ejs but it’s just easy to cheat with and I’m lazy, so I know I’ll end up doing it. Handlebars.js on the other hand, is reasonably restrictive… although it’s still possible to cheat. The difference with Handlebars is if you want to do something a bit sneaky you have to go out of your way to do so at which point alarm bells start ringing.

So, the above example in Handlebars:

<ul>
  {{#each galleryItems}}
  	<li>
      <h2>{{title}}</h2>
      <time datetime="{{dateTaken}}">{{dateTaken}}</time>
      <img src="{{src}}" alt="{{alt}}" />
    </li>
  {{/each}}
</ul>

This is pretty similar but I find it easier to read due to it being a bit more concise. One nice difference is that within the loop the properties on the gallery item are accessible via ‘global’ scope (conceptually anyway, I don’t know how Handlebars actually implements this). In short, it means we can stop referencing the current iterations item.

For completeness sake I’ll mention that in the above any HTML contained in those variables will be automatically escaped, so it’ll print the HTML as text. This escaping occurs in ejs with <%= %> and Handlebars with {{ }}. If you have raw HTML you want to render as HTML you would use <%- %> and {{{ }}}, respectively.

If you’re thinking you’d like to use Handlebars in sails you’re in luck, it’s actually very simple to set up. This is due to a decision they made to make use of the node module consolidate.js. It supports a wide variety of template engines so if handlebars isn’t your thing you can still follow the steps below to get something else working.

Note I’m using sails v0.10.5.

Firstly we need to tell sails we want to use handlebars and not ejs:

config/views.js

module.exports.views = {
  engine: 'handlebars',
  layout: false,
}

Here we have set the engine to handlebars and disabled layouts. The layout functionality was actually deprecated in ejs but the guys working on sails decided to continue maintaining it so as to be backwards compatible with older versions of sails. If you are using a different template engine it is expected that you use their features to achieve layout functionality.

That’s all that is actually required to get up and running. Files will be picked up using the same conventions as before, except it’ll now be looking for .handlebars files. So, given a rather contrived controller:

api/controllers/ItemController.js

module.exports = {
  find: function(req, res) {
    res.view({
      item: {
        title: 'Title',
        message: 'Message',
      },
    });
  }
};

It will render our simplistic view:

views/item/find.handlebars

<div>
  <h1>{{title}}</h1>
  <p>{{message}}</p>
</div>

That’s great, but what about the repeatable code that used to live in the layout? Well, I haven’t found a particularly good way to handle this using handlebars alone (this third party module looks useful though). But I opted for including a partial for the header and footer of the page in the template. I find this is good enough for my needs. The view modifications are as follows:

views/item/find.handlebars

{{> header}}

<div>
  <h1>{{title}}</h1>
  <p>{{message}}</p>
</div>

{{> footer}}

We also need to alter the controller to pass the partials to the view:

api/controllers/ItemController.js

module.exports = {
  find: function(req, res) {
    res.view({
      item: {
        title: 'Title',
        message: 'Message',
      },
      partials: {
        header: '../partials/header',
        footer: '../partials/footer',
      },
    });
  }
};

This is a bit of a pain but I haven’t found a way to get sails to automatically pass partials to the template engine. I might look into this further and submit a PR if required. But for now this is okay, if it’s too much hassle to add the partials on every res.view call then this could be moved into a function in the controller or a service. Now for the partials:

views/partials/header.handlebars

<!DOCTYPE html>
<html>
  <head>
    <title>Page Title</title>
  </head>
  <body>
    <div id="site-header">
      <header><h1>Cool Item Stuff</h1></header>
    </div>
    <div id="site-content" role="main">

views/partials/footer.handlebars

    <footer class="site-footer">
      <p>(C) Michael Burton 2014</p>
    </footer>
    </div>
  </body>
</html>

As said, it’s not perfect but it’s okay. I haven’t found it an issue on my site as I only have a couple of public facing views. I have an admin section but this is all client side rendered so I only require a single server side view anyway which is unique so doesn’t really benefit from a layout/partials.

There is still one more issue though, automatic asset injection. This is a useful feature in sails, along with most web application frameworks. A bit of background, essentially sails will keep your ‘source’ assets in sync with the ‘dist’ assets. For images it will simply copy them across to a .tmp folder in your application root which is linked up to the root in your views (so it would be accessed like /images/img.png). CSS and JS files are much the same but in production they get minified. It will also automatically compile LESS to CSS and CoffeeScript to JS, in addition to minifying and merging the sources into a single file in production. All this is achieved via Grunt.js. Asset injection is handy though because sails can automatically inject the source files into your views (including partials). I’m not going to go into the details of this here, you can read the documentation to find out more. What I will say is that this injection only occurs for html and ejs files so we should fix this.

All the files related to grunt magic are under tasks/.

Disclaimer, as I’ve said, I’m lazy. I know I don’t want to use ejs, so I’m just going to edit the existing tasks to look at handlebars files instead of ejs:

previous tasks/config/sails-linker.js

devJs: {
  options: {
  startTag: '<!--SCRIPTS-->',
  endTag: '<!--SCRIPTS END-->',
  fileTmpl: '<script src="%s"></script>',
  appRoot: '.tmp/public'
  },
  files: {
    '.tmp/public/**/*.html': require('../pipeline').jsFilesToInject,
    'views/**/*.html': require('../pipeline').jsFilesToInject,
    'views/**/*.ejs': require('../pipeline').jsFilesToInject
  }
},

after tasks/config/sails-linker.js

devJs: {
  options: {
  startTag: '<!--SCRIPTS-->',
  endTag: '<!--SCRIPTS END-->',
  fileTmpl: '<script src="%s"></script>',
  appRoot: '.tmp/public'
  },
  files: {
    '.tmp/public/**/*.html': require('../pipeline').jsFilesToInject,
    'views/**/*.html': require('../pipeline').jsFilesToInject,
    'views/**/*.handlebars': require('../pipeline').jsFilesToInject
  }
},

That’s just an example of one of the tasks, all need to be done but the file is quite large so I decided not to paste the whole thing here.

That’s it, you can include the special tags and have files injected into your views automatically:

views/partials/header.handlebars

<!DOCTYPE html>
<html>
  <head>
    <title>Page Title</title>
    <!--STYLES-->
    <!--STYLES END-->
  </head>
  <body>
    <div id="site-header">
      <header><h1>Cool Item Stuff</h1></header>
    </div>
    <div id="site-content" role="main">

views/partials/footer.handlebars

    <footer class="site-footer">
      <p>(C) Michael Burton 2014</p>
    </footer>
    <!--SCRIPTS-->
    <!--SCRIPTS END-->
    </div>
  </body>
</html>

The better solution to this would be to create some new grunt tasks in sails-linker.js specifically for handlebars. Then these are registered in tasks/register/linkAssets.js, tasks/register/linkAssetsBuild.js, tasks/register/linkAssetsProd.js, tasks/register/prod.js. Jade templates are configured like this so if you want to do it this way I’d advise using them as a guide.

The great thing is that as I said at the beginning this will work for anything which is supported by consolidate.

So that’s job done. If you have any comments/questions post below.

comments powered by Disqus