Subscribe to my Feed, follow me on Twitter, recommend me on Working With Rails or see my code on GitHub
Unobtrusive Ajax patterns: the modal dialog box
(Another one that doesn’t deal with Ajax directly, but is very useful when combined with it. Example at the bottom of the article)
The modal dialog is very familiar to the desktop environment. It’s the window that pops up from the main application window to ask you something or let you know something happened. On the web, there is one particular form of the modal dialog that’s popular, the “lightbox”.
Ok, so how do we create a modal dialog for a web site? It’s quite simple really, you just need an element that can be switched “on” or “off” and some CSS to make it appear the way we want. We’ll keep the JavaScript used to control the dialog inside a global Modal object that encapsulates the element so we don’t actually mess around with it directly when using the dialog.
We create the element on page load, and insert it into the body of the document:
var Modal = (function(){
var box = new Element('div', {id:'modal-dialog'});
$$('body').first().insert({bottom:box});
return {
element: box,
show: function(){},
hide: function(){},
update: function(){}
};
})();
The Modal object has three basic methods: show to open the dialog, hide to close it and update to update its contents. These abstract away what actually goes on when using the dialog, so if you want to change how something is done later, it won’t break code that relies on it.
The show and hide methods are quite simple. Let’s throw in a toggle method too, while we’re at it.
//...
return {
show: function(){
box.addClassName('active');
},
hide: function(){
box.removeClassName('active');
},
toggle: function(){
this[this.isActive() ? 'hide' : 'show']();
},
isActive: function(){
return box.hasClassName('active');
}
};
//...
The dialog is considered to be open when it has the class name “active”. This will be targeted with CSS to make it appear and disappear. Note that we’re using the box variable which is the DOM element representing the dialog. The methods have access to it because they’re inside a closure where box is a local variable. This in effect makes it private (although it’s mostly for convenience, as we’re actually letting the outside access it by adding it to Modal.element).
The update method is also quite simple, it basically acts as a proxy to the element’s update method:
update: function(){
box.update.apply(box, arguments);
}
Now, to display a message in the modal, we can do this:
Modal.update('I just popped up to say I love you');
Modal.show();
We can do better, though. The update method will almost always be called just before show, so we can reduce that to one step by allowing show to pass the content on to update:
show: function(){
if (arguments.length) { this.update.apply(this, arguments); }
box.addClassName('active');
}
Modal.show('I just popped up to annoy you');
Now that we have the functionality ready, we just have to add some style to make it look like a modal box:
#modal-dialog {
display: none;
}
#modal-dialog.active {
display: block;
position: fixed;
top: 5em;
left: 5em;
background: #fff;
border: 0.5em solid #ccc;
padding: 2em;
}
This can be enhanced in a number of ways (like making it display in the center of the page), but the needs will vary from site to site and have to be added on a case-by-case basis. There’s one thing we can add though, that can be considered quite universal: The ability to close the dialog from within the dialog itself.
To do this, we just need to hook up an element inside the dialog to the Modal.hide method. To make it easy to use, we’ll do this to every element with a class name of modal-dialog-close. This way you can just add this class name to content that gets added to the dialog, and it will work automagically.
The hooking up happens in the update method, after updating the element’s contents:
update: function(){
box.update.apply(box, arguments);
box.select('.modal-dialog-close').each(function(close){
close.observe('click', function(e){
e.stop();
Modal.hide();
});
});
}
Done
The entire script now looks like this:
Modal = (function(){
var box = new Element('div', {id:'modal-dialog'});
$$('body').first().insert({bottom:box});
return {
element: box,
show: function(){
if (arguments.length) { this.update.apply(this, arguments); }
box.addClassName('active');
},
hide: function(){
box.removeClassName('active');
},
toggle: function(){
box[this.isActive() ? 'removeClassName' : 'addClassName']('active');
},
isActive: function(){
return box.hasClassName('active');
},
update: function(){
box.update.apply(box, arguments);
box.select('.modal-dialog-close').each(function(close){
close.observe('click', function(e){
e.stop();
Modal.hide();
});
});
}
};
})();
Below is an example which will popup the dialog with the contents from the textarea. I’ve added a few extras to the code above, like a wrapper element to create the shadow effect and style for an optional title element.
