Flylib.com

Books Software

 
 
 

Section 3.4. Ajax Forms


3.4. Ajax Forms

To Ajaxify this form, all we need to do is replace the form_tag helper with its form_remote_tag alternative and add a place for the response to be inserted:

<%= form_remote_tag :update => "reversed",
                    :url    => { :action => 'reverse' } %>
  <p>Text to reverse: <%= text_field_tag 'text_to_reverse' %></p>
  <p id="reversed"></p>
  <p><%= submit_tag 'Reverse!' %></p>
<%= end_form_tag %>

The options here should look familiar, because they're exactly the same as the options for link_to_remote . The :update option specifies which HTML element will be updated with the Ajax response, and :url provides the URL for the Ajax request. Try out the new form, and you'll get something like Figure 3-5. As you can see, that won't do.

Figure 3-5. Oops, that's not right

The layout is being rendered twice, heading and all. The problem is that every action (such as our reverse ) will render within layouts/application. rhtml unless told otherwise . To specify a layout (or turn them off), the action needs an explicit render statement:

def reverse
  @reversed_text = params[:text_to_reverse].reverse
  render :layout => false
end

With that line added, try the Ajax form again, and everything should work as expected, as seen in Figure 3-6.

Figure 3-6. Rendered without layout

The rendered result of the form_remote_tag uses Prototype's Ajax.Updater , just like link_to_remote did:

<form action="/chapter3/reverse" method="post" 
   onsubmit="new Ajax.Updater('reversed','/chapter3/reverse',
     {asynchronous:true, evalScripts:true,
      parameters:Form.serialize(this)});
     return false;">

Just as the onclick attribute hijacks a link, onsubmit hijacks the behavior of forms.

The Ajax counterpart to form_for (the helper for creating forms to work with model objects) is remote_form_for . Using it works exactly like form_for , except that the options hash may also contain the usual Ajax options, such as :update and :complete .



3.5. Buttons

Notice something about the previous form_to_remote example: in the generated HTML, the only difference between a regular form and an Ajaxified form is the addition of an onsubmit attributethe rest of the form, including the submit buttons, are vanilla HTML. Where form_to_remote creates a special, Ajaxified form with normal submit buttons, submit_to_remote does the opposite : it creates a special submit button for a plain form. For example:

<%= form_tag :action => 'reverse' %>
  <p>Text to reverse: <%= text_field_tag 'text_to_reverse' %></p>
  <p id="reversed2"></p>
  <p><%= submit_to_remote 'submit', 'Reverse!',
          :update => 'reversed2',
          :url => { :action => 'reverse' } %></p>
<%= end_form_tag %>

The first parameter to submit_to_remote determines the name attribute on the button, and the second sets the value, which appears in the button. When you click the button, the end result is exactly the same as before. However, the difference is that the form can be submitted both via Ajax or non-Ajax methods . Consider this variation with two submit buttons:

<%= form_tag :action => 'reverse' %>
  <p>Text to reverse: <%= text_field_tag 'text_to_reverse' %></p>
  <p id="reversed"></p>
  <p><%= submit_to_remote 'submit', 'Submit via Ajax',
          :update => 'reversed',
          :url => { :action => 'reverse' } %></p>
  <p><%= submit_tag "Submit non-Ajax" %></p>
<%= end_form_tag %>

In practice, a common application for submit_to_remote would be checking a form for validity before actually submitting it for creation. For example, during a sign-up process you could allow the user to check whether a chosen username is available.

3.5.1. Buttons for Arbitrary Functions

The button_to_function helper creates a button that triggers a JavaScript function. Just like link_to_function , the first argument becomes the text inside the button, and the second argument is the JavaScript to be evaluated. For example:

<%= button_to_function "Greet", "alert('Hello world!')" %>

To create a button that initiates an Ajax request, you can combine button_to_function with remote_function . That helper takes the same arguments as link_to_remote and returns the JavaScript needed for a remote function.

<%= button_to_function "Check Time",
      remote_function(:update => "current_time",
        :url => { :action => 'get_time' }) %>

3.5.2. Custom Helpers

Given the existence of link_to_function and link_to_remote , you would expect that button_to_function would have a corresponding button_to_remote but there is no such beast . Fortunately, it's easy to implement, and it gives us a good reason to examine how to implement custom helpers. Because we're working in the chapter3 controller, custom helpers can be defined in either app/helpers/chapter3_helper.rb or app/helpers/application_helper.rb they'll be accessible from our templates either way. For the new button_to_remote helper, we want to mimic the API of link_to_remote : the first parameter should be the button label, and the second should be a hash of options that's passed to remote_function . Here's an implementation:

def button_to_remote name, options = {}
  button_to_function name, remote_function(options)
end

As you can see, this is little more than a wrapper for button_to_function , but it allows us to have the same familiar API as link_to_remote :

<%= button_to_remote "Get Time Now",
      :update => "current_time",
      :url    => { :action => 'get_time' } %>

Custom helpers are an invaluable tool for keeping templates clean and maintainable . Any time you find yourself creating complicated logic or repeating yourself in the view, consider extracting the job to a helper.