* Backbone and Rails can provide a flexible combination when used thoughtfully, but that flexibility opens up a lot of room for ambiguity and mixed responsibilities. This article walks through a progression from Rails views with imperative jQuery-powered client code to a Backbone solution with declarative client-side templating. *

As the complexity of real-time user interaction expected of web apps continues to grow, more and more Rails projects are adding Javascript MVC frameworks and libraries to manage client-side code. Backbone.js is an obvious choice in many cases, both as the earliest player to see widespread adoption and as a lightweight, fairly freeform option which can be easily integrated into sections of existing pages without rewriting a lot of code. Backbone stays close to the DOM and doesn’t dictate much about how to perform your rendering and interactions. This freedom can be both a blessing and curse: migrating to a rich client with Backbone can be an ambiguous process where many patterns are available and seem equally compelling.

An important challenge in adding Backbone to a project is deciding on how dynamic content is inserted into views – what gets produced by the server and sent to the browser, how much heavy lifting the client-side JS is expected to do, etc. A developer starting a new project “front-end first” might make significantly different architectural decisions than someone constrained to port an existing Rails front end over to Backbone incrementally. A lurking danger of the step-by-step progressive enhancement approach is the introduction of extra complexity as the client code gradually takes on responsibilities which the server also still retains.

To confront a few typical issues, we’re going to step through the process of converting a very simple Rails-backed feature from an initial jQuery version to a more robust Backbone app.

version 1: imperative jQuery

Many Rails developers coming to Backbone are used to working with a front end where Rails views are rendered on the server, sent to the browser, and then lightly enhanced on the client side by jQuery (or another JS library, or even plain Javascript). Let’s look at a very stripped-down example: a view in a photo-sharing app where you can cycle through a user’s publicly posted pictures by clicking on a button or link.

On every request, the server injects some dynamic data into a view template, which we’ll do here by interpolating Ruby into a HAML template (to display the user’s name, the total number of pictures available, the name and URL of the current picture, etc.):

%h1 Pictures by #{@user.name} #current-pic %h4 Currently viewing: %span#pic-index= @pic_index + 1 of #{@pics.size} = link_to "Next", root_path, id: "next-pic" %h5= @current_pic.name = image_tag @current_pic.url 1 2 3 4 5 6 7 8 9 10 11 12 %h1 Pictures by #{@user.name} #current-pic %h4 Currently viewing: %span#pic-index= @pic_index + 1 of #{@pics.size} = link_to "Next", root_path, id: "next-pic" %h5= @current_pic.name = image_tag @current_pic.url

It doesn’t take much JS to make a bare-bones jQuery click-handler, so that clicking on the “Next” link will flip to the user’s next picture without reloading the page:

app/assets/javascripts/pictures.js.coffee $ -> $("#next-pic").on 'click', (e) -> e.preventDefault() gon.picIndex = (gon.picIndex + 1) % gon.pics.length currentPic = gon.pics[gon.picIndex] $('#pic-index').text "#{gon.picIndex + 1}" $('#current-pic h5').text currentPic.name $('#current-pic img').attr "src", currentPic.url 1 2 3 4 5 6 7 $ -> $ ( "#next-pic" ) . on 'click' , ( e ) -> e . preventDefault ( ) gon . picIndex = ( gon . picIndex + 1 ) % gon . pics . length currentPic = gon . pics [ gon . picIndex ] $ ( '#pic-index' ) . text "#{gon.picIndex + 1}" $ ( '#current-pic h5' ) . text currentPic . name $ ( '#current-pic img' ) . attr "src" , currentPic . url

Note that we’re using Gon to pass data from the Rails controller to the JS code in an unfussy way, so that the client automatically has information about the available pictures. The code here operates in a largely “imperative” manner. It provides a list of instructions to perform one after the other every time the user clicks an element, in order to update the DOM: find the element with ID pic-index and replace the number of the current picture being viewed, replace the name of the picture, etc. The app code of the example at this stage is available on Github in the branch “jquery”.

version 2: Backbone with imperative rendering

Onward to a first version re-organized into client-side models and views with Backbone. (Of course, in the real world, if this were the entire extent of the required client-side interaction, it would make little sense to introduce an MV* framework at this point. But a real design for this kind of feature would ask for much more than a simple picture-cycling button.)

The path of absolute least resistance is to maintain the overall structure of the existing front end, utilizing Backbone largely as a vehicle for code organization in a first iteration. The Rails controller and view remain untouched, but the one-off app/assets/javascripts/pictures.js.coffee is replaced by a standard Backbone app directory structure (generated by the rails-backbone gem). As the diff shows, apart from the addition to the Gemfile, there are absolutely no changes to the app outside of app/assets/javascripts/ .

Working off the same Gon variables as before for server-provided data, the declaration of Backbone models to wrap that data is trivial, and the main interest lies in the Backbone view for our picture-swapper:

