Team LiB
Previous Section Next Section

Standards-Based DHTML

It would seem that for true DHTML, we need to employ browsers in which CSS, DOM, and (X)HTML standards are actually well supported. While complete support for CSS1, CSS2, DOM1, and DOM2 cannot be found in all browsers, more often than not there is sufficient support to permit most DHTML applications you can think of using the standard rather than relying on the ideas presented in the preceding section.

One of the most fundamental tasks in DHTML is to define a region of the page whose appearance or content you wish to manipulate. In standards-based browsers, this is easy: just about any tag such as <<p>>, <<h1>>, or <<pre>> can be used. However, these tags come with a predefined meaning and rendering in most browsers, so (X)HTML provides two generic tags that have no default rendering or meaning: <<div>> (the generic block-level element) and <<span>> (the generic inline element). Note that in most of the examples we will stick with the <<div>> tag since its support with CSS and JavaScript tends to be the most consistent across browser versions.

Style Object Basics

The appearance of content areas defined by <<div>> or other tags for that matter is best manipulated via the object’s style property (corresponding to the contents of the style attribute for the element). The Style object found in this property exposes the CSS attributes for that object, enabling control of the content’s visual characteristics such as font, color, and size. For a full list of Style properties, see Chapter 10 or Appendix B.

Consider the following simple text-based rollover effect:

<<a href="http://www.google.com" onmouseover="this.style.fontWeight='bold';" 
onmouseout="this.style.fontWeight='normal';">>Mouse over me!<</a>>

When the user mouses over the link, the font is switched to bold (the equivalent of a font-weight: bold CSS binding), and the font is switched back on mouseout. This is similar to the ideas from the section on rollovers, but rather than changing the source of the image, we instead change the CSS properties of the object. We could, of course, set an arbitrary CSS property if we follow the convention of taking the CSS property name and removing the dash and upper-casing the initial letter of merged words to get its JavaScript/DOM property. So given the CSS property, font-style in JavaScript becomes fontStyle, background-image becomes backgroundImage, font-size becomes fontSize, and so on.

To illustrate broad-based appearance changes, the following example will change the appearance of a region when it is clicked.

<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>
<<html xmlns="http://www.w3.org/1999/xhtml">>
<<head>>
<<title>>Standards-based DHTML<</title>>
<<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />>
<<script language="JavaScript" type="text/javascript">>
<<!--
var prevObj;  // So we can revert the style of the previously clicked element
function handleClick(e) 
{
  if (!e) 
   var e = window.event;
  // e gives access to the event in all browsers

  // If they previously clicked, switch that element back to normal
  if (prevObj)
   {     
    switchAppearance(prevObj);
   }
  if (e.target)  // DOM
  {
   prevObj = e.target;
   switchAppearance(e.target);    
  }
 else if (e.srcElement)  // IE
  {
   prevObj = e.srcElement;
   switchAppearance(e.srcElement);   
  }
}

function switchAppearance(obj) 
{
  obj.style.backgroundColor = ((obj.style.backgroundColor == "lightblue") ?
                               ("") : ("lightblue"));
  // IE can't handle a value of inherit so pass it a blank value
  // Avoid messing with the border around form fields
  if (obj.tagName.toLowerCase() != "input") 
  {
    if (obj.style.borderStyle.indexOf("solid") != -1) 
        {
          obj.style.borderStyle = "none";
          obj.style.borderWidth = "0px";
        } 
       else 
        {
          obj.style.borderStyle = "solid";
          obj.style.borderWidth = "1px";
        }
   }
}
// Register DOM style events
if (document.addEventListener)
  document.addEventListener("click", handleClick, true);
// Register IE style events
if (document.attachEvent)
  document.attachEvent("onclick",handleClick);

//-->>
<</script>>
<</head>>
<<body>>
<<h2>>Click anywhere on the page to see the content regions!<</h2>>
<<br />><<br />>
<<p style="float: left;">>Some content that floats to the left.<</p>>
<<p style="float: right;clear: none;">>Some content that floats to the right.<</p>>
<<br clear="all"/>><<hr />>
<<form action="#" method="get">>
Here's a form!<<br />>
<<input type="text" />><<br />>
<<input type="text" />><<br />>
<<input type="text" />><<br />>
<</form>>
<<p>>And another paragraph!<</p>>
<</body>>
<</html>>
Note 

