September 21, 2011

Rails 3.1 Asset Pipeline and JavaScript Templates

Backbone.JS on Rails 3.1 asset pipeline

Today I want to share a small proof of concept with you, taken from Jeff Casimirs talk at Frozenrails 2011: complete decoupling of controllers and views in Rails. So all the controllers do is to serve and consume JSON data. The front-end is build around this data.

Now you might ask: “Why should I want to do THAT?”. For one, the way view rendering currently works in Rails isn’t very object-oriented. Also, most of the time you’re coupling your controller tightly with your view. Jeff Casimir named much more reasons in his talk - so you might check it out for more in depth reasoning on this end.

To me, the most interesting point is the potential speed improvements for the end-user: You’re entire interface can be minified and cached on the client side this way, greatly reducing bandwidth usage and improving UI responsiveness.

Disclaimer: this idea is not new. But until now it has been somewhat difficult to implement this since when combining Backbone.js with Rails you’d normally lack all the small syntactic sugar for writing your views.

This basically is just a problem with the asset-pipeline, since the FormHelpers are not available there. But since nobody wants his Handlebars templates to clutter his erb or haml templates, it seems like a good place to put them in.

Here are 4 simple steps to get you up and running:

Step 1: setup dependencies

Assuming you already got Backbone.js and its dependencies installed: add the following to the Gemfile and run bundle install:

gem 'ejs'
gem 'haml', :git => 'https://github.com/infbio/haml.git', :branch => 'form_for_fix'
gem 'haml_assets', git: 'https://github.com/infbio/haml_assets.git'

gem 'tilt'
gem 'handlebars_assets'

gem 'asset_pipeline_routes'
gem 'handlebars_haml_assets', git: 'https://github.com/nicolai86/handlebars_haml_assets.git'

gem 'execjs', :git => 'git://github.com/sstephenson/execjs.git'

Now you have all the power of HAML templates and Rails FormHelpers at your fingertips - inside your asset pipeline. Just create a new .jst.hbs.haml file and you’re good to go.

Step 2: load your templates

Before you can use your HAML templates you have to tell Sprockets to actually load and compile them. So:

//= require handlebars
//= require_tree ../templates

I’m assuming that you place your HAML templates in your app/assets/templates folder.

This will require the Handlebars library which basically be used to compile your views and make them available in a variable called JST.

Step 3: write HAML templates for Handlebars

In your new apps/assets/templates folder go create a new template, e.g. users/edit.jst.hbs.haml. You can reference this view in your JavaScript files by calling

JST['users/edit'] # => content from app/assets/templates/users/edit.jst.hbs.haml

To use it you’ll have to call it with a context to evaluate in:

JST['users/edit'] @user

Here’s an example content for this file:

{{ site.lcb }}{{ site.lcb }}#attributes{{ site.rcb }}{{ site.rcb }}
= hbs_form_for :user, url: r.user_path, method: :post, id: 'users' do |f|
  = f.text_field :name
  = f.text_field :email
  %br
  = f.submit
= link_to_function 'cancel', "app.navigate('#{r.user_path}', true)"
{{ site.lcb }}{{ site.lcb }}/attributes{{ site.rcb }}{{ site.rcb }}

So what’s going on there? First, this view is used by passing in a Backbone.Model instance as context when evaluated. So, in order to get the attributes to actually show up in the view I scope my Handlebars template to the attributes only.

Then, I’m actually not using the form_for helper, but rather the hbs_form_for helper. This basically enables me to automatically bind = f.text_field :name to the {{name}} attribute of the current context.

The eagle eyed might have noticed me using r as a helper to access routing information. Again, it’s just a short hand, because r automatically replaced dynamic fragments with handlebars attribute bindings.

If you want to know more about these two helpers, take a look at the following github projects:

Step 4: using the templates with Backbone.JS

Please note: the usage shown here is not limited to Backbone.JS. In theory you should be able to plug-in whatever JS frontend framework you like and it should work.

Normally, when creating Backbone.Views, you define a template and use it later on, like this:

class MyView extends Backbone.View
  template: _.template($("#my-template").html())

  # snip

  render: =>
    $(@el).append @template @model.toJSON()

Now, since the actual template preprocessing logic is taken care of by handlebars_assets, you can reduce this to

class MyView extends Backbone.View
  render: =>
    $(@el).append JST['my/view'] @model

As you can see, JST got you covered. Nice way to shorten your source code a little.

That’s about it! It’s this easy! Plus, when you deploy to production, you’re going to get all this minification and concatenation for free - and your users benefit!

At this point I’d like to thank Les Hill (@leshill @ github) for his great asset-pipelining solutions which actually made all this possible! Thank you!

One last word: handlebars_assets, haml_assets, asset_pipeline_routes and handlebars_haml_assets are all in their early development stages. The programming API might change at some point, but for now, this should get you going ;)

© Raphael Randschau 2010 - 2022 | Impressum