app/assets/javascripts/backbone/views/picView.js.coffee class PicApp.Views.PicView extends Backbone.View initialize: -> @collection = @model.collection @index = @collection.models.indexOf(@model) @model = @model.clone() @model.on("change", => @render()) render: -> @$('#pic-index').text "#{@index + 1}" @$('h5').text @model.get("name") @$('img').attr "src", @model.get("url") events: "click #next-pic": "advancePic" advancePic: (e) -> e.preventDefault() @model.set(@nextPic()) nextPic: -> @collection.at(@index = (@index + 1) % @collection.length) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class PicApp . Views . PicView extends Backbone . View initialize: -> @collection = @model . collection @index = @collection . models . indexOf ( @model ) @model = @model . clone ( ) @model . on ( "change" , = > @render ( ) ) render: -> @ $ ( '#pic-index' ) . text "#{@index + 1}" @ $ ( 'h5' ) . text @model . get ( "name" ) @ $ ( 'img' ) . attr "src" , @model . get ( "url" ) events: "click #next-pic" : "advancePic" advancePic: ( e ) -> e . preventDefault ( ) @model . set ( @nextPic ( ) ) nextPic: -> @collection . at ( @index = ( @index + 1 ) % @collection . length )

Although the code is more verbose than the first version, we’ve made a few gains here. There is a clearer separation of view behavior from model logic, which could be independently tested. Moreover, through the use of an observer on the View’s Pic model, the rendering pipeline is dynamically bound: any time the model data changes the view will react and render itself. This explicit wiring up of observers and rendering calls is a far cry from the sophisticated automatic data bindings provided by some other frameworks like AngularJS, but it basically gets the job done.

It’s still not a very good solution, though. Notice how the render() method still duplicates server-side logic just as in the jQuery version; for instance, both server and client use picIndex + 1 to show which picture is currently being displayed. We could ameliorate this situation by removing all the dynamic content injection during the server’s rendering of the template and ensuring that the Backbone View gets rendered upon pageload. This is a step in the right direction – eliminating the mixture of server-side and client-side interpolation – but it’s not enough. The render() method still ties the JS code intimately to the DOM structure; a simple modification to CSS classes or IDs in the server-side HAML template can easily break the client’s rendering. If you update either the Backbone rendering code or the server’s HAML, the other one has to be kept in sync.

version 3: Backbone with templating

A way to mitigate these problems of coupling rendering code to the DOM is to adopt a more “declarative” approach with templates. Rather than listing individual DOM manipulations to occur every time the view is rendered, we simply want to describe how our view should look, with slots for dynamic data to be interpolated, and let the rendering infrastructure take care of the details of refreshing the DOM as necessary. We want to move the server-side template code onto the client.

There are numerous Javascript templating libraries available; for ease of comparison with the HAML template code above, the example app uses haml_coffee_assets, which provides HAML templating with Coffeescript interpolation. The client-side version of the view template for the picture-swapper in app/assets/javascripts/backbone/templates/pic.hamlc looks very similar to the original server version:

app/assets/javascripts/backbone/templates/pic.hamlc %h4 Currently viewing: %span#pic-index= @index + 1 of #{@collection.length} %a#next-pic{:href => "/"} Next %h5= @model.get("name") %img{:src => @model.get("url")} 1 2 3 4 5 6 7 8 9 %h4 Currently viewing: %span#pic-index= @index + 1 of #{@collection.length} %a#next-pic{:href => "/"} Next %h5= @model.get("name") %img{:src => @model.get("url")}

The Backbone view-rendering code has become trivial, simply calling the compiled template function:

app/assets/javascripts/backbone/views/picView.js.coffee class PicApp.Views.PicView extends Backbone.View ... template: JST["pic"] render: -> @$el.html @template @ 1 2 3 4 5 6 class PicApp . Views . PicView extends Backbone . View . . . template: JST [ "pic" ] render: -> @ $ el . html @template @

And the new version of the server-side template is entirely stripped down:

%h1 Pictures by #{@user.name} #current-pic 1 2 3 %h1 Pictures by #{@user.name} #current-pic

The #current-pic tag is now essentially nothing more than a cubbyhole for the JS client to fill in and take over entirely. I would in many cases strive to take this a step further and remove the server-side template-rendering altogether, but as a less radical first stage, this version at least has the advantage of keeping the server-side and client-side templates out of each other’s way. (For more on the idea of stripping down the server when working with rich JS client code, see my post on Lean, mean client-server Javascript architecture with AngularJS and Express.)

The code for the switch to client-side templating is again a fairly small step from the previous version, but it brings great rewards. The Backbone structure is no longer a simple code organizer for JS functionality piled on top of a Rails view, but a small independent app which needs nothing from the server except the bare data (which in a more complex app we would often fetch via JSON API) and a place to insert its views. Its components can be tested independently of the Rails app. The points of communication between server app and client app have become much smaller and more well-defined, reflecting a more modular design. If you’re going the route of adding MVC Javascript to an existing app, do yourself a favor and let the framework do as much as it can out of the box; you’ll have a much happier time maintaining and updating both server and client.