Rails #3: AJAX
In my previous articles, I showed you how to get started using Rails, and how to create model objects that are associated with each other. In this article, we will clean up the way that information is displayed and add support for dynamic HTML, or as the cool kids are calling it these days, AJAX.
A view to die for
First, clean up app/views/articles/show.hml.erb
. I am assuming you know enough HTML to be able to make it look reasonably blog like. Following is a suggested starting point. The only thing that is critical to follow allow the text of this tutorial, is the id of the “new_comment” div. This will be used as a target for our AJAX code.
< %=h @article.title %>
-----------------------
###
By < %=h @article.author %>
< %= link\_to "[ edit ]", edit\_article\_path(@article) %>
< %=h @article.content %>
Comments
--------
< % for comment in @article.comments %>
### < %=h comment.title %> (by < %=h comment.author %>)
< %=h comment.body %>
< % end %>
Add your comment
----------------
< %= error\_messages\_for :comment %>
< % form\_for :comment, :url => comments\_url(@article) do |f| %>
< %= f.label :title %> < %= f.text\_field :title %>
< %= f.label :author %> < %= f.text\_field :author %>
< %= f.label :body %>
< %= f.text\_area :body, :rows => 5 %>
< %= f.submit "Create" %>
< % end %>
Partially better
The easiest way to use Rails AJAX support is to redraw parts of the view from the server. You can remove many sources of error by making sure that you’re using exactly the same HTML + ERb code to render the new views from the AJAX requests. This is a good place to introduce another Rails technique: Partials.
- Replace the code from
<% for comment %>
to the corresponding<% end %>
section with a call torender
method like so:<%= render :partial => 'comments/comment', :collection => @article.comments %>
- Move the
<div>
used for the comment to a new file,app/views/comments/_comment.html.erb
- The call to
render :partial
means to call the fileapp/views/comments/_comment.html.erb
for each comment in @article.comment. Each comment will be assigned to the variablecomment
in turn. - That’s it - your comment code is refactored out
Second, we do the same with the form for adding new comments:
- Replace the
<div id="new_comment">
for new comments with a call to another partial:<%= render :partial => 'comments/new' %>
- Move the code for the new comment unchanged into a new file called
app/views/comments/_new.html.erb
.
The view still looks the same in the browser, but we’re now posed to awe the world with our mad AJAX skills.
AJAX
Using AJAX is very easy, so I will just jump straight into it:
- Make the client loads the necessary Javascript files: Add the line
<%= javascript_include_tag :defaults %>
to the<head>
section inapp/views/layout/articles.html.erb
. I bet you wondered where the title on the pages came from. Now you know. - In
app/views/comments/_new.html.erb
, replace the call toform_for
with an identical call toremote_form_for
. - In theory, this is all you need to do, and your form is now AJAX enabled, but we also have to specify what actions should be performed when the submit button is pushed. Right now, if you fill in a form and press submit, it will look as if nothing is happening. But if you refresh the page, you will see that the comment was indeed added to the database.
- Declare that creating a comment should support AJAX: In
app/controllers/comments_controller.rb
, find the methodcreate
and addformat.js
in the “if” block of method. Notice that no {} or anything is needed for this line (just like with most format.html calls in the other controller methods). - Add the file
app/views/comments/create.js.rjs
. This will contain the code for what should be rendered when the user submits the form. - RJS files are plain Ruby files, with a special object called page available. In
app/views/comments/create.js.rjs
, just enter a single line:page.insert_html :before, 'new_comment', :partial => "comments/comment"
- That’s it, if you post a comment, after some delay, it is displayed right before the new comment form.
Make it good
The AJAX now works, but it leaves a lot to be desired. Here are some improvements:
Make the new comment stand out visually after it is created. Add the following to
app/views/comments/create.js.rjs
:page[dom_id(@comment)].visual_effect :highlight
. (Or pick another effect from the script.acol.us webpage)Clear the form. Add the following to
app/views/comments/create.js.rjs
:@comment = Comment.new page['new\_comment'].replace :partial => "comments/new" page['new\_comment'].visual\_effect :slide\_down
Add a progress indicator: In
app/views/comments/_new.html.erb
, add the following line inside the “new_comment” div:<div id="progress" style="display: none">Saving...</div>
and the :loading option to the call to remote_form_for::loading => "Element.show('progress')"
.Even better, create a progress indicator at ajaxload.info. Download the image as
public/images/ajax-loader.gif
, and replace the contents of the progress div with<%= image_tag "ajax-loader.gif", :alt => "saving..." %>
.Error handling doesn’t work. If you add some validation to the model, you will notice that the page behaves very poorly when the validation fails. Add the following line to
app/models/comment.rb
:validates_presence_of :title, :author, :body
. If you try to submit a form that is missing one of these fields, the page will never update and the progress indicator will just keep going. To fix this, you can add the following line to the “else” block of thecreate
method ofapp/controllers/comments_controller.rb
:format.js { render :update do |page| page['new_comment'].replace :partial => 'new'; end }
. This acts just as the RJS template from before, but without using a separate file (if you make this change, you will have to update your tests).You might also want to do something about the “flash”, but explaining that requires too much details, so I will leave that for another day.
AJAX goodness
We’ve covered the basics of AJAX: Using partials to make your views flexible, using remote_form_for to make the form submit asynchronously using JavaScript, and using RJS templates to generate JavaScript using Ruby. There are many other options for using AJAX out there, and you might want to explore the field further. Ultimately, nothing beats JavaScript for JavaScript. That being said, built-in AJAX support in Rails is my favorite way to get started. I hope you’ve enjoyed this tutorial. Some of the topics I am considering covering now are: Sessions, XML and RSS, development howtos like deployment, testing and application structure, or something completely different. Thanks a lot your for comments to my previous articles via the blog or email. Let me know what you want next.
Comments:
abercrombie and fitch - Sep 14, 2010
This post is very useful, I have learn much from this, thanks
[Sreeragamratheesh] - Mar 29, 2012
nice one
[Giulio] - Dec 19, 2012
Thanks for the great post, although I don’t quite understand how you do a few of the changes. for example: what do you mean by “find the method create and add format.js in the “if†block of method.” ?
Could you please post the complete final code? That would help a lot.
thanks
Johannes Brodwall - Dec 19, 2012
Hi Giulio. I would recommend not looking too much at this post. Rails have progressed enormously in the last four years, and this information is out of date. :-)