Introduction

In user interface design, a modal window (sometimes referred to as a modal dialog) is a window that blocks input to other windows. It has to be closed before the user can continue to operate the application and are frequently an element of Multiple Document Interface (MDI) applications or desktop applications like Windows or OS X. One of their purposes is to prevent the software from being operated in an ambiguous state.

While researching the best way to recreate a modal window for our current project, we ran into Lokesh Dhakar’s lightbox.js and we knew we found a winner delivery container. Dhakar’s method, however, while fantastic, was a bit too specific for our purposes and so we created our own implementation that we think is a bit more flexible for extending a web site’s interface. In this tutorial, we’ll take a look at how to create a modal window using some nifty JavaScript and CSS.

See it in Action

Our demo illustrates a few of the possibilities available to a developer using our Lightbox Gone Wild Script. Just click on the links to see how lightbox can be used to provide additional info, show an image or present a form for user input. Lightbox makes it easy to extend a web application’s interface without having to add clutter.

Implementation

For those of you at work wanting to impress the boss, here’s the low down on getting the script working. After a quick walkthrough, we’ll take a look at the JavaScript and CSS files that make it all tick.

Download the Code

Note: This demo and the tutorial that follows use the Prototype.js framework. You’ll find a compressed version of Prototype in the zip file above.

Include the Files

Upload the lightbox.css, lightbox.js and prototype.js files into your web directory and include them in the head of your html document like so:

<link rel="stylesheet" href="css/lightbox.css" type="text/css" /> <script src="scripts/prototype.js" type="text/javascript"></script> <script src="scripts/lightbox.js" type="text/javascript"></script>

Apologies if you’re not a Prototype fan, but it saves a lot of time around here and it’s our flavor of choice. Obviously, you’re always free to reverse engineer any Prototype class into normal JavaScript.

Create some Lightboxes

Create an external document that contains the markup for whatever you want to be loaded into a lightbox. For example, we created a file called text.html and included the following markup:

<h3>What is a Lightbox?</h3><p class="highlight">Lightbox is an unobtrusive script used to overlay content on the current page. Because it places content above your current page, it frees you from the constraints of the layout, particularly column widths.</p>

Because we’re just inserting the HTML snippets into the lightbox you could display a login form, a photo, additional settings for your web app, help documentation, etc. The possibilities are endless.

Activating Lightbox

To call your lightbox interface, just link to the external file and set the class to lbOn .

<a href="form.html" class="lbOn">Email This</a>

Deactivating Lightbox

If you want people to be able to close the lightbox after it’s open, include a link with class lbAction and a rel of deactivate in the external file:

<a href="#" class="lbAction" rel="deactivate">Close Lightbox.</a>

Linking to a Another Lightbox within a Lightbox

If you want to load a different lightbox within an already open lightbox set the rel attribute to insert and your href to the file you want to load instead.

<a href="confirm.html" class="lbAction" rel="insert">Go to Another Lightbox</a>

And you’re done.

How it Works

In a nutshell, when the user clicks on a link with a class of lbOn , a transparent div is positioned on top of the webpage to present a visual cue that the attention is now focused on our lightbox modal window. After the overlay is set, a div (lightbox) is positioned on top of the transparent overlay and loaded with information the user can interact with. When the page first loads, our script inserts the following markup right before the closing body tag.

<div id="overlay"></div> <div id="lightbox"> <div id="lbLoadMessage"> <p>Loading</p> </div> </div>

The overlay div is responsible for holding dimming the rest of the page. We’ll be inserting our different lightbox interfaces inside the lightbox div. Feel free to change the information inside the lbLoadMessage if you want to present something more exciting than just some text. When the page loads, we’re attaching the lightbox object to each element with a class of lbOn .

function initialize(){ lbox = document.getElementsByClassName('lbOn'); for(i = 0; i < lbox.length; i++) { valid = new lightbox(lbox[i]); } }

One of the differences between the original lightbox.js script and ours is that Dhakar was using JavaScript to determine the size of the HTML content so he could find and fix the position of the lightbox and overlay.png at the center of the screen. Wanting to move as much of the presentation to CSS, we decided to use position:fixed to center our lightbox so that it simplifies things in all modern browsers. Unfortunately, “all modern browsers” doesn’t really include IE6 and below and so we’ll still have to use some JS to help them out.

In IE6, there’s no easy way to stretch the overlay div, which dims our page across the entire content of the HTML document to achieve a position fixed for our lightbox div. (This is a non-problem in the recent IE7 beta, but until that becomes popularly adopted, we’ll need to do the following.) To remedy this problem, we’re just going to use position:absolute instead in the CSS and hide that we’re using position:fixed for IE browsers.

#lightbox{ display:none; position: absolute; top:50%; left:50%; z-index:9999; width:500px; height:400px; margin:-220px 0 0 -250px; } #lightbox[id]{ /* IE6 and below Can't See This */ position:fixed; }#overlay{ display:none; position:absolute; top:0; left:0; width:100%; height:100%; z-index:5000; background-color:#000; -moz-opacity: 0.8; opacity:.80; filter: alpha(opacity=80); } #overlay[id]{ /* IE6 and below Can't See This */ position:fixed; }

If you’re familiar with Dhakar’s method, you’ll notice that we’ve decided not to use a transparent png to get our effect and instead use CSS for our transparency effect. This makes it easier to change the percentage of transparency and the color of the transparency in the overlay CSS rules. To make the overlay stretch out to the full screen for IE users, we’ll need to set the body and html elements’ height to 100% and the overflow to hidden for those browsers.

