Rails 3 Remote Links and Forms Part 2: Data-type (with jQuery)
Continued from Rails 3 Remote Links and Forms: A Definitive Guide.

Since writing the Rails 3 Remote Links & Forms Definitive Guide, one question keeps coming up:
How can we make our remote links and forms retrieve JS.ERB, instead of an HTML partial?
In the last article, we requested an HTML partial to be inserted into the page by our AJAX callbacks. But what if we want JavaScript to be executed? Or JSON or XML to be parsed? Or plain text to be displayed?
Spoiler Alert: this article concludes with a complete working example using js.erb.
Equal parts Rails & jQuery
First, we must understand the :remote => true process in Rails 3. It’s equal parts Rails and jQuery magic. But don’t worry, it’s very little magic, bundled into a 4-step process:
- Rails tells jQuery, “Hey, bind your ajaxy goodness to this sweet little link/form,” with the
:remote => trueoption. - jQuery hi-jacks the element’s click/submit event and binds it to the
.ajax()method. Now that element submits via AJAX instead of loading a new page in the browser. - Rails receives the AJAX web request when the element is clicked/submitted and responds with some content.
- jQuery receives the response content with the
.ajax()method that hi-jacked our element, and provides callbacks for us to handle the response in the page.
In this article, we’re exploring the different ways we can specify the format of the AJAX response and handle it accordingly, which actually spans all 4 steps above.
Steps 1 & 2: Setting the data-type
When the browser sends web requests back and forth between the browser and the server, part of the request/response header can specify the format of the content. When loading a page in the browser, the content type is typically inferred from the extension in the URL. jQuery, though, can directly set the data-type desired in the AJAX request header.
jQuery allows dataType parameter
jQuery’s .ajax() method provides an optional parameter called dataType to specify the desired data-type of the response. This allows jQuery to specify the format type in the request’s HTTP Accept header, and then to encapsulate the response content in the appropriate data object for easier manipulation.
As of jQuery 1.4, if you do not specify the data-type of the response, jQuery will actually inspect the MIME type header of the response and make an “intelligent guess” as to the data-type, changing the data-type of the response object on-the-fly.
There’s more information on the .ajax() documentation page, but the basic types are:
| dataType | Behavior |
|---|---|
| “xml” | Returns an XML document that can be processed by jQuery. |
| “html” | Returns HTML as plain text, but evaluates any <script> tags included in the markup. |
| “script” | Evaluates the response as JavaScript and returns it as plain text. |
| “json” | Evaluates the response as JSON and returns a JavaScript object. |
| “jsonp” | Loads response in a JSON block using JSONP. |
| “text” | Returns a plain text string. |
Rails.js sets the dataType parameter
The Rails UJS driver sets our AJAX dataType from the data-type attribute we specified on our remote link or form. If we didn’t specify data-type explicitly for that element, then the default data-type is used from our global $.ajaxSettings. If we haven’t set that either, then a generic request is sent that will accept any type of response.
Older versions of the UJS driver would default to a data-type of ‘script’ rather than send a generic request. While this seemed like a sensible default, it would cause our Rails app to throw an exception if we didn’t have format.js defined in our controller action.
Newer versions of the UJS driver simply leave jQuery’s default dataType of ‘*/*’. This tells the server, “Give me whatever you’ve got.” However, this would make the controller respond with the first format that happens to be listed in the Responder (see next section). So if format.html is listed before format.js, the app will respond with the HTML response (which means it will try redirecting for POST or DELETE method AJAX requests). This isn’t ideal either.
So in the newest versions, we figured out how to set the default, such that it tells the server, “I’d prefer JS, but I’ll take whatever you’ve got.” Now, if format.js is defined at all in the available Responder formats, JS will be returned. If not, the controller will then respond with the first format listed. (See the discussion thread here.)
if (dataType === undefined) {
xhr.setRequestHeader('accept', '*/*;q=0.5, text/javascript');
}
Step 3: Rails responds (from the controller)
Now the AJAX request has been made, with the desired data-type specified in the header. Our Rails app receives the request, routes it to the appropriate controller action, and renders a response.
Our controller decides what content to render as the response, and how to format it. The respond_with and respond_to methods from the Rails Responder class inspect the Accept header of the request (set by dataType) to render the appropriate response. [2]
For object-based data-types, like JSON and XML, we’d typically serialize and return an object in the requested format. And this is exactly what the Responder does by default. And for content-based data-types like JS or HTML, we would usually render a js.erb or html.erb file.
Technically we could also have a custom json.erb or xml.erb template to render a custom data object as well. Responder will look for these templates and render them if they’re there.
Step 4: jQuery handles the response
From the last article, we bind our response-handling jQuery code to the ajax:success, ajax:error, and ajax:complete callbacks.
With our new understanding of the various data-types and how to set them, we can now modify our response callbacks to handle our desired data-type. In the last article, our callback bindings handled an HTML response.
Handling an HTML request:
.bind('ajax:success', function(evt, data, status, xhr){
var $this = $(this);
// Append response HTML (i.e. the comment partial or helper)
$('#comments').append(xhr.responseText);
// Clear out the form so it can be used again
$this.find('input:text,textarea').val('');
// Clear out the errors from previous attempts
$this.find('.errors').empty();
})
.bind('ajax:error', function(evt, xhr, status, error){
// Display the errors (i.e. an error partial or helper)
$(this).find('.errors').html(xhr.responseText);
});
We can easily change this now to handle a JSON response for example.
Handling a JSON request:
.bind('ajax:success', function(evt, data, status, xhr){
var $this = $(this);
// do something with 'data' response object
$this.find('input:text,textarea').val('');
$this.find('.errors').empty();
})
.bind('ajax:error', function(evt, xhr, status, error){
var responseObject = $.parseJSON(xhr.responseText),
errors = $('<ul />');
$.each(responseObject, function(){
errors.append('<li>' + this + '</li>');
})
$(this).find('.errors').html(errors);
});
And finally, to handle a JS request, we don’t actually need to bind any callback functions. Remember if dataType: 'script', jQuery automatically executes the response JavaScript on the page.
Handling a JS request:
If our response wasn’t automatically executed for us, our handler might look something like this:
eval(xhr.responseText);
});
Notice we’d bind to the generic ajax:complete callback, instead of the ajax:success|error callbacks. This is because our success/error handling is in the js.erb file.
The above handler isn’t exactly the same as the automatically-executed function. The function above would execute our script in the context of the $('#i-want-js') element, while jQuery automatically executes our script in the context of $(window).
Now let’s put it all together.
Working example using js.erb
Just like the last article, we’ll create a comment and submit it via AJAX. Except this time, we’ll respond with js.erb.
In the controller, we’ll add the ability to respond to requests via HTML, JS, or JSON.
comments_controller.rb
respond_to :html, :js
...
def create
@comment = Comment.new( params[:comment] )
flash[:notice] = "Comment successfully created" if @comment.save
respond_with( @comment, :layout => !request.xhr? )
end
end
If you’re new to the Rails 3 responder, the above is equivalent to:
...
def create
@comment = Comment.new( params[:comment] )
respond_to do |format|
if @comment.save
flash[:notice] = "Comment successfully created"
format.html { redirect_to(@comment), :layout => !request.xhr? }
format.js { render :js => @comment, :status => :created, :location => @comment, :layout => !request.xhr? }
else
format.html { render :action => "new", :layout => !request.xhr? }
format.js { :js => @comment.errors, :status => :unprocessable_entity }
end
end
end
Now, we’ll set up our index page to list all comments, and include our AJAX form to create a new comment. We don’t need to include any data-type HTML5 attribute in our form, since the Rails UJS will prefer JS by default.
index.html.erb
<%= form_for :comment, :remote => true, :html => { :id => 'new-comment-form' } do |f| %>
<%= f.text_area(:body) %>
<div class="errors"></div>
<%= f.submit %>
<% end %>
And finally, we’ll need to create our js.erb template which will execute and insert our response into the page.
create.js.erb
<% if @comment.errors.any? %>
// Create a list of errors
var errors = $('<ul />');
<% @comment.errors.full_messages.each do |error| %>
errors.append('<li><%= escape_javascript( error ) %></li>');
<% end %>
// Display errors on form
el.find('.errors').html(errors);
<% else %>
// We could also render a partial to display the comment here
$('#comments').append("<%= escape_javascript(
simple_format( @comment.body )
) %>");
// Clear form
el.find('input:text,textarea').val('');
el.find('.errors').empty();
<% end %>
That’s it!
Again, we don’t need to bind to any of our AJAX callbacks here. It just works.
Sometimes, the JavaScript response does not execute as it should, and instead just returns the response as a string. This usually means there is some malformed JavaScript somewhere in the response. It’s annoying, but it won’t throw any visible JavaScript errors from the automatically-executed response.

