Unobtrusive JavaScript via AJAX in Rails

Written by: Daniel P. Clark
9 min read

For now, the main way to add dynamic content to a webpage is with JavaScript. Ideally, we want to update a site's contents from the server without reloading the page. Let's take a look at how we can accomplish this with AJAX in Rails.

Now, many of the brief AJAX examples I've come across show variations of retrieving collections of data from the server. You would then handle that data in JavaScript to manipulate your page. The logical conclusion seems to be to learn a front-end JavaScript framework, but I've never liked the idea of having to write a separate front end and back end to a website when it would be simplest to have just one code base for one site. This really seems like overkill for web design.

But then I came across UJS in Rails, and it solved my need to have just one code base for website design. Unobtrusive JavaScript is generally known as writing your JavaScript outside of your HTML's page structure so as to not pollute your HTML structure.

Overall, I was quite surprised at how easy it is in Rails to have a dynamic website. The way Rails has incorporated AJAX does more than what my earlier research had suggested AJAX was for and what it could do. The beauty of it is that I can run my JavaScript from the server on the client's browser without any complicated AJAX response processing. This makes me happy.

Now along with the benefit of having one code base, we also have jQuery included in Rails. jQuery offers a higher level of JavaScript methods designed to work the same across all browsers, and it allows for cleaner and more legible code. If you add Twitter's Bootstrap with this, you can also integrate many common components into your user's experience that are also cross-browser compliant. If you style your website with jQuery and Bootstrap, you can worry less about what the site will look like in each browser and spend more time thinking about the user's experience.

Rails' AJAX

In Rails, submitting an AJAX request can be done as easily as adding remote: true to a link, button, or form. From there you can have any response be some JavaScript code waiting on the server side, and it will execute in the client's browser. Here's the simplest code example of UJS via AJAX in a link.

Assuming you have a resource generated in Rails, let's modify the show route to pop up an alert of the current ID set in a parameter. We'll call the resource Thing. The show resource for Thing routes from /things/:id, so /things/1 would set the parameter :id to 1. Make sure the controller's before_action doesn't set_thing for the :show method for this example. Modify the :show method as follows in ThingsController.

# /app/controllers/things_controller.rb
class ThingsController < ApplicationController
  def show
    render js: "alert('The number is: #{params[:id]}')"
  end
end

In the index page view for Thing, we'll create a link that calls the show resource via AJAX.

<%# /app/views/things/index.html.erb %>
<%= link_to 'Number Alert', thing_path(42), remote: true %>

When you visit your index page, you will see a link that says Number Alert. Click it, and an AJAX request is sent to the show route for Thing. The :id parameter is set from the router, and the controller renders the JavaScript response and alerts the user that "The number is: 42."

And there's the beauty of it. You didn't have to write anything to handle an AJAX response. You simply wrote JavaScript, and it just works. But it gets better.

Unless you're writing some very short JavaScript responses, you won't want to be putting JavaScript directly in your controller. If we rename our default show template for Thing from show.html.erb to show.js.erb, we can drop our JavaScript in there, remove the render command from the controller, and we get the same behavior.

# /app/controllers/things_controller.rb
class ThingsController < ApplicationController
  def show
  end
end

And the view for show with erb substitution would be written as:

<%# /app/views/things/show.js.erb %>
alert('Number <%= params[:id] %>')

Clicking the link will produce the same result: "The number is: 42."

Targeted Changes

To create the dynamic content we want, we need to target the individual areas of our webpage that we'd like to change. For this, we'll start using jQuery with CSS selectors to select the part of the page to change. CSS selectors refer to HTML document parts by either their pure HTML tag name, their id label proceeded by a pound sign, or class proceeded with a dot. We'll use the index page for the Thing model as an example.

<%# /app/views/things/index.html.erb %>
<%= link_to 'Change Below', thing_path(42), remote: true %>
<div id="target-for-change">
  Now you see "ME"!
</div>

Next we'll create a partial HTML page for replacing the content inside the div tags.

<%# /app/views/things/_show.html.erb %>
Now you don't!

In our show template, we'll target the div id for the index page and render that partial content as HTML inside it.

<%# /app/views/things/show.js.erb %>
$("#target-for-change").html("<%= j render(partial: 'show') %>");

If you click the Change Below link, the content changes instantly from Now you see "Me"! to Now you don't! 

There's a lot happening here, so let me clarify the different parts in show.js.erb. First of all, the dollar sign is a JavaScript reference to jQuery; the parenthesis that follows allows you to select a part of your webpage with a CSS selector. In this case, we're selecting anything that has the id of target-for-change. 

