Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Modeling a Draggable Layer and Loading Dynamic Content to it via XML HTTP

0.00/5 (No votes)
28 May 2005 1  
In this article, we will try to generate a draggable DHTML layer that loads data from an external URL via XMLHTTP connection. The code will be object oriented, cross-browser compatible, and relatively cutting-edge.

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:

  1. Design a layer that looks like a pop-up.
  2. Make our layer draggable.
  3. Find a solution for the nasty HMTL <select>s which render on top of everything, including our pseudo-pop up.
  4. Look at our object prototype, add/redesign methods if necessary.
  5. 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) //moves the layer to given coordinates. 

.resizeTo(intWidth,intHeight)// changes width and height of the layer.

[intLeft] .getLeft()// gets the left distance from the container.

.setLeft(intLeft)//sets the left distance to the container.

[intTop] .getTop()// gets the top distance from the container.

.setTop(intTop)// sets the top distance to the container.

[intHeight] .getHeight() // gets the height of the layer.

.setHeight(intHeight) // sets the height of the layer. 

[intWidth] .getWidth() // gets the width of the layer.

.setWidth(intWidth) // sets the width of the layer.

 .isVisible() // return true if the layer is visible.

.show() // makes layer visible.

.hide() // makes layer invisible.

.collapse() // sets layer's display to 'none'

.expand() // sets layer's display to 'block'

.changeContent(strNewHTML) // changes the contet of the layer.

.addContentBefore(strHTML) // adds content before.

.addContentAfter(strHTML) // adds content after.

 .exists() // returns true if such a layer actually exists.

[obj] .getObject() // returns the object reference to the layer.

[strID] .getID() // returns the id of the layer.

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) {
    /* create a draggable - non resizable layer. */
    /* set the title text */
    /* attach event handler to close button */

    /* override necessary methods */
}
_this._closeBtn_Click=function(evt) {}
/* override necessary methods */

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) {
    /* Create a draggable - non resizable layer. */
    var pop = new DraggableLayer(elmID,null,null,true);
    /* Find the top container. */
    var objPop = pop.getObject();
    var children = objPop.childNodes;
    for (var i=0;i < children.length;i++ ) {
        if(children[i].className) {
            /* Find console. */
            /* Find drag bar. */
            if(children[i].className=="popupDragBar") {
                for(var j=0;j < dragBarChildren.length;j++) {
                    if(dragBarChildren[j].className) {
                        /* Find and attach close event to close button. */
                        /* Find and set title. */
                    }
                }//for

            }//if

        }//if

    }//for

}//constructor

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 ...
}
/** overridden method */
_this.changeContent = function(strNewHTML) {
    ...code goes here...
};
/** overridden method */
_this.addContentBefore = function(strHTML) {
    ...code goes here...
};
/** overridden method */
_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:

/** overridden show method*/
PopupLayer.prototype.show=function() {
    /* Hide nasty selects. */
    /* Call parent object's show method. */
}

/** overridden hide method*/
PopupLayer.prototype.hide=function() {
    /* Show the selects. */
    /* Call parent object's show method. */
}

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;

    /** Override show method. */
    this.show=function() {
        /* Hide dropdowns. */
        /* Call parent object's show method. */
        this._parent_show();
    }

    /** Override hide method. */
    this.hide=function() {
        /* Show dropdowns. */
        /* Call parent object's show method. */
        this._parent_hide();
    }
}

Hiding / Showing Dropdowns

Most probably you have seen such a view somewhere else.

nasty selects

This rendering anomaly occurs, because HTML Selects 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() {
    /* 
     * quick and dirty way to get 
     * cross-browser width and height -- not fully tested 
     */
    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:

  1. Design a cross-browser object which can make HTTP connections to the outside world.
  2. 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) {
        /* _title is a private reference to the title layer. */
        this._title.innerHTML = title;
    }
    /*hide pop-up*/
    this.hide();
    /*open an HTTP request to the url*/
    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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here