O'Reilly logo

Ajax on Rails by Scott Raymond

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Introducing script.aculo.us

Most of the last chapter dealt with the Rails helpers that interact with Prototype. In this chapter, we’ll shift attention to script.aculo.us, and the Rails helpers that use it. script.aculo.us provides eye-catching visual effects and transitions and powerful drag-and-drop elements.

The relationship between Prototype and script.aculo.us is close. They’re both developed in concert with Rails, and they share very similar coding styles and APIs. In fact, some of what is now script.aculo.us was originally part of Prototype. Despite the close ties, the two libraries have different goals. Prototype is designed to be an extension of JavaScript—it provides features that arguably ought to be part of the core language, such as convenient methods for data structures, DOM interaction, and easy Ajax calls. On the other hand, script.aculo.us works at a higher level, closer to the application and UI levels, by providing components built on Prototype. In some cases, those components are surprisingly complex and yet usable with just a few lines of code.

We’ll put the examples for this chapter into a new controller, so from your Rails project directory, run the generator:

script/generate controller chapter4 index

If you already created an application-wide layout (layouts/application.rhtml) and CSS file (public/stylesheets/application.css) from the beginning of Chapter 3, they will automatically be used for this controller as well.

Now let’s take a look at what script.aculo.us is most famous for: its visual effects.

Visual Effects

The most popular component of script.aculo.us is its 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.

What about cross-platform compatibility? In general, the script.aculo.us visual effects work reliably across different browsers (Internet Explorer 6+ for Windows, Firefox, Safari, Konqeror, Camino, and, with a few exceptions, Opera). And because the animated effects are time-based (as opposed to frame-based) they work consistently on systems of different speeds. You might be wondering: just because visual effects are easy, does that mean they’re a good idea? Isn’t it just eye candy? And what does it have to do with Ajax, anyway?

The full answer to those questions will come in Chapter 6, but here’s the short one. More than just mere decoration, visual effects can be essential to providing a good user experience, especially in conjunction with Ajax. For more than 10 years, users have gotten used to the way the Web works, and Ajax undermines many of their expectations. For example, there’s a basic expectation that web pages are static, that they won’t change once they’re loaded. But in the last chapter, all the Ajax examples made changes to the page without reloading, which has the potential to become confusing. To address that, visual effects can provide cues that make the interface more natural and discoverable.

Tip

A word of caution: just like special effects in the movies, script.aculo.us effects are generally best when you don’t notice them—when they are subtle and unobtrusive, they and contribute something to the plot. Remember when desktop publishing arrived in the 1980s, and every neighborhood newsletter suddenly used 10 different fonts, because it could? If at all possible, try not to get similarly drunk on the power of script.aculo.us.

The script.aculo.us’ 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 index.rhtml:

<div id="target" class="green box">
  <div>Here's a DIV with some text.</div>
</div>

Now let’s use the link_to_function to call an effect on the new element. Add this below the DIV:

<%= link_to_function "Fade", "new Effect.Fade('target')" %>

Remember, link_to_function takes two arguments: the first is the text for the link, and the second is a JavaScript statement to be evaluated. In this example, that statement is a method call on script.aculo.us’ Effect.Fade. Load the page in your browser and try out the link—you should see the target element slowly fade away, until it’s removed from the page flow altogether. Internally, the first argument to Fade() is passed through Prototype’s $() function—which means you can pass it either the ID of an element or an element reference itself.

There’s another way to trigger effects, thanks to the fact that Prototype’s Element methods are added to every element that is accessed via $(). That means you can call visualEffect directly on a DOM element:

$('target').visualEffect('fade')

script.aculo.us has five core effects that control fundamental aspects of an element: Opacity, Scale, Move, Highlight, and Parallel. To get a feel for each:

<%= link_to_function "Opacity",
    "new Effect.Opacity('target', {to:0.5})" %>
<%= link_to_function "Scale",
    "new Effect.Scale('target', 200)" %>
<%= link_to_function "Move",
    "new Effect.Move('target', {x:50,y:10})" %>
