By Scott Raymond
Book Price: $39.99 USD
£28.50 GBP
PDF Price: $27.99
Cover | Table of Contents | Colophon
http://www.tadalist.com), the developers needed some simple Ajax functionality. Writing the necessary JavaScript for the project turned out to be painful—and pain is often the first sign that an extraction might be useful. By the time the company embarked on its next Ajax/Rails application, Backpack (http://backpackit.com), Ajax functionality had been added to the framework. The result was that Rails was one of the first web frameworks with first-class Ajax support. And because of the philosophy of extraction, it remains one of the most pragmatically useful environments to work in.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.http://ruby-lang.org. From there, you’ll find downloads for the latest releases. Windows users can take advantage of the One-Click Ruby Installer (http://rubyinstaller.rubyforge.org), which bundles lots of great extensions. Mac users already have Ruby installed as part of OS X—however, it’s not configured correctly for Rails use. To fix that, follow this guide: http://hivelogic.com/articles/2005/12/01/ruby_rails_lighttpd_mysql_tiger.http://tryruby.hobix.com) is a hands-on Ruby tutorial that runs entirely in your browser, with no need to download Ruby first. It’s a great way to familiarize yourself with Ruby’s syntax and conventions.http://www.rubycentral.com/book.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.
<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: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
script/generate controller chapter3 get_time repeat reverse
chapter3 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
yieldscript/generate controller chapter3 get_time repeat reverse
chapter3 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: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 = {}
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 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!')" %>
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 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.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.link_to_remote
:update
=>
... helper and you’ll quickly appreciate how valuable JavaScript can be.