Backbone.js, Rails 3 and asynchronous interfaces


Backbone.js and RailsCurrently at Monterail we’re building a frontend-heavy app. With a lot of cool stuff. Like preloading data for daily views so that you won’t have to wait for every surrounding day to load. Many, many interactive elements that should be really interactive – not in an oldschool, ajax “spinner” way. And a lot of other stuff we shouldn’t talk about yet.

We decided to put more emphasis on Backbone.js with Rails and build most of the app with this combination.

Below you can find some of our findings on the topic of making those two speak to each other in a nice manner.

Asset pipeline

Really makes things easier. Organizing your JS code before it meant a lot of things: handling dependencies, minifying & compressing the code, organizing it into a lot of different files and building connections between them. With asset pipeline and it’s awesome “require” directive, you can forget about handling it manually. You just create the structure in app/assets/javascripts. Or app/assets/javascripts/backbone. Your choice. You can have many different files in whatever directories you like – in the end you’ll get one (or more, if you decide to) JS file that has everything your webapp need.

Don’t forget to:

  • gzip your JS/css assets
  • minify them!

Rails-backbone gem

Also makes things easier. It puts backbone.js (& underscore.js) library code into your asset pipeline. It gives you some great generators to get started if you don’t know where to start. Definitely worth including in your Gemfile.

http://rubygems.org/gems/rails-backbone

Coffeescript & underscore.js

Obviously. It makes Javascript code feel fresh. You can fold, iterate and do a lot of stuff. Be sure to check out underscore.js docs. And coffescript docs, of course, but I bet you know it already.

“V” in MVC done right

Views are classes. You handle logic inside them using the hardcore code, which is receiving so much hate in Rails views. They can have many methods, they do not have to render a single “action”. And templates are templates – and nothing else. There is a lot of discussion recently on the matter of Views/Templates in Rails, but you can start implementing it right away. Go and try it. You’ll fall in love.

You have many options for templates – eco, Handlebars.js, {{ mustache }}, but we recommend haml_coffee_assets. It gives you the HAML you (probably) already know with the ability to use coffeescript within it.

Single-page app

Yes. You will use Rails to render only the core view of the application. Of course, you will have to handle all things around the core of your app.

For example, you should probably leave out:

  • authentication logic: sign up / in, reset password
  • steps necessary to use the app: billing pages
But, other than that, you can use the views rendered in JS. What does that mean?
  • if they do not include any dynamic data, they are lightning fast
  • if they include dynamic data, you can preload it and they still will be lightning fast
Backbone comes with it’s own router. You can utilise pushState without any effort. How cool is that?

Asynchronous interface

Since you’ll be doing most of the association/validation work in background, you don’t have to worry too much about server responses. It’s the same with rendering elements: you’ll be doing it in JS, not Rails. You don’t have to wait for the request to return the rendered element to actually display them. If you create something, the UI should respond right away and the ajax request can persist the data with it’s own pace.

You should, however, prepare a global “something went wrong, reload the page” request rescuer – in case of network outages, backend app failures and so on. But it’s way easier to manage.

API

You get it for free. It doesn’t have to be public, but if you decide to, it’s a matter of adding OAuth as the way of authenticating requests and it’s done. (Or you could go with http basic auth, which is obviously bad for a lot of reasons).

With smart caching (give redis-store a try), you’ll save tons of server power. You know, relating to the old ruby saying, “views are most resource-consuming”.

Conclusion

There are, obviously, downsides, which I’ll cover in future (I silently hope that there won’t be any reason to do that). And topics I haven’t covered. I’m curious about other stacks and ways of doing things. Share in the comments or ping me on Twitter!

Chosen: multiple choice select inputs made easy

Link

http://harvesthq.github.com/chosen/

Great tool for enhancing selects, multiple selects, grouped selects etc. made by the Harvest team (great timetracking tool we’re using on a daily basis).
We’re all about using native controls as much as possible, but multiple select boxes were always a non-usable pain in the ass. This one is a great solution in tight areas where you can’t afford displaying a list with checkboxes.

 

 

 

Rails 2.2.2, Ajax and respond_to

