TweetFollow Us on Twitter

Ajax on Rails

Volume Number: 22 (2006)
Issue Number: 10
Column Tag: Ajax on Rails

Ajax on Rails

Developing Web 2.0 applications with the Rails framework

by Rich Warren

Introduction

In my last article, we built a simple ToDo web application, HoneyDo. This time, we will add fancy new Ajax features. You've heard of Ajax, right? It is the eye of the Web 2.0 buzz-hurricane. Ajax uses JavaScript to dynamically update small portions of a page. This lets you build responsive, interactive web pages. Ajax applications often feel like desktop apps. They respond quickly to user actions, since they do not need to download a completely new page every time you click on a link. Andrew Turner wrote an excellent article, "Adding Ajax To A Website", in the January 2006 issue of MacTech. Please check out that article for an overview of this technology.

Writing an Ajax application often means splitting your content-generation code into two parts. A typical setup might use PHP to communicate with the database and build the initial page, then use JavaScript for live changes. A single file may contain three different languages: standard HTML, PHP, and JavaScript--as well as references to additional files and libraries. As applications become more complex, these parts tend to get muddled. JavaScript calls PHP files, PHP scripts dynamically build JavaScript functions, and everything is embedded in the static HTML.

Rails (and other, similarly-minded toolkits), lets you pull all the dynamic portions into a single language. RHTML templates still mix Ruby and HTML, but now you create 99.9% of your Ajax JavaScript using simple Rails helpers. For example, in this tutorial, we won't write a single line of JavaScript. Rails keeps us safely inside the Ruby sandbox. This allows a cleaner separation between presentation (HTML), and programming (Ruby).

Rails has traditionally (if you can say "traditionally" about anything under 2 years old) had a tight integration with the Prototype and Scriptaculous libraries. Prototype simplifies adding Ajax functions to a page, while Scriptaculous builds visual effects and other goodies on top of the Prototype framework. Rails provides a full suite of helper functions that leverage these libraries. Many of the helper functions are kissing cousins of the regular, html-generating helpers. This makes adding Ajax to a Rails project nearly transparent.

But wait, there's more good news. Ajax in Rails just got easier. The Rails 1.1 release represented a major upgrade. It added features across the entire framework, including RJS templates--allowing us to group a set of dynamic changes, then launch them all as a single action. In this article, we will look at both the old-school Ajax helpers, as well as the new RJS features. However, before we jump into the wonderful world of Web 2.0, let's upgrade Rails.

On the surface, upgrading rails is stupidly simple. Just run the following command at the command line:

sudo gem install rails --include-dependencies

That's it? Well, not quite. That just updates the Rails library. Applications store several Ruby and JavaScript files locally. So, we need to update each application individually. Change to the application's root directory, then use rake to update it.

cd ~/rails_dev/HoneyDo
rake rails:update

That's it then, right? Yes and no. Everything is upgraded--but upgrading is not always a good thing. For example Typo, a popular Rails blogging application, broke. Many people found their blogs in shambles after their web host upgraded to Rails 1.1. Even HoneyDo did not escape unscathed (we'll fix that in a minute).

Let this be a warning. Take extra care before upgrading any production system. If someone else hosts your application, you may have no control over when (or if) they upgrade their systems. But you can protect yourself.

