9.5. Adding a Sprinkle of Ajax

To add a sprinkle of Ajax to the blog example, you are going to allow your users to comment without reloading the page. The first thing that you need to do is to include the default JavaScript libraries that ship with Rails. Do this by modifying the articles.html.erb layout within the <head> tag as shown here:

<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title>The Rails Noob</title>
  <%= auto_discovery_link_tag :atom, formatted_articles_url(:atom) %>
  <%= stylesheet_link_tag 'site' %>
  <%= javascript_include_tag :defaults %>
</head>

The javascript_include_tag is a helper used to include JavaScript libraries. When used in a layout, it makes these libraries available to all of the view templates for which the layout applies. You can pass it the names (with or without extension) of JavaScript files located in publicjavascripts and these will be included on each page for which the layout was rendered. In the highlighted line I used the :defaults symbol, which tells Rails to include both Prototype and script.aculo.us, as well as application.js in publicjavascripts, if it exists. Passing :all will include all the JavaScript files in that directory and its subdirectories. In production, it is usually a good idea to cache all the JavaScript files into a single all.js file. This is done automatically for you by passing the :cache => true option to the helper, and will work as long as ActionController::Base.perform_caching is set to true (which is by default the case for production and not for development).

In the blog application, <%= javascript_include_tag :defaults %> generates code such as the following:

<script src="/javascripts/prototype.js?1215726390" type="text/javascript"></script>
<script src="/javascripts/effects.js?1215726390" type="text/javascript"></script>
<script src="/javascripts/dragdrop.js?1215726391" type="text/javascript"></script>
<script src="/javascripts/controls.js?1215726391" type="text/javascript"></script>
<script src="/javascripts/application.js?1215726391" type="text/javascript"></script>

The next step is to transform the form used to create comments, from a regular form to an Ajax one. Go ahead and edit the appviewscomments\_form.html.erb partial so that it uses form_remote_for as shown in Listing 9-3.

Example 9.3. appviewscomments\_form.html.erb
<% form_remote_for [article, comment] do |f| %>
    <%= f.error_messages %>

    <% field_set_tag do %>

        <div class="field">
            <%= f.label :name %>
            <%= f.text_field :name %>
        </div>

        <div class="field">
            <%= f.label :email %>
            <%= f.text_field :email %>
        </div>

        <div class="field">
            <%= f.label :body %>
            <%= f.text_area :body, :rows => 10 %>
        </div>

    <% end %>

    <% field_set_tag do %>
        <%= f.submit button_value, :class => "button" %>
    <% end %>
<% end %>

This simple change is sufficient because it modifies the way the form is handled upon submission.

The third step is to modify the CommentsController's create action so that it's able to respond to JavaScript requests. Change it by adding the highlighted line:

# POST /comments
# POST /comments.xml
def create
  @comment = @article.comments.build(params[:comment])

  respond_to do |format|
    if @comment.save
      flash[:notice] = 'Comment was successfully created.'
      format.html { redirect_to(@article) }
      format.xml  { render :xml => @comment, :status => :created,
                                             :location => @comment }
      format.js
    else
      format.html { render :action => "new" }

format.xml  { ender :xml => @comment.errors, :status =>
:unprocessable_entity }
    end
  end
end

This tells ActionPack that a create.js.rjs template should be used to formulate a response, when the incoming request is an Ajax one.

You might have noticed that if the comment fails to save, nothing happens to the existing form. This may or may not be the desired outcome, depending on the application. In some instances, you may want to handle the failed attempt by informing the user about the problem that prevented the object from being saved; for example, by placing a format.js { render :template => "shared/error.js.rjs" } in the else branch and implementing an error.js.rjs template.

The fourth and last step (I didn't say that it would be hard, now did I?), is therefore to create an RJS template. Go ahead and create appviewscommentscreate.js.rjs, then copy the code from Listing 9-4.

Example 9.4. appviewscommentscreate.js.rjs
page.insert_html :bottom, :comments, :partial => "articles/comment", :object => @comment
page.replace_html :comments_count, pluralize(@article.comments.size, 'Comment')
page[:new_comment].reset
page.replace_html :notice, flash[:notice]
flash.discard

If you require nice visual effects when an object is created, destroyed, or a change is made, use the method page.visual_effect.

Before delving into the analysis of this snippet, start the Web server and try to add a new comment to an existing post. You should see that the comment is immediately added without reloading the page, the comment counter before the comments section was updated, and the reassuring green, flash message "Comment was successfully created." should be displayed. Congratulations, you just added a simple Ajax feature to your first Rails application.

The RJS template in Listing 9-4 is easy to understand. page is a JavaScriptGenerator object that represents the page that issued the request, so that it can be easily manipulated from Ruby code. In the first line, you append, at the bottom of the DOM element with id comments, the partial articles\_comment.html.erb, which displays the comment you've just created:

page.insert_html :bottom, :comments, :partial => "articles/comment",
                                     :object => @comment

insert_html is a helper that's defined in the class ActionView::Helpers::PrototypeHelper::JavaScriptGenerator::GeneratorMethods. Similar helpers defined by the same class are replace_html, hide, show, and toggle, which switches between hiding and showing a given element.

WATIR and Selenium

Aside from regular functional and unit testing, many Rails developers opt to further test their applications by using software that is able to automate the browser interaction with the application, and compare the expected results with what was actually obtained.

This sort of black-box testing, before the release of an application, is a form of automated Acceptance Testing and can be very beneficial when it comes to improving the Q&A of a Web application. And because the browser is automatically operated, as opposed to performing some sort of emulation, Rich Internet Applications are not a problem. No matter how much Ajax your application uses, these types of tests will be able to interact with the application and capture the produced output.

In the Rails world, two tools are very popular: Watir (Web Application Testing in Ruby, pronounced "water") and Selenium. Watir works through OLE to automate Internet Explorer, whereas Selenium is multi-platform and can be used with a variety of modern Web browsers. There is also a project, FireWatir, which is working to bring Watir to Mozilla Firefox. An effort to merge these two is currently underway. I highly encourage you to check out Watir and Selenium and try to give them a spin.


In the second line of Listing 9-4, you replaced the DOM element with id comments_count (with the updated number of comments):

page.replace_html :comments_count, pluralize(@article.comments.size, 'Comment')

You now need to clean up the form that was filled in to create the form. You do this by using the method reset on the page element with id new_comment (your form):

page[:new_comment].reset

You then need to replace the empty flash notice in the page with your success message. You do this as usual with replace_html:

page.replace_html :notice, flash[:notice]

And finally, you invoke flash.discard to discard the flash message. You need this final step to clear the flash for the next page reload. If you left out this line of code, the flash would still be on hand for the next request.

As demonstrated by this small example, Rails really simplifies the process of working with Ajax and JavaScript. Professional Web developers will inevitably end up writing JavaScript code as well, but Rails tries to keep everything as simple as possible by providing you with a Ruby DSL.

A Bug and an Exercise for the Reader

If you pay close attention, you will notice that the RJS template updates several elements in the page, but it doesn't update the number of comments in the right sidebar, just below the "Published on" date and time. So you will have a page that reads "4 comments" in the right sidebar, and above the comment section "5 comments." Solving this bug, which was intentionally left in, will be your exercise. All you'll have to do is add an id to the element in the sidebar, and then add a line to the RJS template so that its content is replaced as well when the request is completed.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.152.198