Enhancing the User Interface with Visual Effects

Problem

You want to enhance your user's experience by adding visual effects to the interactive elements of your application. Specifically, you have a list of terms that are links, and you want the definition of each term to slide down when a term is clicked.

Solution

Use the visual_effect JavaScript helper to define the :blind_down callback of the script.aculo.us library.

Create a table called terms, and populate it with some terms and their definitions. The following migration sets this up:

db/migrate/001_create_terms.rb:

class CreateTerms < ActiveRecord::Migration
 def self.up
 create_table :terms do |t|
 t.column :name, :string
 t.column :definition, :text
 end
 Term.create :name => 'IPv6', :definition => <<-EOS
 The successor to IPv4. Already deployed in some cases and gradually
 spreading, IPv6 provides a huge number of available IP Numbers - over
 a sextillion addresses (theoretically 2128). IPv6 allows every
 device on the planet to have its own IP Number.'
 EOS
 Term.create :name => 'IRC', :definition => <<-EOS
 Basically a huge multi-user live chat facility. There are a number of
 major IRC servers around the world which are linked to each other.
 Anyone can create a channel and anything that anyone types in a given
 channel is seen by all others in the channel. Private channels can
 (and are) created for multi-person conference calls.
 EOS
 Term.create :name => 'ISDN', :definition => <<-EOS
 Basically a way to move more dataover vexisting regular phone lines.
 ISDN is available to much of the USA and in most markets it is priced
 very comparably to standard analog phone circuits. It can provide
 speeds of roughly 128,000 bits-per-second over regular phone lines.
 In practice, most people will be limited to 56,000or 64,000
 bits-per-second.
 EOS
 Term.create :name => 'ISP', :definition => <<-EOS
 An institution that provides access to the 
 Internet in some form, usually for money.
 EOS
 end
 def self.down
 drop_table :terms
 end end

Next, include the Prototype and script.aculo.us libraries in your layout by passing :defaults to the javascript_include_tag helper method. Additionally, define a style for the term definition element that will appear when a term is clicked.

app/views/layouts/terms.rhtml:

<html>
<head>
 <title>Terms: <%= controller.action_name %></title>
 <%= javascript_include_tag :defaults %>
 <%= stylesheet_link_tag 'scaffold' %>
 <style >
 .def {
 position: relative;
 width: 400px;
 background-color: #ffc;
 border: 1px solid maroon;
 margin-top: 20px;
 padding: 10px;
 }
 </style>
</head>
<body>
 <%= yield %>
</body>
</html>

Define two actions in your TermsController named list and define.

app/controllers/terms_controller.rb:

class TermsController < ApplicationController
 def list
 @terms = Term.find :all
 end
 def define 
 term = Term.find(params[:id])
 render :partial => 'definition', :locals => { :term => term }
 end end

Next, create a view that iterates over the terms and displays them as links:

app/views/terms/list.rhtml:

<h1>Term Definitions</h1>
<% for term in @terms %>
 <h3><%= link_to_remote term.name,
 :update => "summary#{term.id}",
 :url => { :action => "define", :id => term.id },
 :complete => visual_effect(:blind_down, "summary#{term.id}", 
 :duration => 0.25, :fps => 75 ) %></h3>
 <div id="summary<%= term.id %>" class="def" ></div>
<% end %>

To display each term definition, define a partial called definition.rhtml. This file should also include a link for hiding each definition.

app/views/terms/_definition.rhtml:

<%= term.definition %>
<i><%= link_to_remote 'hide', 
 :update => "summary#{term.id}",
 :url => { :action => "define", :id => term.id },
 :complete => visual_effect(:blind_up, "summary#{term.id}",
 :duration => 0.2 ) %></i>

Discussion

The :blind_down effect is named after a window blind; each definition is "printed" on a blind that rolls down when it is needed. Once a definition is fully visible, it can be rolled up (hidden) with the :blind_up option.

The solution defines a list method in the Terms Controller that passes an array of terms to the view. The view, list.rhtml, iterates over the @terms array, creating a link_to_remote call and a corresponding, hidden div element for each term definition. The id of each of these div elements is uniquely named using the term.id (e.g., summary1, summary2). The link_to_remote call uses this unique id to pair the term links with their definition elements.

The :url option of link_to_remote points to the define action of the Terms Controller. This action gets a term object and renders the definition partial, passing the term object as a local variable to that partial. Finally, the definition.rhtml partial is rendered, unveiling the term definition over a period of a quarter second (at 75 frames per second). The displayed definition elements contain a "hide" link that rolls the element back up when clicked.

The blind effect in the solution can really help with an application's usability if it is used thoughtfully. For example, there is little question that the definition of each term applies to the term above it because this where the unrolling definition originates.

The script.aculo.us library includes a number of other interesting effects including puff, switch_off, slide_down, and pulsate, etc.

shows the terms from the solution as links that "blind down" their definitions when clicked.

Figure 8-10. A term's definition appearing via the blind-down visual effect

See Also