Web Developer 2

More DHTML

OK, so we've figured out how to reference objects on the page, but have we really gained access to everything about all of the objects?  The sad answer is not really.  In fact, at this point we've only referenced a single style sheet property for a single layer.  If we want to access as many properties as possible, we've got some more work to do.

Before we get to the work ahead, we've got to distinguish between two types of properties: style sheet properties and html properties.  Conceptually it is pretty simple.  Style sheet properties are those style rules that we can include as part of the style attribute in any html tag.  We've used the left and top properties already and manipulated them via JavaScript.  Html properties are all of the other attributes for a given html tag.  They include things like value, width, and src.  We also did some work with html properties when we explored the JavaScript object model and when we did image rollovers.

As we've seen, when we access the style sheet properties we'll use something like this:

eval(pre + 'layerName' + post);

where pre and post mask the particular strings necessary for a given browser.  That's fine for accessing the style sheet properties but what about when we want to access the html properties?  Well, we'll have to include some more masking variables to accomplish this.  The three major browsers (once again!) access the html properties a little bit differently.  So we'll need to modify our browser masking scheme to allow us access to the html properties in a cross-browser fashion.  Along the way, we'll find that Netscape 4's DOM won't allow us access to many of the properties.  We'll just have to make do and hope our code degrades gracefully!

Always By Example

Let's start out with a simple HTML page:

<html>
<head>
<title>Expose the DOM</title>
</head>
<body>
<div id="text1" style="position: absolute; font: 16pt sans-serif; top: 10; left: 10; color: red; z-index: 1;">This is some text</div>
<div id="text2" style="position: absolute; font: 14pt sans-serif; top: 30; left: 10; color: blue; z-index: 2;">Here is an image:</div>
<div id="image1" style="position: absolute; top: 50; left: 10; z-index: 3;" height="64" width="64">
<img id="pic1" border="0" src="images/bubbles.gif" width="64" height="64"></div>
</body>
</html>

Before we begin, please note that our image - bubbles.gif - is contained within the image1 layer.  In DHTML terms, the image is nested inside the layer.  This will present some special problems for Netscape 4 but we'll investigate that a little later on.  OK, now let's write our script to expose the html  and style sheet properties:

<script language="JavaScript">
//Netscape 6
if(document.getElementById){
pre = 'document.getElementById("';
post = '").style';
}
//Netscape 4
if(document.layers){
pre = 'document.';
post = '';
}
//IE
if(document.all){
pre = 'document.all.';
post = '.style';
}
function begin(){
var styleObj1 = eval(pre + 'text1' + post);
var styleObj2 = eval(pre + 'text2' + post);
var styleObj3 = eval(pre + 'image1' + post);
var htmlObj1 = eval(pre + 'text1');
var htmlObj2 = eval(pre + 'text2');
var htmlObj3 = eval(pre + 'image1');
var htmlObj4 = eval(pre + 'pic1');
alert(styleObj1.left);
alert(styleObj2.color);
alert(styleObj3.zIndex);
alert(htmlObj1.id);
alert(htmlObj2.width);
alert(htmlObj3.height);
alert(htmlObj4.src);
}
</script>

View the sample here.

