Modal Dialog Box

Source Code Back to Tutorials Example
Compatibility:
IE
Firefox
Opera
Netscape
Safari
Konqueror

What is a Modal Dialog?

A modal dialog is a dialog box that will keep the focus until it has been closed. There are many times when you may need this functionality. For example if you wish the user to confirm an operation before continuing the processing, you don't want them to just minimize the confirmation and continue on. There are several ways to achieve a modal dialog. I'll mention a few along with the benefits and the pitfalls associated with them and then go into the method I have developed to handle this situation.

The simplest method of creating a modal dialog is with javascripts confirm function. This is a very simple command and is built into javascript. The downsides are that you have no control over the formatting or the title of the modal window. If you are looking for something quick and easy though, this is the way to go.
Here's a quick example:
var YesNo = confirm("Are you Sure?");
if (YesNo) return true;
else return false;
Another common method is open a popup with code to bring the focus back to it whenever it loses focus. This method actually works fairly well. This allows us to format the dialog however we want as it is just opening another HTML page. The problem we run into here is that pop-up blockers are likely to open the dialog box. This can result in unexpected and unreliable results depending on what the dialog A couple of other issues that can be a problem with this method are cross-browser compatibility, for example this works fairly well in IE, but in Firefox if you click on the parent page twice it allows the focus to go back to the parent screen. Another problem is that one pop-up can't call a second call another pop-up. Here's an example:
window.open("myWindow.html",,"width=300,height=150")
Then in the dialog window you add code like this for the body tag:
<body onblur="self.focus();">
A third option is to use the showModalDialog command. Much like using the window.open option this opens a new window that the dialog will be in. This allows full control of the dialog. The downsides of this option are also similar to those of the window.open option. The biggest problem with this option is that it only works in Internet Explorer. This can also have problems with pop-up blockers.
var dialog = showModalDialog("myWindow.html",,"dialogWidth=300,dialogHeight=150")
My solution to this is to use an in-window dialog box. By this I mean that instead of opening a new window put the dialog in the existing window. This nicely gets around all of the issues with pop-up blockers. The way we do this is we keep a hidden div on the page and only show it when we need to. My early experiments with this method were quite simple and did not have a lot of functionality. Also, they did not prevent the user from working with the controls on the page behind the pop-up. My later experiments strived to get more of the look and feel of a true pop-up window.

The current version, shown here, adds a number of functions to the basic "show a hidden div" idea. First we start by adding a style sheet to the page calling the function. We then create a semi-transparent div that will cover the entire page. This is to give the page a washed out look as an indicator to the user that it is disabled. Next we disable all of the controls on the page. We then create a new div of the specified size and center it in the visible portion of the page. We add a div within this newly created div to be the title bar, giving it a close button and adding the text from the title parameter. After this we add another div below the title div to hold the body of the dialog box. So lets look at the code and see how all of this is done.

First We add a style sheet to the page:
/* Add the required Style Sheet to the page
 ------------------------------------------ */
e = document.createElement("link");
e.type = "text/css";
e.rel = "stylesheet";
e.href = "modalDialog.css";
e.media = "screen";
document.getElementsByTagName("head")[0].appendChild(e);
Now we get to the actual function to create the pop-up. The first thing we do in the function is validate that the id entered as a parameter is valid. If not, then there is no sense creating the pop-up. We also clean out any spaces in the width and height parameters at this point.
function createPop(width,height,title,id) {
  // ensure the given ID exists
  obj = document.getElementById(id);
  if (obj == null ) return;
  width = width.replace(/\s/g,'')  // remove spaces from width parameter
  height = height.replace(/\s/g,'')  // remove spaces from height parameter
The next thing we do is to create a semi-transparent div to cover the body of the page and we disable the form elements on the page. (See the Supporing Functions) section at the bottom of this page for the code of the disableAll function). The opacity rule does not work in newer versions of Konqeror. Hopefully support for opacity will be introduced again.
  // Create semi-transparent div to cover the screen
  e = document.createElement("div");
  e.id = "NSPopTransparent";
  e.style.opacity = ".5";                  
  e.style.MozOpacity = ".5";               
  e.style.KhtmlOpacity = ".5";
  e.style.filter = "alpha(opacity=50)";
  e.style.height = "100%";
  e.style.width = "100%";
  oContainer = document.body.appendChild(e);
  // if the document is longer than the screen height 
  if (document.body.offsetHeight + 24 > oContainer.offsetHeight)
    e.style.height = (document.body.offsetHeight + 24) + "px";
  disableAll(document.body,"input");
  disableAll(document.body,"select");
  disableAll(document.body,"textarea");
