A lightweight general purpose JavaScript DOM element popup class.

This single class can create tooltips, dropdown menus, modal and non-modal popup elements. Popups can be draggable and have auto-close or click-to-close functionality. Scriptaculous effects can be applied when opening and closing popups. Behavior can be customized with over a dozen options, but the defaults are reasonable and options are often not required.

The script is Prototype based and uses Scriptaculous effects and dragdrop libraries (which come by default in Ruby on Rails projects).

Also included are examples showing how to use DOM Popups with Ruby on Rails to create rich information popups and modal forms (AJAX and non-AJAX).

Take a look at the demo page to see DOM popups in action.

Credits

DOM popup was inspired by:

Lightbox2 http://www.huddletogether.com/projects/lightbox2/
Lightbox Gone Wild http://particletree.com/features/lightbox-gone-wild/
Tooltip http://blog.innerewut.de/pages/tooltip
Prototype library http://www.prototypejs.org/
Scriptaculous library http://script.aculo.us/

DOM popup uses:

Finally, DOM Popups would not have been possible without the help of Firebug — if you program JavaScript and/or CSS and value your sanity you need this tool.

Download

A tarball containing script, CSS and documentation files can be downloaded here http://www.methods.co.nz/popup/popup_1.0.1.tar.gz.

How it works

Popups are created by instantiating JavaScript Popup objects on your browser page. When the nominated DOM event (normally mouseover or click) triggers a popup the nominated DOM element appears on the browser page.

popup

The DOM element that pops up.

trigger

The DOM event that activates the popup.

link

The DOM element which triggers the popup.

auto-open

If the trigger event is mouseover then the popup opens automatically once the mouse cursor enters the link (see also the enter_delay option).

auto-close

If the popup has no closebox elements the it will close automatically once the mouse cursor is outside the link and the popup (see also the leave_delay option).

click-to-close

A popup that is closed by clicking one of its closebox elements.

anonymous popup

A popup created without a link. Anonymous popups are opened programmatically.

The Popup class is defined in the popup.js script, a basic examples CSS file popup.css is also included.

Requirements

Prototype JavaScript library prototype.js
Scriptaculous JavaScript libraries effects.js, dragdrop.js

Browser compatibility

Usage

This section describes how to use the Popup class, the Using Popups with Rails section shows how this is applied in a Rails environment.

The best way to get a feel for the popup is to take a look at the demo page popup_demo.html.

To create a popup instantiate a Popup object with this JavaScript statement:

new Popup(popup, link, options);

link and popup are DOM elements, options is an optional object specifying popup options. For example:

new Popup('popup_4','popup_link_4',{modal:true});

Popup options

modal

If true underlying page control interaction is disabled. Default false.

hidden

Initial visibility of popup. Default true.

trigger

The link event that opens the popup. Can be any DOM event type that the link is capable of generating however 'click' and 'mouseover' are the usual ones. Defaults to 'click' if modal is true else defaults to 'mouseover'.

position

'auto', 'center', 'below' or 'x,y' where x and y can be either numbers (pixel units) or valid CSS left or top property values. If modal is true defaults to 'center' else defaults to 'auto'.

effect

'fade', 'blink', 'slide'. Default 'fade' (but see Bugs below).

duration

Effect show/hide duration in seconds. Default 0.5.

show_duration, hide_duration

Can be applied to override duration option for separate show and hide durations.

opacity

Opacity of modal overlay. Default 0.5.

closebox

CSS class name of click-to-close elements. Default 'popup_closebox'. Multiple closebox elements are allowed.

draghandle

CSS class name of drag handle elements. Default 'popup_draghandle'. Multiple draghandle elements are allowed.

cursor_margin

Distance of popup from cursor in pixels when position is 'auto'. Default 5.

show_delay

Milliseconds delay before auto-open is triggered. Eliminates unnecessary popup activation when the mouse passes over the link. Default 500.

hide_delay

Milliseconds delay before auto-close is triggered. Default 200.

The following options can be set globally on the Popup object: duration, show_duration, hide_duration, opacity, show_delay, hide_delay, cursor_margin. For example Popup.duration = 0.1 will set the the duration option to 0.1 in all subsequently created Popup objects.

