Unobtrusive JavaScript via AJAX in Rails

Development

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

Subscribe via Email

Over 60,000 people from companies like Netflix, Apple, Spotify and O'Reilly are reading our articles.
Subscribe to receive a weekly newsletter with articles around Continuous Integration, Docker, and software development best practices.



We promise that we won't spam you. You can unsubscribe any time.

Join the Discussion

Leave us some comments on what you think about this topic or if you like to add something.

  • Julie

    I’ve used this method before but found that it doesn’t keep a history of changes. So say someone clicks the back button, all of the user’s changes are gone. Do you have a good fix for that?

    • It sounds like you want to implement a undo/redo memory store for UJS actions. You can use your session store on the server or a cookie on the client side. Then add each UJS action called per page in a hash mapped by page. Make sure to remove UJS calls that revert things on the page (e.g. expanded content, then un-expanded. otherwise the page will dance) and then you may trigger the same UJS calls saved in the page state hash when the page is return to. It really depends on the content and what’s been done.

  • Clemens Kofler
    • As I understand it RJS is it’s own templating system “An RJS template generates JavaScript from Ruby code”. I haven’t seen it used since Rails 3. According to your first article it was extracted out in Rails 3.1. The file.js.erb format is used with the erb template engine to set values in prewritten JavaScript template file rather than generating JavaScript from Ruby. But I don’t believe this is the point you are making.

      In the articles you’ve listed it goes into detail about the dangers of eval-ing code from sources. This is always a concern for any kind of input. There should be stringent restrictions on any user input and what the server reveals should be specific. Cross site forgery, data injection, and data leaking seam to be the main concerns for website security from the user interface. These can be prevented.

      Rails 4 has added a cross-site forgery field to protect against potential cross site scripting. That is an added security feature which UJS utilizes.

      With due diligence on guarding input and the added benefit of preventing cross site forgeries it can give us better peace of mind about our own security.

  • Nice article! I agree that using a frontend framework is overkill for small webapps. However, the danger is that your application grows to the point where you need a frontend framework. At this point you need to rewrite everything – which is a difficult job.

    At what point would you recommend switching from using Rails to using a frontend framework?

  • Next version of Rails (5.1) doesn’t include jQuery. Sorry folks, it’s obsolete, we shouldn’t really be teaching jQuery anymore.

    • Vas

      Why is this? Jquery is a feature used by almost everybody, both directly and through gems like Turbolinks. I am all for using the latest and greatest and I know that the remote: feature isn’t being phased out, and that’s actually why I checked the comments after reading this, to see if this guide was still considered best practice because I heard remote was getting phased out.

      So my question is – what will replace this? I never used the remote feature because they said it was getting removed but I still used Ajax calls manually to get my controllers that rendered JSON strings I built in them. This happened in the past few weeks. Is UJS getting removed also? I never used it but I was planning to after reading this tutorial.

      When I read the Rails guide about forms, when it said the warning about remote deprecation, it did not say a recommended alternative.

      • Rails 5 still has UJS, but reimplemented without jQuery. The niche that jQuery filled was providing a cross-browser interface for DOM query and manipulation with a straightforward syntax. Those needs are now met by improvements in modern Javascript and in browser standards implementation.

        Ajax is not a jQuery specific capability.

        Nothing stops you re-adding jQuery back in if, say, you’re porting a Rails 4 app to Rails 5, but consider the modern web API (querySelectorAll, addEventListener etc) for new apps. Have a read of the rails-ujs source (https://github.com/rails/rails-ujs) for a worked example of doing Ajaxy business without jquery. And take a look at, say, Vue.js for a different and vastly more productive paradigm.