Next we create a div of the specfied size and center it in the visable section of the page. This uses some supporting functions that I'll show later. These are getWindowSize() which gets the size of the window, getScrollXY that gets how far down and across the page is scrolled, and getNumericPortion which returns the numeric portion of a string. The width and height variables are parameters from the function call.
  // create outside div
  e = document.createElement("div");
  e.id = "NSPopUP";
  e.style.height = height;
  e.style.width = width;
  // center the outside div within the viewable section of the document
  var top = (getWindowSize()[1] / 2) - (getNumericPortion(height) );
  if (width.substr(width.length-1) == "%")
    var left = (getWindowSize()[0] / 2) + getScrollXY()[0] - (getNumericPortion(width)/100 * getWindowSize()[0] /2 );
  else
    var left = (getWindowSize()[0] / 2) + getScrollXY()[0] - (getNumericPortion(width) / 2 );
  if (height.substr(width.length-1) == "%")
    var top = (getWindowSize()[1] / 2) + getScrollXY()[1] - (getNumericPortion(height)/100 * getWindowSize()[1] /2 );
  else
    var top = (getWindowSize()[1] / 2) + getScrollXY()[1]- (getNumericPortion(height) / 2 );
  e.style.left = left + "px";
  e.style.top = top + "px";
  oPop=document.body.appendChild(e);
To this div we add another div to be the title bar The title bar holds two elements, an image for the close button (this could be text if you prefer not to use a graphic) and the text for the title of the pop-up. The text is supplied to the function as a parameter. We also add functions to the onmousedown event to handle moving the pop-up.
  // create title bar
  e = document.createElement("div");
  e.id = "NSPopTitle";
  // Make the box movable when the mouse is pressed on the title bar
  e.onmousedown = movePop
  oTitle = oPop.appendChild(e);

  // Add a close button to the title bar
  e = document.createElement("img")
  e.style.width = "18px";
  e.style.height = "18px";
  e.style.magin = "1px";
  e.src = "close.gif";
  e.id ="NSPopCloseButton";
  e.onclick = closePopUp;
  oTitle.appendChild(e);

  // Add the Title Text
  e = document.createElement("span");
  e.id = "NSPopTitleText";
  e.innerHTML = title;
  oTitle.appendChild(e);
Now we add a div to hold the body of the pop-up and we're almost done. Notice that we use the height of the pop-up minus the height of the title bar for our hight. I also remove 2 pixels to make up for border space. By specifying the height it allows the scroll bar to line up as you would expect. If we had simply set the height to 100% then the div would go beyond the lower bounds by the height of the title bar.

There are two divs here so that we can apply padding to the body area without affecting the positioning of the box itself.

The final parameter used to call the pop-up is the ID of an object (usually a hidden div) on the page. This function gathers the innerHTML of the specified object and copies it into the pop-up body div we created.
  // create body of popup
  e = document.createElement("div");
  e.id = "NSPopBodyFrame";
  e.style.height = (oPop.offsetHeight - oTitle.offsetHeight - 2) + "px";
  oBody = oPop.appendChild(e);
  e = document.createElement("div");
  e.id="NSPopBodyText";
  e.innerHTML = obj.innerHTML;
  oText= oBody.appendChild(e);
}
Now all we need is a function to remove the created div's when we are done with it. the closePopUp() function does just that as well as enabling all of the form elements.
/* function closePopUp()
--------------------------------
   Removes the semi-transparent div
   Removes the Pop-Up window
   enables all elements
--------------------------------   */
function closePopUp(){
  oDiv = document.getElementById("NSPopTransparent");
  document.body.removeChild(oDiv);
  oDiv = document.getElementById("NSPopUP");
  document.body.removeChild(oDiv);
  enableAll(document.body,"input");
  enableAll(document.body,"select");
  enableAll(document.body,"textarea");
}   