To make the example work in Internet Explorer 6, we had to employ the cross-platform event capture ideas presented in Chapter 11 since this browser does not support the DOM Level 2 style of event listeners.

This example changes the background color and border of the content regions on the screen defined by the (X)HTML. The example is not just useful in that it shows style changes with events, but it illustrates that markup and CSS structure are inherent in any document. A sample rendering in the Mozilla browser after clicking the form is shown in Figure 15-4.

Click To expand
Figure 15-4: DHTML in standards-supporting browsers requires knowledge of CSS.

The previous example illustrates two very important points. The first observation is that to employ DHTML to manipulate the appearance of pages requires an intimate knowledge of CSS. Otherwise, you’re limited to manipulating elements’ (X)HTML attributes rather than their Style objects. The second observation is that properties of Style objects contain CSS values, and these values might not be what you’d expect. To illustrate, consider the following JavaScript:

<<p id="mypara">>Oy.<</p>>
<<script type="text/javascript">>
document.getElementById("mypara").style.borderWidth = 3;
alert(document.getElementById("mypara").style.borderWidth);
<</script>>

The results under Internet Explorer and Mozilla might surprise you:

First, notice that we assigned the border width with a numeric value without specifying any units. In the case of border-width we should have specified the units directly and passed in a string value rather than employing implicit type conversion, like so:

document.getElementById("mypara").style.borderWidth = "3px";

Even more interesting is that you see under Mozilla-based browsers how border-width actually is shorthand for the four sides of the border, thus it shows four values. As you can see when you set a property of the Style object, the value is parsed as if it appeared in a style sheet. Thus, the browser generally fills in any missing or implied CSS rules (such as units like “px”) you might have omitted or it may just simply ignore the value in some cases. Intimate knowledge of CSS really is required, but in case your CSS is a little rusty, you might follow these best practices for manipulating Style properties to help stay out of trouble:

  • Do not use Style properties to store state if possible. For example, if you want to keep track that you’ve set a background to red, use a separate variable (possibly an instance property of the Style) instead of inferring state from style.color. Doing so will save you from the headaches of dealing with unexpected values filled in by the browser.

  • If you must examine Style properties, do so using substrings and/or regular expressions rather than direct comparisons with operators like ==. Doing so reduces type-conversion errors and problems related to properties whose implied values are filled in by the browser.

  • Set Style properties as strings, and always be as specific as possible. For example, instead of using style.borderWidth = 2, use style.borderWidth = "2px". This will reduce the risk of error and increase compatibility with less forgiving CSS-JavaScript implementations.

Effective Style Using Classes

Setting a large number of Style properties dynamically can be tiresome and error prone. A better technique is to bind the CSS properties you want to a class, and then swap an object’s class dynamically. The following example illustrates the technique by mirroring the value of the class attribute from any <<div>> the user mouses over into a target content region:

<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>
<<html xmlns="http://www.w3.org/1999/xhtml">>
<<head>>
<<title>>Changing Classes<</title>>
<<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />>
<<style type="text/css">>
#mirror {border-style: solid; border-width: 1px; width: 100%;}
#theStyles {border-style: dashed; border-width: 1px; width: 80%; padding: 5%;}
h1 {text-align: center;}

/* the classes to swap */
.big {font-size: 48pt;}
.small {font-size: 8pt;}
.important {text-decoration: underline; font-weight: bold;}
.annoying {background-color: yellow; color: red;}
<</style>>
<<script type="text/javascript">>
<<!--
function changeClass(whichClass) 
{
  document.getElementById("mirror").className = whichClass;
}
//-->>
<</script>>
<</head>>
<<body>>
<<h1>>Result<</h1>>
<<div id="mirror">>Mouse over any of the text below and watch this text mirror
  its CSS properties.<</div>>
<<br />><<br />>
<<h1>>Styles to Test<</h1>>
<<div id="theStyles">>
 <<div onmouseover="changeClass(this.className)" class="big">>
  This text is big!
 <</div>>
