[ Team LiB ] Previous Section Next Section

13.3 Controlling Positioning Via a DHTML JavaScript Library

NN 4, IE 4

13.3.1 Problem

You want a reusable library of routines that ease the scripted positioning of elements in a page.

13.3.2 Solution

Use the DHTML library, DHTMLAPI.js (shown in Example 13-1 in the Discussion), to simplify cross-browser scripting of positionable elements. The library is compatible with IE 4 or later and Navigator 4 or later. It provides API routines for moving, hiding/showing, and modifying the stacking order of positioned elements. For example, you can move an absolute-positioned element whose ID is helpWindow to a location 300 pixels to the right and 200 pixels down from the top-left corner of the document with the following function call:

shiftTo("helpWindow", 300, 200);

Additional utility routines in the library retrieve object references given an element's ID, element dimensions, element positions, and some browser window dimensions.

If you save this library under the name DHTMLAPI.js, you can link it into any document with the following tag:

<script language="JavaScript" type="text/javascript" src="DHTMLAPI.js"></script>

The library is self-initializing if no other onload event handlers are defined for the page. If you specify any onload event handlers in your page, be sure to include a call to the library's initialization routine, as shown in the Discussion.

13.3.3 Discussion

Example 13-1 shows the full DHTMLAPI.js library. In addition to providing a set of cross-browser tools for controlling positioned elements, it also defines five global Boolean variables about the browser environment that your other scripts may use.

Example 13-1. The DHTMLAPI.js library
// Global variables
var isCSS, isW3C, isIE4, isNN4, isIE6CSS;

// Initialize upon load to let all browsers establish content objects
function initDHTMLAPI( ) {
    if (document.images) {
        isCSS = (document.body && document.body.style) ? true : false;
        isW3C = (isCSS && document.getElementById) ? true : false;
        isIE4 = (isCSS && document.all) ? true : false;
        isNN4 = (document.layers) ? true : false;
        isIE6CSS = (document.compatMode && document.compatMode.indexOf("CSS1") >= 0) ? 
            true : false;
    }
}

// Set event handler to initialize API
window.onload = initDHTMLAPI;
   
// Seek nested NN4 layer from string name
function seekLayer(doc, name) {
    var theObj;
    for (var i = 0; i < doc.layers.length; i++) {
        if (doc.layers[i].name =  = name) {
            theObj = doc.layers[i];
            break;
        }
        // dive into nested layers if necessary
        if (doc.layers[i].document.layers.length > 0) {
            theObj = seekLayer(document.layers[i].document, name);
        }
    }
    return theObj;
}
   
// Convert object name string or object reference
// into a valid element object reference
function getRawObject(obj) {
    var theObj;
    if (typeof obj =  = "string") {
        if (isW3C) {
            theObj = document.getElementById(obj);
        } else if (isIE4) {
            theObj = document.all(obj);
        } else if (isNN4) {
            theObj = seekLayer(document, obj);
        }
    } else {
        // pass through object reference
        theObj = obj;
    }
    return theObj;
}
   
// Convert object name string or object reference
// into a valid style (or NN4 layer) reference
function getObject(obj) {
    var theObj = getRawObject(obj);
    if (theObj && isCSS) {
        theObj = theObj.style;
    }
    return theObj;
}
   
// Position an object at a specific pixel coordinate
function shiftTo(obj, x, y) {
    var theObj = getObject(obj);
    if (theObj) {
        if (isCSS) {
            // equalize incorrect numeric value type
            var units = (typeof theObj.left =  = "string") ? "px" : 0;
            theObj.left = x + units;
            theObj.top = y + units;
        } else if (isNN4) {
            theObj.moveTo(x,y)
        }
    }
}
   
// Move an object by x and/or y pixels
function shiftBy(obj, deltaX, deltaY) {
    var theObj = getObject(obj);
    if (theObj) {
        if (isCSS) {
            // equalize incorrect numeric value type
            var units = (typeof theObj.left =  = "string") ? "px" : 0;
            theObj.left = getObjectLeft(obj) + deltaX + units;
            theObj.top = getObjectTop(obj) + deltaY + units;
        } else if (isNN4) {
            theObj.moveBy(deltaX, deltaY);
        }
    }
}
   
// Set the z-order of an object
function setZIndex(obj, zOrder) {
    var theObj = getObject(obj);
    if (theObj) {
        theObj.zIndex = zOrder;
    }
}
   
// Set the background color of an object
function setBGColor(obj, color) {
    var theObj = getObject(obj);
    if (theObj) {
        if (isNN4) {
            theObj.bgColor = color;
        } else if (isCSS) {
            theObj.backgroundColor = color;
        }
    }
}
   
// Set the visibility of an object to visible
function show(obj) {
    var theObj = getObject(obj);
    if (theObj) {
        theObj.visibility = "visible";
    }
}
   
// Set the visibility of an object to hidden
function hide(obj) {
    var theObj = getObject(obj);
    if (theObj) {
        theObj.visibility = "hidden";
    }
}
   
// Retrieve the x coordinate of a positionable object
function getObjectLeft(obj)  {
    var elem = getRawObject(obj);
    var result = 0;
    if (document.defaultView) {
        var style = document.defaultView;
        var cssDecl = style.getComputedStyle(elem, "");
        result = cssDecl.getPropertyValue("left");
    } else if (elem.currentStyle) {
        result = elem.currentStyle.left;
    } else if (elem.style) {
        result = elem.style.left;
    } else if (isNN4) {
        result = elem.left;
    }
    return parseInt(result);
}
   