Supporting Functions


There are a number of supporting functions used in this function In this area I will list them and explain what they do.

First the functions to move the pop-up. This is actually a collection of functions that provide this functionality. We need a few global variables to keep track of where the current position is while the pop-up is being moved.
/* Global Variables required for popup move events
-------------------------------------------------- */
var mouseStartX = -99999;
var mouseStartY = -99999;
var popLeft;
var popTop;

When the use presses the mouse on the title bar it calls the movePop() function. This fucntion sets two variables(mouseStartX and mouseStartY to a value that is unlikely to be a real value for the mouse position. It then stores the current pop-up left and top position in popLeft and popTop respectively. Then it sets functions to the onmousemove and onmouseup events for the document object.
/* function movePop()
----------------------------------
  Starts the watch for the mousemove
  and mouseup events on the document 
  object.
---------------------------------- */  
function movePop() {
  mouseStartX = -99999;
  mouseStartY = -99999;
  popLeft= document.getElementById("NSPopUP").offsetLeft;
  popTop = document.getElementById("NSPopUP").offsetTop;
  document.onmousemove = movePop1;
  document.onmouseup = endMovePop;
}
movePop1() is called by moving the mouse after the movPop function has been called. This function gets the current X and Y coordiates of the mouse and compares them to the last known values for the mouse position (mouseStartX and mouseStartY). If these values are at there programmed starting point (-99999) we know that this is the first time that the function has been called since the mouse button was pressed, so we set mouseStartX and mouseStartY to the current coordinates. Otherwise we subtract the starting coordinates from the current coordinates to see how far the mouse has moved. We then add this offset value to the left and top positions for the pop-up, making sure not to move it off the screen.
/* function movePop1(e)
----------------------------------
   perform the actual move of the popup
   e = mouse move event
---------------------------------- */   
function movePop1(e) {
  if (!e) var e = window.event;  // if IE, then we need to assign e
  if (e.pageX) {    // FF, opera, netscape
    posX = e.pageX;   
    posY = e.pageY;
  }
  else if (e.clientX) {  // IE
    posX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;  
    posY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
  }
  obj = document.getElementById("NSPopUP");
  // alert(vLeft)
  if (mouseStartX == -99999) mouseStartX = posX;  // -99999 is an arbitrary number.  It is unlikely to be hit 
  if (mouseStartY == -99999) mouseStartY = posY;
  var offsetX = posX - mouseStartX; // determine the difference from where the mouse started to where it is now
  var offsetY = posY - mouseStartY;
  vPosX = popLeft + offsetX;
  vPosY = popTop + offsetY;
  // ensure that the pop-up is not moved off the screen
  if (vPosX < 0) vPosX = 0;
  if (vPosX + obj.offsetWidth > document.body.offsetWidth +10) vPosX = document.body.offsetWidth - obj.offsetWidth +10;
  if (vPosY < 0) vPosY = 0;
  if (document.body.offsetHeight > getWindowSize()[1]) {
    if (vPosY + obj.offsetHeight > document.body.offsetHeight + 20) vPosY = document.body.offsetHeight - obj.offsetHeight + 20;
  }
  else {
    if (vPosY + obj.offsetHeight > getWindowSize()[1] -1) vPosY = getWindowSize()[1] - obj.offsetHeight -1;
  }
  obj.style.left = vPosX + "px";
  obj.style.top = vPosY + "px";
}
Last, but not least we have the endMovePop() function. As its name implies this function is called when the move is completed. More specifically it is called by the onmouseup event of the document object. This function sets the onmousemove and onmouseup events of the document object to empty functions.
/* function endMovePop()
----------------------------------
   disables the watch for the mousemove and 
   mouseup events on the document object
---------------------------------- */   
function endMovePop() {
  document.onmousemove = function() {};
  document.onmouseup = function() {};
}
Enabling and Disabling the form elements on the main page is handled by a pair of functions enableAll() and disableAll(). These functions accept two paraments: obj, which is the object that we will be disabling the objects in, and type which is the HTML tag name for the elements that we wish to disable. Using a type that does not support disable will result in an error.
/* function disableAll(obj,type)
--------------------------------   
  Disable all elements of the specified type
  within the specified object
-------------------------------- */
function disableAll(obj,type) {
  oInput = obj.getElementsByTagName(type);
  for (i=0;i<oInput.length ;i++) {
    oInput[i].disabled = true;
  }
}
 
/* function enableAll(obj,type)
--------------------------------   
   Enable all elements of the specified type
   within the specified object
--------------------------------  */   
function enableAll(obj,type) {
  oInput = obj.getElementsByTagName(type);
  for (i=0;i<oInput.length ;i++) {
    oInput[i].disabled = false;
  }
}
Two important supporting function are getWindowSize() and getScrollXY(). I did not write these functions. I have tried to locate where I found the code originally so that I could give proper credit, and have been unable to find the site. In any case the getWindowSize function as its name implies gets the width and height of the window. this is used for centering the pop-up box within the window. The getScrollXY shows how far the window is scrolled horizontally and vertically. This is also used in ensuring that the pop-up box is placed in the correct position initially. If we didn't take the scroll position into account, then if the user were scolled down on a long page, they may have needed to scroll back up to see the pop-up box. This way it shows up centered in the visible section of the page.
function getWindowSize() {
  var myWidth = 0;
  var myHeight = 0;
  if( typeof( window.innerWidth ) == 'number' ) { 
    //Non-IE
    myWidth = window.innerWidth;
    myHeight = window.innerHeight;
  } 
  else if(document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
    //IE 6+ in 'standards compliant mode'
      myWidth = document.documentElement.clientWidth;
      myHeight = document.documentElement.clientHeight;
  } 
  else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
    //IE 4 compatible
    myWidth = document.body.clientWidth;
    myHeight = document.body.clientHeight;
  }
  return [myWidth, myHeight] ;
}