As I wrote some time ago in the article about Rails, Ajax and jQuery, sometimes there are problems with Rails not interpreting correctly content type headers of ajax requests. It’s because not all web browsers send that header in the same way. 

What I proposed was to sort the request.accepts array (array containing content type headers sent by browser) so that xml content type would be the first element. That would then trigger format.xml in our respond_to block.

However that approach does not work in Rails 2.2.2, because now the request.accepts array is frozen and it cannot be modified. I spent some time googling for the solution, but with no effect. So I dived into the API and Rails’ source code and came up with pretty nice and simple solution to the problem.

class ApplicationController < ActionController::Base
  before_filter :xhr_to_xml
 
  def xhr_to_xml
    request.format = :xml if request.xhr?
  end
end

This piece of code is an equivalent of the snippet I proposed in the article I referred to at the beginning. Now all ajax request will trigger format.xml in respond_to blocks.

Rails, Ajax and jQuery

The more ajaxified application, the more fun it is to use. But it is also more painful do develop. What is written below is my approach to pairing Rails and Ajax. It’s a mix of tips I found over the net on blogs and forums. I use jQuery for JavaScript, but I don’t use jRails or any JS/Ajax helper methods provided by Rails. Note that all Javascript/HTML code presented here can be used even if you dont use Rails or Ruby as your web development platform. Let’s begin.

Rails is RESTful

Thanks to Rails’ RESTfulness the only thing to take care of server side is setting proper response in controllers’ actons.

class PostsController < ActionController::Base
  def index
    @posts = Post.find :all
    respond_to do |format|
      format.html
      format.xml { render :xml => @posts.to_xml }
    end
  end
end

Rails decides which format block to call basing on routes defined in routes.rb file (map.connect ‘:controller/:action/:id.:format’) and accept headers sent with request by the client.

In most cases we want Ajax requests to trigger format.xml blocks in our controllers’ actions, so we need to set proper accept headers. Let’s do it just once with application-wide setting.

// All ajax requests will trigger the format.xml block
// of +respond_to do |format|+ declarations
$.ajaxSetup({
  'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/xml")}
});

Browsers’ quirks

There is something worth noting here, a problem I had once with IE and Safari. The code above may work differently in various browsers. Browser set text/html accept header by default. Here IE and Safari will append text/xml to it so you’ll get something like ‘text/html; text/xml’, while Firefox will replace text/html with text/xml and you’ll get ‘text/xml’ only. This is very important because Rails will take the first format it detects in accept header and trigger respective block in controller’s action, which will be html for IE and Safari. Here’s a fix for this that shifts application/xml (if it is present) to the beggining of accept headers array.

class ApplicationController &lt; ActionController::Base
  before_filter :correct_safari_and_ie_accept_headers
 
  def correct_safari_and_ie_accept_headers
    request.accepts.sort!{ |x, y| y.to_s == 'application/xml' ? 1 : -1 } if request.xhr?
  end
end

Ajaxify your links

Here’s a quick way to ajaxify your existing links. Add this JavaScript to your application.js file.

jQuery(document).ready(function() {
  // All A tags with class 'get', 'post', 'put' or 'delete' will perform an ajax call
  jQuery('a.get').livequery('click', function() {
    var link = jQuery(this);
    $.get(link.attr('href'), function(data) {
      if (link.attr('ajaxtarget'))
        jQuery(link.attr('ajaxtarget')).html(data);
    });
    return false;
  }).attr("rel", "nofollow");
 
  jQuery('a.post').livequery('click', function() {
    var link = jQuery(this);
    $.post(jQuery(this).attr('href'), "_method=post", function(data) {
      if (link.attr('ajaxtarget'))
        jQuery(link.attr('ajaxtarget')).html(data);
    });
    return false;
  }).attr("rel", "nofollow");
 
  jQuery('a.put').livequery('click', function() {
    var link = jQuery(this);
    $.post(jQuery(this).attr('href'), "_method=put", function(data) {
      if (link.attr('ajaxtarget'))
        jQuery(link.attr('ajaxtarget')).html(data);
    });
    return false;
  }).attr("rel", "nofollow");
 
  jQuery('a.delete').livequery('click', function() {
    var link = jQuery(this);
    $.post(jQuery(this).attr('href'), "_method=delete", function(data) {
      if (link.attr('ajaxtarget'))
        jQuery(link.attr('ajaxtarget')).html(data);
    });
    return false;
  }).attr("rel", "nofollow");
 
  jQuery('a.get, a.post, a.put, a.delete').removeAttr('onclick');
});