prepareIE: function(height, overflow){ bod = document.getElementsByTagName('body')[0]; bod.style.height = height; bod.style.overflow = overflow; htm = document.getElementsByTagName('html')[0]; htm.style.height = height; htm.style.overflow = overflow; },

To make everything seamless for IE users, we’ll also use a getScroll() function to find the current position of the scrollbar to jump the user to the top of the page where the lightbox is located (because it’s position absolute rather than fixed) and then use setScroll() to bring them back to their location when they deactivate the lightbox.

getScroll() & setScroll()

getScroll:function (){ var yScroll; if (self.pageYOffset) { yScroll = self.pageYOffset; } else if (document.documentElement && document.documentElement.scrollTop){ // Explorer 6 Strict yScroll = document.documentElement.scrollTop; } else if (document.body) {// all other Explorers yScroll = document.body.scrollTop; } this.yPos = yScroll; },setScroll:function(x, y){ window.scrollTo(x, y); },

hideSelects()

Due to a bug in IE, select elements tend to position themselves on top of the overlay (on to of everything actually, including flash). To fix this, we’re just going to hide them. We’ve also had some problems with Firefox and Flash 8. If you’re using the latest Flash in your application, you can modify the script to hide them too.

hideSelects: function(visibility){ selects = document.getElementsByTagName('select'); for(i = 0; i < selects.length; i++) { selects[i].style.visibility = visibility; } },

The Lightbox Class

initialize()

The most important thing initialize() does is attach activate() to the link onclick , which gets the lightbox process rolling.

initialize: function(ctrl) { this.content = ctrl.href; Event.observe(ctrl, 'click', this.activate.bindAsEventListener(this), false); ctrl.onclick = function(){return false;}; },

activate()

activate() calls the methods responsible for setting the png overlay, manipulating the scrollbar position, and displaying the empty lightbox div.

activate: function(){ if (browser == 'Internet Explorer'){ this.getScroll(); this.prepareIE('100%', 'hidden'); this.setScroll(0,0); this.hideSelects('hidden'); } this.displayLightbox("block"); },

displayLightbox()

The last method called in activate() is displayLightbox() . This method sets the overlay and lightbox classes to display:block, and makes the png overlay and lightbox visible. displayLightbox() then calls loadinfo() to populate the empty lightbox div with information.

displayLightbox: function(display){ $('overlay').style.display = display; $('lightbox').style.display = display; if(display != 'none') this.loadInfo(); },

loadInfo()

During initialize() , a member variable, content , was created in order to hold a file location. Using this file location, loadInfo() pulls information into the lightbox. After the info is loaded into the lightbox, processInfo() is called.

loadInfo: function() { var myAjax = new Ajax.Request( this.content, {method: 'post', parameters: "", onComplete: this.processInfo.bindAsEventListener(this)} ); },

processInfo()

Responsible for actually inserting the information into the lightbox class.

processInfo: function(response){ info = "<div id='lbContent'>" + response.responseText + "</div>"; new Insertion.Before($('lbLoadMessage'), info) $('lightbox').className = "done"; this.actions(); },

actions()

If you want to trigger an event inside of the lightbox, create a link with a class ‘lbAction’. Also, set the link’s rel to the function you want called inside of the lightbox class. For example, we often want the user to have the ability to close the lightbox by clicking the cancel link. deactivate() is the method responsible for closing a lightbox and can be triggered with the following code.

<a href="#" class="lbAction" rel="deactivate">cancel</a>

actions() makes the previous link meaningful with the following code.

actions:function(){ lbActions = document.getElementsByClassName('lbAction'); for(i = 0; i < lbActions.length; i++) { Event.observe(lbActions[i], 'click', this[lbActions[i].rel].bindAsEventListener(this), false); lbActions[i].onclick = function(){return false;}; } },

deactivate()

As we saw previously, deactivate() is called when we want to close the lightbox. deactivate() is similar to activate() , but instead of displaying the lightbox and overlay, it removes them. Take note that the information loaded into the lightbox must be removed during deactivate() since the lightbox div is only hidden and not removed.

deactivate:function (){ Element.remove($('lbContent')); if (browser == "Internet Explorer"){ this.setScroll(0,this.yPos); this.prepareIE("auto", "auto"); this.hideSelects("visible"); } this.displayBlock("none"); }

insert()

As we explained in the intro you can easily link to another lightbox from inside a lightbox. insert() is the method which makes this possible and is seen below.

insert: function(e){ link = Event.element(e).parentNode; Element.remove($('lbContent')); var myAjax = new Ajax.Request( link.href, {method: 'post', parameters: "", onComplete: this.processInfo.bindAsEventListener(this)} ); },

Conclusion

Well, there you have it. We believe the lightbox to be a great tool for presenting more information to a user without taking them to a new page or forcing a pop-up. As I said in the tutorial, there have been some problems with Flash 8 on Firefox and select elements in IE. If you find any other problem areas, please let us know.

UPDATE 2/2/06 : Kevin just made an improvement to the Lightbox CSS file. Instead of using a png for the transparency, the script now uses CSS rules to set the color and percentage of transparency for the div. This will allow for greater customization for those that don’t want to change pngs everytime they want a different feel or effect.