function getScrollXY() {
  var scrOfX = 0;
  var scrOfY = 0;
  if( typeof( window.pageYOffset ) == 'number' ) {
    //Netscape compliant
    scrOfY = window.pageYOffset;
    scrOfX = window.pageXOffset;
  } 
  else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
    //DOM compliant
    scrOfY = document.body.scrollTop;
    scrOfX = document.body.scrollLeft;
  } 
  else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
    //IE6 standards compliant mode
    scrOfY = document.documentElement.scrollTop;
    scrOfX = document.documentElement.scrollLeft;
  }
  return [ scrOfX, scrOfY ];
}

A great deal of the formatting of this box is handled by a stylesheet (modalDialog.css). Here is the code from this style sheet. Changing colors will make no change in the output of the pop-up box, but many of the rules in this sheet affect the positioning of the elements and it is best not to change them unless you know what effect it will have. To add styles to the code in the dialog box use in-line styles or classes in your own style sheet.
div#NSPopUP 
{
    background-color:#e4e4e4;
    overflow:hidden;
    position:absolute;
    z-index:70;
    border: solid 1px #000;
}
div#NSPopTitle
{
  height: 20px;
  width: 100%;
  background-color: #0060ff;
  font-weight: bold;
  color: white;
  text-align:left;
}
img#NSPopCloseButton 
{
  float: right;
  margin-top:1px;
}
div#NSPopTitleText 
{
    margin-left: 5px;
    padding-top:3px;
}
div#NSPopTransparent 
{
  top: 0px;
  left: 0px;
  position: absolute;
  background-color: #0000cc;
  z-index: 69;
}

div#NSPopBodyFrame 
{
  position: relative;
  width: 100%;
  overflow: auto;
}
div#NSPopBodyText 
{
    padding: 5px;
}
I hope you have found this informative. It was certainly fun to create.
NS