<%= link_to_function "Highlight",
    "new Effect.Highlight('target')" %>
<%= link_to_function "Parallel",
    "new Effect.Parallel([
      new Effect.Move('target', {x:50,y:10}),
      new Effect.Opacity('target', {to:0.5})
     ])" %>

In your application, you’ll usually use combination effects, which are composed of the core effects—often by means of Effect.Parallel. script.aculo.us includes 16 standard combination effects, but you can define as many new ones as you like. Here are the standard ones:

Fade 
Appear
Gradually decreases or increases an element’s opacity. Once a fade is finished, the element’s display property is set to none, so the rest of the page will reflow as if it’s not there. 
BlindUp
BlindDown
Works like Venetian blinds: gradually changes the height of the element, leaving the contents of the element fixed in place. 
SlideUp
SlideDown
Similar to BlindUp and BlindDown, except that the contents of the element appear to slide up and down with the element. Note that unlike the other combination effects, the slide effects require a wrapper DIV surrounding the content inside of the target DIV. 
Shrink
Grow
Resizes the entire element, including its contents, from the center point. 
Highlight
Changes the background color of the element (to a pale yellow by default), and then gradually returns to the previous color. Commonly used when you need to draw the user’s attention to part of a page. 
Shake
Causes an element to slide left to right a few times, commonly used to indicate that an element is invalid. 
Pulsate
Rapidly fades an element in and out several times—a modern twist on the much-beloved <blink> tag. 
DropOut
Simultaneously fades an element and moves it downward, so it appears to drop off the page. 
SwitchOff
Simulates an old television being turned off: a quick flicker, and then the element collapses into a horizontal line. 
Puff
Makes an element increase in size while decreasing in opacity—so that it appears to dissolve into a cloud. 
Squish
Similar to Shrink, but the element’s top-left corner remains fixed. 
Fold
First reduces the element’s height to a thin line and then reduces its width until it disappears. 

To try out all the standard combination effects, you could write a link for each one. Instead, let’s keep things DRY by iterating through an array instead:

<% %w( Fade Appear Highlight Fold Pulsate SlideUp SlideDown 
       Shrink Grow Squish Shake DropOut SwitchOff Puff BlindUp 
       BlindDown ).each do |name| %>
  <%= link_to_function name, "new Effect.#{name}('target')" %>
<% end %>

Toggling

Some of the effects are grouped into pairs (Fade/Appear, BlindUp/BlindDown, and SlideUp/SlideDown). script.aculo.us provides a convenient method to toggle between the effects, Effect.toggle:

Effect.toggle('target') /* uses Fade/Appear */
Effect.toggle('target', 'blind')
Effect.toggle('target', 'slide')

Options

The Effect.* methods take an optional second parameter: a hash of options. Some options are effect-specific, but we’ll look at those that apply to every effect.

duration specifies how long the effect should last, in seconds. For example:

<%= link_to_function "Fade",
      "new Effect.Fade('target', { duration:5 })" %>

fps determines the frames per second. The default is 25, and it can’t exceed 100. For example:

<%= link_to_function "Choppy Fade",
      "new Effect.Fade('target', { duration:10, fps:2 })" %>

Note that because script.aculo.us effects are time-based, rather than frame-based, slower systems will automatically drop frames as necessary.

delay specifies the time in seconds before the effect will be started. For example:

<%= link_to_function "Fade",
      "new Effect.Fade('target', { delay:2 })" %>

from and to define the starting and ending points of the effect as values between 0 and 1. For example, you could jump directly to the halfway point of an effect, then gradually fade to 25 percent, and then stop:

<%= link_to_function "Fade with from",
      "new Effect.Fade('target', { from:0.5, to:0.25 })" %>

Queues

In some circumstances, you may want to chain effects, so that they occur sequentially. As a first attempt, you might simply call one effect after the other:

<%= link_to_function "Blind Up/Down",
      "new Effect.BlindUp('target');
       new Effect.BlindDown('target')" %>

