Cross-Platform Position Scripting

Reconciling the differences between the object and rendering models of Navigator 4 and Internet Explorer 4 is one of the biggest challenges you face if you want to script positionable elements for both platforms in the same HTML document. The key factors to take into account are:

  • How to address positionable elements when the object references are so vastly different

  • How to make adjustments to differently named properties in a truly interactive and dynamic environment

You cannot avoid having your scripts branch to execute platform-specific statements. What you must decide for your application is how and where the branching occurs. There are three basic techniques you can use to implement cross-platform position scripting in a document:

  • Explicit branching

  • Platform-equivalent referencing

  • Custom APIs

Explicit branching and platform-equivalent referencing 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 amounts of scripted positioning, a custom API lets you push the ugly branching code off to the side in an external library. In essence, you create a meta-language 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.

Browser Flags

Regardless of the approach you take, you will need to set up global variable Boolean flags (JavaScript global variables scope only within the current document) that indicate which browser is running the script. 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.

JavaScript provides many ways to set browser flags. Some of the variation depends on how granular you want the detection to be. Browser detection can get down to the x.0x version, if a particular feature you use is buggy in earlier releases. You must also decide if detection only needs to find a floor for compatibility (e.g., Version 4 or later) or should be restricted to one generation of browser only.

Browser makers have been pretty good about maintaining backward compatibility for browsers. Therefore, it is generally safe to let the browser detection script set the flag when the browser version is greater than or equal to the minimum version you need for your application. This technique lets the page you write today run tomorrow on the next major release of the browser. Example 4.1 shows a script sequence that should run as a page loads, to set flags arbitrarily named isNav and isIE; the script also redirects older browsers to another page.

Example 4-1. A JavaScript Browser Detection Script

var isNav, isIE
if (parseInt(navigator.appVersion) >= 4) {
    if (navigator.appName == "Netscape") {
        isNav = true
    } else {
        isIE = true
    }
}
if (!isNav && !isIE) {
    top.location.href = "noDHTML.htm"
}

With the two flags initialized as null values in the first statement, you can safely use either one as a control structure condition expression, since a value of null evaluates to false in those situations. That’s precisely how the last if statement operates (but with the flags preceded by the ! operator, since the script is interested in the values not being true).

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. All you need to do is determine the platform-specific versions of the statement(s) to be executed and embed them inside a simple if construction. 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 body. For the Navigator version, the script takes advantage of the layer object’s moveTo() method; for IE, the script adjusts the pixelLeft and pixelTop properties. Notice, too, that the object references follow the conventions of the respective browser’s object model.

Example 4-2. Simple Branching

function placeIt() {
    if (isNav) {
        document.face.moveTo(25,15)
    } else {
        document.all.face.style.pixelLeft = 25
        document.all.face.style.pixelTop = 15
    }
}

There is no prohibition against using this technique on a complex document involving dozens of such branches. The primary penalty is the unnecessarily expanded amount of script code in the document. For some scripters, however, this technique is easiest to debug. It also comes in handy when the positionable objects are nested to different depths. Other techniques discussed in the following sections can also work with layers at different levels in Navigator, but usually not as easily.

Platform-Equivalent Referencing

Platform-equivalent referencing involves finding a common denominator approach to building references to positionable objects on both platforms. One way to do this is to create global variables to hold the platform-specific components of object references.

If you study the format of references to the Internet Explorer style properties of positionable objects, you see they always fall into the following format:

document.all.elementName.style

In contrast, single-level-deep Navigator layer objects are referenced according to the following format:

document.layerName

If you assign the unique Internet Explorer pieces to global variables when running in that browser, but assign empty strings to those same globals when running in Navigator, you can use the JavaScript eval() function to derive a valid object reference for either browser by assembling one reference, as shown in Example 4.3. This example embeds the global variable setting in the script segment that also sets the browser Boolean flags. It concludes with a function that takes advantage of the identical property names for a particular positioning property in both browsers.

Example 4-3. Platform-Equivalent Variable Setting and Object Evaluation

