Object Detection, Encapsulation, and Cross-Browser Objects






Object Detection, Encapsulation, and Cross-Browser Objects

With the release of CSS and Netscape's Navigator 4.x, as well as Microsoft's Internet Explorer 4.x, web-page developers could finally create sophisticated page effects such as animated page contents, collapsing menus, and in-page notifications. The only problem was that not all of the browsers used the same object model when providing this capability.

One way around this cross-browser incompatibility was to access the agent string to determine what browser was accessing the page, and change the JavaScript accordingly. However, this approach, commonly called browser sniffing, was abandoned fairly quickly in favor of another approach: object detection.

Object Detection

With object detection, the JavaScript accesses the object being detected in a conditional statement. If the object doesn't exist, the condition evaluates to false. In Chapter 9, I mentioned one object that's commonly used in older scripts: document.all. Checking for document.all can detect a browser that supports the IE 4.x model. Another common object detection is to check for document.layers, which was supported by Netscape's Navigator 4.x:

if (document.layers) ...

Luckily, all modern browsers support a fairly consistent model. All support the document.getElementById, which is critical for accessing specific elements. All support the style property (covered in the next chapter), which allows you to change the CSS style properties of an element.

Still, even now, there are differences. Though I'll cover JavaScript manipulation of CSS properties in Chapter 12, we'll look at one specific property that differs between Internet Explorer and other browsers: opacity.

An element's transparency is determined by the percentage of its opacity. Microsoft was the first to provide a way to change an element's opacity dynamically, through a proprietary filter called the alpha filter. Later, the Mozilla group created a variation of the filter, called the moz-opacity. At about the same time, the KHTML effort (represented by the Safari browser and Konquerer on Linux) derived a property called khtml-opacity. With the release of CSS 3.0, a universal property was defined for opacity, simply named opacity.

The Mozilla line of browsers has moved to the new CSS3 standard, as has Safari. Oddly enough, Microsoft has decided not to support this property and still persists in using the alpha filter, even with the new IE 7. Object detection is necessary, then, to create an effect that works with IE as well as the other browsers that support the CSS3 opacity property.

In Figure, object detection is used to determine which approach to usethe alpha filter or setting the CSS opacity. The target is an image embedded in the page. Its opacity is decreased 10 percent each time the page is clicked. Because the Microsoft alpha filter uses a percentage rather than a digital value, the variable used to hold the current opacity is multiplied by 100 when used with IE.

Using object detection to determine how to adjust the opacity style

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Object Detection</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<script type="text/javascript">
//<![CDATA[

var opacity = 1.0;
document.onclick=adjustOpacity;

function adjustOpacity(  ) {
    opacity= opacity - 0.1;
    var img = document.getElementById("img1");
    if (img.style.filter) {
       opacity = opacity * 100;
       img.style.filter = "alpha(opacity:"+opacity+")";
    } else if (img.style.opacity) {
      img.style.opacity = opacity;
    } else {
      alert("Opacity not supported");
    }
}
//]]>
</script>

</head>
<body>
<img id="img1" src="fig01-1.jpg" style="opacity: 1.0; filter: alpha(opacity=100)"/>
</body>
</html>

In Mozilla, Navigator, Camino, Firefox, Safari, and IE, the image loses opacity with each click, fading away until it's completely transparent. With Opera, which doesn't support opacity, the message is given instead.

In Figure, the initial opacity is set using an inline style setting. Without this initial setting, the opacity style setting returns null for any browser. The reason for this is that stylesheets and default settings aren't usually reflected with the style object when accessed by JavaScript. Stylesheets can be accessed as an array off the document object, and their individual rules accessed, using document.stylesheets[0].cssRules[0] (for W3C-complaint browsers), or document.stylesheets[0].rules[0] (for IE). You can also swap out an existing stylesheet using the stylesheets array.


This is an effective technique to work around cross-browser differences, but you might be asking yourself, what does this have to do with creating custom objects?

Encapsulating Objects

Earlier I touched on being able to pass page objects in as a parameter when constructing a new object. The custom object then wraps, or encapsulates, the page object, allowing you to create a set of functionality that hides most of the implementation details. When using a library that has this capability, instead of having to provide all of the JS yourself to change an object's opacity, you can just call a method that changes it for you.

