In-Depth
Simplify Web Development with Visual Studio 6.0
If you want to build the flashiest, most dynamic site possible and reach the largest possible audience, read on
by Eric Krock
In the Web-wide war for eyeball hours, the fastest, flashiest site will have the edge. That means bringing your Web pages to life with JavaScript and Dynamic HTML. It also means facing the fact that Microsoft JScript and Netscape JavaScript are not fully compatible with each other. Navigator 4 and Internet Explorer 4 also have different Document Object Models (DOMs). Because it will be a long time before production versions of the Mozilla browser and Internet Explorer 5 are released and installed by the majority of Web users, developers will probably want to support Nav4 and IE4 until the Millenium Bug strikes and we're all drafted to become COBOL programmers.
Can you afford to write for just one browser or the other? No way. Would you pay for a television commercial that could be seen only on wide screen TVs? Of course not. If you build a Web site that only works for one browser or the other (or a particular platform) you automatically lock out a third or more of your potential customers. If you don't mind raising your per-impression development costs by half or cutting your advertising revenue and sales by a third, stop reading now. If you want to build the most dynamic site possible and reach the largest possible audience, read on!
How do you write for both? That's the number one question among Web builders today. The secret to site-building success is to write cross-browser JavaScript and Dynamic HTML which gives you the most dynamic pages for the latest browsers, yet gracefully degrades for users with older browsers. Compared to the lowest common denominator approach that many people follow for universal access, you'll gain a lot of functionality with very little compromise.
Dynamic HTML (DHTML) is a set of technologies for manipulating HTML elements. Netscape and Microsoft define DHTML differently, but both companies support:
Cascading Style Sheets, level 1 (CSS1) for formatting the elements;
Cascading Style Sheets Positioning (CSSP) for positioning, hiding, showing, and stacking elements in z-order;
A DOM for making the elements and their properties accessible from JavaScript;
An event model for intercepting and handling user events;
The ability to hide the browser's user interface and use the full screen as a DHTML canvas.
If you aren't familiar with these foundation technologies already, read through the free online tutorials and articles listed in the sidebar "Cross-Browser DHTML Resources." Cross-Browser DHTML (XBDHTML) is an informal subset of DHTML that I define as both fully functional on Navigator 4, Internet Explorer 4, and later versions of the browsers; and that degrades to load without errors in older browsers.
Success with CSS
Your first rule of thumb should be to use Cascading Style Sheets for static settings and JavaScript for dynamic or conditional settings. Both Nav4 and IE4 support CSS1 and CSSP markup. If you always want an element to have the same format or to appear in a particular location, you don't need to use JavaScript at all. You can just use static CSS1 and CSSP markup, and this will eliminate the need to deal with cross-browser DOM issues. For example, if you always want the element with identifier (ID attribute value) "foo" to be positioned 100 pixels from the left of the page and 50 pixels down from the top and display its text in red, you can use this CSS markup:
<HTML><HEAD>
<TITLE>Static CSS markup</TITLE>
<STYLE TYPE="text/css">
<!- /* use HTML comment to hide CSS markup from older browsers */
#foo { position:absolute;
left: 100px; top:50px;
color: red; }
--></STYLE>
</HEAD><BODY> <DIV ID="foo">
<P>some text</P></DIV>
</BODY></HTML>
Note that we use HTML comments to hide CSS markup from older browsers. This ensures that users of text-only browsers and non-CSS browsers like Nav3 won't see the CSS markup displayed on their page.
It is also important to know your CSS markup and realize that no browser implements all CSS1 or CSSP features, that there are serious differences in cross-platform support for some features, and that there may be differences in how browsers implement the same CSS feature. For CSS1 development, you can use online checklists to verify feature support across browsers and platforms. I've found I can achieve cross-browser layouts with CSSP that look the same or very similar in both Nav4 and IE4 by using absolute positioning, defining positions in pixels, positioning each element independently, avoiding element nesting, and testing carefully. With those caveats, I consider the CSSP properties position, left, top, width, height, clip, z-index
, and visibility
safe for cross-browser use.
Know Your Browser
Because the DOMs in Nav4 and IE4 are different, you will often need to evaluate different JavaScript code, using different objects, to accomplish the same thing. Thus, detecting the current browser (see Listing 1) and conditionally evaluating the appropriate JavaScript is a key technique for cross-browser scripting success. This code, a simplified version of the Ultimate JavaScript Client Sniffer found at
http://developer.netscape.com/docs/examples/javascript/browser_type.html, will detect the current browser and create an object with Boolean properties to indicate the vendor and version and integer and float properties to indicate the version number. The object is named "is".
Once you have detected the current browser, you can conditionally evaluate the appropriate code for it:
<SCRIPT LANGUAGE="JavaScript1.2"><!--
if (is.nav4up)
{ /* Nav4-and-later code here */
}
else if (is.nav4up)
{ /* IE4-and-later code here */
}
//--></SCRIPT>
Note that we used the properties is.nav4up
and is.ie4up
(rather than is.nav4
and is.ie4
) so that our code will keep working on future releases of Navigator and Internet Explorer.
Some developers advocate using object detection rather than browser detection to determine which code to evaluate. There are certain objects like document.layers that currently exist only in the Nav4 DOM, and others such as document.all that only exist in the IE4 DOM. Instead of using explicit browser vendor and version detection, fans of object detection write their code like this:
<SCRIPT LANGUAGE="JavaScript1.2"><!--
if (document.layers)
{ /* Nav4-and-later code here */
}
else if (document.all)
{ /* IE4-and-later code here */
}
//--></SCRIPT>
If you use object detection, do so with caution for two reasons. First, Netscape has never guaranteed that it won't add the document.all property name (or other property names that happen to exist in the IE DOM) to its browser DOM at some point in the future. If Netscape were to use the document.all property name in a future release, code which used the existence of this property as the only check before running IE-specific code might be evaluated and cause errors. Conversely, the same problem could occur if Microsoft in the future uses property names currently found only in the Netscape DOM.
Second, other browsers such as Opera or Java-based clients may in the future include support for their own DHTML-capable JavaScript DOMs that may be a mixture of objects currently found in the Explorer and Navigator DOMs. Such future clients could fail with errors if they include their own partially compatible implementations of objects that are found in the IE4 or Nav4 DOMs today.
Any code that uses object detection is, in principle, vulnerable to these two problems with future compatibility. Basically, it's a safety versus potential future browser support tradeoff: if you use browser detection, you guarantee that your code will not be run by future unknown browsers and avoid the risk of JavaScript error messages; if you use object detection, your page may be viewable by future browsers which compatibly emulate one DOM or the other, but you accept the risk that such browsers might also fail with error messages.
Use SCRIPT SRC= With Care
The original way to provide backward compatibility with older browsers was to use the LANGUAGE= attribute of the SCRIPT tag to designate which browsers should load your code:
<SCRIPT LANGUAGE="JavaScript">
/* loaded by all JS browsers */
</SCRIPT>
<SCRIPT LANGUAGE="JavaScript1.1>
/* loaded by Nav3+ */
</SCRIPT>
<SCRIPT LANGUAGE="JavaScript1.2>
/* loaded by Nav4+, IE4+ */
</SCRIPT>
Browsers skip SCRIPT tags which have a LANGUAGE= attribute higher than the JavaScript version they support. For example, Nav2 and IE3 support JavaScript 1.0, so they only load inline scripts with LANGUAGE="JavaScript". Navigator 3 supports JavaScript 1.1, so it will load inline SCRIPT tags with LANGUAGE="JavaScript" and LANGUAGE="JavaScript 1.1" but not LANGUAGE="JavaScript1.2". Nav4 and IE4 will load all three.
The LANGUAGE= attribute works fine for inline scripts like the above, but when you start using the SRC= attribute to put your JavaScript in external files, things get more complicated. Putting your JavaScript in external files is, in principle, a good idea because it makes it easy to reuse a single script in many pages and maintain it in one place over time. However, IE3.x doesn't support SRC= at all, so if you want a SCRIPT to be loaded on IE3 (or Nav2, for that matter), you must put the script inline.
Nav3, on the other hand, has a bug which causes it to load external scripts with LANGUAGE="JavaScript1.2", even though it should skip them. This means that a page that is DHTML-enabled and uses external JavaScript 1.2 files may cause error messages if loaded by Nav3. If you want to degrade gracefully in Nav3, there are three ways to work around this problem.
First, put your JavaScript 1.2 script inline (instead of in an external file) to avoid the Nav3 bug completely.
Second, enclose the JavaScript 1.2-specific code in explicit version checks and accept that Nav3 will load the external JavaScript 1.2 file. Version check code looks like this:
if (is.major >= 4) { /* JS 1.2 code here */ }
With this approach, avoid using certain JavaScript 1.2-specific tokens that trigger parser error messages in Nav3 when read in, even though the code is never run. Tokens to avoid include regular expression literals like /\d*/
and the switch statement, but there may be others as well.
Third, have separate paths through your site so that Nav3 users never see or load the DHTML-enabled page in the first place.
You may find it easiest to use a mix of these three techniques on your site. For example, JavaScript 1.2 code that triggers Nav3 parser errors could be placed in an inline script with LANGUAGE="JavaScript1.2"
so Nav3 skips it; JavaScript 1.2 code that does not trigger Nav3 parser errors could be placed in an external file for ease of maintenance and sharing; and you could create a separate area of your site to make some all-DHTML pages inaccessible to Nav3 and earlier browsers.
Conditional Formatting
Now that we've dealt with browser detection and backward compatibility, let's return to the cutting edge. If you want to conditionally format elements in Nav4 and IE4, you'll need to access CSS1 properties from JavaScript through the Document Object Model. Fortunately, it's possible to define CSS1 rules from JavaScript in a way that works on both browsers. Here's how:
Put an empty STYLE element in the document's HEAD with a unique identifier. For example: <STYLE ID="ietssxyz" TYPE="text/css"></STYLE>
.
Immediately after the STYLE element, place a SCRIPT element that contains the JavaScript code for both Nav4 and IE4. This ensures that the CSS1 rules declared in JavaScript are at the same logical location in the document in IE4 and Nav4 and are given the same CSS1 priority.
In IE4, use the addRule method to add CSS1 rules to the empty style sheet (for example: document.styleSheets["ietssxyz"].addRule("P", "color:red"); )
.
In Nav4, use the DOM to define CSS1 rules. For example: document.tags.P.color=
"red";)
.
Define the IE4 and Nav4 CSS1 rules in the same order to make sure each rule gets the same CSS1 priority level on both browsers. In CSS1, rules defined later have higher priority than those that came before.
For a simple example, suppose that we had some online technical documentation that included instructions for both Nav4 and IE4 users, and we wished to show default text in black, text specific to the user's browser in bold red, and text specific to the other browser (for the user's reference only) in grey. Here are the key lines in the HEAD:
<STYLE ID="ietssxyz" TYPE="text/css"> </STYLE>
<SCRIPT LANGUAGE="JavaScript1.2"><!--
if (is.nav4up) {
document.classes.nav4.all.color="red";
document.classes.nav4.all.fontWeight=
"bold";
document.classes.ie4.all.color="gray";
document.classes.ie4.all.fontWeight=
"normal";
} else if (is.ie4up) {
document.styleSheets["ietssxyz"].addRule
(".nav4", "color:gray");
document.styleSheets["ietssxyz"].addRule
(".nav4", "font-weight:normal");
document.styleSheets["ietssxyz"].addRule
(".ie4", "color:red");
document.styleSheets["ietssxyz"].addRule
(".ie4", "font-weight:bold");
}
//--></SCRIPT>
In the document's body, we place elements with Nav4-specific text into class nav4 and elements with IE4-specific text into class ie4:
<P>First, bookmark this page.</P>
<P CLASS="nav4">On Navigator 4, choose Bookmarks/Add Bookmark.</P>
<P CLASS="ie4">On Internet Explorer 4, choose Favorites/
Add to Favorites.</P>
<P>more text ...</P>
For the full HTML document, see Listing 2. Listing 3 gives another example of changing formatting, this time in a scheduling application.
To make the process of creating such pages even easier, Netscape provides a free tool for automatically generating cross-browser JavaScript code for defining CSS1 rules. The TechNote that explains and includes this code generator can be found at http://developer.netscape.com/docs/technote/dynhtml/css1tojs/css1tojs.html.
A final note: When setting CSS1 properties from JavaScript, be sure to place the JavaScript code (or the link to it) in the document's HEAD so that it is evaluated before the document loads. Setting CSS1 properties from JavaScript after the document loads has no effect in Nav4.
Dynamic Positioning, Conditional Text, and Z-Index Stacking
If you want to dynamically set the position, z-index stacking order, or visibility of elements, you'll need to access CSSP properties from JavaScript through the Document Object Model, which once again differs in Nav4 and IE4. Following are the secrets to bridging the two DOMs.
Name your elements and declare them to be positioned. You must give the element a unique name to identify it when you wish to set its properties, and you must declare it to be a positioned element so it is exposed for manipulation within the Nav4 DOM. This code will name a DIV "foo" and declare it to be a positioned element:
<HTML><HEAD><TITLE>position foo</TITLE>
<STYLE TYPE="text/css"><!-
#foo { position: absolute; }
-></STYLE>
</HEAD><BODY>
<DIV ID="foo"><P>foo's text</P></DIV>
</BODY></HTML>
Practice safe naming. Follow these rules when picking names for your elements:
- The name must be unique (used only once) within the HTML document; if two elements have the same ID, the file is not a valid HTML document.
- The name should be all lowercase, alphanumeric characters.
- The first character should be a letter, and the remaining characters should be letters or numbers.
- Do not use special characters in the name like the hyphen or underscore as that may cause the element to be ignored by Nav4.
Position absolutely, not relatively. Frankly, relative positioning is often confusing and counter-intuitive. I've found it to be much easier to work with absolutely positioned elements.
Access individual elements and set their CSSP properties after the page loads. The JavaScript object that represents a positioned HTML element is not guaranteed to exist until the page has finished loading. Strictly speaking, you can usually set an element's JavaScript properties from within a script that appears later in the page than the element itself, but this technique should be reserved for bug workarounds where there is no alternative. The safest approach is to define your JavaScript function in the HEAD (or in a script linked from the HEAD) and then call it from within the document's onload method, which is guaranteed not to be invoked until the page has finished loading.
There are two ways to create an onload method: you can specify it in the HTML markup, or you can set it from JavaScript. The same HTML markup will work on both browsers:
<BODY ONLOAD="positionElements();">
This causes the function positionElements( )
to be called when the document has finished loading. (Presumably, positionElements( )
was defined in a script in the head and it positions as well as perhaps makes visible for the first time some previously hidden elements.) Since we are supplying a string of literal JavaScript code that will be evaluated when the document finishes loading, we must follow the function name with parentheses and enclose the string in quotes.
If you prefer to define the onload method from within a script, in Nav4 you set the onload property of the document object. In IE4 you set the onload property of the window object. In this case, we are setting JavaScript properties to the function object positionElements instead of making a function call, so we leave out parentheses:
if (is.nav4)
document.onload = positionElements;
else if (is.ie4)
window.onload = positionElements;
Avoid nesting. The CSSP specification gives minimal guidance about how nested elements should be positioned relative to each other. As a result, Nav4 and IE4 often make different guesses about what particular markup means. This is especially true if the elements are relatively positioned. To save time and confusion, avoid nesting completely if possible and use only top-level, absolutely positioned elements. If you do nest elements, never nest more than one level deep.
Dynamically generate optimized HTML markup, if necessary, for each browser. Since IE4 doesn't support the LAYER tag, my default approach for XBDHTML development is to use positioned DIVs, which are supported by both browsers. However, for more sophisticated applications, development is sometimes simplified if you dynamically generate a positioned DIV in IE4 and a LAYER tag on Nav4. Listing 4 is an XBDHTML JavaScript API that includes a function called writeElt( )
that automatically generates and writes out a DIV in IE4 or a LAYER tag in Nav4. For example, to create a positioned element called "foo" as a DIV in IE4 or a LAYER in Nav4, you would insert this function call into the body of your HTML page:
<SCRIPT LANGUAGE="JavaScript1.2"><!--
writeElt ("foo", "text");
//--></SCRIPT>
In IE4, this would write out the markup:
<DIV ID="foo" STYLE="position:absolute;
overflow:none;">text</DIV>
In Nav4, it would write out the markup:
<LAYER ID="foo">text</LAYER>
The writeElt( )
function is also documented online and downloadable from Netscape's site at http://developer.netscape.com/docs/technote/dynhtml/csspapi/csspapi.html.
Use a cross-browser DHTML API to insulate your application code from the browser-dependent DOM code. As mentioned before, the Nav4 DOM and the IE4 DOM differ, so you will frequently need to evaluate different JavaScript commands to achieve the same result. Danny Goodman provides an excellent overview of
this issue in his article "CSSP Positioning: The Dynamic DHTML DMZ" at http://developer.netscape.com/news/viewsource/goodman_cssp/goodman_cssp.html. I'll illustrate the problem briefly with two examples.
The first example is the code necessary to get a positioned element's JavaScript object. Because the DOMs are different, you must use different code in Nav4 and IE4. For example, if you have a top-level positioned DOM called "foo", in Nav4 you evaluate this code:
var fooElt = document.layers["foo"];
In IE4 you evaluate this code to do the same thing:
var fooElt = document.all.foo;
If the element is nested, it gets even more interesting. IE4 provides a flattened view of the elements in the document with the document.all collection. Nav4 provides a nested model in which each positioned element has its own document object property which in turn contains all the positioned child elements. So if element "bar" is nested inside element "big", in Nav4 you evaluate this code:
var barElt = document.layers["big"].document.layers["bar"];
And on IE4 we again use the document.all
collection:
var barElt = document.all.bar;
Fortunately, you don't need to hand-code this conditional JavaScript. Listing 4 includes a function called getElt( )
that takes a positioned element's name as its argument and returns the corresponding JavaScript object. If the target element is nested, you pass all the parent element names as arguments, starting with the top-level ancestor element's name and working downwards. So the following code would work in both Nav4 and IE4:
var fooElt = getElt ("foo");
var barElt = getElt ("big", "bar");
The second example involves setting element properties. Once you have retrieved a positioned element's JavaScript object, you often need to evaluate different code to set a given CSSP property as the property names frequently differ in Nav4 and IE4. The property for setting an element's horizontal position is a case in point. In Nav4 you set the left property of the element's JavaScript object; in IE4 you must set the style.pixelLeft property instead. Listing 4 provides a function called setEltLeft(elt, x)
to bridge this difference:
/* Sets position of left edge of elt in pixels. */
function setEltLeft (elt, x) {
if (is.nav4up) elt.left=x;
else if (is.ie4up) elt.style.pixelLeft=x;
}
In the same fashion, Listing 4 provides functions to get and set an element's left, right, z-index, visibility, width, height, clip, background color, and background image properties. You can see these functions in use (albeit unexcitingly) in the API's validation test suite online at http://developer.netscape.com/docs/technote/dynhtml/csspapi/csspapi.html. A more practical example is Netscape's XBDHTML presentation template at http://developer.netscape.com/docs/technote/javascript/prestemp/prestemp.html. This API was, in part, derived from another freeware DHTML API provided by Mike Hall at http://members.aol.com/MHall75819/dhtml/cbdhtml.html.
Beware CSSP property initialization bugs. If you set a positioned element's CSSP properties with static CSSP markup, the element's JavaScript properties will be initialized accordingly, right? Wrong! At least not always. This problem is particularly widespread in IE4; for numerous CSSP properties, if you set the element's property with static CSSP markup and then later try to read the property value from JavaScript, the property value comes back undefined. In Nav4 this generally is not a problem, but Mike Hall reports that it did occur for the width and height properties in some code he wrote. If you encounter this problem, the workaround in both cases is the same: Before you attempt to read the property from JavaScript, first set it once to the same value you specified in the CSSP markup. Thereafter you should be able to read the property value without trouble.
Handling Events Uneventfully
As with the DOM, Nav4 and IE4 provide
different models for event handling. Danny Goodman has again provided an excellent overview of this problem in his article "Dueling Event Models: A Cross-Platform Look" at http://developer.netscape.com/news/viewsource/goodman_events2/goodman_events2.html.
The short take is that Nav4 provides a "top down" model in which events can be intercepted first at the window level or later by positioned elements therein. IE4 provides a "bubble up" model in which events can be intercepted at the element level or thereafter by enclosing parent elements and finally at the window level. In Nav4, the event object is passed as an argument to the event handler function; in IE4, it is a property of the window object. In Nav4, remember to call the document's captureEvents method to register a given event for interception and handling. The good news is that if you intercept events at the level of the window and look in the appropriate place for the event object, you can write event handler code that works on both
browsers.
Netscape's XBDHTML presentation template at http://developer.netscape.com/docs/technote/javascript/prestemp/prestemp.html provides a practical example. This code intercepts the "p" and "n" keypress events and calls functions to display the previous or next slides:
var nextKeys = new String("nN ")
var prevKeys = new String("pP")
function handleKeys(e) {
var keyChar;
if (is.nav4up) keyChar =
String.fromCharCode(e.which);
else if (is.ie4up) keyChar =
String.fromCharCode
(window.event.keyCode);
if (prevKeys.indexOf(keyChar) != -1)
{ prev_slide(); return false }
else if(nextKeys.indexOf(keyChar) != -1)
{ next_slide(); return false }
else return true;
}
if (is.nav4up) document.captureEvents(Event.KEYPRESS);
Use lowercase event handler names. IE4 lacks support for mixed-case JavaScript event handler names such as onMouseOver and onClick, so to ensure cross-browser compatibility, use the less-readable, all-lowercase equivalents such as onmouseover and onclick when setting event handlers from JavaScript.
Handle resize events carefully. Window resize events can cause dynamically positioned elements to lose their state in Nav4. To avoid this problem, make your DHTML window non-resizable, use the sample code at http://developer.netscape.com/docs/examples/dynhtml/resize.html. It automatically reloads the page and restores its original state, or save your elements' positions in a safe place like a session cookie, retrieve the information, and reset the properties when the window is resized.
The Bottom Line
All of us look forward to the day when the World Wide Web Consortium (W3C) DOM is complete, both Netscape Navigator and Microsoft Internet Explorer fully support it, and all Internet users have downloaded their own copy of a fully W3C DOM-compliant, DHTML-capable browser. In the meantime, we've got Web sites and applications to build. Fortunately, if you plan from the beginning to support both browsers and study the DOMs, event models, and idiosyncrasies of each, it's possible to build DHTML applications that run in both browsers, support multiple platforms, and degrade gracefully. If cross-browser DHTML ever seems like a pain, just remember, you could be debugging COBOL code in a year's time!
Eric Krock is a Technology Evangelist for Netscape. His specialties include Java, JavaScript, Dynamic HTML, and Netscape's CommerceXpert and Application Server products. Prior to joining Netscape, Eric worked for Interleaf corporation for 4 years in Tokyo, and graduated from Stanford University with a B.S. in Computer Science and a B.A. in Japanese. The opinions expressed in this article are the author's own and do not necessarily represent Netscape Communications. He can be reached at [email protected].