Next, we call the jQuery method on the selected part of the page to replace the HTML within the selected content to the string provided. The ERB part you see with render(partial: 'show') gets the HTML from _show.html.erb, and the j method string-escapes it in order to be acceptable as a proper string value in JavaScript. Anytime you render a "partial," the file name will always be proceeded with an underscore so there is no confusion between _show.html.erb and show.js.erb in template rendering.

Design

Now that you have the ability to dynamically update the content of your website with one code base, the possibilities are endless. You can defer labor-intensive calculations to be displayed on a webpage after the page has already loaded, such as calculating unread messages or connection requests. If you have the server query the database to count all the records before sending the page, it will slow down page load time and create a bad user experience.  

If instead you let the page load quickly and then trigger an AJAX request for a "slow" query, you won't interfere with the user's experience. Rather you'll improve it, and the content will update in no time.

When creating custom routes for your UJS requests, it's important to make sure you're using the right method of request. I generally like to use the PATCH method for UJS requests. Along with adding remote: true to my links, buttons, or forms, I also add method: :patch.

# /config/routes.rb
Rails.application.routes.draw do
  resources :things
  scope :ujs, defaults: { format: :ujs } do
    patch 'thing_totals' => 'things#totals'
  end
end

I like to both scope UJS to /ujs routes and declare in the routes that the format is :ujs. This isn't strictly necessary, but I believe it's important to be clear about the intent of the routes in use. The relevant part of the code here is simply the patch method, which routes to our ThingsController#totals method.

# /app/views/things_controller.rb
class ThingsController < ApplicationController
  def totals
    value = 42 # Some expensive database query
    render js: "$('#dashboard-totals').html('#{value}')"
  end
end

Next I'll demonstrate an index page with a dashboard that delays retrieving the totals from the server until after the page is loaded.

<%# /app/views/things/index.html.erb %>
Dashboard totals: <span id="dashboard-totals"><%=
  link_to ".",
  url_for({controller: "things", action: "totals"}),
  method: :patch, remote: true,
  class: "dashboard-totals"
%></span>
<%= javascript_tag '$(".dashboard-totals").click();' %>

When the page first loads, the dashboard totals show a period as a link: Dashboard totals: .

But once the page has fully loaded, the JavaScript from the javascript_tag gets executed. Using jQuery, it selects everything with the class of dashboard-totals and then hits the link. This submits the AJAX request for the link to the right controller action via the patch method, and the server returns the JavaScript response. This replaces the content inside the span tag with the appropriate value since it's identified as dashboard-totals. So the page seems to immediately have the value 42 for the dashboard total. This is a great tool for deferred content loading!

Bootstrap

Bootstrap has many kinds of components you can inject into your page, some of which need to be called via a JavaScript method call to be seen, like the modal.  

A modal in Bootstrap is like a windowed dialog that grays out your webpage as it comes into focus. When you insert the HTML code into your page for a modal via an AJAX request, it's not visible until you activate it. Since the code won't interfere with your page's layout, you may add an HTML tag to your main Rails layout page near the end of the body's content -- something simple, like <div id="target-modal"></div>. This won't show up on your page, and once you fill the content with the modal, there is still no visible change to your page.  

The details for how to implement all of Bootstrap's various features are available here. But as for the UJS view, you can simply enter another line to call the modal, and it promptly displays itself.

$("#target-modal").html("<%= j render(partial: 'suggestions/new_suggestion') %>");
$('#newSuggestionModal').modal();

Another Bootstrap component I like using in order to dynamically load content is collapsible items. I defer some of the page's content with expand to see more links, which will grab the information from the server, fill in the content, and then slide open the content in place as if it were hidden under a rug. This way you don't have to query the database for so much information all at once and make a big slow view. For example, you can show previews for each contact with details available via a click.

Summary

Anything you thought you would need a JavaScript front end framework for in Rails, you actually don't. Rails really stands up as an all-in-one solution. You can also add the ability to dynamically write JavaScript with Opal or CoffeeScript, and then you're in quite a nice place to be. 

Having one code base just makes sense when it comes to web design. And with Rails, you can have as dynamic a site as you want with a simple JavaScript template and the unseen AJAX communications all handled for you.

PS: If you liked this article you might also be interested in one of our free eBooks from our Codeship Resources Library. Download it here: Efficiency in Development Workflows

Stay up to date

We'll never share your email address and you can opt out at any time, we promise.