Fork Me
webcloud / log / Building modal panels with jQuery

A modal box is a great interface solution when you want to focus the user’s attention to a specific task. It’s ideal for displaying things like alert messages, confirmation dialogues and photos.

What we're building

If you are too lazy to type/copy/paste along, here's some images for you to try it out before we start

You can also look the JavaScript code itself

Modal panels have become increasingly popular lately and there is a tendency of using them for the wrong reasons. Remember a modal panel is extremely disruptive, and should therefore only be used for important tasks that must interrupt with the users workflow.

Designing for graceful degradation

As always when developing interface with the help of JavaScript we should make sure that the functionality is gracefully degradable. The content must still be accessible and not dependant on the workings of JavaScript. In this scenario, we can easily assure this by using appropriate markup pointing to the content we want to load.

<a href="photo.jpg" rel="modalPanel">
    <img src="photo_thumbnail.jpg" alt="Photo">
</a>

Later on we will trigger our plugin to react to the ”rel” attribute on our anchor. If the JavaScript fails for some reason, the link will still work and point to the content; thus it’s still accessible.

The modal overlay

We’re going to build a very simple and straight-forward modalbox plug-in to display images/photos. If you’re not familiar with the anatomy of a plugin I suggest you take a look at Building jQuery plugins.

The first thing we want to do is disrupt the user’s attention from the main content by adding a transparent overlay. We also want to be able to close the overlay with a mouse click or an escape key press. Let’s start with some code:

(function($) {
    $.fn.extend({
      modalPanel: function() {

          //Create our overlay object
          var overlay = $("<div id='modal-overlay'></div>");

          return this.each(function() {

              //Listen for clicks on objects passed to the plugin
              $(this).click(function(e) {

                  //Append the overlay to the document body
                  $("body").append(overlay.click(function() { 
                      modalHide(); 
                  }))

                  //Set the css and fade in our overlay
                  overlay.css("opacity", 0.8);
                  overlay.fadeIn(150);

                  //Prevent the anchor link from loading
                  e.preventDefault();

                  //Activate a listener 
                  $(document).keydown(handleEscape);		
              });
          });

          //Our function for hiding the modalbox
          function modalHide() {

              $(document).unbind("keydown", handleEscape)
              var remove = function() { 

                  $(this).remove(); 
              }
              overlay.fadeOut(remove);
          }

          //Our function that listens for escape key.
          function handleEscape(e) {

              if (e.keyCode == 27) {

                  modalHide();
              }
          }
      }
  });
})(jQuery);

We also need some CSS for our overlay:

#modal-overlay {
    position: fixed;
    z-index:100;
    top: 0px;
    left: 0px;
    height:100%;
    width:100%;
    background: #000;
    display: none;
}

Now we need to select some elements to pass into our plug-in. I’m going to pass all the anchors with a rel property that equals to modalPanel. We call the plugin from inside the jQuery constructor method ($) which is bound to the document ready event, which ensures that our markup has properly loaded. So it's bascially a shortcut for the more commonly used $(document).ready() function.

$(function() {
    $('a[rel*=modalPanel]').modalPanel();		
});

Now you can click any link which has rel="modalPanel" defined.

Overlay Demo

Try out the overlay!

Loading and displaying the image

The next step is to load the image that our anchor points to. Just after were we created our overlay object, let’s create a modal-window:

var modalWindow = $("<div id='modal-window'></div>");

And after our “key-down” listener we want to add the image loading function:

var img = new Image();
$(img).load(function () {
    var imageWidth = img.width / 2 ;
    var imageHeight = img.height / 2;
    modalWindow.css({
        "margin-left": -imageWidth,
        "margin-top": -imageHeight
    });
    modalWindow.append(img);
    $(this).addClass("modal-image");
    $("body").append(modalWindow);
    modalWindow.fadeIn(150);
})
.attr({ src: this.href }).click(function() {
    modalHide();
});

In the above code we start with creating an Image object and pass it into a load method. We send a function into the load method that will execute once the image has loaded. In this function we calculate the image width and height, divide it by two. Then readjust our window position using these values with negative margins in order to place it in the absolute centre of our screen. We then append the image to our modal window, and finally perform a nice fade in effect.

We need some CSS for the modal window and the image:

#modal-window {
    position: fixed;
    z-index: 102;
    display:none;
    top:50%;
    left:50%;
}
#modal-window img 
{
    display: block;
    border: 3px solid #ccc;
}

We also need to update our modalHide() function to remove the window and the image:

function modalHide() {
    $(document).unbind("keydown", handleEscape)
    var remove = function() { $(this).remove(); };
    overlay.fadeOut(remove);
    modalWindow
        .fadeOut(remove)
        .empty();
}

Adding a loading animation

The code we have so far should work fine, although it doesn’t give the user a clue what’s going on. It would be useful to add some kind of progress bar to let the user know that the image is loading. Let’s add this to our existing code, just after where we appended the overlay:

$("body").append("<div id='modal-load'></div>");

And add code to remove the loading box before appending the image to the modal-window:

$("#modal-load").remove();

We also need some CSS for our modal-load window:

#modal-load{
    position: fixed;
    height:13px;
    width:208px;
    background: url(loadingAnimation.gif) no-repeat;
    z-index:103;
    top: 50%;
    left: 50%;
    margin: -6px 0 0 -104px; /* -height/2 0 0 -width/2 */
}

We’re positioning it to the centre of the screen, to do this we have use negative margins that are half the size of the loading image in order to adjust it, just like we did with modal-window.

Oh noes! Everything is broken in IE6

Surprisingly, what we’ve done so far doesn’t quite make it work in our favourite browser: Internet Explorer 6. We have to add a few tweaks to the CSS to start with:

* html #modal-overlay { /* ie6 hack */
    position: absolute;
    height: expression(document.body.scrollHeight > 
            document.body.offsetHeight ? 
            document.body.scrollHeight : 
            document.body.offsetHeight   'px');
}
* html #modal-window,
* html #modal-load { /* ie6 hack */
    position: absolute;
    margin-top: expression(0 - parseInt(this.offsetHeight / 2)
    (TBWindowMargin = document.documentElement &amp;&amp; 
    document.documentElement.scrollTop || document.body.scrollTop) 'px');
}

Yup I know, it’s not very elegant. The expression() function inside our CSS is used to execute JavaScript . Since IE6 doesn’t support position: fixed, what we’re doing here is setting the positioning to absolute, and adjusting the heights in order to correctly place them in the centre of the browser window.

We need to make some more CSS adjustments but We're going to do them inside our plugin, just before we append the overlay and the load box add the following code:

if (typeof document.body.style.maxHeight === "undefined") { //if IE 6
                
    $("body","html").css({height: "100%", width: "100%"});
}

The body.style.maxHeight property does not exist in IE6 and therefore the code block will execute. We then set some CSS properties that are required to make everything display nicely.

Conclusion

So now we have it: Our very own, very simple, not so fantastic modal box. And this is where this article ends. You hopefully have the basic understanding of how to make a simple cross-browser compliant modal box. With these basics you hopefully will be able to extend your plug-in to handle more than just images.

I want to end this article by saying thanks to Larry for his help and input. High Five!