JQuery Ajax + Rails
UPDATE
Old stuff here! See Errtheblog’s post on jQuery for more up-to-date information…
***
I’ve been working on integrating jQuery’s Ajax capabilities into a Ruby on Rails application; here’s what I’ve discovered so far:
RJS templates (yes, you can use them!)
UPDATE see Javascript + embedded Ruby templates with Rails, out-of-the-box for a way to skip using RJS templates altogether and write .js.erb templates (Edge Rails only.)
After playing around a bit with various options (like rendering Javascript from a standard .rhtml partial) I was surprised to discover that I could use RJS templates to write jQuery-specific Javascript.
Given that the Prototype syntax for element proxies — $(id) — is the same syntax as the jQuery object — $(selector) — the RJS element proxy syntax — page['someid'] — can be used to output a jQuery object. Built-in method_missing magic handles the arbitrary methods chained to it. Examples:
#=> $("#posts").append("Post #29 info…");
page[".hide-this"].hide
#=> $(‘.hide-this’).hide();
page["#foo"].html("bar").append("baz")
#=> $("#foo").html("bar").append("baz");
page["h1"].add_class "make-red"
#=> $("h1").addClass("make-red");
In addition to the element proxy, the following JavaScriptGenerator methods can be used as well, since they output non-Prototype specific Javascript:
page.alert ‘hello from rjs’
end
page.call ‘myfunction’
page << "arbitrary js code"
page.assign ‘foo’, ‘bar’
Any commands that can’t be written with the page['selector'].method syntax can always be added to the page as raw Javascript with page << “javascript”.
Unobtrusive Ajax links
I have no need for a jQuery version of the link_to_remote helper — I just use the standard link_to and assign behavior to it unobtrusively via jQuery’s $(document).ready() function. Here’s how I build a jQuery Ajax link that will retrieve and evaluate Javascript from a RESTful Rails controller method with a respond_to block:
<script type="text/javascript"><!–mce:0–></script>
Since the default HTTP Accept header for jQuery Ajax requests is “text/xml”, I need to change it to “text/javascript” with the setRequestHeader method so that the request triggers the appropriate response format (format.js) in the controller method’s respond_to block. Alternately, I could set the link href to formatted_post_path(post, :js), but that of course wouldn’t degrade as well.
If I want to simply return an HTML partial and insert it into the DOM, I can change the Ajax object so that it expects HTML, and add a success handler function to process the returned HTML:
<script type="text/javascript"><!–mce:1–></script>
Just like Prototype, jQuery sets the X-Requested-With header to XMLHttpRequest for Ajax requests, so I can query request.xhr? in my controller if I need to distinguish between standard and Ajax requests. Here’s an example from a controller action where I render a partial if the request is Ajax, and the default .rhtml template for that action if it’s a standard request:
@post = Post.find(params[:id]) respond_to do |format|
format.html { render :partial => ‘post’, :locals => {:post => @post} if request.xhr? }
format.xml { render
ml => @post.to_xml }
format.js
end
end
Here’s how I could create a link with a DELETE method instead of GET (similar to adding the :method => :delete option to the link_to):
<script type="text/javascript"><!–mce:2–></script>
But of course, this won’t degrade successfully; with Javascript turned off, this link will just GET the show method. For a degradable, form-based solution, I can use…
Unobtrusive Ajax forms
For Ajax forms, I just use the JQuery Form Plugin to unobtrusively Ajaxify a standard form — no remote_form_for necessary! Example:
<%= f.text_field :title %>
<%= f.text_area :body %>
<%= submit_tag "Create" %>
<% end %>
<script type="text/javascript"><!–mce:3–></script>
Testing
assert_select_rjs won’t work for parsing jQuery-specific Javascript (and the ARTS plugin only seems to work for certain simple cases.) Instead, I’m just using assert_match on the @response.body in my functional test:
xhr :get, :show, :format => ‘js’, :id => 1
assert_response :success
assert_match /text/javascript/, @response.headers[‘type’]
assert_match /post title/, @response.body
end
…and all of this works out-of-the-box, no special helpers or plugins required!
Just load jQuery (and jQuery.form, if needed) in your application layout and you’re good to go.
Other stuff to check out
I’ve yet to test out Dan Webb’s MinusMOR plugin, which kind of turns rjs templates inside out — instead of writing your Javascript with Ruby, you embed Ruby in your Javascript via .ejs templates, which are served just like .rjs templates.
And there’s Yehuda Katz’s yet-to-be-released JQuery on Rails, which sounds promising.
15 Comments