Dynamic HTML: The Definitive Reference, 2rd Ed.Dynamic HTML: The Definitive ReferenceSearch this book

4.4. Cross-Platform Position Scripting

Reconciling the differences among scriptable, CSS-enabled browsers is far easier than in the days when Navigator 4 was a part of the equation. In fact, if you prefer to support only those browsers that use W3C DOM element and CSS attribute referencing syntax, your primary job is fashioning your code to allow other browsers to degrade gracefully. The following sections discuss three basic techniques you can use to implement position scripting in a document that may be viewed by various browser generations:

Browser support flagging and explicit branching place the branching code directly in your scripts. For a limited amount of scripted positioning, having all the branching code in your scripts is manageable and actually easier to debug. But if you are doing a serious amount of scripted positioning, a custom API (Application Program Interface) lets you push the ugly branching code off to the side in an external library. In essence, you create a metalanguage that gives you control over the specific syntax used in both browsers. A custom API requires a lot more work up front, but once the API code is debugged, the API simplifies not only the current scripting job, but any subsequent pages that need the same level of scriptability.

4.4.1. Browser Support Flagging

If you intend to support multiple browser classes, you will likely prefer to set up global variable Boolean flags (JavaScript global variables scope only within the current document) that indicate browser features that are important to your scripts. In the same code that establishes those variables, you should include code that redirects browsers not capable of rendering positionable elements to another page that explains the browser requirements. Unlike pages that use regular style sheets, which generally degrade acceptably for older browsers, pages with positioned elements fare very poorly when viewed with older browsers, especially if the intended design includes overlapping and/or hidden elements.

As I discussed in Chapter 2, you are best served using object detection to establish whether the current browser supports what you need. In the case of combined support for positioned elements in the IE 4 and W3C DOM environments, globals can be set for both element referencing types, as well as support for the style property. Example 4-1 shows a script sequence that should run as a page loads, to set flags arbitrarily named isW3C, isIE4, and isCSS; the script also redirects older browsers to another page.

Example 4-1. A JavaScript browser support flag script

// Global variables
var isCSS, isW3C, isIE4;
// initialize upon load to let all browsers establish content objects
function initFlags( ) {
    if (document.images) {
        isCSS = (document.body && document.body.style) ? true : false;
        isW3C = (isCSS && document.getElementById) ? true : false;
        isIE4 = (isCSS && document.all) ? true : false;
    }
    if (!isW3C && !isIE4) {
        top.location.href = "noDHTML.htm";
    }
}
// set event handler to initialize flags
window.onload = initFlags;

Global flag initialization occurs inside a function that is invoked after the page loads. Some browser versions do not treat the document node or body element as valid objects until the <body> tag loads. Since this element provides a good signal for a browser's DHTML support, the flags will more accurately reflect the browser's capabilities if the test is performed after the page loads.

Note that you should exercise care in assigning a function to window.onload in external script libraries. If multiple libraries assign a function to this event handler property, only the last one to load "wins." Similarly, if the <body> tag includes an onload event handler, it can override event handler property assignments. You cannot pile on multiple assignments through event handler property assignments.

Because the document.body property is supported in any browser that supports scripted positioning, that property is used as a gateway to verify support for the style object. In this example, the isCSS flag is part of the decision trees for both the isW3C and isIE4 flags. Later scripts can rely on just these two flags to indicate style support, as well. The final segment of the initFlags( ) function in Example 4-1 provides an escape route for other browsers. They are led to another page that won't show positioned elements.

This kind of flagging works well for introductory pages that direct visitors to parts of a site tailored to browser features, such as one path each for DHTML and non-DHTML browsers. Rather than immediately redirecting a visitor to an alternate page, scripts behind the clickable links on the page can use the flags quietly to lead users to subsequent pages best suited for their browsers' powers.

4.4.2. Explicit Branching