Just add a CSS class .get, .post, .delete, or .put to a link to make turn it into an ajax-link. I recommend you use LiveQuery plugin which will automatically bind click events to new links that appear on the page (loaded with Ajax call for-example). You can optionally set ajaxtarget attibute of the link. It expects a selector of a container in which you want to place the response.

link_to 'my cool article', article_path(@article), :class => 'get', :ajaxtarget => '#article_container'

Ajaxify your forms

For this you’d need jQuery Form Plugin.

  jQuery('form.ajax').livequery('submit', function() {
    jQuery(this).ajaxSubmit();
    return false;
  });

Now all your forms that have “ajax” class will be submitted via Ajax.

<form class="ajax">
  ...
</form>

CSRF and authenticity token

Rails has built-in protection from cross-site request forgery attacks. It relies on an authenticity token which Rails look for when dealing with POST, PUT or DELETE requests, so this token needs to be sent by the browser together with the request. The token is automatically added as a hidden field to any form you create with form_for method, it is also attached to links that have :method param set to :post, :put or :delete. In fact the token is added dynamically by Javascript code placed in link’s onclick attribute. However in one of code snippets above we stripped that onclick attribute from links to prevent the page reload after we click the link. Now we need to attack that token ourselves. First we will alter our application layout:

<head>
  <% if protect_against_forgery? %>
    <script type='text/javascript'>
    //<![CDATA[
      window._auth_token_name = "#{request_forgery_protection_token}";
      window._auth_token = "#{form_authenticity_token}";
    //]]>
    </script>
  <% end %>
</head>

Now we need to ensure that the token is sent together with ajax requests.

jQuery(document).ready(function() {
  // All non-GET requests will add the authenticity token
  // if not already present in the data packet
  jQuery("body").bind("ajaxSend", function(elm, xhr, s) {
    if (s.type == "GET") return;
    if (s.data && s.data.match(new RegExp("\\b" + window._auth_token_name + "="))) return;
    if (s.data) {
      s.data = s.data + "&";
    } else {
      s.data = "";
      // if there was no data, $ didn't set the content-type
      xhr.setRequestHeader("Content-Type", s.contentType);
    }
    s.data = s.data + encodeURIComponent(window._auth_token_name)
                    + "=" + encodeURIComponent(window._auth_token);
  });
});

We’re done, we have our ajax requests protected from CSRF attacks.

Modifing page after Ajax calls

Standard way to do page modification after Ajax call is to use Javascript code that inserts content returned by the call somewhere on the page. The other method is to put the modifying code in views that are returned by the server and just execute it in the browser. For this I’d recommend another jQuery plugin – Taconite. As the author says: “The jQuery Taconite Plugin allows you to easily make multiple DOM updates using the results of a single AJAX call. It processes an XML command document that contain instructions for updating the DOM”. Thanks to this you can for example easily use flash messages in your Ajax views.

Let this be a part of your usual layout:

<div id="flash_notice" class="flash"<%= ' style="display: none"' unless flash[:notice] %>><%= flash[:notice]  %></div>

Now let this be your taconite layout you’d use when returning views for Ajax requests:

<taconite>
  <hide select="#flash_notice" />
  <% if flash[:notice] %>
    <replaceContent select="#flash_notice">
      <%= flash[:notice] %>
    </replaceContent>
    <fadeIn select="#flash_notice" arg1="slow" />
  <% end %>
  <%= yield %>
</taconite>

This will display flash notice messages with fade-in effect after Ajax requests. Similarly you can update other elements of the page.

What’s in your toolbox?

I would love to hear from you on how you deal with Ajax in your web applications. What libraries/plugins do you use?