Working with cross-browser and cross-version DHTML scripting objects
According to a recent article at ZDNet, Netscape should be shipping the first beta of Navigator 5.0 sometime in December 1999. Navigator 5.0 will have a new layout engine, code-named gecko, that has been available as part of the Mozilla open source effort. Navigator 5.0 should have complete support for CSS1 and HTML 4.0, in addition to support for the World Wide Web Consortium's DOM, or Document Object Model, and Extensible Markup Language (XML). Additionally, support for CSS2, specifically much of the positioning capability of this specification, is also present in the Mozilla milestone deliveries.
What Navigator 5.0 won't have, though, according to a document accessible from an article at the mozillaZine site, is support for certain browser-specific features such as Navigator 4.x's LAYER tag, and the associated document.layers collection. Instead, Navigator 5.0 (from Mozilla source) is providing support only for those features of Navigator 4.x that are supported in the HTML 4.0 and the W3C DOM. In other words, chances are that your Navigator 4.0 DHTML code will not work with Navigator 5.0, particularly if you use the LAYER tag and the document.layers array.
It's a given that Internet Explorer proprietary objects and code won't work with Navigator 5.0. But you might be surprised to find that IE 5.0 pages, particularly those that limit the use of DHTML to that supported in CSS1 and CSS2, are more likely to work with Navigator 5.0 than code designed specifically for Navigator 4.0.
Now is the time to begin thinking about how you're going to support DHTML pages that work with not only IE and Navigator, but across versions 4.x and 5.x of both browsers. This includes working with each browser's proprietary object models as well as the W3C DOM. An approach I've used to handle cross-browser differences since both Navigator 4.0 and IE 4.0 were in beta release is to create scripting objects to hide browser-specific implementations. You can extend these scripting objects to add cross-version/cross-DOM support as well as cross-browser support.
First, though, you have to remove any tags that won't work across browser versions, and you have to modify some other tags to include new HTML 4.0 attributesa process I'll refer to as DHTML normalization.
Normalize Your Web Page
Normalization is based on the scientific technique of reduction to a standard state. With Web pages, this means removing non-standard, proprietary elements from a page and standardizing the elements that remain. For instance, the HTML 4.0 specification does not support the LAYER or the ILAYER tag, implemented with Navigator 4.0. Because these tags are non-standard, Mozilla (hence Navigator 5.0) does not support them.
However, you can replace the LAYER tags with HTML 4.0-compliant DIV tags, which perform basically the same function of the LAYER tag: grouping one or more HTML elements into a single block that share the same CSS settings and that can be manipulated as a whole with script. So, instead of creating a block using this code:
<LAYER left=200 top=200>
<H1>hello</H1>
</LAYER>
You would use the DIV tag and CSS as follows:
<DIV style="position: absolute; left: 200px; top: 200px">
<H1>hello</H1>
</DIV>
In addition to replacing the LAYER or ILAYER and other non-standard tags, you also normalize the Web page by adding in new attributes, or replacing tag attributes that have been deprecateddefined as being obsolete and not to be used.
An example of a deprecated attribute is the LANGUAGE attribute for the SCRIPT tag. Previously, this attribute was used to designate the scripting language used within the Script block, and assigned values such as "VBScript" or "JavaScript". However, this attribute has been deprecated in favor of the TYPE attribute, which takes a MIME type, such as "text/javascript" or "text/vbscript", describing the scripting language. So, to normalize your page, instead of using something such as this:
<SCRIPT LANGUAGE="JavaScript">
You would use:
<SCRIPT TYPE="text/javascript">
However, older browsers such as IE 4.0 and Navigator 4.0 don't recognize the new TYPE attribute. So, to ensure that scripting is picked up with older as well as newer browsers, you can use both attributes within the same SCRIPT element:
<SCRIPT LANGUAGE="JavaScript" TYPE="text/javascript">
This approach isn't truly "standard" in the sense that you do not sacrifice browser version compatibility to adhere to standards; but it does pass the HTML 4.0 transitional test using the W3C HTML Validator. Best of all, the code enclosed within the SCRIPT beginning and ending tag will be picked up by both 4.x and 5.x browsers.
The W3C recommends that user agents (browsers) still support the deprecated attribute or element for backwards compatibility, but warns Web page authors that the element could become obsolete and hence unsupported in the future. So, Navigator 5.0 should support the LANGUAGE attribute for now, but Navigator, and other browsers, could stop supporting it in the future, in which case the attribute will be ignored. By including both attributes now, you can prepare for future browser releases while still supporting older browsers.
Once you've normalized your Web page, you need to process DHTML in such a way that the effects work with all the browsers you support, not just one. To do this, you can create scripting objects to handle cross-browser differences and extend these objects to also handle cross-DOM differences.
Scripting Objects as DHTML Wrappers
Creating objects within JavaScript has been supported since the earliest releases of Navigator, and is supported in IE as well as Navigator. The concept is too involved to cover completely in this article (see Netscape's Working with Objects for more in-depth detail). However, for the purpose of creating DHTML compatibility objects, a scripting object is defined by a constructor function, which contains references to methods and properties supported by the object and accessible by a Web page application. The actual implementation of these exposed methods and properties is totally hidden from the application developer who uses the objects.
As an example, I've created variations of the scripting objects I used at my Web site. These are only limited to a few DHTML methods: objHide to hide a page element; objShow to show a previously hidden page element; objChangeFontColor to change the color of the font contained in the element; and objChangeBackgroundColor to change the background color of the element.
To handle cross-browser differences, you define scripting objects to provide DHTML methods for IE and for Navigator 4.0. The IE object works with both IE 4.x and 5.x code, and the constructor for this objects looks as follows:
// create IE DHTML object
function ie_object(obj) {
this.css = obj;
this.name = obj.name;
this.objHide = ieHide;
this.objShow = ieShow;
this.objChangeFontColor = ieChangeFontColor;
this.objChangeBackgroundColor = ieChangeBackgroundColor;
}
The object constructor takes a Web page object, such as a DIV block, as a parameter, assigns it to an internal property of the scripting object, and then assigns pointers to IE-specific functions to the exposed object methods. So, a DHTML application calling objHide on an object that has been wrapped in the IE-specific object wrapper really results in a call to ieHide internally. You then implement the IE-specific methods using IE-specific functionality:
// IE/DOM hide element
function ieHide() {
this.css.style.visibility = "hidden";
}
// IE/DOM show element
function ieShow() {
this.css.style.visibility = "visible";
}
// IE/DOM change font
function ieChangeFontColor(clr) {
this.css.style.color=clr;
}
// IE/DOM change font
function ieChangeBackgroundColor(clr) {
this.css.style.backgroundColor = clr;
}
The Navigator object has the same exposed methods and properties, but its methods point to Navigator 4.x-specific method implementations. The Netscape constructor and methods are:
// Navigator object
function ns_object(obj) {
this.css = obj;
this.name = obj.name;
this.objHide = nsHide;
this.objShow = nsShow;
this.objChangeFontColor = nsChangeFontColor;
this.objChangeBackgroundColor = nsChangeBackgroundColor;
}
// hide element
function nsHide() {
this.css.visibility = "hidden";
}
// show element
function nsShow() {
this.css.visibility = "inherit";
}
// change color
function nsChangeFontColor(clr) {
}
// change background
function nsChangeBackgroundColor(clr) {
this.css.bgColor = clr;
}
Notice that the nsChangeFontColor method is an empty function. This is because Navigator 4.x does not support altering CSS styles on an element after a page has displayed. However, you must include the function to maintain a consistent interface for both the Navigator and the IE objects.
Navigator doesn't support changing the background color using CSS either, but it does support changing the background color using the deprecated bgColor attribute, which is used in the method nsChangeBackgroundColor.
So how does the DHTML application access the right scripting object to match the browser? You call a function, create_objects, from the onLoad page event handler, which then tests for browser type and calls a browser-specific function to create a global array of browser-specific objects. Within the function, you pass each page element to the browser-specific object constructor, and then add the newly wrapped DHTML object to the array. So, in the example, create_objects looks similar to:
// call correct object loading method
function create_objects() {
if (navigator.appName == "Microsoft Internet Explorer")
create_ie_objects();
else
create_ns_objects();
}
The IE-specific function to create the array of objects pulls the elementsin this case DIV elements onlyfrom the document.all.tags collection, calls the IE DHTML object constructor, and passes the page element to the scripting object constructor:
// create IE objects
function create_ie_objects() {
theelements = document.all.tags("DIV");
theobjs = new Array();
for (i = 0; i < theelements.length; i++)
theobjs[theelements[i].id] = new ie_object(theelements[i]);
}
You then assign the new DHTML object to the global array theobjs.
The Netscape-specific function accesses the document.layers array to pull all layer elements (CSS-positioned DIV elements, in the example) from the page and calls the Netscape-specific DHTML object constructor to wrap the page element:
// create Navigator objects
function create_ns_objects() {
theobjs = new Array();
for (i = 0; i < document.layers.length; i++)
theobjs[document.layers[i].name] = new ns_object(document.layers[i]);
}
You assign the elements to the global array by their names, so you can access them by name in the code.
Now, to access the browser-specific object, which in turn accesses the browser-specific DHTML functions, you access the global array of elements by their given name (id):
theobjs["test"].objChangeFontColor="red";
You make the objects reusable by packaging them in a separate JavaScript (JS) source code file. You then include this file in whatever page wants to use the DHTML:
<SCRIPT language="javascript" src="cbobjects.js">
</SCRIPT>
Most likely, if you've worked with DHTML in the past, you've seen the use of scripting objects to handle cross-browser differences. I've discussed them in previous articles (see my WDVL and NetscapeWorld articles), as well as in my book Dynamic HTML, and other developers have used them, including folks at Netscape and Microsoft.
However, you also might have noticed that the techniques used to load the objects into the array depends on each browser's own proprietary object model in order to access the objects. The object loading function for Navigator uses document.layers, and the object loading function for IE uses document.all.tags. From what we know of Navigator 5.0, neither of these object-loading techniques will work. Instead, you need to implement a scripting object that will support the W3C DOM, as well as each browser's proprietary object model.
Implementing Cross-DOM DHTML Scripting Objects
In order to provide support for browsers that implement the DOM and HTML 4.0, as well as CSS1 and CSS2 (eventually), you need to add a new browser object; one that uses standardized W3C DOM techniques to perform DHTML. This new object is named dom_object, and is defined as:
// create DOM object
function dom_object(obj) {
this.css = obj;
this.name = obj.name;
this.objHide = domHide;
this.objShow = domShow;
this.objChangeFontColor = domChangeFontColor;
this.objChangeBackgroundColor = domChangeBackgroundColor;
}
Next, you add the new object's method implementations, but you'll see from the code that these methods are identical to those listed for IE:
// IE/DOM hide element
function domHide() {
this.css.style.visibility = "hidden";
}
// IE/DOM show element
function domShow() {
this.css.style.visibility = "visible";
}
// IE/DOM change font
function domChangeFontColor(clr) {
this.css.style.color=clr;
}
// IE/DOM change font
function domChangeBackgroundColor(clr) {
this.css.style.backgroundColor = clr;
}
All CSS style attributes are accessible from a Style object, which is, in turn, accessible as a property of an individual Web page object. IE 4.x and IE 5.x support this approach to modifying CSS styles on an element, as does Navigator 5.0, at least as seen in the recent release of Mozilla, Milestone 10.
The set of methods defined on the scripting objects for this example are the same for both the IE and the DOM objects, so you use the same methods for both objects and rename them to fit the more general object. For example, you rename ieChangeFontColor to domChangeFontColor, and so on. Even though at this time the IE and DOM objects are sharing all methods, you still keep them as separate objects. The reason is that not all DHTML techniques will be as easily compatible across browsers as those shown in the example.
You create a new method that uses the W3C DOM scripting binding technique to load objects for DOM supporting browsers. This new method uses the DOM method getElementsByTagName to get a list of DOM objects or nodes. You traverse the list and add each node object (comparable to a page element of the designated tag type, in this case all DIV elements) to the global array:
// create DOM objects
function create_dom_objects() {
theelements = document.getElementsByTagName("DIV");
theobjs = new Array();
for (i = 0; i < theelements.length; i++) {
var obj = theelements[i];
theobjs[obj.id] = new dom_object(obj);
}
}
You then modify the create_objects method to include support for the new DOM loading function. In this method, you check the application version property of the Navigator object, navigator.appVersion, to see if the browser is a 5.0 browser. If so, you use the DOM loading technique (both IE 5.0 and Navigator 5.0 support this approach). Otherwise, you call the IE loading technique for IE 4.0 and the Navigator loading technique for Navigator 4.0.
Note that this technique for testing the browser and browser version is a highly simplified approach. Eric Krock of Netscape has an article, "Updating Your JavaScript and CGI Scripts for Version 5 Browsers," that provides a sophisticated, lengthy code script to test for client browsers.
You can view the complete source code for the objects created for this article. Next, you'll use these objects to implement a simple interactive page, which displays and hides text based on the movement of the Web page reader's mouse.
Using the DHTML Wrapper Scripting Objects
As a demonstration of how to use the cross-DOM wrapper objects, create a Web page that contains three DIV blocks, each of which contains a reference to a GIF file. Position the DIV blocks using absolute positioning, and capture the onMouseOver and onMouseOut event handlers for each block. You call separate functions based on these events. The HTML for the three DIV blocks is as follows:
<DIV id="one"
style="position:absolute; left:50px; top:30px; z-index: 1; width: 135px; height: 136px"
onMouseOver="showMsg('one')" onMouseOut="hideMsg('one')">
<img src="obj2.gif" width=135 height=136 alt="Object Model">
</DIV>
<DIV id="two"
style="position:absolute; left:50px; top:170px; width: 135px; height: 136px"
onMouseOver="showMsg('two')" onMouseOut="hideMsg('two')">
<img src="obj1.gif" width=135 height=136 alt="Object Properties">
</DIV>
<DIV id="three"
style="position:absolute; left:50px; top:310px; width: 135px; height: 136px"
onMouseOver="showMsg('three')" onMouseOut="hideMsg('three')">
<img src="obj3.gif" width=135 height=136 alt="Object Events">
</DIV>
When the mouse is over one of the image DIV blocks, you call the showMsg function; when the mouse moves away from the block, you call the hideMsg function.
In addition to the three image-enclosing DIV blocks, create three more CSS-positioned DIV blocks containing text, each of which is hidden when the page first loads, and each of which is positioned in the same position as the other two text blocks.
<DIV id="onecontent"
style="position:absolute; left: 250px; top: 60px; width: 400px; padding: 10px;
visibility:hidden">
<H4>Object Model</H4>
<p>Hide browser and version differences by creating a set of objects
that expose a common set of methods, and that are accessed by the DHTML application.
</p>
<p>Navigator 4.x and up (including Mozilla),
and IE 4.x and up support DHTML and support the
creation of scripting objects. These browsers and their versions also support
accessing page elements as objects, and the ability to wrap these page elements
within the scripting objects.
</P>
<p>Scripting objects can be defined to include any number of methods, and
the exposed methods point to implementation-specific methods.</p>
<p>The method implementations then provide the browser/version-specific processing.</p>
</DIV>
<DIV id="twocontent"
style="position:absolute; left: 250px; top: 60px; width: 400px; padding: 10px;
visibility:hidden">
<H4>Object Properties</H4>
<p>When creating cross-browser/cross-version DHTML objects, provide a
common set of interfaces that are accessed by the application, but which
are implemented differently, internally.
</P>
<p>The application sets or gets a property using the interface methods, and
the browser-specific implementation takes care of the browser-specific processing
transparent to the DHTML application developer.
</DIV>
<DIV id="threecontent"
style="position:absolute; left: 250px; top: 60px; width: 400px; padding: 10px;
visibility:hidden">
<H4>Event Handling</H4>
<p>Accessing the page elements via the exposed object model (or models), and
modifying element properties is not the only area where browser implementations differ.
</p>
<p>Event handling is also different between the browser versions.
</P>
<p>For instance, event capturing within an individual element is exposed in
IE 4.x, more so in IE 5.x, as well as Mozilla (Navigator 5.x). However, event
capturing is limited with Navigator 4.x, and is either not supported for elements,
or only supported through direct event capturing.
</p>
</DIV>
The DHTML effect in this sample page displays the text associated with an image when the Web page reader moves his or her mouse over the image. So, if the reader moves the mouse over the DIV block named "one", you display the text block named "onecontent". When the reader moves the mouse away from the image block, you hide the text content again.
The images contained in the DIV blocks look like buttons, each with a different label and color. To spice up the effect, you change the background color of the content to reflect the color of the corresponding button. You also change the content font color to make it contrast more with the new background color.
Using the DHTML cross-DOM objects, the functions to display and hide the content, as well as change the content's font and background colors are:
// for background colors assoc with each button
var colorArray = new Array(3);
colorArray["one"] = "red";
colorArray["two"] = "magenta";
colorArray["three"] = "green";
// showMsg code
function showMsg(nm) {
clr = colorArray[nm];
nm = nm + "content";
if (theobjs[nm]) {
theobjs[nm].objChangeFontColor("white");
theobjs[nm].objChangeBackgroundColor(clr);
theobjs[nm].objShow();
}
}
// return to hideMsg - hide
function hideMsg(nm) {
nm = nm + "content";
if (theobjs[nm])
theobjs[nm].objHide();
}
An array holds the colors to use with each context block. You could have defined the colors within the context block instead of setting the colors using DHTML, but then you wouldn't get the chance to try out the new object methods.
Testing this example, you'll find that it works well with IE 4.0 and IE 5.0, and Mozilla Milestone 10 (Navigator 5.0), but that the DHTML effect doesn't work with Navigator 4.0. The reason is that there is another difference between browsers and browser versions: event handling.
The DOM, and IE and Navigator 5.0 support event handlers on most HTML elements; Navigator 4.x, on the other hand, only supports event handlers on certain elements, such as anchor tags and form elements. Navigator 4.x ignores the event handlers in the DIV blocks containing the images in the example.
Though Navigator 4.x doesn't support event handlers in the DIV tag, it does support event capturing on certain types of tags, including those of CSS-positioned DIV blocks as the image-enclosing DIV blocks of the example. So, the workaround to get the DHTML effect to work with Navigator 4.x is to capture the MOUSEOVER and MOUSEOUT events for the DIV blocks, and assign each event handler to a specific function.
// setup test for Nav 4.0
var isNav4 = false
if ((navigator.appName == "Netscape") &&
(navigator.appVersion.search("5.0") == -1))
isNav4 = true
// for Nav 4.0 - capture events
function objSetEvents() {
if (isNav4) {
document.one.captureEvents(Event.MOUSEOVER);
document.one.onMouseOver=ns4_showMsg;
document.one.captureEvents(Event.MOUSEOUT);
document.one.onMouseOut=ns4_hideMsg;
document.two.captureEvents(Event.MOUSEOVER);
document.two.onMouseOver=ns4_showMsg;
document.two.captureEvents(Event.MOUSEOUT);
document.two.onMouseOut=ns4_hideMsg;
document.three.captureEvents(Event.MOUSEOVER);
document.three.onMouseOver=ns4_showMsg;
document.three.captureEvents(Event.MOUSEOUT);
document.three.onMouseOut=ns4_hideMsg;
}
}
// nav4 specific processing
function ns4_showMsg(evt) {
showMsg(evt.target.name);
}
function ns4_hideMsg(evt) {
hideMsg(evt.target.name);
}
The event handler functions pull the object name from the event object's target property, and pass this name to the functions defined to hide and show the text content.
Now, when you run the example in Navigator 4.0, it processes the DHTML effect using the Navigator 4.x-specific implementations, which means that the content associated with the button is displayed and the background color is changed to reflect the color of the button. One difference that remains is that the font color is not changed as it is with IE 5.0 and Mozilla (Navigator 5.0), because Navigator 4.0 does not support modification of CSS properties after an element is displayed.
Changing the font color is not critical to the exampleno functionality is lost due to this effect not being implemented. Thus you can consider the example to work successfully with IE 4.x and IE 5.x, as well as Navigator 4.x and Navigator 5.x. It works across browsers, versions, and DOMsas tested on certain platforms. In this case, the IE 4.x and up tests occurred on Windows NT 4.0, Windows 98, and Windows 2000. The Navigator 4.x code was tested in Windows NT and Linux, and the Mozilla Milestone 10 (Navigator 5.0) test was performed in Windows NT, only.
You can try out the cross-DOM example, or view a text page containing the example page. You can also download the complete example. The page and example has passed the HTML 4.0 Transitional validity test.
It seems as if we have barely started using DHTML and now we are going through another evolution (revolution?) in DHTML techniques. However, hiding implementation details using scripting objects has worked in the past, and continues to work going into the future. The most difficult aspect of modernizing pages with this approach will be the normalization process necessary to provide support for the newest HTML and DOM standards.
The normalization process and using cross-DOM techniques will also be necessary to support DHTML in Navigator 5.0, if the decision not to support proprietary Navigator 4.0 elements and code moves from the Mozilla open source effort into Navigator 5.0. Not providing backwards compatibility is a bit of a gamble for the Mozilla/Netscape Navigator folks, and will definitely mean work for those of us who use DHTML at our Web sites. It will be interesting to see the reaction to this "time to bite the bullet and update your Web sites" approach as time goes on and Navigator 5.0 is released.