For the occasional need to control the property of a positionable element, an explicit branch does the job without a lot of fuss. Because standard CSS-style properties are identical for W3C and IE DOMs, all that's needed is a way to equalize element references. Example 4-2 shows a script fragment whose job it is to move an element (named face) to a particular coordinate point relative to the positioning context of the document. This fragment assumes that global flags similar to those shown in Example 4-1 have been set elsewhere in the document.

Example 4-2. Simple branching

function placeFace( ) {
    var elem = (isW3C) ? document.getElementById("face") :
        ((isIE4) ? document.all("face") : null);
    if (elem) {
        elem.style.left = "25px";
        elem.style.top = "15px";
    }
}

Repeated use of the global flags in many such functions in a document is marginally more efficient than using the expanded condition tests for the existence of document properties. The less object evaluation that takes place in a script, the better—even at the expense of a few more variables.

4.4.3. Custom APIs

If you find yourself doing a lot of scripting of positionable elements in your applications, it is probably worth the effort to create a custom API that you can link into any application you create. A custom API can take care of the "grunt" work for common position-scripting tasks, such as moving, hiding, showing, and resizing elements, as well as setting background colors or patterns. When you define a custom API library, the methods you write become the interface between your application's scripts and various positioning tasks. Many programmers prefer to make things happen via function calls, rather than assigning values to properties.

Example 4-3 gives you a sample of what such an API library might look like. For the sake of readers who used this API from the first edition (and may therefore still need to support Navigator 4), the API in Example 4-3 includes branches for Navigator 4's unique syntax, as well as the W3C and IE formats. Although internal processing of this new version is different from the first one, this API is compatible with the previous version. The API defines the following functions:

getObject(obj)
Takes a positionable element (either the element's ID string or valid object reference) from the default positioning context and returns an object reference for either the Navigator 4 layer or the style property of the element

getRawObject(obj)
Takes a positionable element (either the element's ID string or valid object reference) from the default positioning context and returns an object reference for the element (not its style property)

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 in the X and Y axes of the object's positioning context

setZIndex(obj, zOrder)
Sets the z-index value of the object

setBGColor(obj, color)
Sets the background color of the object

show(obj)
Makes the object visible

hide(obj)
Makes the object invisible

getObjectLeft(obj)
Returns the left pixel coordinate of the object within its positioning context

getObjectTop(obj)
Returns the top pixel coordinate of the object within its positioning context

getObjectWidth(obj)
Returns the width, in pixels, of the element[5]

[5]The API does not distinguish between IE's backward- and standards-compatible modes in IE 6 for Windows. For IE/Windows operating in standards-compatible mode (see the <!DOCTYPE> element in Chapter 8), the methods return only the content dimensions; for all other versions of IE/Windows, the values include margins and borders, if any, but not padding.

getObjectHeight(obj)
Returns the height, in pixels, of the element*

Additional helper functions for determining the height and width of the browser window's content region assist with many positioning tasks relative to the window (rather than the document). Example 4-3 provides a custom API for positionable elements.

Example 4-3. A custom API for positionable elements

// DHTMLapi.js custom API for cross-platform
// object positioning by Danny Goodman (http://www.dannyg.com).
// Release 2.0. Supports NN4, IE, and W3C DOMs.
  
// 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) {
        if (elem.scrollWidth && (elem.offsetWidth != elem.scrollWidth)) {
            result = elem.scrollWidth;
        } else {
            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;
}

Notice that every function call in the API invokes the getObject( ) function. If the parameter passed to a function is already an object, the object reference is passed through to the function's other statements. But it also accepts a string value for an element ID (or Navigator 4 layer name).

You can use the custom API in Example 4-3 as-is or as a foundation for your own extensions that fit the kinds of positioning tasks your applications require. Your version will probably grow over time, as you further enhance the positioning techniques used in your applications.

When you write a custom API, save the code in a file with any filename that uses the .js extension. Then, you can link the library into an HTML document with the following tag pair in the head portion of the document:

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

Once you do this, all the functions and global variables in the custom API library become immediately available to all script statements in the HTML document.



Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.