The savior is rake, Rails own Swiss Army Knife. Rake started as a Ruby version of make (Ruby + make = rake, get it?). If you've ever complied a Unix application, you're probably familiar with the ubiquitous make. Rails, however, uses rake for much more than just builds. To see a full list of its features, execute the following (again, you must be in an application's root directory):

rake --tasks

The command we need is rake rails:freeze:gems. This makes a copy of the current Rails library and saves it in your application. Your application then runs off the local version. Your web host can change their system willy-nilly, and your application stays in a known state. Just thought you should know.

Upgrading Honeydo

Two things broke when we upgraded. Most importantly, the delete links no longer delete anything. Testing also produced a few odd errors (though the functions work fine when checked by hand). There might be something wrong with scaffold. Given the rapid rate of Rails development, the Rails crew will probably fix these problems before you can read this, but let's fix it by hand, just to keep us all on the same page.

Scaffolding creates seven default actions: :list, :show, :new, :create, :edit, and :destroy. You can override any of these by writing your own versions. In HoneyDo, we have overridden or replaced all the default actions except :show and :destroy.

Scaffolding comes in two flavors: dynamic scaffold and ScaffoldGenerator. Dynamic scaffold automatically creates these actions (and their associated views) behind the scenes. You won't find the :show or :destroy actions anywhere in the HoneyDo source code. ScaffoldGenerator, on the other hand, actually creates all the files and code needed by the default actions. You run a script, and it builds the necessary controllers and views. Be careful, however. ScaffoldGenerator will overwrite existing files.

To fix the upgrade errors, I made a copy of the entire HoneyDo tree, and then ran ScaffoldGenerator on that copy.

script/generate scaffold item item

ScaffoldGenerator created all the default actions and views. We just need to copy the :show and :destroy actions, as well as the show view. You also need to comment out the call to dynamic scaffold at the top of item_controller.rb.

Listing 1: Item Controller

app/controllers/item_controller.rb

Add the following methods to the item controller. These methods replace the dynamic scaffold handlers for the :destroy and :show actions.

class ItemController < ApplicationController
   before_filter :login_required
   #scaffold :item
   layout 'template'
   
def destroy
   Item.find(params[:id]).destroy
   redirect_to :action => 'list'
end
def show
   @item = Item.find(params[:id])
end

Listing 2: Show View

app/views/item/show.rhtml

Create the show.rhtml template. This replaces dynamic scaffold's show view.

<% for column in Item.content_columns %>
<p>
  <b><%= column.human_name %>:</b> <%=h @item.send(column.name) %>
</p>
<% end %>
<%= link_to 'Edit', :action => 'edit', :id => @item %> |
<%= link_to 'Back', :action => 'list' %>

That's it. All the tests should run without any errors. Clearly (at least as I write this), dynamic scaffold and ScaffoldGenerator do not produce 100% compatible code. In the past, I preferred dynamic scaffolding, since it keeps the clutter to a minimum. However, ScaffoldGenerator is less opaque--and you can learn a lot by examining the code it generates.

ScaffoldGenerator also correctly escapes strings from the database. Dynamic scaffold still lets html tags go through (at least the ones I've tested). This could be a severe security risk. So, for now, I would recommend using ScaffoldGenerator to build any new applications.

Ajax Rails Style

As I mentioned earlier, Rails includes several helper functions to create Ajax code. Lets take a quick look at the four general-purpose workhorses: link_to_remote(), form_remote_tag(), observe_field() and periodically_call_remote(). There are others. Heck, we'll use some of them. But, let's get our toes wet first.

link_to_remote() creates a hypertext link. It takes two main parameters: a string and a hash of options. The string contains the link's text. The option hash generally contains both :update => 'itemID' and :url => {:action => :action_name} entries. For the :update option, you provide the id of an existing tag (<div> tags are usually a safe bet). The :url option defines the source of the new html. Usually, this will be an action or a controller/action pair. When you click the link, Rails calls the named action, and then pours the resulting html into the listed tag. This replaces the tag's current contents; everything else on the page remains unchanged.

link_to_remote() can also take a second hash of html options. In most cases, you can safely ignore this.

There is one small gotcha. In our rails application, the html generated by each action is automatically wrapped in the default template. While this guarantees that every page has the same look and feel, we really don't want additional copies of the header and footer dropped into the middle of our page. To prevent this, add the following in the action's definition: render(:layout => false).

form_remote_tag() is even simpler. It replaces the form_tag() helper. Again, you pass in a hash with :update and :url values. When you submit the form, it sends the contents of its fields to the :url's action. Again, the resulting html replaces the :update tag's contents.

periodically_call_remote() takes an additional option :frequency. This will automatically call the :url action every :frequency seconds, pouring the results into the :update tag.

Finally, observe_field() is the most complicated. It takes a reference to a form's field and a frequency. Then, once every :frequency seconds, it sends the value of the field to the selected action and performs the update.

Listing 3: Ajax Sample

Sample Ajax View

The following example demonstrates calling the four basic Ajax helpers in a typical RHTML template.

<div id='test'></div>
<%= link_to_remote( 'Ajax Link', :update => 'test', 
   :url => {:action => :link}) %>
<%= form_remote_tag( :update => 'test', :url => {:action => :submit}) %>
<%= text_field_tag :mytext %>
<%= observe_field(:mytext, :frequency => 0.5, 
   :update => 'test', :url => {:action => :observe}) %>
<%= periodically_call_remote(:frequency => 10, 
   :update => 'test', :url => {:action => :periodic}) %>
<%= end_form_tag %>

And that just scratches the surface. We haven't even looked at effects yet.

For Our First Trick...

If the helpers weren't enough, Rails has several special-purpose Ajax functions. For example, Rails can automatically create auto-complete text fields using auto_complete_for() and a little bit of spit and binding twine. We'll use this to replace our user drop-down list with an Ajax-powered text box.

The auto_complete_for() method takes a model object, an accessor method, and an optional hash. The hash contains parameters that auto_complete_for() then passes to the model's find() method. In our case, we will want the :login method for the :user model with no options. By default, auto_complete_for() returns the first ten records, sorted by the method's return value. That should work just fine.

auto_complete_for :user, :login

The :new and :edit actions no longer need a list of all users. Instead, we just want the current user. Since the forms are changing, :save and :create will need slight adjustments.

Listing 4: Updated Item Controller

app/controllers/item_controller.rb

To enable the auto-complete text box, first call auto_complete_for(). Then make the following changes to the new(), edit(), save() and create() methods.

class ItemController < ApplicationController
   before_filter :login_required
   auto_complete_for :user, :login
   
   #scaffold :item
   layout 'template'
   def destroy
      Item.find(params[:id]).destroy
      redirect_to :action => 'list'
   end
   def show
      @item = Item.find(params[:id])
   end
   def list
      @user = @request.session[:user]
      @item_list = @user.todo_items
      @name = @user.login.capitalize
      @pages, @items = paginate(:item, :order_by => 'priority DESC, date', 
         :conditions => ['user_id = ?', @user.id])
   end
   
   def new
      @item = Item.new
      @user = @request.session[:user]
   end
   
   def edit
      @item = Item.find(@params['id'])
      @user = @request.session[:user]
   end
   
   def create
      user = @params['user']['login']
      sender = @request.session[:user]
      
      @item = Item.new(@params['item'])
      @item.sender = sender
      @item.user = User.find(:first, :conditions => ["login = :name",
         {:name => user}])
      @item.date = Time.now
      if @item.save
         flash[:notice] = "#{@item.title} successfully added to" + 
            " #{@item.user.login}'s ToDo list!"
         redirect_to :action => 'list'
      else
         @users = User.find(:all, :order => 'login')
         render_action 'new'
      end
   end
   def save
      user = @params['user']['login']
      item_hash = @params['item']
      @item = Item.find(item_hash['id'])
      # don't update the sender!
      @item.user = User.find(:first, :conditions => ["login = :name",
         {:name => user}])
      # don't update the time!
      if @item.update_attributes(item_hash)
         flash[:notice] = "#{@item.title} successfully updated!"
         redirect_to :action => 'list'
      else
         @users = User.find(:all, :order => 'login')
         render_action 'edit'
      end
   end
end

So far, so good. But, before the Ajax functions will work, we need to include all the Prototype and Scriptaculous JavaScript files (see http://prototype.conio.net/ and http://script.aculo.us/ websites for more information). Not surprisingly, Rails includes a helper function for this: javascript_include_tag :defaults. Let's add this to the main template. That will provide the Ajax functions to all our views.

<%= stylesheet_link_tag 'scaffold' %>
<%= javascript_include_tag :defaults %>
<title><%= controller.action_name%></title>

In both the edit and new templates (app/views/item/edit.rhtml and new.rhtml respectively), look for the collection_select() helper. That currently creates the user drop-down menu.

<td><b>Send To User:</b></td>
<td>
   <%= collection_select(:item, :user_id, @users, :id, :login)%>
</td>

Replace that with the text_field_with_auto_complete() helper as shown below. Just like auto_complete_for(), text_field_with_auto_complete() takes a model and a method. Typically, the method is an accessor or a virtual accessor. The function creates a list of every item in the model. It calls the accessor on each item, then compares the result with the text field's contents. If the text field's contents are a substring of the accessor's value, it is a match. text_field_with_auto_complete() returns the first ten matches. In our case, we will search through all the users and try to match their login.

<td><b>Send To User:</b></td>
<td>
   <%= text_field_with_auto_complete :user, :login %>
</td>

That's it. Start up MySQL and the WEBrick server, then point your browser at localhost:3000, login and try to add a new ToDo item. Your login should automatically appear in the "Send To User:" field. Try changing it, and play around with the auto-completion functions.

Editing on the Same Page

Ajax is all about getting away from the click-on-a-link-move-to-a-new-page paradigm. As a gross oversimplification, this means doing everything on a single page. So, let's let users view and edit ToDo items directly on the list view.

In item_controller.rb, add render(:layout => false) if request.xhr? to the end of the :edit and :show actions. This keeps the action from dressing its html in the default template when the action is called by an Ajax request. However, it still functions properly if you access the action directly or connect using a standard link.

def show
   @item = Item.find(params[:id])
   render(:layout => false) if request.xhr?
end

Next, in the list view, add a second table.

<table width="50%" align="center">
   <tr><td><p><div id='scratchpad'></div></p></td></tr>
</table>

Only the <div id='scratchpad"></div> portion is important. Our Ajax functions will use it to display the :show and :edit html. The rest of the table is just rough formatting. In a real-world project, I would recommend using CSS to format the text instead, but for now let's keep things simple.

Next, we need to add the actual Ajax calls. Open the row partial. Change the link_to() helper to link_to_remote() for both the show and edit actions.

Listing 5: Row Partial

app/views/item/_row.rhtml

The link_to_remote() helper function creates a link and the required Ajax JavaScript. Clicking on the link will call the :show or :edit action respectively. The returned html then replaces the contents of our scratchpad <div>.

<%= link_to_remote('Show', 
      :update => 'scratchpad',
      :url => {:action => 'show', :id => row.id}) %> |
<%= link_to_remote('Edit',
      :update => 'scratchpad',
      :url => {:action => 'edit', :id => row.id}) %> | 

That's it--more or less. Of course, we'll want to make a few tweaks to the edit and show templates. We'll also want to add a clear action, to clear the scratchpad. But it works as is. Try it out. Bask in the warm glow of Ajax.

Ok, too much basking. Let's add the clear action. Here we use the link_to_function() helper. As the name implies, this creates an html link which fires off a JavaScript function. In our case, we will use the visual_effects() helper to create a fade effect.

Edit both show.rhtml and edit.rhtml. In the show view, replace link_to 'Edit' and link_to 'Back'. In edit, just append the code after the table.

<%=link_to_function 'close', visual_effect(:fade, 'scratchpad')%>

Fade causes our scratchpad to fade out, eventually hiding it. To display new items in our scratchpad, we need to make it visible again. Let's add an :appear effect to the original link_to_remote() functions. Here the :complete option launches its JavaScript function once the action has finished.

Listing 6: Rows With Effects

app/views/item/_row.rhtml

The :complete option launches the :appear effect once the :show or :edit action completes.

<%= link_to_remote('Show', 
   :update => 'scratchpad',
   :url => {:action => 'show', :id => row.id}, 
   :complete => visual_effect(:appear, 'scratchpad')) %> |
<%= link_to_remote('Edit',
   :update => 'scratchpad',
   :url => {:action => 'edit', :id => row.id}, 
   :complete => visual_effect(:appear, 'scratchpad')) %> |

As you can see, you can pass the Ajax helper functions a wide range of options, allowing you to tweak their behavior. I recommend browsing through the Rails documentation. Specifically, look at ActionView::Helpers::JavaScriptHelper, ActionView::Helpers::PrototypeHelper and ActionView::Helpers::ScriptaculousHelper.

One last, little tweak. Back in list.rhtml, we want the scratchpad to start hidden, otherwise, the initial :appear effect will not work.

<div id='scratchpad' style='display:none'></div>

That's it. Take the new interface for a spin. I'd also suggest taking a moment to stretch. Maybe get a cup of coffee. We've plucked all the low-hanging fruit. The next step is a little more challenging.

The Next Step

As you can see, you can accomplish a lot with the built-in Ajax functions. The Rails helpers are particularly effective when each action changes just one part of the page. But, what if you want to make several changes at once? Enter RJS templates.

RJS templates (or more precisely JavaScriptGenerator templates) are a new addition with Rails 1.1. As you might guess, RJS templates are views that end with .rjs. Unlike RHTML (and RXML), RJS does not provide instructions for rendering a new page. Instead, these templates contain a list of Ajax commands that alter the currently rendered page.

When an Ajax helper fires off an action, the action automatically calls the associated RJS template. The template provides access to a JavaScriptGenerator object named page. You can use page to insert or remove html; to replace, show or hide page elements; or to create visual effects. Check out the documentation for ActionView::Helpers::PrototypeHelper::JavaScriptGenerator::GeneratorMethods for more information.

OK, let's put all this to use. Currently, when we edit a page we're redisplaying the entire page. Instead, we would like to update only those parts that actually change. That, however, opens a real can of worms--how many changes are possible? It seems simple. Obviously, the application needs to update the row we just edited. We also want a flash message saying the item has changed. But it's a little more complicated than that. Items are sorted by their priority. If we change the priority, then we need to reorder the rows. If you have more than ten items in your ToDo list, then the edited item could shift to a different page. We could try to determine exactly which tags need changed, then update them with surgical precision--but that would be difficult, probably bug prone, and possibly computationally expensive. Instead, let's replace the entire table.

First, we need to move the table into its own partial. In list.rhtml, copy the upper table, and place it in its own file, _table.rhtml.

Listing 7: Table Partial

app/views/item/_table.rhtml

Move the following code from list.rhtml to _table.rhtml. Moving the table-generation code to its own partial lets us regenerate the table on demand.

<table border="1" cellspacing="0px" cellpadding="5px" 
   align="center" id="todo">
   <tr bgcolor="cc9966">
      <th>Item</th>
      <th>Priority</th>
      <th>Date</th>
      <th>Sender</th>
      <th>Description</th>
      <th></th>
   </tr>
   <%= render :partial => 'row', :collection => items %>
</table>

Within the list view, replace the table with the following code:

<%= render :partial => 'table', :locals => {:items => @items} %>

So far, we haven't really changed anything. The page should still look and function the same as before. However, moving the table to its own partial, lets us update the table whenever we want.

Next, in the main template add a <div> tag around the flash notice as shown. This allows us to change the flash text. We could try to update the <p> tag directly; however, the application only creates that tag when there is a flash[:notice] message. You can't change something that doesn't exist. Creating a separate <div> guarantees we have a valid target.

<div id='flash'><%= 
   "<p style='color: green'>#{h(@flash[:notice])}</p>" if @flash[:notice]
%></div>

Normally an Ajax helper will look for a .RJS file whose name matches the action. Logically, we should then create a file named save.rjs that contains the code shown in listing 8.

Listing 8: Sample RJS Code

Sample app/views/item/save.rjs

Under normal circumstances, Rails would call the following code automatically whenever an Ajax function called the corresponding :save action.

page[:scratchpad].hide
page.replace 'todo', :partial => 'table', :locals => {:items => items}
page["row#{@item.id}"].visual_effect :highlight, 
   :startcolor => '#00ff00', :duration => 5
page.replace_html 'flash', 
   "<p style='color:green'>#{@item.title} successfully updated!</p>"
page[:flash].show
page[:flash].visual_effect :pulsate, :queue => {:position => 'end',
   :scope => 'flash'}
page[:flash].visual_effect :fade, :queue => {:position => 'end',
   :scope => 'flash'}

Unfortunately, this doesn't work. Our application adds the edit form to the page dynamically. The form was not part of the original page. I suspect this confuses _edit.rhtm's Ajax helpers. However, there is a way around this problem. We can move the code into the controller itself. I don't like this. It mixes display code with controller code. But, you gotta do what you gotta do.

Open up item_controller.rb, and change the :save action. Listing 9 also includes code to catch saving errors.

Listing 9: Corrected Save Action

app/controllers/item_controller.rb

Our :save action does not correctly call the save.rjs template. Instead, move the Ajax changes into the item controller. The controller creates a JavaScriptGenerator object. We then use that object to create a series of dynamic changes to the current page.

def save
   user = @params['user']['login']
   item_hash = @params['item']
   @item = Item.find(item_hash['id'])
   
   # don't update the sender!
   @item.user = User.find(:first, :conditions => ["login = :name", 
      {:name => user}])
   # don't update the time!
   if  @item.update_attributes(item_hash)
      user_model = @request.session[:user]
      item_list = user_model.todo_items
      pages, items = paginate(:item, :order_by => 'priority DESC,
         date', :conditions => ['user_id = ?', user_model.id])
      render :update do |page|
         page[:scratchpad].hide
         page.replace 'todo', :partial => 'table', :locals => 
            {:items => items}
         page["row#{@item.id}"].visual_effect :highlight, 
            :startcolor => '#00ff00', :duration => 5
         page.replace_html 'flash', 
         "<p style='color:green'>#{@item.title} successfully updated!</p>"
         
         page[:flash].show
         page[:flash].visual_effect :pulsate, :queue => 
            {:position => 'end', :scope => 'flash'}
         page[:flash].visual_effect :fade, :queue => 
            {:position => 'end', :scope => 'flash'}
      end
   else
      @user = @request.session[:user]
      render :update do |page|
         page.replace_html 'scratchpad', :partial => 'edit'
         page[:ErrorExplanation].visual_effect :pulsate, :duration => 3
      end
   end
end

Good enough, but what does the code do? Well, render :update creates a JavaScriptGenerator for the current page, and passes it a block. Within the block, we use the page object to make our changes. You access elements using their id: page['id'] or page[:id].

If the save is successful, we hide the scratchpad, then replace the table with new data generated by the table partial. Next, we highlight the changed row. Specifically we change the background color to green and then have it fade back to its normal color over 5 seconds.

For the flash message, we replace the contents of the flash <div> with the success message. We then show the message, cause it to pulse, and then make it to fade away. There are two important things to note here. First, look at the difference between page.replace and page.replace_html. page.replace replaces the named tag, while page.replace_html only replaces the tag's contents. When we replace the flash <div>, the <div> remains (allowing us to send it more messages later).

Next, look at the flash message's visual effects. Normally, visual effects run in parallel--which often means the last effect overrides the others. Here, we are creating a named queue, and adding the events to the end of the queue. Effects in a queue run sequentially. This means the pulsate effect will run. When it finishes, the fade effect runs.

One last detail, the current code always replaces the table with the first page. This is fine when you have less than ten items, or if you only work on the first page. Otherwise, we need to track the current page.

We want to access the page number in different actions, so let's save it in the session. Add the following to the end of item_controller's list():

session[:page] = @pages.current.number

Now, we need to recover this value in edit(). Again, add the following code:

@page = session[:page]

@page is an instance variable, and Rails passes all the controller's instance variables to its views. So, within the _edit.rhtml partial, we can access the @page variable as shown:

<%= form_remote_tag :url => {:action => 'save', :page => "#{@page}"} %>

This inserts the page number into the parameters that we then pass to the :save action. Inside save(), the pagination() function automatically picks up the page number, and produces the correct page. Everything else works auto-magically.

OK, try it out. Edit an item, and watch the changes. I know, I know. The effects are a bit much. But, I hope you can see the possibilities.

Up next, adding and deleting ToDo items. I leave those as homework problems. You should be able to follow the edit example above. However, pay attention to :destroy. It has additional complications. What happens if you delete the only item on a page?

Cleaning Up The Code

I recommend subscribing to the RSS feed for the Ruby On Rails blog (http://weblog.rubyonrails.org/). A lot of good information flows through here. Recently, an article pointed out that the code used by many Rails tutorials and books (including my last article) has been deprecated. Specifically, you should no longer access the @params variable directly. Instead, access the parameters through the params() method (just delete the @). This is generally good programming advice--use the accessor method to access the data. This allows you to change how and where the data is stored without breaking the application. The same rule applies to @request, @response, @session, @headers, @template, @cookies, and @flash. In addition, @content_for_layout should be replaced with a simple yield() call.

I will not go through all these changes here. But, I have tried to clean up the sample code. Please forgive me if I've missed the occasional variable.

Changing from standard actions to Ajax helpers causes some of our test cases to fail. Again, testing is beyond the scope of this article; however, I have updated all the tests in the sample code. I have also added sample integration tests (also new with Rails 1.1). Check out the sample code for more information.

Ajax Dark Side

Ajax is impressive, but it has a few problems. First, it uses CSS and JavaScript heavily--both are famous for producing different behavior on different browsers. Older browsers, in particular, may choke on all the Ajax code. The Prototype and Scriptaculous libraries try to isolate you from many of these ugly, cross-platform details, but compatibility problems will occasionally raise their pointy little heads. If you're building a production system, test on as many different browsers as possible. Also, you might want to define a fallback command for non-JavaScript browsers using form_remote_tag's :html option or link_to_remote()'s :href html option. See the Rails API for more information.

The second problem is a little subtler, and deserves a lot more thought. After you Ajaxify your web page, the back button may not work as expected. The back button takes you back to the previous page. It does not undo any changes made to the current page.

Sometimes a functioning back button is more important than cool Ajax effects. Think about the application's workflow. Often, you can split an application into logical groups. Use standard links to move between groups (enabling the back button), but Ajax techniques within a group.

Finally, Ajax often complicates testing. This is particularly true if you are testing the html itself. The Rails testing suite has a number of tools to pull apart html tags and verify the presence (or absence) of specific tags. Unfortunately, most Ajax functions return html embedded in JavaScript. The tags are apparently no longer accessible. However, this might be for the best. HTML-level testing is very fragile. It often breaks when you change the layout. Your time is probably better spent creating more-robust test cases. For example, integration tests can handle Ajax functions just fine.

Conclusion

Rails takes much of the pain and general ugliness out of writing Ajax applications. Ajax adds a layer of complexity to your application--but Rails effectively manages this complexity, making it almost transparent. So, try it. You might like it.


Rich Warren lives in Honolulu, Hawaii with his wife Mika and daughter Haruko. He is a freelance writer, programmer, and part-time Graduate student at the University of Hawaii in Manoa. When not playing on the beach with his daughter, he can be found writing, studying, doing research or building web applications--all on his MacBook Pro. You can reach Rich at <rikiwarren@mac.com>.

 
AAPL
$99.18
Apple Inc.
-1.57
MSFT
$45.90
Microsoft Corpora
-0.46
GOOG
$568.27
Google Inc.
-9.09

MacTech Search:
Community Search:

Software Updates via MacUpdate

Cocktail 8.0 Beta 2 - General maintenanc...
Cocktail is a general purpose utility for OS X that lets you clean, repair and optimize your Mac. It is a powerful digital toolset that helps hundreds of thousands of Mac users around the world get... Read more
QuickBooks 2015 16.0.0.1352 R1 - Financi...
QuickBooks 2015 helps you manage your business easily and efficiently. Organize your finances all in one place, track money going in and out of your business, and spot areas where you can save.... Read more
Mac DVDRipper Pro 5.0.1 - Copy, backup,...
Mac DVDRipper Pro is the DVD backup solution that lets you protect your DVDs from scratches, save your batteries by reading your movies from your hard disk, manage your collection with just a few... Read more
Apple OS X bash Update 1.0 - Fix for sec...
The OS X bash Update fixes a security flaw in the bash UNIX shell on OS X 10.9.5 (also on OS X 10.8 and 10.7 [see Related Links below]). OS X 10.9.5 or later Downloads for OS X 10.8 and OS X 10.7 in... Read more
SyncTwoFolders 2.0.5 - Syncs two user-sp...
SyncTwoFolders simply synchronizes two folders. It supports synchronization across mounted network drives and it is a possibility to run a simulation showing in a log what will be done. Please visit... Read more
FinderPop 2.5.7 - Classic Mac utility, n...
FinderPop is a Universal preference pane that extends OS X's contextual menus using a FinderPop Items folder much as the Apple Menu Items folder used to do for the Apple menu. It has other features... Read more
VueScan 9.4.45 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
LibreOffice 4.3.2.2 - Free Open Source o...
LibreOffice is an office suite (word processor, spreadsheet, presentations, drawing tool) compatible with other major office suites. The Document Foundation is coordinating development and... Read more
calibre 2.4 - Complete e-library managem...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital... Read more
Default Folder X 4.6.9b1 - Enhances Open...
Default Folder X attaches a toolbar to the right side of the Open and Save dialogs in any OS X-native application. The toolbar gives you fast access to various folders and commands. You just click... Read more

Latest Forum Discussions

See All

iKeywi - Customizable 5-Row Keyboard (U...
iKeywi - Customizable 5-Row Keyboard 1.0 Device: iOS Universal Category: Utilities Price: $.99, Version: 1.0 (iTunes) Description: Want to add an extra row to your iPhone/iPad? One of the most popular keyboard extension in iOS... | Read more »
Manage Your Cloud – Wunderlist Now Suppo...
Manage Your Cloud – Wunderlist Now Supports Dropbox Posted by Jessica Fisher on October 1st, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Nexticy Review
Nexticy Review By Jennifer Allen on October 1st, 2014 Our Rating: :: IDEAL FORM CREATIONiPad Only App - Designed for the iPad Nexticy allows you to make your own forms for research purposes or to organize your business better. It’s... | Read more »
Tiny Troopers: Alliance Marches onto the...
Tiny Troopers: Alliance Marches onto the App Store Tomorrow Posted by Jessica Fisher on October 1st, 2014 [ permalink ] Tiny Troopers: Alliance, by Kukouri, is a | Read more »
HeroCraft Introduces Unlimited Sequel to...
HeroCraft Introduces Unlimited Sequel to WW2: Sandbox. Strategy & Tactics Posted by Jessica Fisher on October 1st, 2014 [ permalink ] | Read more »
RGB Express Review
RGB Express Review By Jennifer Allen on October 1st, 2014 Our Rating: :: DELIGHTFUL PUZZLINGUniversal App - Designed for iPhone and iPad Guide trucks along their delivery routes in RGB Express, a testing but charming puzzle game... | Read more »
The Sagas of Fire*Wolf (Games)
The Sagas of Fire*Wolf 1.0 Device: iOS Universal Category: Games Price: $9.99, Version: 1.0 (iTunes) Description: | Read more »
BuggyFun Review
BuggyFun Review By Amy Solomon on October 1st, 2014 Our Rating: iPad Only App - Designed for the iPad BuggyFun allows children to create their own tracks for bugs to interact with for a unique open-ended experience.   | Read more »
Fold the Adventure Review
Fold the Adventure Review By Jennifer Allen on October 1st, 2014 Our Rating: :: AWKWARD FOLDSUniversal App - Designed for iPhone and iPad Fold pieces of paper to create platforms for a princely rabbit in this puzzle game; something... | Read more »
WW2: Sandbox. Strategy & Tactics (G...
WW2: Sandbox. Strategy & Tactics 1.0.0 Device: iOS Universal Category: Games Price: $9.99, Version: 1.0.0 (iTunes) Description: ***NOTE: Compatible with iPhone 4s and up, iPad 2 and up - may not work properly on earlier devices... | Read more »

Price Scanner via MacPrices.net

Amazon offers 13-inch MacBook Air for $899, $...
Amazon.com has the 13″ 1.4GHz 128GB MacBook Air on sale for $100 off MSRP including free shipping: - 13″ 1.4GHz 128GB MacBook Air: $899.99 Read more
Apple resting On Its iPhone Laurels? – The ‘B...
Apple calls its new iPhone 6 and 6 Plus “The Biggest Advancements in iPhone History,” but does reality live up to the hype? “Seldom have so many waited so breathlessly for so little,” tweeted veteran... Read more
Roundup of Apple Mac and iPad Education disco...
Purchase a new Mac or iPad at The Apple Store for Education and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free,... Read more
Apple Boycotts German Magazine Computer Bild...
Apple has revoked its PR accreditation of Germany’s Computer Bild, Europe’s best-selling PC magazine, in reaction to Bild’s posting of a “#Bentgate” YouTube video. Axel Telzerow, editor in chief of... Read more
iPhone 6 & iPhone 6 Plus Available in Chi...
Apple has announced that iPhone 6 and iPhone 6 Plus will be available in China beginning Friday, October 17 from the Apple Online Store (http://www.apple.com), Apple’s retail stores, and an expansive... Read more
MacBook Airs on sale for $100 off MSRP, start...
Best Buy has the new 2014 MacBook Airs on sale for $100 off MSRP on their online store. Choose free home shipping or free local store pickup (if available). Prices valid for online orders only, in-... Read more
Apple Releases OS X Mavericks bash Update 1.0...
Apple has released a patch update for OS X Mavericks users to address the recently-detected “Shellshock” security bug in BSD UNIX’s bash shell. Apple says only a few Mac users who had manually... Read more
Pivotal Payments Ready for Apple Pay – FlexPo...
Pivotal Payments, a provider of merchant services and global payment processing solutions, has announced its proprietary FlexPoint platform will support credit and debit transactions through Apple’s... Read more
iStabilizer Announces Tabarm — First Friction...
iStabilizer, a specialist in universal lightweight compact tripods, steady cams, dollies, mounts, and remotes for smartphones, tablets, and cameras, announced today the iStabilizer tabArm, the first... Read more
IStabilizer Flex Smartphone Tripod Wins Usa T...
iStabilizer, a specialist in universal lightweight compact tripods, steady cams, and other products for smartphones, tablets, and cameras, has announced today that its iStabilizer Flex smartphone... Read more

Jobs Board

*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Project Manager / Business Analyst, WW *Appl...
…a senior project manager / business analyst to work within our Worldwide Apple Fulfillment Operations and the Business Process Re-engineering team. This role will work Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.