Page Specific Javascript in Rails

UPDATE: I’ve written an updated article on this and the larger topic of organizing Javascript within a Rails project that includes Turbolinks.

The asset pipeline has made including javascript in Rails applications a breeze. Without any additional changes, the code included in your app is executed on every page. However, with a combination of CSS class scopes and a jQuery plugin, you can isolate certain javascript code to only run on specific pages.

The Problem

Imagine you have 2 pages in your application (landing and contact) and need an alert box to show up on the contact page only. How would you do it?

Traditionally, if it’s something small, you might bite the bullet and just include it in the view template:

<% # app/views/page/contact.html.erb %>

<p>This is the contact page</p>

<%= javascript_tag do %>
  alert("My example alert box.");
<% end %>

To me, this always felt dirty. Mixing UI and javascript concerns in an HTML template is easy to forget. Imagine your app has matured and want to change the message of the alert box. Rather than find an associated javascript file (the logical path), you’re forced to dig through HTML to find the alert box text.

Another solution is to insert our javascript in to its own file:

// app/assets/javascripts/alert.js

alert("My example alert box.");

And including this file only in the view we want it to execute:

<%# app/views/page/contact.html.erb %>

<%= javascript_include_tag "alert" %>

<p>This is the contact page</p>

And don’t forget to include your new file in the list of files to be compiled:

# config/environments/production.rb

config.assets.precompile += %w( alert.js )

Like the previous solution, it too, mixes the concerns of HTML and javascript, but introduces a new problem - this javascript file is included and download separately from the rest of the app’s asset. This causes the browser to make another request to the server, thus increasing the page load time and in turn, causing bad bad user pain (Ok, maybe not that bad…). But it’s still not ideal.

The Solution

The solution I’ve found the most elegant requires adding CSS classes to the layout’s body tag and using the jquery-readyselector plugin.

First, in order to scope the pages through CSS selectors, let’s add some classes to our layout:

<%# app/views/layouts/application.html.erb %>

<body class="<%= controller_name %> <%= action_name %>">
  <%= yield %>

Assuming your contact page action was inside a controller named PagesController, the rendered result would be the following:

<body class="pages contact">

Add this small bit of javavascript code to a new file: vendor/assets/javascripts/jquery-readyselector.js.

Include the new plugin in the application.js manifest file:

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require jquery-readyselector
//= require_tree .

Like before, let’s put the javascript code in an isolated asset file, but scope it to the page:

// app/assets/javascripts/

$("").ready ->
  alert "My example alert box."

Because this file is picked up with the //= require_tree . line in the application.js manifest, there’s no additional step to load the asset. And that’s it! Your fancy alert box will now only run on your contact page.

Let me what you think of this solution and if you’ve come across this issue before. I’d love to hear about other solutions!

If you’re looking for solutions that work with Rails 4 and Turbolinks, see my latest article on organizing Javascript in a Rails application that includes Turbolinks.