AJAX

Introduction

AJAX Use Cases

AJAX Links


link_to_remote("Destroy", :url => {:action => 'destroy', :id => item},
:confirm => "Are you sure?" 

link_to_function "Cancel", "$('create_form').hide();" 

link_to_function "Cancel" do |page|
  page[object.dom_id("rate_link")].show
  page[object.dom_id("rate")].hide
end

AJAX Form


<% form_remote_tag :url => {:action => 'update'} do %>
  <%= hidden_field_tag "prompt[id]", @prompt.id %>
  <%= render :partial => 'form', :locals => {:mode => 'edit'} %>
  <%= submit_tag "Edit" %>
<% end %>

RJS

Example: Posting a Comment – The View


# In your .rhtml view:
<% form_remote_tag
  :url => {:controller => "comments", :action => "create", :id => user},
  :html => { :id => "comment_form"},
  :before => "$('spinner').show()",
  :complete => "$('spinner').hide()" do %>
  <%= text_area "comment", "body", :cols => 80 %><br/>
  <%= submit_tag 'Submit' %>
<% end %>

# The form tag generated by form_remote_tag:
<form action="/comments/create/1" id="comment_form" method="post" 
  onsubmit="$('spinner').show(); new Ajax.Request(...">

Example: Posting a Comment – The Controller


def create
  @comment = Comment.new(params[:comment])
  if @comment.save
    render :update do |page|
      page.insert_html :bottom, 'comment_list', :partial => 'comment'
      page.visual_effect :highlight, @comment.dom_id
      page['comment_form'].reset
    end
  else
    render :update do |page|
      page.alert @comment.errors.full_messages.join("\n")
    end
  end
end

Example: Deleting a Comment


# In your .html.erb view:
<%= link_to_remote "Delete", :url => {:action => 'destroy', :id => comment},
  :confirm => "Are you sure you want to delete your comment?",
  :update => comment.dom_id %>
</div>
# In the controller
def destroy
  @comment = Comment.find(params[:id])
  assert_authorized
  @comment.destroy
  render :text => ''
end

Options for Remote Forms and Links


# Callbacks:
:before # before request is initiated and before request object is created
:after # request object's open method has not been called yet
:loading # request has not been sent yet
:loaded # request has been initiated
:interactive # response is being received
:success # response is ready and in 200 range
:failure # response is ready and is not in 200 range
:complete # response is ready
# Other options
:submit # id of a form to submit, can also be just a div with form elements
:confirm # JavaScript confirm dialog before request
:condition # JavaScript expression that must be true for request to happen

Server Responses to AJAX Requests

Options for Updating the Page

Prototype Basics


$A(document.getElementsByTagName('a')).first()
$H({'ren':'happy', 'stimpy':'joy'}).keys()
$('some_id').hide()|show() # instead of document.getElementById('some_id')
$F('login') # The value of field input login
$$('div#footer').invoke('hide') # CSS selector
$$('a').each( function(element) { element.hide() })

Example: An AJAX Shopping Cart


# In index.rhtml:
<% form_remote_tag :url => { :action => :add_to_cart, :id => product } do %>
<%= submit_tag "Add to Cart" %>
<% end %>

# The action:
def add_to_cart
  product = Product.find(params[:id])  
  @current_item = @cart.add_product(product)
  redirect_to_index unless request.xhr?
end

Example: Shopping Cart RJS Template


# The RJS template add_to_cart.js.rjs:
page.select("div#notice").each { |div| div.hide }
page.replace_html("cart", :partial => "cart", :object => @cart)
page[:cart].visual_effect :blind_down if @cart.total_items == 1                 
page[:current_item].visual_effect :highlight, :startcolor => "#88ff88", :endcolor => "#114411" 

RJS Methods


# Position argument is one of :before, :top, :bottom, :after
page.insert_html :bottom 'todo_list', "<li>#{todo.name}</li>" 
page.replace_html 'flash_notice', "Todo added: #{todo_name}" 
page.replace 'flash_notice', :partial => 'flash', :object => todo
page[:flash_notice].remove|show|hide|toggle # page[:flash_notice] <=> $('flash_notice') 
page.alert "The form contains the following errors: #{errors.join(“, “)}" 
page.redirect_to :controller => 'blog', :action => 'list'

More RJS Methods


# Available effects: :fade, :appear, :blind_up/down,
# :slide_up/down, :highlight, :shake, :pulsate, etc.
page.visual_effect :pulsate, 'flash_notice'
page.delay(3) do
  page.visual_effect :fade, 'flash_notice'
end
page.select('p.welcome b').first.hide
page.select('#items li').each do |value|
  value.hide
end

Observing Forms


<%= observe_form('search_form', 
    :url => {:action => 'search_count'},
    :frequency => 3,
    :condition => (@model_name == 'Contact' ? 
        "!$('search_form').submitting && !contact_search_form_empty()" :
        "!$('search_form').submitting && !outlet_search_form_empty()"),
    :with => "search_form",
    :loading => "$('spinner').show();",
    :complete => "$('spinner').hide();") %>

Observing Fields


<%= observe_field("show_hide_select", 
    :url => { :action => 'toggle_column', :item_count => item_count },
    :with => "'column_name=' + value") %>

Model Object DOM id

The Spinner Icon


# Spinning icon that is displayed when an AJAX request is in progress
<img class="spinner" id="ajax_spinner" 
  src="/images/spinner.gif" alt="Comment being processed" style="display: none" />

Global AJAX Hooks for the Spinner Icon


# In public/javascripts/application.js. Will show the spinner whenever
# an AJAX request is in process.
Ajax.Responders.register({ 
  onCreate: function(){ 
    $('spinner').show(); 
  }, 
  onComplete: function() { 
    if(Ajax.activeRequestCount == 0) 
      $('spinner').hide(); 
  } 
}); 

Drag and Drop Example: The Request Side


# This example is about dragging books in list to a shopping cart in the menu bar.
<li class="book" id="book_<%= book.id %>">
  ...book info here...
</li>
<%= draggable_element("book_#{book.id}", :revert => true) %>
<div id="shopping_cart">
  <%= render :partial => "cart/cart" %>
</div>
<%= drop_receiving_element("shopping_cart", :url => { :controller => "cart", :action => "add" }) %>

Drag and Drop Example: Controller Action


def add
    params[:id].gsub!(/book_/, "")
    @book = Book.find(params[:id])
    @item = @cart.add(params[:id])
    if request.xhr?
      flash.now[:cart_notice] = "Added <em>#{@item.book.title}</em>" 
      render :action => "add_with_ajax" 
    elsif request.post?
      flash[:cart_notice] = "Added <em>#{@item.book.title}</em>" 
      redirect_to :controller => "catalog" 
    end
end

Drag and Drop Example: RJS Template


# add_with_ajax.js.rjs:
page.replace_html "shopping_cart", :partial => "cart" 
page.visual_effect :highlight, "cart_item_#{@item.book.id}", :duration => 3
page.visual_effect :fade, 'cart_notice', :duration => 3

Auto Completion


# Installation
script/plugin install git://github.com/rails/auto_complete.git

# Controller
class BlogController < ApplicationController
  auto_complete_for :post, :title
end

# View
<%= text_field_with_auto_complete :post, title %>

In-Place-Edit


# Installation
script/plugin install git://github.com/rails/in_place_editing.git

# Controller. Warning: does not do validation.
class BlogController < ApplicationController
  in_place_edit_for :post, :title
end

# View
<%= in_place_editor_field :post, 'title' %>

Degradability for When there is No JavaScript

RJS Reuse


module ApplicationHelper
  def replace_article(article)
    update_page do |page|
      page[:article].replace partial => :article, :locals => {:article => article}
    end 
  end
end 

# In the controller action
render :update do |page|
  page << replace_article(@article)
  page.highlight :article
end

Browser Tools

Firebug and the Web Developer Extension for Firefox are great tools when working with AJAX. You can use Firebug Lite in other browsers (i.e. IE on windows).