Depending on which browser you're using, you'll be greeted by a number of message boxes indicating the value of the given property.  IE 5 generates the correct values at this point (We didn't assign a width to the text2 layer so it really is undefined.).  Netscape 4 generates some of the properties but others are undefined.  Netscape 6 doesn't display any alert boxes but it correctly renders the page.  What a mess!

Of course, the problem is the same problem DHTML has always had.  We aren't referencing the objects on the page correctly.  We'll need to modify our script to mask a few more variables:

<script language="JavaScript">
//Netscape 6
if(document.getElementById){
pre = 'document.getElementById("';
post = '").style';
htm = '")';
}
//Netscape 4
if(document.layers){
pre = 'document.';
post = '';
htm = '';
}
//IE
if(document.all){
pre = 'document.all.';
post = '.style';
htm = '';
}
function begin(){
var styleObj1 = eval(pre + 'text1' + post);
var styleObj2 = eval(pre + 'text2' + post);
var styleObj3 = eval(pre + 'image1' + post);
var styleObj4 = eval(pre + 'pic1' + post);
var htmlObj1 = eval(pre + 'text1' + htm);
var htmlObj2 = eval(pre + 'text2' + htm);
var htmlObj3 = eval(pre + 'image1' + htm);
var htmlObj4 = eval(pre + 'pic1' + htm);
alert(styleObj1.left);
alert(styleObj2.color);
alert(styleObj3.zIndex);
alert(styleObj4.width);
alert(htmlObj1.id);
alert(htmlObj2.width);
alert(htmlObj3.height);
alert(htmlObj4.src);
}
</script>

All we've done is add another masking variable to handle html properties and then reference it when we create an html properties object.  This allows us to access the html properties of the elements on the page.  At this point, IE 5 still displays the properties correctly.  Netscape 4 displays some of the properties and then quits when it hits styleObj4.  The undefined properties for Netscape 4 are the width and height attributes for the text layers, the color property, and the src attribute for the <img> element.  Now we know we can access the src attribute of an <img> element - we've done it before!  So there is still some reference problem we'll need to solve for Netscape 4.  Netscape 6 correctly displays all of the properties except for the height and width of the text layers.  I've got bad news - neither Netscape DOM exposes these two properties for the <div> element.  Netscape 6 does, however allow access to the style object's width property but Netscape 4 doesn't.

The problem with referencing the pic1 layer in Netscape 4 is that the image is nested inside the image1 layer.  Netscape 4's DOM treats each layer in the document as if it were the window object.  So, to reference the pic1 image using only the Netscape 4 DOM you might write:

document.image1.document.pic1

In the Netscape 4 DOM, the image1 layer gets its own document object which contains the pic1 <img> element.  The result is that if we want to support Netscape 4 (and at 10% of the market as of this writing we should) then we'll need yet another browser masking variable.  Let's update our script:

<script language="JavaScript">
//Netscape 6
if(document.getElementById){
pre = 'document.getElementById("';
post = '").style';
htm = '")';
nest = '';
}
//Netscape 4
if(document.layers){
pre = 'document.';
post = '';
htm = '';
nest = '.document.';
}
//IE
if(document.all){
pre = 'document.all.';
post = '.style';
htm = '';
nest = '';
}
function begin(){
var styleObj1 = eval(pre + 'text1' + post);
var styleObj2 = eval(pre + 'text2' + post);
var styleObj3 = eval(pre + 'image1' + post);
var styleObj4 = eval(pre + 'pic1' + post);
var htmlObj1 = eval(pre + 'text1' + htm);
var htmlObj2 = eval(pre + 'text2' + htm);
var htmlObj3 = eval(pre + 'image1' + htm);
var htmlObj4 = eval(pre + 'pic1' + htm);
if(document.layers){
styleObj4 = eval(pre + 'image1' + nest + 'pic1' + post);
htmlObj4 = eval(pre + 'image1' + nest + 'pic1' + htm);
}
alert(htmlObj4);
alert(styleObj1.left);
alert(styleObj2.color);
alert(styleObj3.zIndex);
alert(styleObj4.width);
alert(htmlObj1.id);
alert(htmlObj2.width);
alert(htmlObj3.height);
alert(htmlObj4.src);
}
</script>

In addition, we'll have to add a name attribute to our <img> element.  Netscape 4 doesn't support the id attribute for the <img> element and Netscape 6 doesn't support the name attribute.  Interestingly enough, IE 5 supports both and is perfectly happy with either the name or id attribute or even both!  Take care to use the same value for the name attribute as you do for the id attribute or the differing browsers will try to access different elements:

<img name="pic1" id="pic1" border="0" src="images/bubbles.gif" width="64" height="64">

View the example here.

Note that even though we are masking the DOM, we still require logic forking in our code.  You'll need to get used to that.  Since vendors decide how much of any given specification they will adhere to, we, as developers, must figure out which browser the client is using before actually doing anything.  This is the major downside to DHTML.

Things are changing however.  Both Microsoft and Netscape have pledged to adhere to the Worldwide Web Consortium (W3C) standards and in IE 5.5 and Netscape 6 that is largely true.  The problem is that now there are three distinct browsers that we must target.  IE 5 and Netscape 6 are converging toward the W3C standards and development targeted at those two browsers will be much easier if Netscape 4 support is not needed.  As of this writing, as stated previously, Netscape 4 represents around 10% of the market share.  Netscape 6, on the other hand, represents only one half of 1%.  Until Netscape 6 becomes more prevalent among loyal Netscape users, we will be stuck with all those complex conditionals in our code.

A General Strategy

So what are we to do?  I've developed a design philosophy over the past few years that seems to work for me:

We'll see that although IE and Netscape 6 can be considered cousins, Netscape 6 and Netscape 4 can no longer even be considered the same animal!  And yes, there will be plenty of times when the code that works for IE doesn't work for Netscape 6.  We'll need to fork our logic there as well to create truly cross-browser code.  Remember, our goal is to write a single script for any given page that will work on all three major browsers.

I suppose an argument can be made that we should be writing different scripts for each of the browsers and then serving up the proper script based on the client's browser.  I've found that to be unproductive given the fact that although it doesn't appear so, the browsers actually share a lot of common traits.  Think about all the JavaScript we've written thus far.  We haven't had to sniff the browser yet!

Well, we're well on our way to writing web applications rather than web pages.  With DHTML, we'll be moving from a static, content-based medium to an interactive medium whereby user's can truly experience our content.  Are you ready?