Web Developer 2

Intro to Dynamic HTML

Until very recently, web pages that contain only HTML code were the norm.  They functioned very much like the pages in a book: you read what was on the page then turned to the next page (or clicked on a link, if you like) to continue reading.  There is, of course, a downside to the use of these static HTML pages.  Each time you try to access the next bit of information, a request must go out over the wire to fetch the document and your users must wait for that request to be fulfilled.  Since most of the world is surfing along at 56kbps (plain old telephone service - POTS) that request can take an agonizing amount of time.

When Netscape introduced layers that could be shown or hidden on demand in Netscape 4, the static web began to disappear.  Developers could embed any number of layers - or "pages" - into a single page and toggle the visibility of any given layer based on user interaction.  The result was instantaneous navigation because the layer had already been downloaded!  The web began to move from the read and wait stage to a read and participate stage.

The technology that makes this concept a reality is Dynamic HTML (DHTML).  DHTML is actually a hybrid technology consisting of HTML, CSS, a scripting language, and the Document Object Model (DOM).  We've already covered HTML and CSS.  We've also delved into JavaScript - the only cross browser scripting language.  So let's focus on the DOM.

The Document Object Model

To put it as simply as possible, the DOM defines how HTML objects like text or graphics are exposed to the scripting language.  Even more simply, the DOM controls how the scripting language is used to change the attributes of HTML elements.  We've actually used the DOM extensively when we did image rollovers.  We found that we could take an <img> element and manipulate its src attribute using JavaScript.

The good news is that the DOM is very much like the object model we've been using for JavaScript all along.  It contains a window object, a document object, a frames object (for frames based pages), and a forms object.  All of these objects contain other objects and/or properties.  For example, the document object contains an image object for each <img> element on the page.

If all this sounds familiar, it should.  The DOM and JavaScript very much go hand in hand.  So what's all the fuss about?  Well, the DOM actually enables developers to create their own own elements or objects.  That doesn't mean that you can start writing your own HTML tags.  If you'd like to do that you'll have to wait a little until we cover XML.  What it does mean is that you can use the <div> element to create objects that you are free to manipulate.  Inside these <div> elements, you can attach CSS styles and then modify the style properties on the fly.  And that's where the real power lies.

Dueling DOMs

The bad news (of course you knew there had to be some bad news!) is that each of the major browsers implements a different DOM.  From a developer's perspective, this is a real pain.  Remember, the DOM defines how to control the elements on a page.  It also defines the possible ways to manipulate the elements.  Sadly, the DOM for Netscape 4 is different than that of Internet Explorer 4 which is close but not identical to Internet Explorer 5.  To make matters worse, Netscape has just released Netscape 6 which has a different DOM than Netscape 4, IE 4, and IE 5.

Netscape 4's DOM is extensive but it is limited by the fact that manipulating objects must be done prior to the page loading.  In fact, the only style attributes that are updateable on the fly are position, visibility, and clipping.  If you want to change any other style property or object attribute, you must first reload the page.  This is the main reason why developers find the Netscape 4 DOM so limiting.

Internet Explorer's DOM, on the other hand, exposes all objects on the page to scripting.  This includes any HTML attributes as well as style attributes.  Further, you can attach an event to any object on the page that calls a script.  A perfect example is our very first image rollover project.  In that project, we had an image that we wanted to change whenever the mouse moved over it.  Since Netscape's DOM doesn't support events for the <img> element, we had to wrap the <img> element inside an <a> element.  In Netscape's DOM, the <a> element is exposed to the scripting language.  If we knew that our target audience would only use IE, we could have skipped the <a> element altogether and done something like this:

<img src="img1.gif" onMouseOver="on()" onMouseOut="off()">

The above code works beautifully in IE4+ but fails miserably in Netscape 4.  The reason, of course is the differing DOMs.

The World Wide Web Consortium has stepped in to create a standard DOM that is platform and language neutral.  It's DOM closely resembles that of IE 5 but is more specific to the newly released Netscape 6.  Netscape 6 is being advertised as the only standards compliant browser and, to some extent, that's largely true.  Future browsers are almost certain to implement the W3C DOM.  The hope is that developers will be able to write a single script that works identically in any compliant browser.  The problem, of course is that IE represents around 90% of the market and it is not yet completely compliant with the W3C DOM.  Not to mention the fact that of the 10% remaining of the browser market, 9% belongs to Netscape 4.  So, for the foreseeable future, DHTML will necessarily need to determine which browser is being used before executing any scripts.

Cross Browser DHTML

The focus of all the lessons we'll cover with DHTML will be to write a single script on a single page that works identically in all browsers.  As you'll soon see, getting browsers to recognize standards will help immensely.  Getting users to upgrade their browsers, however, will not be so easy.  It will happen, but until we see more than 95% of users browsing with standards based browsers, we'll have to accommodate everyone.

So, how do we write cross browser DHTML?  The key lies in determining which browser is being used.  Once we've figured that part out, we'll be able to write conditionals that work for whichever browser is being used.  The most frustrating part of writing cross browser DHTML is the fact that each DOM refers to objects differently.  What we'll do, after determining which browser is being used is mask, or hide, the different references.

Browser Sniffing

Browser Sniffing is what web developers call checking a user's browser.  There are a number of ways to sniff a client's browser.  The conventional way is to make use of the window.navigator object and then query the properties of the navigator object.  You might write something like this:

browserName = navigator.appName;
browserVersion = parseInt(navigator.appVersion);
if(browserName=='Netscape' && browserVersion==4) ver = 'n4';
if(browserName=='Microsoft Internet Explorer' && browserVersion==4) ver = 'e4';

