Cross-Browser Style Objects

In Chapter 6 ("Too Many Browsers? Not Really") of Designing with JavaScript, 2nd Edition, we explore various techniques for dealing with browser incompatibilities by using browser detection. We can serve different CSS stylesheets to each browser, contain incompatible DHTML in a single frame, or just check if an object is supported.

In this article, adapted from Chapter 11 ("Advanced Applications") of the book, I extend the capabilities of older browsers instead, by recreating crucial W3C DOM features like the style object and the document.getElementById() method, and making them available to 4.0 browsers with proprietary DOMs.

Yes, it seems like you'd need a magical incantation to make all dynamic styles compatible across all browsers, but it is possible. Like all magic, this piece of JavaScript trickery relies on misdirection. By reconstructing DOM features out of each browser's proprietary DHTML extensions, and tying them to common names, this example creates another naming convention different from either the 4.0 browsers' or the W3C's, which you can then use to write browser-independent code.

This example applies the excellent work done by Bob Clary for Netscape, called xbStyle. You can find the latest version of xbStyle, as well as more information on cross-browser scripting, at DevEdge.

Application Programming Interfaces

xbStyle falls into a curious category of scripts called APIs
(Application Programming Interface). An API doesn't give a piece of software many new capabilities, as do most scripts, but rather creates an interface through which you can make better use of the software in your own scripts. You could say that the DOM is an API for scripting HTML.

For example, the DOM doesn't invent new HTML elements, but it does let you manipulate those elements that already exist in ways JavaScript can't. xbStyle is an API for Internet Explorer 4, Netscape 4, and W3C style manipulation, giving you access to dynamic CSS styles across all three browsers through one convenient interface. This means that, rather than branching your code to work around incompatibilities, or creating three versions of your scripts altogether, you can write one script using xbStyle that will work the same across all browsers.

In this example, we'll be taking the sliding menus script from Chapter 10 ("Interactive DHTML Techniques") of Designing with JavaScript, and rewriting it to use xbStyle. The original script uses the W3C DOM to slide a layer across the screen, then snaps it back into place when finished. This version follows the same logic as the original, but changes the names to fit xbStyle's conventions.

In order to use xbStyle, you must first link the xbstyle.js file to your document:

<script language="javascript" src="xbstyle.js"></script>

xbStyle also makes use of the Ultimate Browser Sniffer, another JavaScript library from Bob Clary. You'll have to link that file to your document as well:

<script language="javascript" src="ua.js"></script>

Once you've linked your document to the Ultimate Browser Sniffer, you can start writing your script using the API.

Using XBStyle

First, here is the original sliding menu script, outside of its HTML context:

This script uses common features of the DOM, including document.getElementById() and the style object. Unfortunately, the way elements are found and how their style properties are accessed vary in the 4.0 browsers. For example, finding an element by its ID, a fundamental DOM function, can be tricky to implement correctly in both Netscape 4 and IE4.

In IE4, elements are accessed through a method similar to document.getElementById(), document.all[]; xbStyle merely takes the ID passed to getElementById(), and redirects it to document.all[] instead, which then returns the appropriate element. In Netscape 4, a <div> with an ID can be accessed only through the document.layers[] array, which is a numbered list of all layers on the page; to compensate, xbStyle checks every element in that list against the ID you're looking for, stops when it finds a match, and returns the correct element.

To your script, it doesn't matter whether xbStyle had to pass the ID to a Netscape or an IE-specific function; it sees only the element the function returned. In other words, all you have to worry about is input and output; if you input the ID, you'll get back the element. This is the essence of an API, to hide the hard stuff and let you get real work done.

Below is the source of xbStyle's getElementById() method; take a look behind the curtain and see how the magic works:

Cross-Browser Sliding Menus

The two functions in our cross-browser sliding menu, showLayer() and hideLayer(), mimic the behavior of our original script, while changing only what is necessary to achieve compatibility. First and foremost, the primary function of xbStyle is to create a cross-browser style object. However, rather than being an object built into all other elements, as it is in the W3C DOM, you must create a new instance of xbStyle for every element whose styles you need to manipulate. You do this by using the xbStyle constructor function, which takes one parameter, an element ID.

var styleObject = new xbStyle("id");

Once you've created a style object, you can use one of xbStyle's built-in methods, which expand on the W3C and 4.0 DOMs, to manipulate style properties. In this example, we'll be using the following methods from xbStyle:

moveBy(x, y): Moves an element a specified number of pixels along the X (horizontal) and Y (vertical) axes, relative to its current position. This method is equivalent to adding to or subtracting from both the "top" and "left" style properties at once.

moveTo(x, y): Moves an element to a fixed point on the page, defined by its X and Y coordinates. This method is equivalent to setting the "top" and "left" style properties at once.

getLeft(): Returns the value of the style object's "left" property as a number. Note that this is different from what the W3C DOM would return because the number includes the unit "px," for "pixels," at the end of it's value. This means the value is a text string, not a plain number. This is important to remember, because while you can compare two numbers as greater than or less than, as in the example below, you can't do the same with text.

setLeft(n): Takes one parameter, a number, and sets the "left" property of the style object.

document.getElementById(id): Yes, it's also part of the DOM, but xbStyle creates a method of the same name for 4.0 browsers that don't support the W3C DOM, with the same general functionality. If the browser does support the DOM, xbStyle does nothing, and the method works just as expected.

The documentation at DevEdge contains a full listing of all xbStyle objects, methods, and properties.

So, let's take a look at the new and improved sliding menu script, now using the methods above (the letters on the left are for reference only):

A. Here is the first usage of document.getElementById(), now possible in all 4.0 and 5.0 browsers.

B. We create a new xbStyle object, which provides an interface to dynamic styles across browsers. Because the various Netscape and IE browsers have wildly varying DHTML style support, we must create a new xbStyle object, and associate it with an HTML element every time to ensure that a style object exists, and that it works consistently.

C. We store the menu's "left" style property in a variable named left, so that two lines later we can compare its current position with its final position.

D. Here we check if some part of the menu is still off the screen by comparing the current left position we've stored as "left" with its final position, 0 pixels from the left edge of the screen. If the menu is still partly offscreen, the function moves the menu, and repeats.

E. In the previous version of this script, we moved the menu along 5 pixels by adding 5 to the current left position, and setting the "left" property to that sum. Now, with xbStyle, we can just use the moveBy() method, and save ourselves some math.

F. Similarly, we replace the code to return the menu to its original position with this method, moveTo().

G. Finally, we use setLeft() to return the menu to its original position.

Try adapting some of your scripts to use XBStyle as well, and you'll save yourself a lot of coding and debugging time while reaching a larger Web audience.