If the underlying implementation changes because of what the browser supports, object encapsulation can hide all of the details for managing this alteration. The applications don't have to change because the underlying implementations have. This makes sophisticated interactive and dynamic applications so much easier to develop. If the browser's implementation is modified, you no longer have to worry about changing multiple applications.

Additionally, you no longer have to run a continuous set of operations that check whether the browser supports this functionality. Your code, or the JS library you're using, checks it up front when the objects are created (usually when the page loads).

Figure shows a self-contained application that demonstrates how object encapsulation can work in JavaScript, and how to manage cross-browser differences. The application includes a tiny object library that manages opacity. The page has two DIV elements, each of which contains an image. Both elements are positioned absolutely in the page: one is opaque, the other transparent. When the page loads, a function is called that creates an instance of the custom object, passing in each DIV element in turn. The first element's opacity is set to 1.0 (visible); the second to 0 (completely transparent). Clicking on the page decreases the opacity of the visible object and increases the opacity of the originally invisible object, creating a transformation effect between the two objects.

Object encapsulation

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Object Detection</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<style type="text/css">
div {
        position: absolute;
        top: 30px;
        left: 50px;
    }
</style>

<script type="text/javascript">
//<![CDATA[

var theobjs = new Array(  );

function alphaOpacity(value) {
   var opacity = value * 100;
   this.style.filter = "alpha(opacity:"+opacity+")";
}

function cssOpacity(value) {
   this.obj.style.opacity = value;
}

function getOpacity(  ) {
   if (this.obj.style.filter) {
       return this.obj.style.filter.alpha;
   } else {
       return this.obj.style.opacity;
   }
}
function changeOpacity(  ) {

   // div1
   var currentOpacity = parseFloat(theobjs["div1"].objGetOpacity(  ));
   currentOpacity-=0.1;
   theobjs["div1"].objSetOpacity(currentOpacity);

   // div2
   currentOpacity = parseFloat(theobjs["div2"].objGetOpacity(  ));
   currentOpacity+=0.1;
   theobjs["div2"].objSetOpacity(currentOpacity);
}

function DivObj(obj) {
   this.obj = obj;
   this.objGetOpacity = getOpacity;
   this.objSetOpacity = obj.style.filter ? alphaOpacity : cssOpacity ;
}

function setup(  ) {
  theelements = document.getElementsByTagName("DIV");
  for (i = 0; i < theelements.length; i++) {
      var obj = theelements[i];
      if (obj.id) {
         theobjs[obj.id] = new DivObj(obj);
      }
  }

  // set initial opacity
  theobjs["div1"].objSetOpacity(1.0);
  theobjs["div2"].objSetOpacity(0.0);

  // event handlers
  document.onclick=changeOpacity;
}

//]]>
</script>
</head>
<body onload="setup(  )">
<div id="div1">
<img src="fig01-1.jpg" />
</div>
<div id="div2" style="opacity: 0.0; filter: alpha(opacity=0)">
<img src="fig01-3.jpg" />
</div>
</body>
</html>

In the example, rather than implementing the methods directly in the object, they're implemented outside as separate functions. You can use this approach if you're creating cross-browser objects where all versions of the objects can use some of the methods, such as the getOpacity function (which uses object detection each time it's called), but some methods are specific to types of support (such as the two methods for changing the opacity of the object, set by object detection when the object is created). It also, in my opinion, can make the code a little easier to read as you document each function, and you don't have an excessive amount of nesting.

The example also used parseFloat to ensure that the numbers are accessed as numbers, not strings. Later, in the section on exception handling, I'll demonstrate what happens when you don't use this function.


The use of object detection, custom objects, and encapsulation is not as important today as it was in the past when browser DHTML support varied rather significantly. However, it's still a great way to hide browser differences, not to mention enforce the old "code once, use many times" philosophy of application development.

Note the DOM Level 2 functionality of getElementsByTagName to access all DIV elements, which are then passed to the custom-object constructor to be wrapped in all that cross-browser goodness. For allover page effects, wrapping the page elements in DIV elements and then encapsulating each as a custom object is an approach that simplifies the development of more sophisticated functionality. We'll look at this in more detail in the next two chapters.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows