Introduction
This article can be considered as a follow up to my former article: Modeling a Draggable Layer and Loading Dynamic Content to it via XML HTTP. Have a look at it, if you have not done already, so that you can catch up faster.
In this article, we will try to:
- Create a cross-browser, draggable DHTML modal dialog.
- Send an AJAX request using HTTP-POST.
- Get the response and display it inside the modal dialog.
And best of all, we will do all these in less than 20 lines of code, with the help of the sardalya API.
Please note that the API enclosed in this article's source archive is a rather simplified version of sardalya. You can visit sardalya's website for its latest full version.
Before starting, you may want to see the final result in action first.
Creating the View
The HTML of our ModalDialog
is fairly simple:
<div id="ModalBG"></div>
<div id="DialogWindow">
<div id="DialogHeader">
<span id="DialogTitle">Title comes here</span>
<img id="DialogActionBtn" src="icn_close.png"
alt="close icon" title="" />
</div>
<div id="DialogIcon"><img id="DialogIcon"
src="icn_alert.png" alt="alert icon" title="" /></div>
<div id="DialogContent">...</div>
</div>
"ModalBG
" is a layer that is placed between the page content and the ModalDialog
window so that we prevent accidental clicks on other page elements, and at the same time, put some visual emphasis on the ModalDialog
by fading the page in the background.
The CSS
To make our "dialog window" layer resemble an actual modal dialog, we need some CSS tweaks:
Master.css
#DialogWindow
{
border: 1px #FFFFFF outset;
width: 550px;
display: none;
background: #FFFFFF;
z-index: 1000;
position: absolute;
top: 0;
left: 0;
}
#DialogContent
{
float: right;
margin-right: 10px;
margin-bottom: 10px;
margin-top: 24px;
width: 450px;
display: block;
font-size: 90%;
}
#DialogIcon
{
padding: 10px;
float: left;
}
#DialogHeader
{
border-bottom: 1px #00449E outset;
background: #00449E;
text-align: right;
}
#DialogTitle
{
float: left;
padding: 8px;
color: #FFFFFF;
}
#DialogActionBtn
{
cursor:pointer;
}
And we need an "Opacity.css" for transparency support:
Opacity.css
#ModalBG
{
width:100%;
display:none;
background-color:#333333;
position:absolute;
top:0;
left:0;
height:100%;
z-index:999;
opacity:.40;
filter:alpha(opacity=40)
}
With the help of this CSS, our boxes will pretty much look like a modal dialog, except for certain browsers:
Be Kind to Opera
I hear you say, "Why am I to be kind to Opera all the time? Why is Opera never kind to me?!" and I truly understand you :). But let us be kind to Opera once again:
To make our transparent background work on Opera, we need several more CSS tweaks:
Master.css
.modalOpera
{
background-image: url("maskBg.png") !important;
}
That's it! Opera does not understand CSS transparency, but it fully supports .png transparency, therefore a transparent mask as a background will make our ModalDialog
work equally good in Opera.
There is one remaining issue here. We need to selectively apply this class if and only if the user agent is Opera. That is, other browsers such as Mozilla does not require this transparency hack, and we should not use the "modalOpera
" class if our user agent is one of them. We will address this issue in a second.
The Script
First of all, we need to prepare the modal dialog at page load:
window.onload=function()
{
DOMManager.sweep();
addExtensionsForOpera();
attachOpacityCSS();
adjustHeight();
g_Modal=new ModalDialog("ModalBG","DialogWindow",
"DialogContent","DialogActionBtn");
EventHandler.addEventListener(document,"dblclick",document_dblclick);
EventHandler.addEventListener(window,"resize",window_resize);
};
sweep
is a utility method of sardalya's DOMManager
object. It removes empty text nodes from the DOM structure.
Now, let us look at other methods one by one:
function addExtensionsForOpera()
{
var ModalBG=new CBObject("ModalBG").getObject();
if(typeof(window.opera)!="undefined")
{
ModalBG.className="modalOpera";
}
}
As seen, we only append the "modalOpera
" class name to "ModalBG" if the user agent is Opera.
Note that we do not sniff the user agent (navigator.userAgent
) but do an "object detection" instead (window.opera
).
Browsers love to fool scripts by sending false user agent strings and therefore object detection is the way to go. Although details of it is the subject of an entire article, I can say that browser sniffing is so '90s. As a rule of thumb, always use object detection.
Then comes attaching the opacity CSS piece:
function attachOpacityCSS()
{
var opacityCSS = document.createElement("link");
opacityCSS.type="text/css";
opacityCSS.rel="stylesheet";
opacityCSS.href="Opacity.css";
document.getElementsByTagName("head")[0].appendChild(opacityCSS);
}
And the height adjustment:
function adjustHeight()
{
var intWindowHeight=WindowObject.getInnerDimension().getY();
var dynModalBG=new DynamicLayer("ModalBG");
var intModalHeight=dynModalBG.getHeight();
if(intModalHeight<intWindowHeight)
{
dynModalBG.setHeight(intWindowHeight);
}
}
Then we create the modal dialog in just a single line:
g_Modal=new ModalDialog("ModalBG","DialogWindow",
"DialogContent","DialogActionBtn");
"ModalBG
" is the ID of the transparent background, "DialogWindow
" is the ID of the modal dialog container, "DialogContent
" is where messages are displayed when calling the show
method of ModalDialog
, and "DialogActionBtn
" is the ID of the close button.
And finally, we attach a double-click event to the document which will trigger an AJAX action:
EventHandler.addEventListener(document,"dblclick",document_dblclick);
Now let us have a look at the document_dblclick
method:
function document_dblclick(evt)
{
var ajax = _.ajax();
ajax.removeAllFields();
ajax.addField("name","John");
ajax.addField("surname","Doe");
ajax.oncomplete=ajax_complete;
ajax.onerror=ajax_error;
g_Modal.show("Fetching data... Please wait...");
g_Modal.disableClose();
ajax.get("externalScript.html");
new EventObject(evt).cancelDefaultAction();
}
The comments should be self-explanatory.
And finally, the two methods that are triggered after the server's post back:
function ajax_complete(strResponseText,objResponseXML)
{
g_Modal.show(strResponseText);
g_Modal.enableClose();
}
function ajax_error(intStatus,strStatusText)
{
g_Modal.show("Error code: ["+ intStatus+ "] error message: [" +
strStatusText + "].");
g_Modal.enableClose();
}
That's it!
What About those Nasty SELECTs ?
The ModalDialog
object internally handles it, by replacing them with SPAN
elements with the class "modalWrap
" whenever ModalDialog
opens. This sorts out the well known "SELECTs bleed through my top layer" issue.
Here is the CSS of it for the sake of completeness:
.modalWrap
{
border: 2px #ffffcc inset;
background:#ffffcc;
margin:5px;
}
You can add as many rules as you like to it. The more the SPAN
resembles a SELECT
element, the better (you can apply width and line-height, set display to inline-table... etc., I did not change it too much to keep it simple).
For those who wonder how the replacement of those SPAN
s and SELECT
s are done, the corresponding private method is given below. You can observe the source code of the article's Zip file for more details.
In the former version, we were simply hiding the SELECT
s by setting their CSS visibility to hidden
. Having tested it in real-life scenarios, I saw that the "all of a sudden" disappearance of SELECT
s was annoying to some of the users.
In my opinion, transforming the SELECT
s is much better than hiding them completely.
... And no, I do not want to use IFRAME
s :)
Here follows the code:
_this._replaceCombos=function(blnReplaceBack)
{
var arSelect = document.getElementsByTagName("select");
var len=arSelect.length;
var objSel=null;
var strNodeValue="";
var objSpan=null;
var o=null;
if(!blnReplaceBack)
{
blnReplaceBack=false;
}
for(var i=0;i<len;i++)
{
objSel=arSelect[i];
strNodeValue=objSel.childNodes[objSel.selectedIndex
].childNodes[0].nodeValue;
objSpan=new CBObject(objSel.id+"_ModalWrap");
if(objSpan.exists())
{
o=objSpan.getObject();
o.parentNode.removeChild(o);
}
objSpan=document.createElement("span");
objSpan.id=objSel.id+"_ModalWrap";
objSpan.appendChild(document.createTextNode(strNodeValue));
objSpan.className="modalWrap";
objSel.parentNode.insertBefore(objSpan,objSel);
if(blnReplaceBack)
{
new DynamicLayer(objSpan).collapse();
new DynamicLayer(objSel).expandInline();
}
else
{
new DynamicLayer(objSpan).expandInline();
new DynamicLayer(objSel).collapse();
}
}
};
Conclusion
In conclusion, we modeled and created a draggable DHTML modal dialog and established an AJAX connection to an external script; we did some cross-browser tweaks to make our application work on as many browsers as possible, and we did some OO coding.
And as always, happy coding!
History
- 2006-06-02: Article created.