// Retrieve the y coordinate of a positionable object
function getObjectTop(obj)  {
    var elem = getRawObject(obj);
    var result = 0;
    if (document.defaultView) {
        var style = document.defaultView;
        var cssDecl = style.getComputedStyle(elem, "");
        result = cssDecl.getPropertyValue("top");
    } else if (elem.currentStyle) {
        result = elem.currentStyle.top;
    } else if (elem.style) {
        result = elem.style.top;
    } else if (isNN4) {
        result = elem.top;
    }
    return parseInt(result);
}
   
// Retrieve the rendered width of an element
function getObjectWidth(obj)  {
    var elem = getRawObject(obj);
    var result = 0;
    if (elem.offsetWidth) {
        result = elem.offsetWidth;
    } else if (elem.clip && elem.clip.width) {
        result = elem.clip.width;
    } else if (elem.style && elem.style.pixelWidth) {
        result = elem.style.pixelWidth;
    }
    return parseInt(result);
}
   
// Retrieve the rendered height of an element
function getObjectHeight(obj)  {
    var elem = getRawObject(obj);
    var result = 0;
    if (elem.offsetHeight) {
        result = elem.offsetHeight;
    } else if (elem.clip && elem.clip.height) {
        result = elem.clip.height;
    } else if (elem.style && elem.style.pixelHeight) {
        result = elem.style.pixelHeight;
    }
    return parseInt(result);
}
   
// Return the available content width space in browser window
function getInsideWindowWidth( ) {
    if (window.innerWidth) {
        return window.innerWidth;
    } else if (isIE6CSS) {
        // measure the html element's clientWidth
        return document.body.parentElement.clientWidth;
    } else if (document.body && document.body.clientWidth) {
        return document.body.clientWidth;
    }
    return 0;
}
   
// Return the available content height space in browser window
function getInsideWindowHeight( ) {
    if (window.innerHeight) {
        return window.innerHeight;
    } else if (isIE6CSS) {
        // measure the html element's clientHeight
        return document.body.parentElement.clientHeight;
    } else if (document.body && document.body.clientHeight) {
        return document.body.clientHeight;
    }
    return 0;
}

The purpose of this API is to present a single script interface for what can be nightmarish compatibility issues when trying to address three position-aware document object models: IE 4, NN 4, and W3C DOM. Your scripts invoke simple, one-size-fits-all functions, and the library takes care of the syntactic and conceptual differences among the three object models.

The DHTML library shown here first appeared in Dynamic HTML: The Definitive Reference, Second Edition (O'Reilly). It contains numerous functions that may be invoked from other JavaScript code loaded in the same page. The utility functions you are most likely to invoke directly are:

shiftTo( obj,x,y)

Moves an object to a coordinate point within its positioning context

shiftBy( obj,deltaX,deltaY)

Moves an object by the specified number of pixels along the x and y axes of the object's positioning context

setZIndex( obj,zOrder)

Sets the z-index value of the object

show( obj)

Makes the object visible

hide( obj)

Makes the object invisible

All functions require as a parameter something to let the function know which element to operate on. Because the library cannot predict how your scripts may be referencing an element when the functions are needed, all functions welcome either full-fledge object references or just the ID of the element (as a string). If you supply only the ID, other internal functions obtain the correct reference needed to modify the desired style properties. This works even for the peculiar way that Navigator 4 requires references to layer objects, rather than style properties.

To move a positioned element to a coordinate within its positioning context, simply invoke the shiftTo( ) function, passing as parameters a reference to the element (or just its ID) and the left and top (x and y) coordinate points of the destination:

shiftTo("moveableFeast", 340, 500);

To increment the location along one or both axes, you can use the shiftBy( ) function. For example, to move an element three pixels across and five pixels up, the statement is as follows:

shiftBy("moveableFeast", 3, -5);

In the shiftBy( ) function, positive values move the element to the right or downward; negative values move the element to the left or upward.

Your scripts, of course, may invoke any of the functions of the library if they help the cause. For example, two utility functions at the end of the library return the height and width of the browser window's content region. These values may be useful in positioning or sizing an element under script control to the current browser window size or proportion.

This library includes a self-initializing onload event handler, assigned to the window object (essentially the equivalent of embedding an onload event handler in the body element). If your page contains an onload event handler in the <body> tag, it overrides the assignment made within the library. Therefore, your own initialization routines also need to invoke initDHTMLAPI( ) to get some key global variables in place for other library functions to operate.

As with many .js libraries, you can eliminate the functions that you don't use, and even embed the remaining functions into a page's scripts if you like. Loading a big library to use only a few functions is a waste of bandwidth. Just exercise care that you don't remove helper functions invoked by the main functions you call directly. For example, if you wanted to use only the shiftBy( ) function, you also need the initDHTMLAPI( ), getObject( ), getRawObject( ), getObjectLeft( ), and getObjectTop( ) functions as a supporting cast.

If your browser audience consists exclusively of browsers that support the style property of all elements (IE 4 or later and Netscape 6 or later), you can simplify scripted positioning without the need of a library. Look at the shiftTo( ) function's code for an example of how to reposition an element within its context by assigning a measure (including units, together as a string) to both the style.left and style.top properties of the element. This works only on positioned elements. Browsers tend to restrict rendering of repositioned content until an execution sequence completes execution. Therefore, even though repositioning an element requires adjustments along two different axes, the user sees a single jump from one position to another. For animation, you need to use the setInterval( ) mechanism to force the browser to continually redraw the position along a path (see Recipe 13.9 and Recipe 13.10).

13.3.4 See Also

Most recipes later in this chapter and many recipes in later chapters utilize this API for positioning tasks.

    [ Team LiB ] Previous Section Next Section