Using Unobtrusive JavaScript and AJAX with Rails 3

Using Unobtrusive JavaScript and AJAX with Rails 3

Tutorial Details
    • Topic: Rails 3
    • Difficulty: Intermediate
    • Estimated Completion Time: 1 hour

As I mentioned in my previous Ruby on Rails tutorial, Unobtrusive JavaScript (UJS) is one of the coolest new features in Rails 3. UJS allows Rails-generated code to be much cleaner, helps separate your JavaScript logic from your HTML layouts, and uncouples Rails from the Prototype JavaScript library. In this tutorial, we’re going to look at these features and learn how to use them in a simple Rails 3 application.


Background: What is Unobtrusive JavaScript?

To start off, what exactly is UJS? Simply, UJS is JavaScript that is separated from your HTML markup. The easiest way to describe UJS is with an example. Take an onclick event handler; we could add it obtrusively:

<a href='#' onclick='alert("Inline Javscript")'>Link</a>

Or we could add it unobtrusively by attaching the event to the link (using jQuery in this example):

<a href='#'>Link</a>
<script>
$('a').bind('click', function() {
    alert('Unobtrusive!');
}
</script>

As mentioned in my introduction, this second method has a variety of benefits, including easier debugging and cleaner code.

“Rails 3, on the other hand, is JavaScript framework agnostic. In other words, you can use your JavaScript framework of choice, provided a Rails UJS implementation exists for that framework.”

Up until version 3, Ruby on Rails generated obtrusive JavaScript. The resulting code wasn’t clean, but even worse, it was tightly coupled to the Prototype JavaScript framework. This meant that unless you created a plugin or hacked Rails, you had to use the Prototype library with Rail’s JavaScript helper methods.

Rails 3, on the other hand, is JavaScript framework agnostic. In other words, you can use your JavaScript framework of choice, provided a Rails UJS implementation exists for that framework. The current UJS implementations include the following:

Rails 3 now implements all of its JavaScript Helper functionality (AJAX submits, confirmation prompts, etc) unobtrusively by adding the following HTML 5 custom attributes to HTML elements.

  • data-method – the REST method to use in form submissions.
  • data-confirm – the confirmation message to use before performing some action.
  • data-remote – if true, submit via AJAX.
  • data-disable-with – disables form elements during a form submission

For example, this link tag

<td><a href="/posts/2" class="delete_post" data-confirm="Are you sure?" data-method="delete" data-remote="true" rel="nofollow">Destroy</a></td>

would send an AJAX delete request after asking the user “Are you sure?.”

You can imagine how much harder to read that would be if all that JavaScript was inline.

Now that we’ve reviewed UJS and how Rails implements UJS, let’s set up a project and look at some specific applications. We’ll be using the jQuery library and UJS implementation in this tutorial.


Step 1: Setting up the Project

Since we’re creating a new project from scratch, the first thing we need to do is create the project by typing the following:

	rails new blog --skip-prototype

Notice that I’m instructing Rails to skip the prototype JavaScript file, since I’m going to be using the jQuery library.

Let’s start the server just to make sure everything appears to be working.

And, voila!

Now that we’ve set up our project, we need to add jQuery and the jQuery UJS to our project. You are free to organize your JavaScript however you want, but the Rails convention for structuring your JavaScript files is as follows (all these files go in public/javascripts):

  • framework JavaScript file (jquery.js, prototype.js, or mootools.js)
  • rails.js – the code implementing rails UJS (for whatever framework you’ve chosen)
  • application.js – your application JavaScript

If you haven’t already, download jquery.js (or refer to a CDN) and rails.js and include them in your public/javascripts directory.

The last thing we need to do to get up and running is to actually tell Rails to include these js files on each of our pages. To do this, open application.rb in your config directory and add the following line

config.action_view.JavaScript_expansions[:defaults] = %w(jquery rails application)

This configuration item tells Rails to include the three JavaScript files mentioned above by default.

Alternatively, you could grab jQuery from a CDN (i.e. http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js) by manually included a script tag pointing to the correct location. If you do this, be sure to remove ‘jquery’ from the JavaScript_expansions configuration item.


Step 2: Generating Some Code

To demonstrate the rails UJS functionality, we’re first going to have to have some code to work with. For this demo we’re just going to have a simple Post object. Let’s generate that now

	rails generate scaffold Post name:string title:string content:text

And then let’s migrate our database to create the posts table.

	rake db:migrate

Ok, we’re good to go! If we navigate to http://localhost:3000/posts/new, we should see a form to create a new Post.

Ok, it’s all working! Now let’s dig in and see how to use the UJS and AJAX functionality baked into Rails.


Step 3: Adding AJAX

Now that all the required JavaScript files are being included, we can actually start using Rails 3 to implement some AJAX functionality. Although you can write all of the custom JavaScript that you want, Rails provides some nice built-in methods that you can use to easily perform AJAX calls and other JavaScript actions.

Let’s look at a couple of commonly used rails helpers and the JavaScript they generate

AJAX Form Submission and Javascript ERB Files

If we look at our Posts form, we can see that whenever we create or edit a Post, the form is manually submitted and then we’re redirected to a read-only view of that Post. What if we wanted to submit that form via AJAX instead of using a manual submission?

Rails 3 makes it easy to convert any form to AJAX. First, open your _form.html.erb partial in app/views/posts, and change the first line from:

<%= form_for(@post) do |f| %>

to

<%= form_for(@post, :remote => true) do |f| %>

Prior to Rails 3, adding :remote => true would have generated a bunch of inline JavaScript inside the form tag, but with Rails 3 UJS, the only change is the addition of an HTML 5 custom attribute. Can you spot it?

<form accept-charset="UTF-8" action="/posts" class="new_post" data-remote="true" id="new_post" method="post">

The attribute is data-remote="true", and the Rails UJS JavaScript binds to any forms with that attribute and submits them via AJAX instead of a traditional POST.

That’s all that’s needed to do the AJAX submit, but how do we perform a callback after the AJAX call returns?

The most common way of handling a return from an AJAX call is through the use of JavaScript ERB files. These work exactly like your normal ERB files, but contain JavaScript code instead of HTML. Let’s try it out.

The first thing we need to do is to tell our controller how to respond to AJAX requests. In posts_controller.rb (app/controllers) we can tell our controller to respond to an AJAX request by adding

format.js

in each respond_to block that we are going to call via AJAX. For example, we could update the create action to look like this:

def create
    @post = Post.new(params[:post])

    respond_to do |format|
      if @post.save
        format.html { redirect_to(@post, :notice => 'Post created.') }
        format.js
      else
        format.html { render :action => "new" }
        format.js
      end
    end
end

Because we didn’t specify any options in the respond_to block, Rails will respond to JavaScript requests by loading a .js ERB with the same name as the controller action (create.js.erb, in this case).

Now that our controller knows how to handle AJAX calls, we need to create our views. For the current example, add create.js.erb in your app/views/posts directory. This file will be rendered and the JavaScript inside will be executed when the call finishes. For now, we’ll simply overwrite the form tag with the title and contents of the blog post:

	$('body').html("<h1><%= escape_javaScript(@post.title) %></h1>").append("<%= escape_javaScript(@post.content) %>");

Now if we create a new Post we get the following on the screen. Success!

The advantage of this method is that you can intersperse ruby code that you set up in your controller with your JavaScript, making it really easy to manipulate your view with the results of a request.

AJAX Callbacks Using Custom JavaScript Events

Each Rails UJS implementation also provides another way to add callbacks to our AJAX calls – custom JavaScript events. Let’s look at another example. On our Posts index view (http://localhost:3000/posts/), we can see that each post can be deleted via a delete link.

Let’s AJAXify our link by adding :remote=>true and additionally giving it a CSS class so we can easily find this POST using a CSS selector.

<td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete, :remote=>true, :class=>'delete_post' %></td>

Which produces the following output:

<td><a href="/posts/2" class="delete_post" data-confirm="Are you sure?" data-method="delete" rel="nofollow">Destroy</a></td> 

Each rails UJS AJAX call provides six custom events that can be attached to:

  • ajax:before – right before ajax call
  • ajax:loading – before ajax call, but after XmlHttpRequest object is created)
  • ajax:success – successful ajax call
  • ajax:failure – failed ajax call
  • ajax:complete – completion of ajax call (after ajax:success and ajax:failure)
  • ajax:after – after ajax call is sent (note: not after it returns)

In our case we’ll add an event listener to the ajax:success event on our delete links, and make the deleted post fade out rather than reloading the page. We’ll add the following JavaScript to our application.js file.

	$('.delete_post').bind('ajax:success', function() {
		$(this).closest('tr').fadeOut();
	});

We’ll also need to tell our posts_controller not to try to render a view after it finishes deleting the post.

  def destroy
    @post = Post.find(params[:id])
    @post.destroy

    respond_to do |format|
      format.html { redirect_to(posts_url) }
      format.js   { render :nothing => true }
    end

Now when we delete a Post it will gradually fade out.


Conclusion

Well, there you have it. Now you know how to make AJAX calls using Rails 3 UJS. While the examples explained were simple, you can use these same techniques to add all kinds of interactivity to your project. I hope you’ll agree that it’s a big improvement over previous versions, and that you’ll try it out on your next Rails project.

What techniques do you use when implementing AJAX in Rails?

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://ronnieaarebrot.com Roaa

    Nice article, cheers!

  • Dave

    There are a number of errors in this tutorial.

    The class is ‘delete_post’

    $(‘.delete_post’).bind(‘ajax:success’, function() {
    $(this).closest(‘tr’).fadeOut();
    });

    The .js ERB should be contain escape_javascript, not escape_JavaScript

    $(‘body’).html(“”).append(“”);

    • http://incisivepoint.com Jack Starr

      I have some problem in this line…

    • alexkad

      Thanks! Lower case helps me!

    • http://www.domainsuperstar.com John Gadbois
      Author

      Thanks for catching that Dave, the uppercase J got in there when I searched and replace to keep casing consistent in the tuturial.

      • Al Brown

        Would be great to have the errors corrected in the text.

        Thanks.

  • http://grigio.org grigio

    Nice article. Unfortunatly some features like observe_field aren’t avalaible anymore out-of-the-box

    • luchol

      you should try lowpro for jquery or prototype

      • http://artenlinea.com miguel michelson

        yeah ! lowpro rocks!

  • mike

    Is the ajax call not working for anyone else? i followed it exactly as it is above.

    • alexkad

      Yeah, it doesn’t work for me too.
      I used:
      rails 3.0.1
      jquery 1.4.2 and then 1.4.3
      safari 5 for mac

      Create button creates new post but ajax doesn’t work!

      • Americo

        Same issue Here.

      • http://www.punkgrok.org roman

        Also broken for me.

        rails 3.0.0
        jquery 1.4.2
        firefox 3.6.11 for mac

      • http://www.punkgrok.org roman

        rails sends back a 406 error to the ajax call. I’m not sure why.

    • http://www.druplitec.de Sebastian

      If you install this one (jquery):

      http://github.com/rails/jquery-ujs

      using automated install you HAVE to CHANGE this LINE:

      config.action_view.JavaScript_expansions[:defaults] = %w(jquery rails application)

      into

      config.action_view.JavaScript_expansions[:defaults] = %w(jquery jquery_ujs application)

      ;-)

      • http://www.druplitec.de Sebastian

        I mean:

        config.action_view.javascript_expansions[:defaults] = %w(jquery jquery_ujs application)

        of course.

        PLEASE UPDATE ARTICLE!!!! :-)

    • Holly

      I had that problem until I updated JQuery. I used this link instead of the one in the tutorial:

      ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js

      It worked just fine after I made that change, for me at least.

  • MDM

    Nice article even with the mistakes. Same thing using Prototype would be useful.

  • Jay

    Maybe you should make it clear that the ajax:success binding stuff in application.js should be placed inside the $(document).ready(…) or jQuery(function($) { … }), like:

    $(document).ready(function() {
    $(‘.deletePost’).bind(‘ajax:success’, function() {
    $(this).closest(‘tr’).fadeOut();
    });
    });

    Just adding it to application.js won’t work.

    • http://JohnGadbois John Gadbois
      Author

      Good point, Jay. Should have made that assumption clear.

    • http://www.bing.com/ Dontarrious

      JYlGRj BION I’m impressed! Cool post!

  • alexkad

    Dave’s comment (October 19, 2010 at 2:08 pm) helps me to find error.

    You should use escape_javascript instead escape_JavaScript.

    • http://www.domainsuperstar.com John Gadbois
      Author

      Sorry about that alexkad, it was a search and replace gone bad as I was editing the article.

  • alexkad

    BTW, John, thanks for this awesome tutorial!

  • http://charmingwp.com Ahmad

    Good tutorial thanks for introducing it

  • http://www.wype.fr Loïc

    Very good tutorial even with a few mistakes.

    Here’s what I had to do :

    Changed :
    config.action_view.JavaScript_expansions[:defaults] = %w(jquery rails application)
    By :
    config.action_view.javascript_expansions[:defaults] = ['jquery', 'rails']

    Changed :
    $(‘body’).html(“<h1><%= escape_javaScript(@post.title) %></h1>”).append(“<%= escape_javaScript(@post.content) %>”);
    By :
    $(‘body’).html(“<h1><%= escape_javaScript(@post.title) %></h1>”).append(“<%= escape_javaScript(@post.content) %>”);

    Changed :
    $(‘.delete_post’).bind(‘ajax:success’, function() {
    $(this).closest(‘tr’).fadeOut();
    });
    By :
    $(function(){
    $(‘.delete_post’).bind(‘ajax:success’, function() {
    $(this).closest(‘tr’).fadeOut();
    });
    });

    Hope I could help a few of you !

    Anyway thanks for the great tutorial :D

    • Koli

      Whats the different between the two line in your second correction?
      $(‘body’).html(“”).append(“”);

      • Koli

        Oh, i see, the javaScript shoud be javascript…

        Thanks to the tutorial, and the comments too…

      • Bransai

        I don’t the difference in the second change also. JavaScript is JavaScript, it didn’t change.

    • Steven

      I agree with these corrections. But it still didn’t work for me.
      What I did was create an empty destroy.js.erb file in app/views/posts folder.
      Then it worked. I don’t know why. :D

      Nice tutorial! ^_^a

  • Oscar B

    Awesome new features!!

    I do really love Rails but I haven’t time to learn it very well :(
    For me, it is the most clear, readable, separated MVC and nice code you can get from a framework.

  • http://top-wordpress.net Mr Pixel

    Wow, nice tutorial, thank you for sharing

  • Mark

    Can anyone please help me to get this to work with jquery (I can get it to work with prototype)?

    I was trying to do this in my application and was having a problem which I can trace to the request header not asking for .js. When I look at the request header in Firefox it shows accept “*/*”. In Safari it shows “application/xml,application/xhtml+xml,text/html”. Not sure why they would be different, but of course in both cases the controller is trying to render the html template instead of the .js template.

    I am having the exact same behavior following the steps here.

    The “data-remote=true” attribute is being supplied (this is my form tag):

    I am using the jquery-ujs from https://github.com/rails/jquery-ujs. I am including only that file and jquery.js in my header.

    My rails version is 3.0.0

    Thanks

    • Scatabrain

      I had the same issue until I submitted the for to “posts.js#create”. Then I get errors based on mismatched doc type and the jquery is returned as html not run as jquery. So not quite there yet.

      The case mistakes in the tutorial is frustrating in an otherwise very helpful post.

    • Rick

      Mark,

      Follow Epnur’s steps. I had to do the same thing.

      Follow the “installation steps” here: https://github.com/rails/jquery-ujs

      And remove this line from your application.rb file. It seems that using the jquery gem precludes the use of this line:

      config.action_view.javascript_expansions[:defaults] = %w(jquery rails)

      Once I took these steps to install the gem, it worked great for me.

    • Rick

      Mark,

      Follow Epnur’s steps. I had to do the same thing.

      Follow the “installation steps” here: https://github.com/rails/jquery-ujs

      And remove this line from your application.rb file. It seems that using the jquery gem precludes the use of this line:

      config.action_view.javascript_expansions[:defaults] = %w(jquery rails)

      Once I took these steps to install the gem, it worked great for me.

  • http://www.datakey.cc dsadaka

    I was just working on a drag-and-drop page and had started out using the Rails 2 approach (ala AWDwR chapter 24) when I ran across this.

    Did I understand that this new approach will only work in browsers supporting HTML 5?

    TIA,
    Dan

  • Problem with manual

    Using escape_JavaScript will not work.

    • kakipo

      escape_javascript would work

  • rubythekid

    (‘body’).html(“”).append(“”);

    to

    (‘body’).html(“”).append(“”);

  • http://www.7vals.com/ kashif

    its is very helpful for me because in now a days i am working on ajax.

  • http://aaroncruz.com Aaron

    This is a great article but why have you left the mistakes in? It’s still relevant but probably confuses the crap out of beginners. And the corrections in the comments also have mistakes. Yikes.

  • some fixes

    some info from here helped me:
    http://joshhuckabee.com/jquery-rails-3

    update the rails.js file from:
    https://github.com/rails/jquery-ujs
    (under src/rails.js)

    also, make sure that none of your jQuery statements have smart quotes in them.

  • Marcin

    remote => true in the following line:

    ‘Are you sure?’, :method => :delete, :remote=>true, :class=>’delete_post’ %>

    didn’t work for me (In index.html.erb)

    I just used:

    :delete, :class=>’delete_post’ %>

    and the AJAXified delete started working.

  • http://rails4me.heroku.com opix

    Many thanks, it’s helped me a lot!

  • ssr

    Many thanks to you for putting such a nice tutorial. Without this, I wouldn’t have followed UJS concept in Rails3.

  • http://devmoose.com JP

    Great tutorial!

    A lot of things are missing in this tutorial though :/
    Took me quite some time to go through the comments to get this to work.

    For those who are having trouble, you NEED to have rails.js included.
    Here’s my github repository for this tutorial: https://github.com/imjp/bluh

    Keep in mind that I’m using rails 3.1.0.rc4 so the javascript files are under app/assets/javascript
    The latest rails.js is in that folder as well.

  • Epnur

    Thanks for the tutorial !

    Was having problems making the first example work on rails 3.0.9 – Debian Squeeze.

    Turns out I had to :

    1 – Add “gem ‘jquery-rails’” to the Gemfile then run “bundle” command.
    2 – run the command “rails generate jquery:install”, as the github page suggests
    3 – Finally, I had to comment out the line
    “config.action_view.javascript_expansions[:defaults] = %w(jquery rails)”.

    This line seems unnecessary when using the jquery-rails gem.
    The jquery.js wasn’t getting properly sourced otherwise.
    The jquery-rails gem generates another needed file “jquery_ujs”.

  • http://www.dynamick.it Mick

    oh… this could help…
    I wrote a personal implementation of a ajaxed nested form for rails 3. Take a look:
    http://www.dynamick.it/rails-3-ajaxed-nested-form-4721.html

    Hope this could be interesting.

  • dave anderson

    I was glad to have found this tutorial – saved a lot of time

    i found one obstacle in my browser – jquery bind only attaches an event listener to items already in the dom – when i added a new row via ujs the event listener did not get attached, so ujs deletes were not possible for the newly added rows

    fortunately jquery provides an easy solution: all you need do is use the jquery “live” method instead of the jquery “bind method.

    $(document).ready(function() {
    $(‘.deletePost’).live(‘ajax:success’, function() {
    $(this).closest(‘tr’).fadeOut();
    });
    });

  • http://dominicsayers.com Dominic Sayers

    Using Rails 3.1.0.rc5, I got this working by adding the following ajax:success listener to posts.js.coffee in place of the raw jQuery in application.js:

    $(document).ready( ->
    $(‘.delete_bookmark’).live(‘ajax:success’, ->
    $(this).closest(‘tr’).fadeOut();
    );
    );

  • http://dominicsayers.com Dominic Sayers

    Using Rails 3.1.0.rc5, I got this working by adding the following ajax:success listener to posts.js.coffee in place of the raw jQuery in application.js:

    $(document).ready( ->
    $(‘.delete_bookmark’).live(‘ajax:success’, ->
    $(this).closest(‘tr’).fadeOut();
    );
    );

    • Steve

      Thanks a lot for the coffeescript Dominic!
      It works for me but I am confused by the .delete_bookmark in place of .delete_post. Does the class name change somehow after the successful delete? Can you explain?

  • Mike

    In your example you put your js code in application.js, which I assume is loaded for the entire app. Would you put there all of unobtrusive js code for the entire app and all the pages or how would you separate the js code for specific pages ?

  • James Amb

    whilst the ideas behind the tute is good, your mistakes in it make it unworkable!
    It just shows that you’re all talk and no go.

    The whole reason why people so badly need to look at this tutorial is to learn how AJAX works in Rails 3 (with Jquery).
    So , for goodness’ sake, fix up your code & test that it’s working and then publish this blog again OR take it down cause it’s polluting the webspace and misleading folks who are genuinely wanting to get Ajax working in their rails 3 apps.

  • juan Antonio Ruz

    for me, it has been necesary to include this ” $(document).ready(function() ”

    $(document).ready(function() {

    $(‘.delete_post’).bind(‘ajax:before’, function() {
    $(this).closest(‘tr’).fadeOut();
    });
    });

    thanks!

    • Josh

      you are right, thanks.

  • Luis

    Why the code in application.js doesn’t belong to a file like destroy.js.erb, like the one before that ?

  • http://www.designdevelopmaintain.com jataun

    Just starting to use Rails.

  • Jose

    Thank you very much for this tutorial. I was wondering if there were any differences to be made with the current version of rails which if I understand correctly comes with jQuery as a standard library and there have been some changes in parameters for some functions.

    I’m trying to get this to work with a submit and delete button on a current site im working on and I’m having a very hard time.

    This is the first time I’m working with ajax and jquery and finding more tutorials for them with rails in the current version is a little difficult right now.

  • http://gameaton.com/ zavis

    looking for books about rails, this tutorial is good though

  • Suresh

    Very nice explanation.Thank you very much

  • Dave

    In case anyone is interested this is a working version with Rails 3.2.3.

    https://github.com/nacengineer/rails-3.1-AJAX-Example

  • Joseph

    I can’t say how much I appreciate this tutorial/post! I struggled with AJAX for a day or two and having this working example really helped. Thanks a bunch! (p.s. – Finally got it working. Doing a little happy dance…)

  • Stonie

    This is full of bugs! (still contains typos pointed out well over a year ago)

    On top of all the other fixes, I needed the following line in application.js (rails 3.2.6)
    //= require jquery_ujs

    This article is so full of mistakes it does more harm than good! :-\

    ** NET TUTS Please REMOVE this article **

  • Francisco

    This post is great….

    But, I also need to know how return the ajax result to a item in a form… can you help me?

  • http://profiles.google.com/dana.tassler dana tassler

    Have you considered updating this to include implementation details for use with the asset pipeline?

    I haven’t gone through the tutorial yet (since I’m using the asset pipeline), but am looking forward to it. Thanks for the scoop!

  • frozzie

    Thanks for the tutorial. This got me started on Ajax after getting nowhere for the past couple of days.
    Dave thanks as well for that git hub repository.

  • Giulio

    Great tutorial, thanks a lot, there aren’t many resources on the network explaining how to do ajax with Rails. I was looking for this.

    How would you change this tutorial if you were to use the new syntax for the responses using “respond_with” ?

    thanks,

  • Giulio

    after experimenting lots, as this wasn’t even remotely working with rails 3, I’ve figured that the javascript callback functions are best put in the .js.coffee rather than in application.js.

    And the events (like “ajax:success”) are risen by the document (in chrome for ubuntu at least), rather than the button, so my file has became in coffeescript:

    $(document).ready(->
    $(document).on(“ajax:success”, (event)->
    $(event.target).closest(‘tr’).fadeOut();
    );
    )

    I’ve got one further problem here though, in my page I would like to bind more ajax powered buttons for doing different activities. These activities will all rise “ajax:success” event, but this code won’t distinguish which action generated the event. I’d like to keep the javascript here as simple as possible, any idea what could I do here?

    thanks,

  • Fernando Soto Villarán

    Dave’s example in Github worked for me.
    Thanks a lot!!