Rails Illustrated

Rails, Web Design and the User Experience

Screencast: Hover Content in Rails and Prototype (Part 1)


Screencast: Hover Content in Rails and Prototype from Erik Andrejko on Vimeo.

It is often a good idea to show content when the user hovers the mouse above a link or an element in a page. This technique has been abused by some, but when used correctly it can enhance the user's experience. This is particularly the case when you have a lot of information that may be relevant to the user's interests but is too much to display all at once.

Screencast

Required

  • LowPro for unobtrusive javascript for Prototype

Optional

Basic Strategy

Hover Content Wrapper Div

We want to place the content that appears when the mouse is hovered as near as possible to the element that generates the hover (the affordance). We accomplish this by means of a wrapper div around each row of contacts.

1. Controller

In the controller the contacts are grouped into groups of 4 so that they can be wrapped in a div.

def index
  @contacts = Contact.find(:all, :order => 'last_name asc, first_name asc')
  @contact_groups = @contacts.in_groups_of(4,false)
end

def show
  @contact = Contact.find(params[:id])
end

2. View

The trick is to wrap each each row of contacts with the wrapper div. This accomplished by the contact_group partial.

<% content_for :scripts do %>
<%= javascript_include_tag 'hover_content' %>
<% end %>


<div id='contact-names'>
<%= render :partial => 'contact_group', :collection => @contact_groups %>
</div>

The contact_group partial wraps the row in a div.

<div class='contact-group'>
    <%= render :partial => 'contact_name', :collection => contact_group %>
</div>

3. Unobtrusive Javascript

The javascript is fairly straightforward. When the mouseover event is received, we load the content to display with an Ajax call.

Bubbling Event Problem

The div that we watch for mouseover events has child elements. When any of those elements received a mouseover event it will be sent to the parent elements. This means that the div will receive extraneous events. We must ignore these extra events to provide smooth visual effects.

The check for bubbling mouseover events from child elements with Protoype:

// from http://groups.google.com/group/prototype-scriptaculous/browse_thread/thread/badf3974a0dd5ac6
function bubbledFromChild(element, event)  {
  var target = $(event).element();
  if (target === element) target = event.relatedTarget;
  return (target && target.descendantOf(element));
}

This check is used on each function that handles either the mouseover or mouseout events. The rest of the Javascript is fairly routine:

Event.addBehavior({
  ".contact-name:mouseover" : function(e){
    if(!bubbledFromChild(this,e)){
      $(this).setStyle({backgroundColor:"#f0f0f0"});
      var id = $(this).readAttribute("id").match(/[0-9]+$/)[0];
      var container = $(this).up(".contact-group");
      $$(".contact").each( function(e) {
        e.hide();
      });
      if($(container).down("#contact-"+id)){
        new Effect.Appear("contact-"+id, {queue: 'end', duration: 0.7})
      }else{
        if(Ajax.activeRequestCount == 0){
          var url = $(this).down("a").readAttribute("href");
          new Ajax.Request(url,{
            method: 'get',
            onSuccess: function(xhr){
              container.insert({bottom: xhr.responseText});
            }});
        }
      }      
    }
  },

  ".contact-name:mouseout" : function(e){
    if(!bubbledFromChild(this,e)){
      $$(".contact").each( function(e) {
        e.hide();
      });      
    }
  }
})

Optional Enhancements

Over the next couple of weeks we shall continue this screencast by enhancing this hover technique to improve usability.

Credits

Intro music thanks to Courtney Williams via Podcast NYC.

Comments  

Add Comment

(required)
(required, won't be displayed)

(Use Markdown syntax)