Cover | Table of Contents | Colophon
XMLHttpRequest, as well as a wealth of methods for manipulating the DOM and JavaScript data structures. The script.aculo.us library builds atop Prototype and focuses on visual effects and advanced UI capabilities, such as drag and drop.XMLHttpRequest object). By the end, you’ll be comfortable creating XMLHttpRequest objects both by hand and by using the Prototype library. Finally, we’ll use Rails’ JavaScript helpers to create simple Ajax interactions without writing any JavaScript. With the foundation in place, you’ll have an accurate understanding of how the Rails helpers work—and also an appreciation for how much trouble they will save you.
XMLHttpRequest directly, without Prototype or Rails’ JavaScript helpers.XMLHttpRequest is often portrayed as being rocket science. But you’ll find that, with a little practice and perhaps a couple new concepts, it’s not as tricky as its reputation suggests.rails ajaxonrails cd ajaxonrails script/server
script/server starts an HTTP server on port 3000). Back at the command line, let’s generate a new controller called Chapter2Controller with an action called myaction. (Since you’re already running the server in one terminal window, you’ll want to open another.)script/generate controller chapter2 myaction
XMLHttpRequest directly, without Prototype or Rails’ JavaScript helpers.XMLHttpRequest is often portrayed as being rocket science. But you’ll find that, with a little practice and perhaps a couple new concepts, it’s not as tricky as its reputation suggests.rails ajaxonrails cd ajaxonrails script/server
script/server starts an HTTP server on port 3000). Back at the command line, let’s generate a new controller called Chapter2Controller with an action called myaction. (Since you’re already running the server in one terminal window, you’ll want to open another.)script/generate controller chapter2 myaction
script/generate without arguments.
<p><a href="#" onclick="alert('Hello !');">Inline alert( )</a></p><script src="/javascripts/prototype.js" type="text/javascript">
</script>
<p><a href="#" onclick="prototypeAlert( );">Call with Prototype</a></p>
<script type="text/javascript">
function prototypeAlert( ) {
new Ajax.Request('/chapter2/myresponse', { onSuccess: function(request) {
alert(request.responseText);
}})
}
</script>prototypeAlert( ) function, the first line creates a new instance of Ajax.Request, one of Prototype’s classes. The first argument takes the URL to be requested, and the second argument is a JavaScript object literal—a collection of key/value pairs, which behaves similar to a hash or associative array in other languages. In this case, the only option given is onSuccess, which is expected to be a callback function.XMLHttpRequest and no mention of readyState codes. Prototype handles those details, leaving you with a far cleaner API.alert( ) box—which, in your real-world applications, is probably not the most common thing you’d want to do. More often, you’ll want to add or modify some content on the page. Here’s a new iteration to add:<p><a href="#" onclick="updateElement( )">Update element </a></p>
<p id="response"></p>
<script type="text/javascript">
function updateElement( ) {
new Ajax.Request('/chapter2/myresponse', { onSuccess: function(request) {
$('response').update(request.responseText);
}})
}
</script>link_to_remote( ) helper method.
<%= %> | The most common one, this holds a Ruby expression—which is output in place of the tag. |
<%= -%> | Works just like the above but suppresses newline characters from the output after the tag, which allows for cleanly organized templates without extraneous whitespace in the HTML output. |
<% %> | This holds a piece of Ruby code but doesn’t output anything. |
<% -%> | Works just like the above but suppresses newline characters after the tag. |
<%# %> | This is a Ruby comment, which is ignored and nothing is output. |
@ sign that they all start with). So, imagine that we have this controller action:def myaction @foo = "Hello, world!" end
@foo, and puts the string Hello,
world! into it. Our template could then contain this:<%= @foo %>
<%= @foo %> would be replaced with script/generate controller chapter3 get_time repeat reversechapter3 with four actions: index, get_time, repeat, and reverse. Take a look at http://localhost:3000/chapter3 and you will see a bare-bones view, as in Figure 3-1.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Ajax on Rails</title>
<%= javascript_include_tag :defaults %>
<%= stylesheet_link_tag "application" %>
</head>
<body>
<h1>Ajax on Rails</h1>
<%= yield %>
</body>
</html>
javascript_include_tag
:defaults, which will include Prototype and script.aculo.us (specifically prototype.js, effects.js, dragdrop.js, and controls.js), as well as application.js, if present. The second is
yield—that’s where the content from your action templates will be inserted. For the sake of nice-looking templates, let’s make a simple CSS file, public/stylesheets/application.cssscript/generate controller chapter3 get_time repeat reversechapter3 with four actions: index, get_time, repeat, and reverse. Take a look at http://localhost:3000/chapter3 and you will see a bare-bones view, as in Figure 3-1.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Ajax on Rails</title>
<%= javascript_include_tag :defaults %>
<%= stylesheet_link_tag "application" %>
</head>
<body>
<h1>Ajax on Rails</h1>
<%= yield %>
</body>
</html>
javascript_include_tag
:defaults, which will include Prototype and script.aculo.us (specifically prototype.js, effects.js, dragdrop.js, and controls.js), as well as application.js, if present. The second is
yield—that’s where the content from your action templates will be inserted. For the sake of nice-looking templates, let’s make a simple CSS file, public/stylesheets/application.css:body {
background-color: #eee;
color: #222;
font-family: trebuchet;
padding: 0;
margin: 25px;
}
h1 {
margin: -25px -25px 20px -25px;
padding: 50px 0 8px 25px;
border-bottom: 3px solid #666;
background-color: #777;
color: #fff;
font: normal 28pt georgia;
text-shadow: black 0px 0px 5px;
}
a { color: #229; }
.box {
border: 1px solid;
width: 100px; height: 100px;
padding: 5px;
font-size: .6em;
letter-spacing: .1em;
text-transform: uppercase;
margin-bottom: 20px;
}
.pink {
border-color: #f00;
background-color: #fcc;
}
.green {
border-color: #090;
background-color: #cfc;
}
.hover {
border-width: 5px;
padding: 1px;
}
ul {
background-color: #ccc;
padding: 5px 0 5px 30px;
}<%= link_to_remote "Check Time",
:update => 'current_time',
:url => { :action => 'get_time' } %>
<div id="current_time"></div>link_to to
link_to_remote and added a new option, :update. The value of :update refers to the HTML element ID where the Ajax response should be inserted—in this case, a DIV. The generated HTML looks like this:<a href="#"
onclick="new Ajax.Updater('current_time', '/chapter3/get_time',
{asynchronous:true, evalScripts:true});
return false;">Check Time</a>
<div id="current_time"></div>Ajax.Updater method. All the Rails Ajax helpers work this same way: they are Ruby methods, embedded in HTML templates, generating JavaScript, calling Prototype.href="#". While technically valid HTML, this kind of “link to nowhere” is generally a bad practice. If the user has JavaScript turned off, or a search engine is indexing the page, the link will be meaningless. Whenever possible, it’s a good idea to provide a useful link, as a fallback for non-Ajax browsers. Chapter 6 covers the idea of degradability in more detail.
onclick attribute, which is a way to hijack the behavior of a link. When an onclick is provided, the browser will evaluate it before following the link. The link will only be followed if the expression evaluates true (or if the user has JavaScript turned off). That’s why the link_to_remote helper puts return
false at the end of the onclick attribute.link_to_remote helper provides a set of callbacks so you can easily make things happen during the life cycle of an Ajax request by providing JavaScript snippets to be evaluated. For example:<%= link_to_remote "Check Time",
:update => 'current_time',
:url => { :action => 'get_time' },
form_tag and end_form_tag helpers create an HTML form element. For example, this:<%= form_tag :action => 'reverse' %> <%= end_form_tag %>
<form action="/chapter3/reverse" method="post"> </form>
text_field_tag(
name
,
value
=
nil
,
options
=
{}
)
options hash will be made into HTML attributes. For example:<%= text_field_tag "name", "Scott",
:size => 5,
:disabled => true,
:style => "background-color: red" %><input type="text" name="name" id="name" value="Scott" size="5" disabled="disabled" style="background-color: red" />
hidden_field_tag(
name
,
value = nil
,
options = {}
)
text_field_tag.password_field_tag(
name = "password"
,
value = nil
,
options = {}
)
text_field_tag.file_field_tag(
name
,
options = {}
)
text_field_tag.check_box_tag(
name
,
value = "1"
,
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 %>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.
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
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;">onclick attribute hijacks a link, onsubmit hijacks the behavior of forms.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.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 attribute—the 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 %>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 %>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.
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!')" %>button_to_function with remote_function. That helper takes the same arguments as
observe_field helper allows you to attach behavior to a field so that whenever it’s changed, the server is notified via Ajax. It can be used like this:
<p>Text to reverse: <%= text_field_tag 'textToReverse' %></p>
<span id="reversed"></span></p>
<%= observe_field 'text_to_reverse',
:update => 'reversed',
:url => { :action => 'reverse' },
:with => 'text_to_reverse' %>text_field_tag—so what does observe_field create? It creates JavaScript:new Form.Element.EventObserver('textToReverse',
function(element, value) {
new Ajax.Updater('reversed', '/chapter3/reverse',
{ parameters:'text_to_reverse=' + value });
}
)Form.Element.EventObserver class, bound to the text_to_reverse field. Whenever the field changes, the observer triggers Ajax.Updater, which we’re familiar with from Chapter 2. For a full description of Form.Element.EventObserver, see Chapter 10.observe_field are the same as link_to_remote (:update, :url, callbacks, etc.), with a few additions. First, the :with option is a JavaScript expression that’s evaluated to determine the parameters that are passed to the server. By default it is value—which, when evaluated in the JavaScript context, represents the value of the field being observed. So if no :with option is provided, the generated JavaScript would look like this:new Form.Element.EventObserver('textToReverse',
function(element, value) {
new Ajax.Updater('reversed', '/chapter3/reverse',
{parameters:value});
}
}params object on the server side. The :with option gives the parameter a name. If :with is set to foo, the code becomes:new Form.Element.EventObserver('textToReverse',
function(element, value) {
new Ajax.Updater('reversed', '/chapter3/reverse',
{parameters:link_to_remote is foundational to Ajax on Rails, because its options and callbacks are echoed through every other Ajax-related helper in the framework. After links we moved on to richer forms of interaction: buttons and forms, in their traditional and Ajaxified guises.script/generate controller chapter4 index
Effect object, which is used to attach a variety of cinematic effects to UI events. Using script.aculo.us effects, many of the slick animated transitions that people have come to associate with Flash can be accomplished without plug-ins at all, and in a way that preserves the benefits of HTML.
Effect object, which is used to attach a variety of cinematic effects to UI events. Using script.aculo.us effects, many of the slick animated transitions that people have come to associate with Flash can be accomplished without plug-ins at all, and in a way that preserves the benefits of HTML.
Effect object is where the magic resides. Let’s look at it. First, we’ll need an element to try our effects on, so add one to the top of the new Draggable class that’s used to add draggability to DOM elements. To get started, create a new template file, draggables.rhtml. In it, add this:
<div id="dragDIV" class="green box">drag</div>
<%= javascript_tag "new Draggable('dragDIV')" %>Draggable class to be created, tied to the given element ID. From then on, you can drag the element around the page. Notice how it becomes slightly transparent while it is dragged—it uses the same Opacity effect we explored earlier. The Draggable constructor takes an optional second parameter for options, which will be detailed later.draggable_element helper to create draggables. Just like Draggable.initialize, the first argument is the ID of an element, and the second is a hash of options. For example:<div id="helperDIV" class="green box">helper</div> <%= draggable_element :helperDIV %>
draggable_element is a <script> element with a new
Draggable statement. If you just need the JavaScript statement without the <script> tags, use draggable_element_js instead. For example:<div id="clickDIV" class="green box">
<%= button_to_function "Make draggable",
draggable_element_js(:clickDIV) %>
</div>link_to_remote
:update
=>
... helper and you’ll quickly appreciate how valuable JavaScript can be.