Introduction
Before starting anything, please note that this article demonstrates an advanced level of Object Oriented, Prototype-Based JavaScript Programming. If you are not familiar to Object Oriented JavaScript, you may want to read my former article or Paul Baker's mini racer which demonstrate the concept.
In this article, we will try to generate a pop-up that loads an external file.
What made me think on such a solution was the increase in the usage of pop-up blockers.
When we implement our solution, our popup will never be blocked. Because it will not be a real pop-up at all. Actually, it will be a DHTML layer. So no popup blocker will be able to block it :)
This implementation has some advantages and some disadvantages:
- Pros
- The solution is cross-browser (recent versions of Netscape, Mozilla, Opera and Internet Explorer support it).
- Cons
- The technology is relatively cutting-edge. So older browser versions will not support it.
What we will be doing can be summarized in five steps:
- Design a layer that looks like a pop-up.
- Make our layer draggable.
- Find a solution for the nasty HMTL
<select>
s which render on top of everything, including our pseudo-pop up.
- Look at our object prototype, add/redesign methods if necessary.
- Find a way to change the inner content of our pop-up asynchronously from an external URL, without refreshing the browser (that's where
XMLHttp
object comes into play).
As I have always been, in this article, I will try to make it as much cross-browser and object oriented as I can.
I assure you, it will be fun.
You may want to see the final result in action before proceeding.
The Layer
This is the easy part. Although it can be made much easily with tables, I prefer to use a css-based layout.
<div id="PopupMasterContainer">
<div id="PopupTopHandle">
<div id="PopupTopButtons"><img
src="images/close.gif" id="IcnClose"
alt="closes the pop-up" /></div>
<div id="PopupTopTitle">Title Goes Here</div>
</div>
<div id="PopupContent">
</div>
</div>
With some CSS
, it will pretty much look like a pop-up.
Movin' It!
There are dozens of move and drag layer scripts around. So you do not have to use my version.
The point is, if you are using some script, you had better know what it does rather than just copy and pasting. This way you can find a way to extend it.
As I mentioned in the introduction, we will try to code OO JavaScript. We will observe examples of multi-level inheritance, method hiding, method overriding.
No we won't be writing C# :) Javascript can be more object oriented than one may imagine.
I've included a simplified version of my DHTML API, adding only drag-drop related parts, so that we can make our pop-up layer draggable.
You can examine different usages of the full version. Or download the entire API. (pages are Turkish; the API is distributed under a non-commercial share-alike license).
Not to deviate from the subject, I'll not go deep into how the drag functionality is implemented. As I said, any drag and drop API would suffice.
Want to try the pop-up with drag support? When you drag the top handle, you will see that it moves.
And here is the JavaScript code that does it:
1. var lyr = new DraggableLayer("PopupMasterContainer",null,null,true);
2. lyr.ignoreLayer("PopupContent");
The script turns "PopupMasterContainer
" into a draggable, non-resizable layer at line1; and tells it to ignore any drag action on the layer "PopupContent
" at line 2.
If you comment out line 2 you will see that not only the top handle but also the bottom content layer will activate the drag action.
That's what I love about object oriented programming: encapsulation!
Once you design the object properly, you rarely need to modify it. All you need to do is to know how to use the interface methods. You need not care about the code behind.
I will not explain how the code does what it does. Else I will be going too out-of-scope. If you have any queries just drop me an e-mail. I'll try to respond as soon as I get time.
Modeling The Pop-Up
Now let us adhere to our good old model-view-controller paradigm and create a model to control our view (i.e. the pop-up).
The skeleton of our model will much probably be like:
function PopupLayer(elmID,strTitle) {}
PopupLayer.prototype.show=function() {}
PopupLayer.prototype.hide=function() {}
PopupLayer.prototype._closeBtn_Click=function(evt) {}
Where the PopupLayer(elmID,strTitle)
constructor will create a draggable pop-up; show
will make it visible; hide
will make it disappear; and _closeBtn_Click
will be a private event handler, which handles the click
event of the close image at the top right corner; it will be attached during object construction.
Then, when we examine the API, we see that the object DynamicLayer
have methods which can make life easier on our side. Here are the list of its public methods:
.moveTo(intLeft,intTop)
.resizeTo(intWidth,intHeight)
[intLeft] .getLeft()
.setLeft(intLeft)
[intTop] .getTop()
.setTop(intTop)
[intHeight] .getHeight()
.setHeight(intHeight)
[intWidth] .getWidth()
.setWidth(intWidth)
.isVisible()
.show()
.hide()
.collapse()
.expand()
.changeContent(strNewHTML)
.addContentBefore(strHTML)
.addContentAfter(strHTML)
.exists()
[obj] .getObject()
[strID] .getID()
The object already defines show
and hide
methods. Moreover it has a full arsenal of additional methods: Take moveTo
for example, which programmatically moves the layer anywhere on the page; or resizeTo
which changes the dimensions of the layer.
DynamicLayer
includes most, but not all, of the methods of our initial prototype. In addition, it has a set of useful methods, which we have not considered in our initial design.
So, we come to a point that neither our PopupLayer
nor DynamicLayer
fully satisfies our needs.
You don't need to be an Oracle to say "Hey, PopupLayer should extend DynamicLayer !" :).
Then let us do it!
Extending PopupLayer
Adding PopupLayer.prototype = new DynamicLayer();
is adequate to make PopupLayer
a DynamicLayer
. Now any method that DynamicLayer
has is also a method of PopupLayer
.
And here is the new prototype with some additional comments to see what we will be doing:
PopupLayer.prototype = new DynamicLayer();
_this = PopupLayer.prototype;
function PopupLayer(elmID,strTitle) {
}
_this._closeBtn_Click=function(evt) {}
And to make our object detect various parts of our container we add distinctive className's to the HTML (the additions are marked in bold).
<div id="PopupMasterContainer">
<div id="PopupTopHandle" class="popupDragBar">
<div id="PopupTopButtons" class="popupButtonContainer">
<img src="images/close.gif" class="popupCloseIcon"
id="IcnClose" alt="closes the pop-up" /></div>
<div id="PopupTopTitle" class="popupWindowTitle">Title Goes Here</div>
</div>
<div id="PopupContent" class="popupConsole">
</div>
</div>
The constructor will iterate the DOM hierarchy, add event listeners, make necessary changes with the aid of those classNames.
Here is the truncated version of the code in the constructor to describe what it does:
function PopupLayer(elmID,strTitle) {
var pop = new DraggableLayer(elmID,null,null,true);
var objPop = pop.getObject();
var children = objPop.childNodes;
for (var i=0;i < children.length;i++ ) {
if(children[i].className) {
if(children[i].className=="popupDragBar") {
for(var j=0;j < dragBarChildren.length;j++) {
if(dragBarChildren[j].className) {
}
}
}
}
}
}
Now that we created the constructor, let us override and extend necessary parts:
Overriding Methods
To make our popup layer work the way we want, we need to override some of the inherited methods above, namely, changeContent
, addContentBefore
and addContentAfter
These three methods change the innerHTML
of the entire layer. However we only want to change the content of the console (i.e. class "popupConsole
") part.
Also we need to override show
and hide
because of a render problem that we'll examine shortly (selects on top of everything issue :) ).
Overriding the first three is the easy part:
PopupLayer.prototype = new DynamicLayer();
_this = PopupLayer.prototype;
function PopupLayer(elmID,strTitle) {
... constructor logic ...
}
_this.changeContent = function(strNewHTML) {
...code goes here...
};
_this.addContentBefore = function(strHTML) {
...code goes here...
};
_this.addContentAfter = function(strHTML) {
...code goes here...
}
Overriding show
and hide
is a bit tricky because we cannot directly cloak the parent object's show
and hide
methods. We need to call the parent object's methods as well.
Here is what we intend to do:
PopupLayer.prototype.show=function() {
}
PopupLayer.prototype.hide=function() {
}
Since we do not have a base
, or parent
keyword in JavaScript, we apply a trick: We copy the references of parent object's methods as member variances.
I think showing the code will be much explanatory than typing:
PopupLayer.prototype = new DynamicLayer();
_this = PopupLayer.prototype;
function PopupLayer(elmID,strTitle) {
... constructor logic ...
this._parent_show = this.show;
this._parent_hide = this.hide;
this.show=function() {
this._parent_show();
}
this.hide=function() {
this._parent_hide();
}
}
Hiding / Showing Dropdowns
Most probably you have seen such a view somewhere else.
This rendering anomaly occurs, because HTML Select
s are rendered on the top of anything in the page. And since our pop-up resides in the page, this is no exception.
To prevent this, we will hide all the dropdowns in the page when the pop-up is opened, and show them again when the pop-up is closed.
And gues what, we will create an object to do it :)
var _this=DOMManager.prototype;
function DOMManager() {}
_this.hideCombos = function() {
var arCombo = document.getElementsByTagName("select");
for(var i=0;i < arCombo.length;i++) {
arCombo[i].style.visibility="hidden";
}
}
_this.showCombos = function() {
var arCombo = document.getElementsByTagName("select");
for(var i=0;i < arCombo.length;i++) {
arCombo[i].style.visibility="inherit";
}
}
That simple!
When you need to hide combos, you call new DOMManager().hideCombos();
and when you want to show combos you call new DOMManager().showCombos();
.
Add Another Widget
One common request of the clients is "I want my pop-ups at the dead center of the page!". And customers are always right :)
Here is an additional method to position the pop-up at the center of the page:
_this.center = function() {
var windowHeight = self.innerHeight?
self.innerHeight:document.body.clientHeight;
var windowWidth = self.innerWidth?
self.innerWidth:document.body.clientWidth;
this.moveTo(
((windowWidth)/2-g_popup.getWidth()/2)-17
,
((windowHeight)/2-g_popup.getHeight()/2)-17
);
};
Here, we see why inheritance is a tool that we always have to keep in our toolbox:
moveTo
method is a method of the parent object of our PopupLayer
. And with the aid of inheritance, we can easily call it to make our pop-up move.
HTTP Request to an External File
Here is what we did up till this point.
We have a draggable layer which we can drag, close, show. But it is not quite the same as a real pop-up. Because, real pop-ups load HTML from an external URL.
However our pseudo-popup displays merely static content.
We will handle this problem in two steps:
- Design a cross-browser object which can make HTTP connections to the outside world.
- Integrate the object with our
PopupLayer
.
Let us start with the easy part. With the help of XMLHTTPRequest
, a few lines of code will do our job. We just need to write a cross-browser wrapper for it:
_this = XHRequest.prototype;
function XHRequest(){}
_this.getObject = function() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
}
else if (window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
else {
alert("XML HTTP Request support cannot be found!");
return null;
}
};
After creating the object, the second part is somewhat straightforward. We create an open
method for our pop-up. It establishes an async connection to the url
, hides the pop-up and shows it when the request is complete.
_this.open = function(url,title) {
if(title&&this._title) {
this._title.innerHTML = title;
}
this.hide();
var request = new XHRequest().getObject();
request.open("GET",url);
var eventSource = this;
request.onreadystatechange = function() {
if(request.readyState == 4) {
eventSource.changeContent(request.responseText);
}
eventSource.show();
}
request.send(null);
};
That's all folks!
Conclusion
In conclusion, we modeled and created a draggable DHTML pop-up that establishes an HTTP connection to an external file; we did some cross-browser OO coding and (hopefully) we had fun.
Happy coding!
History
- 2005-05-28
Article created.