Unfortunately, this won’t have the desired result. As new effects are created, script.aculo.us adds them to a global queue. By default, these effects are executed in parallel—which means these two effects will collide with each other. To specify an effect’s position in the queue, use the queue option:

<%= link_to_function "Blind Up/Down",
      "new Effect.BlindUp('target');
       new Effect.BlindDown('target', { queue: 'end' })" %>

Now the two effects will execute sequentially, rather than at once. If you want more than two effects sequentially, just keep adding them with a queue of end. The queue option can also take a value of front, which causes the effect to be executed before anything else in the queue.

script.aculo.us also supports multiple queues, so that you can create named scopes for effects queues that run independently. For more information on creating queue scopes, see Chapter 11.

Callbacks

The options hash can also take parameters for callbacks that are executed through the effect’s life cycle. beforeStart is called before the main effects rendering loop is started. beforeUpdate is called on each iteration of the effects rendering loop, before the redraw takes places. afterUpdate is called on each iteration of the effects rendering loop, after the redraw takes places. afterFinish is called after the last redraw of the effect was made. Callbacks are passed one argument, a reference to the effect object. For example:

<%= link_to_function "Fade with callback",
      "new Effect.Fade('target', { afterUpdate: function(effect) {
         effect.element.innerHTML = effect.currentFrame;
       }})" %>

Chapter 11 covers Effect callbacks in more detail.

Transitions

The transition option determines the pattern of change—a constant linear rate of change, gradual speed up, or anything else. There are eight standard transitions, and you can easily define new ones. To override the default transition for an effect, use the transition option like this:

<%= link_to_function "Fade with wobble",
      "new Effect.Fade('target',
         { transition: Effect.Transitions.wobble })" %>

The available transitions are: linear, reverse, none, full, sinoidal, pulse, wobble, and flicker. Chapter 11 describes them in detail and explains how to create custom transitions. To get a feel for the possibilities, create a demo for yourself of each transition:

<% %w( linear reverse none full sinoidal pulse 
       wobble flicker ).each do |name| %>
  <%= link_to_function "Fade with #{name}",
        "new Effect.Fade('target',
          { transition: Effect.Transitions.#{name} })" %>
<% end %>

Visual Effect Helper

So far, we’ve been using script.aculo.us’s Effect object directly, without the aid of Rails helpers. Rails also provides a helper to generate visual effects, allowing you to create effects without writing JavaScript. The helper is visual_effect, and it’s used like this:

visual_effect(:fade, :target)

The first argument is the name of a script.aculo.us effect (almost—see the note below), and the second is the ID of a DOM element. The visual_effect helper outputs a JavaScript snippet, so it’s usually used in combination with another helper, like link_to_function:

<li><%= link_to_function "Fade", visual_effect(:fade, :target) %></li>

The toggle effects can be used from the helper method as well:

<%= link_to_function "Toggle Blind",
    visual_effect(:toggle_blind, :target) %>

Tip

Standard Ruby style is to use underscores to separate words in variable and method names. The script.aculo.us effect methods, on the other hand, follow the JavaScript convention of “CamelCase.” So when you are using the visual_effect helper, remember to use the lower-case, underscored versions of the effect names; e.g., BlindUp becomes blind_up.

The visual_effect helper is especially useful when combined with Ajax helpers, such as link_to_remote. For example, you might use the Highlight effect to draw the user’s attention to a portion of the page that has been updated via Ajax. To see it in action, first add a new action to chapter4_controller.rb:

def get_time
  render :text => Time.now
end

And then create an Ajax link to it in views/chapter4/index.rhtml:

<%= link_to_remote "Get Time",
    :update   => "current_time",
    :url      => { :controller => "chapter3", :action => "get_time" },
    :complete => visual_effect(:highlight, :current_time) %>
<div id="current_time"></div>

Notice that, unlike the examples in the last chapter, we aren’t writing custom JavaScript in the :complete option—instead, we let the visual_effect helper write it for us.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required