<<hr />>
 <<div onmouseover="changeClass(this.className)" class="small">>
  This text is small!
 <</div>>
<<hr />>
 <<div onmouseover="changeClass(this.className)" class="important">>
  This text is important!
 <</div>>
<<hr />>
 <<div onmouseover="changeClass(this.className)" class="annoying">>
   This text is annoying!
 <</div>>
<</div>>
<</body>>
<</html>>

Notice how, in the preceding example, we used className to access the (X)HTML class attribute. We must do so because “class” is a reserved word in JavaScript, and we need to therefore avoid using that identifier whenever we can.

Computed Styles

One subtlety of the style property of document objects is that it represents only the inline style applied to that element. Inline styles are those specified using the style (X)HTML attribute. As a result, there’s no guarantee that the values accessed this way represent the style ultimately displayed by the browser. For example:

<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>
<<html xmlns="http://www.w3.org/1999/xhtml">>
<<head>>
<<title>>What's my style?<</title>>
<<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />>
<<style type="text/css">>
  p { text-decoration: underline !important }
<</style>>
<</head>>
<<body>>
<<p id="para">>This text always appears underlined, even when we try to override it
 by setting its inline style<</p>>
<<script type="text/javascript">>
 document.getElementById("para").style.textDecoration = "none";
 alert(document.getElementById("para").style.textDecoration);
<</script>>
<</body>>
<</html>>

As you can see in Figure 15-5, the text remains underlined even though we’ve set the inline style property to “none.” The reason is that there is a CSS rule in the document-wide style sheet that overrides the inline setting using !important. However, alerting the style value clearly shows that the value for textDecoration is none, which is somewhat confusing.

Click To expand
Figure 15-5: Computed style and actual style may vary

Getting the actual style applied to an object can be tricky. In DOM2-compliant browsers, you can use the getComputedStyle() method of the document’s default view. A document’s default view is its default representation in the Web browser, that is, its appearance once all style rules have been applied. The getComputedStyle() method takes two arguments: a node for which style should be gotten and the pseudo-element (e.g., “:hover”) of interest (or the empty string for the normal appearance). You might get the style of the paragraph in the previous example with

var p = document.getElementById("para");
var finalStyle = document.defaultView.getComputedStyle(p, "");

To examine individual properties, use the getPropertyValue() method, which takes a string indicating the property of interest:

alert("The paragraph's actual text decoration is: " + 
      finalStyle.getPropertyValue("text-decoration"));

Unfortunately, as you get into the more esoteric aspects of DOM2, browser support varies significantly from vendor to vendor. Even worse, under IE6 and earlier you won’t find support for this approach but instead will be required to use currentStyle to calculate an object’s current property values. We present an example that works both with the proprietary and DOM syntax here.

<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>
<<html xmlns="http://www.w3.org/1999/xhtml">>
<<head>>
<<title>>What's my style? Take 2<</title>>
<<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />>
<<style type="text/css">>
  p { text-decoration: underline !important }
<</style>>
<</head>>
<<body>>
<<p id="para">>This text always appears underlined, even when we try to override it 
by setting its inline style<</p>>
<<script type="text/javascript">>
 document.getElementById("para").style.textDecoration = "none";
 alert("The paragraph's defined text decoration is: "+ 
        document.getElementById("para").style.textDecoration);
 var p = document.getElementById("para");
 if (p.currentStyle)
   alert("The paragraph's actual text decoration is: " + 
   p.currentStyle.textDecoration);
 else
   {
      var finalStyle = document.defaultView.getComputedStyle(p, ""); 
      alert("The paragraph's actual text decoration is: " + 
      finalStyle.getPropertyValue("text-decoration"));
   }
<</script>>
<</body>>
<</html>>
Note 

Even when computed styles are implemented, you may find that browsers have somewhat limited implementation and not all styles defined by CSS2 are exposed.

In this section we’ve only touched on the fundamental aspects of dynamic manipulation of objects’ style properties. Given a solid understanding of CSS, much more is possible. The extent to which DHTML can be realized in modern browsers is quite amazing: it’s possible to build, modify, and deconstruct documents or parts of documents on the fly, with a relatively small amount of code. We present a few examples of these effects next.


Team LiB
Previous Section Next Section