Thursday 5 April 2012

Adding backbone to Rails 3.1 part two

[Read part one here: Adding backbone to Rails 3.1 part one]

Picking up from previous part

Last time we learnt how to add Backbone.js support to a clean Rails 3.1 application. Even though the application I was basing on already has some content, everything concerning Backbone can be done on a freshly created app.

We ended up having an Initializer class written in Coffeescript that runs Backbone on every Rails view. Now we'll modify add router-based functionality. Simply put: routers are to Backbone what controllers are to Rails. What I'm going to show is how to link controllers with appropriate Routers, so that every time you call a view or action from a Rails controller, Backbone will try to locate corresponding views through an adequate router.

e.g. when I request maps/index from Rails application, upon rendering in my browser, the said view will also call JavaScripts that are located in Doomhub.Backbone.Views.Index view, served through Doomhub.Routers.Map router. To make things even prettier (author`s opinion) there should also be a Root ...router, The-One-To-Rule-Them-All router, a Main one.

The Main Router

I think that for readability and convention's sake a main router is a great thing, it keeps information about all the other routers in one place. Thanks to this, the Initializer can always just blindly call the Main router and pass it some parameters we dug up from Rails server (@controller and @action we defined in the first part), so that the router can work its Backbone magic.

Earlier I said routers in Backbone are like controllers in Rails. Actually they also assume the role of Rails router mechanism (hence Backbone prefers to use this name). In Rails the server routes traffic to specific controllers, basing on the information specified in config/routes.rb file. Since JavaScript is obviously client-based, Backbone distributes its own routing mechanism among the router classes. By having a Main router that redirects to other "named" routers in our Backbone we retain some of the Rails' characteristics we might be so used to, while not going completely out of Backbone's way.


app/assets/javascripts/backbone/routers/main_router.coffee:

class @Doomhub.Routers.Main extends Backbone.Router

  routes:
    'maps/:action': 'maps'

  maps: (action) ->
    @maps_router ?= new Doomhub.Routers.Map()
    @maps_router[action]() if @maps_router[action]?



As one can see, the Main router defines a routes hash, with :router/:action (routing string) as a key, and a method (function) name as its value. This hash actually serves no value for our mechanism, other than information, but it's very Backbone'ish. A pure Backbone application would rely on this hash to conduct routing. You can actually drop it completely. The approach I will use is more... primitive.


Writing a new Helper class ...helps:


app/assets/javascripts/backbone/helper.coffee:



class @Doomhub.Helper
  constructor: ->
    @controller = $('body').data('controller')
    @action = $('body').data('action')



It also requires modifications done to the Initializer class:


app/assets/javascripts/backbone/initializer.coffee:



class @Doomhub.Initializer
  constructor: ->
    window.H = new Doomhub.Helper
    H.initializer = @
    @router = new Doomhub.Routers.Main()
    @run_backbone H.controller, H.action

  run_backbone: (router, action) ->
    console.log "router: #{router} / action: #{action}"
    if @router[router]
        @router[router](action)


Once an instance of Initializer class is created (and because of new Doomhub.Initializer() in the app/assets/javascripts/backbone/doomhub.coffee file it always is, when a proper Rails view is called) it creates a new Main router and calls run_backbone method, which - in turn - will call for a proper action in a related router if it exists.

The last links of the chain

Now we have means to direct the JavaScript flow to a specific point in the Backbone structure, it's high time we build this point.


There needs to be a router we delegate to from the Main router.


app/assets/javascripts/backbone/routers/map_router.coffee:


class @Doomhub.Routers.Map extends Backbone.Router
  
  index_action: ()->
    @view ?= new Doomhub.Views.Maps.Index({ el: $(body) })


Map router creates a new instance of Index View. The constructor requires an HTML element to cling to, as all the events and functions within this view will be limited to this element. You can pass the entire body as the base element, but you really should have a unique container with an id.

app/assets/javascripts/backbone/views/maps/index.coffee:

class @Doomhub.Views.Maps.Index extends Backbone.View

  events:
    'click #my_button': 'do_something'

  do_something: (e) ->
    alert 'ouch'

This is basically the end of the road. The code for the Index view should be self-explanatory, provided you know anything about Coffeescript, and you support yourself with the Backbone.js documentation.

No comments:

Post a Comment