Note
  • Popup objects must be created after the corresponding popup element has been created, this normally means placing the JavaScript new Popup statement after the popup element markup in the document.

  • Do not use the same popup element in more than one Popup. Doing so will register the same element related user interface events in multiple Popup objects — when a user interface event occurs it will be handled in all associated Popup objects and result in unpredictable behavior.

Controlling and creating popups programmatically

In addition to user interaction the Popup show() and hide() functions can be used to programmatically show and hide popup objects. Use the popup element's popup property to access the popup object. Here's an example:

$('new_author_using_ajax_popup').popup.hide();

You can also create anonymous popups from your JavaScript code (an anonymous popup doesn't have a link element). Here's an example:

new Popup('message_popup',null,{modal:true});

Which can now be opened with:

$('message_popup').popup.show();
Note
Don't programmatically open popups with the 'auto' position option — this option uses event mouse coordinates which will not be available.

Using Popups with Rails

The examples presented here were tested under Rails 1.2.1 and Ruby 1.8.5.

First off you need to put the popup script and stylesheet into your page layouts:

<%= javascript_include_tag :defaults %>
<%= stylesheet_link_tag 'popup.css' %>
<%= javascript_include_tag 'popup.js' %>

The javascript_include_tag :defaults includes the required Prototype and Scriptaculous libraries.

A Record Popup

In this example we generate a popup containing details of an article record stored in the Article model. The popup link looks like:

<p><span id="article_link_<%= article.id %>" class="popup_link"><%= article.title %></span></p>
<%= render :partial => 'article_popup', :locals => {:article => article} %>

The draggable auto-close popup is generated by the ./app/views/articles/_article_popup.rhtml partial:

<div id="article_popup_<%= article.id %>" class="popup popup_draghandle" style="display:none">
  <p>
    <b>Title:</b>
    <%=h article.title %>
  </p>
  <p>
    <b>Summary:</b>
    <%=h article.summary %>
  </p>
</div>
<%= javascript_tag "new Popup('article_popup_#{article.id}','article_link_#{article.id}')" %>

The new Popup JavaScript statement associates the popup contents with it's linking element. Here's a screenshot:

popup_screenshot_6.png

Creating a new Record using a modal form

It's often useful to be able to create a related record without having to leave the current page. This example uses a modal popup form to create a new author record.

Here's the click-to-open popup link to bring up the modal form:

<p><span id="new_author_link" class="popup_link">New Author</span></p>
<%= render :partial => 'authors/new_author_popup' %>

Here's the ./app/views/authors/_new_author_popup.rhtml partial that renders the popup form:

<div id="new_author_popup" class="popup" style="display:none">

  <h1>New author</h1>

  <% form_for(:author, :url => authors_path) do |f| %>
    <p>
      <b>First name</b><br />
      <%= f.text_field :first_name %>
    </p>

    <p>
      <b>Last name</b><br />
      <%= f.text_field :last_name %>
    </p>

    <p>
    <%= submit_tag 'Create', :class => 'popup_closebox' %>
    <%= tag :input, :type => 'button', :value => 'Cancel', :class => 'popup_closebox' %>
    <%= hidden_field_tag :back, :value => request.path %>
    </p>
  <% end %>

</div>
<%= javascript_tag "new Popup('new_author_popup','new_author_link',{modal:true})" %>

When the Create button is pressed the popup form closes and a and the form parameters are posted to the author controller's create action. Note how the hidden :back field is used by the author controller's create action to redirect back to the originating page.

def create
  @author = Author.new(params[:author])
  respond_to do |format|
    if @author.save
      flash[:notice] = 'Author was successfully created.'
      format.html do
        if params[:back]
          redirect_to params[:back]
        else
          redirect_to author_url(@author)
        end
      end
      format.xml  { head :created, :location => author_url(@author) }
    else
      format.html do
        if params[:back]
          flash[:notice] = 'Error creating author.'
          redirect_to params[:back]
        else
          render :action => 'new'
        end
      end
      format.xml  { render :xml => @author.errors.to_xml }
    end
  end
end

Creating a new Record using a modal form, AJAX and RJS templates

Here's where things get interesting — this time we submit the form using AJAX and then poke a response back into the originating page using an RJS template. The example also illustrates how to invoke modal information popups programmatically.

The click-to-open popup link on the originating page is similar to the previous example with the addition of:

<p id="new_author"></p>

<p><span id="new_author_using_ajax_link" class="popup_link">New Author using AJAX</span></p>
<%= render :partial => 'authors/new_author_using_ajax_popup' %>
<%= render :partial => 'partials/message_popup' %>

Here's the app/views/authors/_new_author_using_ajax_popup.rhtml partial that renders the popup form. Again similar to previous example but using AJAX to submit the form:

<div id="new_author_using_ajax_popup" class="popup" style="display:none">

  <h1>New author</h1>

  <% form_remote_for(:author, :url => authors_path,
                     :loading => "Element.show('popup_spinner');Form.disable('author_form');",
                     :complete => "Element.hide('popup_spinner');Form.enable('author_form');",
                     :html => {:id => 'author_form'}
                    ) do |f| %>
    <p>
      <b>First name</b><br />
      <%= f.text_field :first_name %>
    </p>

    <p>
      <b>Last name</b><br />
      <%= f.text_field :last_name %>
    </p>

    <p>
    <%= submit_tag 'Create' %>
    <%= tag :input, :type => 'button', :value => 'Cancel', :class => 'popup_closebox' %>
    <%= image_tag 'spinner.gif', :id => 'popup_spinner', :style => 'display:none' %>
    </p>
  <% end %>

</div>
<%= javascript_tag "new Popup('new_author_using_ajax_popup','new_author_using_ajax_link',{modal:true})" %>

Here is the Author controller's create action, it's very similar to the previous example but in this case the response is generated by inline RJS templates:

def create
  @author = Author.new(params[:author])
  respond_to do |format|
    if @author.save
      format.html do
        flash[:notice] = 'Author was successfully created.'
        if params[:back]
          redirect_to params[:back]
        else
          redirect_to author_url(@author)
        end
      end
      format.js do
        render :update do |page|
          page.form.reset 'author_form'
          page << "$('new_author_using_ajax_popup').popup.hide();"
          page.replace_html 'new_author', "<b>New author:</b> #{@author.first_name} #{@author.last_name}"
          page.visual_effect :highlight, 'new_author'
          page.replace_html 'message_popup_message', 'New author created.'
          page << "$('message_popup').popup.show();"
        end
      end
      format.xml  { head :created, :location => author_url(@author) }
    else
      format.html do
        if params[:back]
          flash[:notice] = 'Error creating author.'
          redirect_to params[:back]
        else
          render :action => 'new'
        end
      end
      format.js do
        render :update do |page|
          page.replace_html 'message_popup_message', 'An error occurred.'
          page << "$('message_popup').popup.show();"
        end
      end
      format.xml  { render :xml => @author.errors.to_xml }
    end
  end
end

Because form_remote_for posts a JavaScript request it is routed to the format.js sections, then the RJS template generates JavaScript which is sent back to and executed by the waiting browser. If the new record is saved without errors then:

The last step is probably not necessary, it's just there to demonstrate how to trigger a popup from the server.

If the record cannot be saved the the user is presented with an error message an the form is left open ready for corrections and resubmission.

These screenshots illustrate sequence:

popup_screenshot_1.png
The popup form link
popup_screenshot_2.png
Modal AJAX form
popup_screenshot_3.png
Modal confirmation popup (note the updated new_author element on the background form)
popup_screenshot_4.png
The error message popup
popup_screenshot_5.png
After an error your are returned to the modal form

Here's the app/views/partials/_message_popup.rhtml partial used to create the anonymous message popup:

<div id="message_popup" class="popup" style="width:400px">
  <p id="message_popup_message"></p>
  <form action="">
      <input class="popup_closebox" type="button" value="OK" />
  </form>
</div>
<%= javascript_tag "new Popup('message_popup',null,{modal:true})" %>

Bugs

Feedback

Please send your bugs, comments and suggestions to me, Stuart Rackham, at srackham@methods.co.nz.

CHANGELOG

2007-03-20 1.0.1 release
  • Documentation errata and clarifications.

2007-03-16: 1.0.0 release