Pothibo

Responsive JavaScript in Rails: load what you need when you need it

I gave a talk about this a year ago. When I first saw this technique, it was @dhh showing some views they are using at 37Signals. I notice how they were using JavaScript inside their view template. It was focused, dynamically generated JavaScript. And it made me think.

Instead of loading javaScript up front like we all normally do, why not load the minimal amount of JavaScript up front and load additional JavaScript code when a user acts upon the page.

I call this responsive JavaScript, you may have heard about it by the term pjax, unobtrusive JavaScript, etc. The goal is that JavaScript is loaded from the backend in response to your actions. Whether it's a GET, POST, DELETE, etc. request, if that request is AJAX'ed, the response will be JavaScript. Not JSON.

There is already existing example of this concept in some product you might use. BCX, Github, Airbnb use different flavors of this concept. The problem with this concept is that it's not bound to any library or framework, you have to use your current tool in a certain way to make this happen. However, there's a library in JavaScript that might help you to get started. jQuery-UJS lets you add remote="true" to any link and it will be automatically transferred to an AJAX call.

First, an example

class PostsController < ApplicationController

def index

@posts = Post.all

end

def show

@post = Post.find params[:id].to_i

respond_to do |format|

format.js

format.html

end

end

def create

#.. Create a post

respond_to do |format|

format.js

format.html

end

end

end

    <%= @posts.each do |post| %>

    <%= render_link_to_post(post) %>

    <% end %>

app/views/create.js.erb

(function() {

var $post = $("<%= j render_link_to_post(@post) %>")

$post.hide().prependTo($("ul.posts")).fadeIn()

})()

app/views/show.js.erb

(function() {

var $post = $("<%= j render("overview", post: @post) %>")

$post.dialog("open")

})()

<%= @post.title %>

<%= @post.body %>

#helper.rb

def render_link_to_post(post)

link_to post.title, post_path(post), remote: true

end

In this example, when someone clicks on the link, it will call /posts/:id. From there, if the request is AJAX'ed, it will render show.js.erb and eval the code in the template. If not, it will call show.html.erb. There is also a template named created.js.erb that will be called when you create a resource through an AJAX form. The form was left out to keep the example as light as possible.

URL helps you locate your bug

So you have a bug when you click a link. Using this technique, you can start looking at the URL to know the origin.

posts/:id is associated to the JS template located at app/views/posts/show.js.erb, for example.

Exactly like what you would expect when dealing with HTML views.

Web pages are alive

Look at the example above. When you create a new post using an AJAX form, it will prepend the list with a link to your post. Look at the method used: It's the same as the one when you call /posts. It's not a fluke. You want to avoid code duplication because this way, ajax uses the same component a normal request would except that it injects the result in the current page.

This means that after you create the new post, if you refresh the page, it will show the same list of posts, because it uses the same mechanism.

Reusing the same view logic

This is an extension of the previous point. Since you generate your HTML on the server side, you will find a lot of code duplications in your views. It's going to be easy for you to DRY your code into helpers and decorators.

Since everything is generated from the same place, HTML modification is done in one place only. In the example, if I would like to replace the link in the list by a small description of each post, I could change the implementation details of render_link_to_post (Obviously a bad function name, bare with me here) to accomodate the changes.

The ajax behaviour and the normal URL request would then be updated to the new version.

JavaScript spread between actions & load page optimization

Now, your JavaScript is loaded in 2 different places:

  1. JavaScript served by sprockets. This is loaded at page load and cannot be customized at runtime. The JavaScript will be precompiled;
  2. Loading closure as the response of a user's action on a page.

Nice thing about this is that you have more control over the content you are serving. If you feel an action becomes sluggish because there's a lot of code being parsed, you can transfert the load by abstracting some code off to sprockets.

Load what you need, when you need it

Since a page is rendered on the server side, the need for you to have many libraries when loading the page will be minimal. Maybe you need jQuery, Zepto or whatever, but that's about it. JavaScript is almost (I don't know every use cases) never used to render a page at load time, only afterwards.

Look at the example above. create.js.erb is the one that fades the element in. I could have wrapped a nice function and all, but what's the point if people usually don't create new post.

This gives you a nice balance to play with. When something is used a lot (i.e more than once per user per page), load it with your other library using Sprocket like you would normally do. Everything else should be loaded along the HTML it acts upon.

Repeatable actions (debugging purposes)

Did you ever have a bug in your JavaScript code where you had to take 3-4 different actions before getting to the bug in your app? Because response are standalone JavaScript snippets, you can just copy the response and paste it in your console to recreate the bugged action.

What I do sometimes is that I remove the closure to allow me to access the variable that are in used. This way, I can narrow down a bug by manipulating variables inside the console.

Clarity

Client side JavaScript usually works with notifications or events. When you receive an update from the server, you trigger those events and everything falls into place instantly. It usually works great at first but can become quite hard to track down the road.

When the response is a JavaScript snippet, it's easier to understand the results. Look at the example above. I'm pretty sure you can understand the expected behaviour for the two ajax responses.

Important! CSRF protection

Vlado Cingel commented about some security concerns. When I wrote this, I knew CSRF token were passed with jquery_ujs so I expected rails to actually validates it. It turns out itdoesn't when it's a GET request, and it's how it's supposed to be.

Adding CSRF protection to your callback is quite easy (assuming you use Warden and/or Devise). Also, Rails 3 and 4 uses different implementation details handling CSRF but I believe this code below should work nonetheless. Consider that this code on Rails 4 will override the use of protection methods.

class ApplicationController < ActionController::Base

prepend_before_filter :verify_authenticity_token, :if => :js?

prepend_before_filter :authenticate!

protected

def js?

request.format.js?

end

def handle_unverified_request

warden.reset_session!

end

def verified_request?

!protect_against_forgery? || request.head? ||

form_authenticity_token == params[request_forgery_protection_token] ||

form_authenticity_token == request.headers['X-CSRF-Token']

end

end

That's it

You need to remember that this is how I do it, your mileage may vary. I hope you enjoyed this post and that it gave you the motivation to try it out yourself.

If you do try it out, give me a shout on twitter to tell me how it went!

[mailchimpsf_form]

Get more ideas like this to your inbox

You will never receive spam, ever.