var isNav, isIE
var coll = ""
var styleObj = ""
if (parseInt(navigator.appVersion) >= 4) {
    if (navigator.appName == "Netscape") {
        isNav = true
    } else {
        isIE = true
        coll = "all."
        styleObj = ".style"
    }
}
// set stacking order of "face" element
function setFaceZOrder(n) {
    var obj = eval("document." + coll + "face" + styleObj)
    obj.zIndex = n
}

Notice that the variables for the IE reference pieces—coll (for collection) and styleObj (for style object)—contain specific punctuation to assist the eval() function in assembling a proper string representation of the reference for conversion to a genuine object reference.

The platform-equivalent reference technique is particularly helpful for cases where the property names are identical on both platforms, as shown in Example 4.3. But you can also combine this technique with explicit branching to handle more complex tasks. Example 4.4 shows a hybrid approach to moving an element, adapted from Example 4.2.

Example 4-4. A Hybrid Approach: Explicit Branching and Platform Equivalency

function placeIt() {
    var obj = eval("document." + coll + "face" + styleObj)
    if (isNav) {
        obj.moveTo(25,15)
    } else {
        obj.pixelLeft = 25
        obj.pixelTop = 15
    }
}
                     
                     

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.

Example 4.5 gives you a sample of what such an API library might look like. The API defines the following functions:

getObject( obj )

Takes a positionable element from the default positioning context and returns an object reference for either the Navigator layer or the Internet Explorer style object

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

Example 4-5. A Custom API for Positionable Elements

// DHTMLapi.js custom API for cross-platform
// object positioning by Danny Goodman (http://www.dannyg.com)

// Global variables
var isNav, isIE
var coll = ""
var styleObj = ""
if (parseInt(navigator.appVersion) >= 4) {
    if (navigator.appName == "Netscape") {
        isNav = true
    } else {
        isIE = true
        coll = "all."
        styleObj = ".style"
    }
}

// Convert object name string or object reference
// into a valid object reference
function getObject(obj) {
    var theObj
    if (typeof obj == "string") {
        theObj = eval("document." + coll + obj + styleObj)
    } else {
        theObj = obj
    }
    return theObj
}

// Positioning an object at a specific pixel coordinate
function shiftTo(obj, x, y) {
    var theObj = getObject(obj)
    if (isNav) {
        theObj.moveTo(x,y)
    } else {
        theObj.pixelLeft = x
        theObj.pixelTop = y
    }
}

// Moving an object by x and/or y pixels
function shiftBy(obj, deltaX, deltaY) {
    var theObj = getObject(obj)
    if (isNav) {
        theObj.moveBy(deltaX, deltaY)
    } else {
        theObj.pixelLeft += deltaX
        theObj.pixelTop += deltaY
    }
}

// Setting the z-order of an object
function setZIndex(obj, zOrder) {
    var theObj = getObject(obj)
    theObj.zIndex = zOrder
}

// Setting the background color of an object
function setBGColor(obj, color) {
    var theObj = getObject(obj)
    if (isNav) {
        theObj.bgColor = color
    } else {
        theObj.backgroundColor = color
    }
}

// Setting the visibility of an object to visible
function show(obj) {
    var theObj = getObject(obj)
    theObj.visibility = "visible"
}

// Setting the visibility of an object to hidden
function hide(obj) {
    var theObj = getObject(obj)
    theObj.visibility = "hidden"
}

// Retrieving the x coordinate of a positionable object
function getObjectLeft(obj)  {
    var theObj = getObject(obj)
    if (isNav) {
        return theObj.left
    } else {
        return theObj.pixelLeft
    }
}

// Retrieving the y coordinate of a positionable object
function getObjectTop(obj)  {
    var theObj = getObject(obj)
    if (isNav) {
        return theObj.top
    } else {
        return theObj.pixelTop
    }
}

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. Thus, you might use a combination of techniques to work with nested objects, as in the following call to a custom API function:

if (isNav) {
    setBGColor(document.outer.document.inner, "red")
} else {
    setBGColor(document.all.inner.style, "red")
}

The custom API in Example 4.5 is provided as a starting point for you to create 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" 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.

Get Dynamic HTML: The Definitive Reference now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.