new Popup(popup, link, options);
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.
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:
The getPageSize() function from Lightbox v2.02 by Lokesh Dhakar (http://www.huddletogether.com/projects/lightbox2/).
Adapted the modal overlay technique used in Lightbox v2.02 by Lokesh Dhakar (http://www.huddletogether.com/projects/lightbox2/).
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.
A tarball containing script, CSS and documentation files can be downloaded here http://www.methods.co.nz/popup/popup_1.0.1.tar.gz.
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.
The DOM element that pops up.
The DOM event that activates the popup.
The DOM element which triggers the popup.
If the trigger event is mouseover then the popup opens automatically once the mouse cursor enters the link (see also the enter_delay option).
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).
A popup that is closed by clicking one of its closebox elements.
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.
Prototype JavaScript library | prototype.js |
Scriptaculous JavaScript libraries | effects.js, dragdrop.js |
So far only tested on Firefox2, IE7 and IE6.
Don't have access to Safari.
Doesn't behave in Konqueror 3.5.5.
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});
If true underlying page control interaction is disabled. Default false.
Initial visibility of popup. Default true.
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'.
'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'.
'fade', 'blink', 'slide'. Default 'fade' (but see Bugs below).
Effect show/hide duration in seconds. Default 0.5.
Can be applied to override duration option for separate show and hide durations.
Opacity of modal overlay. Default 0.5.
CSS class name of click-to-close elements. Default 'popup_closebox'. Multiple closebox elements are allowed.
CSS class name of drag handle elements. Default 'popup_draghandle'. Multiple draghandle elements are allowed.
Distance of popup from cursor in pixels when position is 'auto'. Default 5.
Milliseconds delay before auto-open is triggered. Eliminates unnecessary popup activation when the mouse passes over the link. Default 500.
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
|
|
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. |
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.
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:
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})" %>
The popup is created with the modal option.
Both the submit and cancel buttons are popup close boxes.
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
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:
The new_author paragraph element which is a placeholder for the server response.
The app/views/partials/_message_popup.rhtml partial which is an information message popup.
<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})" %>
Note the use of :loading and :complete options to disable the form and give the user some feedback while waiting for the server response.
Unlike the previous non-AJAX example the Create submit button is not a popup close box — the form stays open and displays feedback until the server response arrives.
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 popup form is reset and hidden.
The new author's name is inserted into the new_author paragraph which is then briefly highlighted.
The New author created modal message_popup has it's message set and is the displayed.
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:
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})" %>
The message Popup object is created without a link (the Popup link argument is null) because this popup is triggered programmatically by the server response and not by user interaction.
As with any markup, popup elements don't have to be put in separate partials, but partials are a nice reuse mechanism allowing you to use the same popup anywhere in your application.
The slide, grow and blind effects have been observed to trigger phantom popup opens and other weird behavior when used with auto-open popups. If you experience problems use fade (the default) instead.
If the link element of an auto-open popup has a bottom border then under some circumstances moving across the border into the link fires spurious mouseout events causing the popup to open then immediately close again. The problem affects Firefox but not IE6 or IE7. I haven't figured the exact circumstances but it seems to be links inside table cells. If you approach the link from above the problem does not occur.
Please send your bugs, comments and suggestions to me, Stuart Rackham, at srackham@methods.co.nz.
Documentation errata and clarifications.