[...] UPDATE: Check out Part 2 of this article: Rails 3 Remote Links and Forms Part 2: Data-type (with jQuery). [...]
Wouldn’t you want
not
?
Ah yes, thanks Jeff, good catch. Fixed.
Really terrific article. Thanks for delving into the details!
It seems that
doesn’t work when passed as parameter to respond_with.
Even
doesn’t work as expected.
You’re right, it doesn’t. I’ve actually raised this point on the Rails core mailing list. I guess the Rails opinion here is that the options passed in through
respond_withare meant for the API responses (i.e. everything but html format), and so passed options do not apply to navigational responses (i.e. html format), so that developers don’t unintentionally break them.This works for options like
:status, but obviously if we’re passing in:layout, then we intend it to work for the html format specifically. I will be trying to submit a patch to Rails to make the:layoutoption work at least when I get a chance.In create.js.erb should
Instead be
?
Also, in ‘index.html.erb’ form_for :billable, should be form_for @billable ?
Yes, @test_comment was a copy/paste mistake from one of my test apps. Thanks!
For the :comment vs @comment, it can be, but doesn’t have to be.
Making it @comment will allow the form to be populated with the values of @comment, which only matters if the form is submitted non-ajax style and there is an error saving where the form has to be re-rendered, or if you want to reuse the same form for editing an existing comment.
If the form is submitted ajax-style, then it doesn’t matter, because the form would just stay filled in since the page doesn’t refresh. We could still use @comment to make it more usable if JS is disabled.
However, the downside is that, for it to be @comment, we’d have to initialize a new comment with
@comment = Comment.newin the new view (not a huge deal, but since I left out the new action in this article, I left out the @comment).Thanks!
I only mentioned the @comment bit because my app was throwing an error until I noticed that and changed it.
PS: Now that I’ve gone along with this and learned the .js.erb way of doing things, my app is humming along – updating my calendar’s list and divs and validating errors! Thanks a bunch!
This tutorial saved my buns! I was REALLY stuck and frustrated trying to learn this, but now I really feel like I get it. Much appreciated.
Just a note (unless I’m missing something):
to handle your json response, you don’t need to do a
parseJSON(xhr.responseText). That’s already done, and passed in as the second (data) variable.Yup, for the
ajax:successcallback, you’re right. Good catch! I’ve updated the article. It would still be needed for theajax:errorcallback though.I’m doing with both jquery-ujs and jquery mobile.
I tried the methods according to this post, but the “as JS” never showed up:(
Hey Carey, I just responded to your post on github. The issue you’re having is likely due to the fact that you’re using jQuery Mobile alpha 3, which was still very early stage and had lots of problems and conflicts with other libraries (like jquery-ujs). They’ve since fixed a lot of those problems in jQM beta.
I’m wondering how to refer to elements during my response handling. If I have
<div class="location" id="location_5">
<a href="/locations/5-Lueilwitz-Block" title="loola">Lueilwitz-Block</a>
<a href="/locations/5-Lueilwitz-Block" class="removal" data-method="delete" data-remote="true" rel="nofollow">remove</a>
</div>
</div>
how can I get that id “location_5″ (from the event?) so that I can call
during the response handling of the removal link?
If you’re attaching your response code to the
ajax:completeorajax:successevent hooks, you can get the element clicked from theeventparameter that is passed to your handler function withevent.targetorevent.currentTarget. (see the jQuery Event object docs)However, if you’re referring to a response that is in a
js.erbfile, for example, the way I usually do that is I pass theidof the clicked element as a parameter in the request, so that I can access it viaparams[:target]in my view.If you try to use link_to inside the js.erb it do not response as it should…. why?
I’m not sure I understand. Using
< %= link_to ... %>does work in a js.erb template, since you’re using ERB.Hai i have tried your tutorial but when i submit my post nothing happen. I have to refresh page for see the result, i think the javascript didn’t worked. You have any suggestion ?
If you’re having problems getting the response JS to execute, check out this tutorial on how to debug the problem.
I’m trying to implement your plugin, and it works great, but in no case can I get it to actually respond with JS, it always seems to want to respond with HTML.
Note: I am using a file_field in my form… I have verified that my Rails version 3.1.3, jquery 1.7, does respond with JS in the usual case, however this is already to be expected. Also, in the usual case, file uploads are not processed as :remote => true, and I thought this plugin was supposed to fix this issue.
The most important part for me is to get the controller to tread this as a JS request and respond with my JS partial. Is there something I am doing wrong?
This post is about using the rails 3 jquery-ujs, but based on trying to get the form working, I’ll assume you’re referring to the remotipart gem which I also maintain. Yes, the remotipart gem does solve the issue of jquery-ujs not doing file uploads via ajax by default. Install remotipart, include the file in your asset pipeline, and you’re good to go.
I figured out what my issues was, I had to restart the development server after installing the gem… thanks! Great gem, very helpful!
Oh awesome, glad you got it working. Now that you mention it, I think others have reported this too, perhaps I’ll update the readme with instructions to restart the server.
Steve,
Great article – any thing similar ( more up to date ) exist from you. Have to convert Rails Scaffold app by friday + any code on GitHub.
In some respects I have to go backwards, ignoring the :remote => true process and manually writing the AJAX request myself using the rails.js functions directly. I will be removing the New and Show actions from the controller and creating one combined Index form with New section at the top and a list of say Books at the bottom. I need to render JSON and use JQuery to update the DOM.
Any thoughts – any assistance would be appreciated.
Again, excellent article.
Hi Joe, I know exactly what you’re talking about as I’ve had to do this a few times. I don’t have anything written up on the subject (yet), but maybe this will help. One of the first changes I made when upgrading an old rails2 app using
link_to_remoteto the new rails3 ujs (either jquery or prototpye) was to change this::update => "thing-results",
:url => {:controller => "thing", :action => "edit", :id => @thing.id },
:method => :get -%>
to this:
{:controller => "thing", :action => "edit", :id => @thing.id },
:remote => true,
:data => {
:update => "thing-results"
},
:method => :get -%>
And then add this to add this to application.js if you’re using prototype-ujs (which is often the case when upgrading old apps):
var update = el.readAttribute('data-update');
$(update).update(e.memo.responseText);
});
Or this if you’re using jquery-ujs:
var update = $(this).data('update'); // or $(this).attr('data-update') if it's not an HTML5 doctype
$('#' + update).html(data.responseText);
});
Of course this isn’t the cleanest way to accomplish things (namely, the
link_tohelper options are still more verbose than necessary), but it allowed me to update the app while changing the minimum amount of code possible, which was essential for me to effectively debug anything else that came up.What is the sense of
render :js => @commentin the explanation of respond_with?I just wanted to thanks you for the effort put into this excellent article. It’s exactly what I was looking for and no other guide around was so clarifying!
Regards !