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.
|