Then you could write some script like this:

if(ver=='n4')
do some Netscape stuff
if(ver=='e4')
do some IE stuff

That will work fine for most scripts today but it has the potential to fail miserably in the future.  What happens when ver is undefined?  You'll have to go back  and rewrite all of your scripts to handle any new versions that come out.  The point is that this conventional sniffer isn't forward compatible.  It can only function based on what is available today but it can't function based on what might become available tomorrow.

A better way to sniff the client's browser is through a process called object detection.  With object detection, we aren't really concerned with the browser's name or version but with whether or not the DOM of the browser supports a given object.  The best way to demonstrate this is by example:

if(document.layers)
do some Netscape 4 stuff
if(document.getElementById)
do some Netscape 6 stuff
if(document.all)
do some IE stuff

In the above code, our script queries the DOM to see if it supports the given object.  The layers object is specific to Netscape 4 and the all object is specific to IE.  We'll soon see that the getElementById object needs further study as IE 5 now supports it along with Netscape 6.  For now, though, think of using getElementById when referring to Netscape 6.

Masking the DOM

As stated before, the most frustrating part of DHTML is that the different browsers refer to objects differently.  The best way to show this annoyance is by example.  Suppose we start with a simple page with one layer on it:

<html>
<head>
<title>Masking the DOM</title>
</head>
<body>
<div id="main" style="position: absolute; top: 0; left: 0; color: red; font: 36pt Arial, sans-serif;">
Masking the DOM
</div>
</body>
</html>

Now, let's play around with the style attributes of the main layer.  Right now, we see that the layer is positioned absolutely in the very top left corner of the page.  Let's see if we can move the layer to some new coordinates before the page loads.  Insert the following script inside the <head> element:

function begin(){
if(document.layers){
document.main.top = 200;
document.main.left = 200;
}
if(document.getElementById){
document.getElementById('main').style.top = 200;
document.getElementById('main').style.left = 200;
}
if(document.all){
document.all.main.style.top = 200;
document.all.main.style.left = 200;
}
}

Then attach the begin() function to the <body> element's onLoad event:

<body onLoad="begin()">

When you view the page, the layer's top left corner appears at coordinate (200,200).  This simple script works in any 4 level browser or higher.  It also illustrates the frustration inherent in DHTML.  Let's analyze it a little more.  The first line of the begin() function asks the browser if it supports the layers object.  Since Netscape 4 is the only browser that does support the layers object, we are safe in assuming that if the browser answers yes we can write Netscape-specific code.  And that's what the next two lines do.  Notice that Netscape 4 uses the JavaScript object tree to access the main layer.  The main layer is a child of the document object and we simply assign values to the top and left properties.

The fifth line of the begin() function queries the browser for the getElementById method.  Currently both Netscape 6 and IE 5 support the use of getElementById().  If either of those browsers is used, the next two lines of code will be executed.  Notice that getElementById() takes a String representation of the layer's id attribute as an argument to identify the layer.  It then uses the the style object of the layer to access the top and left properties.

The ninth line queries the browser for support of the all object.  The all object is an IE proprietary object.  It is only supported by IE 4+.  Interestingly, IE 5 supports both the all object and the getElementById() method so if IE 5 is the browser, both conditionals get executed.  This will definitely present a problem for us later on but for now, don't worry about it!  Notice that IE also uses the style object to access the top and left properties.

You may have noticed what could be the most glaring omission in the release of Netscape 6.  If you understand that IE 5 will execute the last two conditionals, you recognize that IE 5 is backward compatible with IE 4.  What that really means is that code written specifically for IE 4 will run just fine under IE 5.  The same cannot be said for Netscape 6.  Code written specifically for Netscape 4 will fail miserably under Netscape 6 as it does not support the layers object.  That doesn't seem to make sense but it is a fact.

The other thing you may have noticed is the fact that the middle conditional - getElementById - is executed by both IE 5 and Netscape 6.  This is an exciting development in the world of DHTML.  It means that we are getting closer to a standard way of coding web pages whereby developers can write a single script that functions identically in any standards-compliant browser.  Should both browsers adhere to the standard, DHTML will become the technology of choice for web page design.

OK, enough spouting off at the mouth!  Our script works nicely in all three major browsers but let's face it, calling it a simple script would be being very nice.  We couldn't get much simpler!  Still, we wrote 12 or so lines of code to reference a single layer.  What happens when our page contains 10 or 12 or even 20 layers?  You can see that handling all of those conditionals every time we need to access a layer could be a problem.  Fear not!  There is a solution!

The solution is to hide the references in variables that are set using object detection.  This process of hiding is called "masking the DOM" and it simply uses intermediary variables to avoid all that logic branching.  Let's modify our script so that it looks like this:

//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(){
eval(pre + 'main' + post).top = 200;
eval(pre + 'main' + post).left = 200;
}

 

What this code does is free us from having to manage referring to objects based on which browser is being used.  We set up two variables, pre and post, and assign Strings to them based on how the browser refers to objects.  We only need to do this once.  We then make use of the eval() method to take our string representation of the object and turn it into a valid JavaScript object.  By doing this, we eliminate the headaches associated with object references.  We can refer to any object in any browser simply by wrapping up the masking variables along with the layer name inside the eval() method.  Cool!  View the sample here.

Well, we're well on our way to creating some cool DHTML effects on our pages.  The next few lessons will expose some of the things you can do with DHTML including layer animation and user interface design.  You'll see that your pages no longer need to be static and that you can design not only a web page but a web application.