This page illustrates how the use of logging can aid JavaScript development, especially in creating scripts that behave identically across multiple browsers. As a real–world example, let’s look at how IE assigns attributes to dynamically–created document elements—something that caused a hiccup in the development of fvlogger.
Included in the styles of this page is the follwing rule:
.labrat { font-weight: bold; color: red; }
Thus, any elements in the document whose class is “labrat”—whether they be a part of the document
when the page is loaded into the browser or appended at some point thereafter—should appear in bold–faced red type, including the following two examples.
This is the first example.
This is the second example.
Easy–peasy, right? Almost, but not quite.
The examples shown above are included in the document (ie: they were created when authoring the rest of this page), so it should come as no surprise that they appear as they should (ie: like this) in every browser that supports CSS. However, with the cross–browser support of the DOM, dynamically creating elements and appending them to the document
has become commonplace (and even required by many websites and applications). Let’s take a quick look to make sure that this method also works.
The code to accomplish our task should look something like the following:
var p = document.createElement("p"); p.appendChild( document.createTextNode("This is the third example") ); p.setAttribute("class", "labrat"); document.appendChild(p);
The above snippet of code works in all modern browser families—WebKit, Gecko, and Opera—except for IE. So in Internet Explrer, the following paragraph will have the same font as all regular paragraphs, even though it has the “labrat” class applied:
After a little investigating, the cause is IE’s implementation of the setAttribute
and createElement
methods. Take a look at the following code, which is used to create the following paragraph…
// create and append the paragraph to the document var p2 = document.createElement("p"); p2.appendChild( document.createTextNode( "This is another dynamically created paragraph.") ); p2.setAttribute("class", "labrat"); var bottle = document.getElementById("bottle"); bottle.appendChild(p2); // use fvlogger to log all attributes to the document var i = 0; var attrs = p2.attributes; for (i = 0; i < attrs.length; i++) { debug("attribute #" + i + ": " + attrs[i].name + "=" + attrs[i].value); } // display a summary of attrs length if (i == 0) { error("new element has no attributes"); } else { info("p has " + i + " attribute(s)"); }
… and the output that it produces, shown at the top of the page. In Opera, Mozilla, and WebKit–based browsers, there are only two logging statements: one debug statement that shows the class of the element that we created, and the other indicating the number of attributes belonging to the element. Because we are only assigning one attribute to the element (its class), the output is exactly what we would expect it to be: the paragraph has 1 attribute named, not surprisingly, “labrat”.
However, Internet Explorer is unique in the way that it creates elements; instead of having zero attributes (as one would expect) immediately after creation, new elements inherit all attributes that are defined in Internet Explorer’s default DTD. Thus, instead of our new element having only one attribute after setting its class
, it actually has 84!
But what’s equally as odd is that when attempting to set the class
of a new element through its setAttribute()
method, IE will actually create another attribute node instead of replacing the value of the existing class
attribute. Thus, we end up with two class
attributes instead of one (attributes #3 and #83 in the log statements shown at the top of the page when viewed with IE).
Luckily, there is, a relatively simple fix IE’s problem: instead of simply setting the value of an attribute based on its name, we can loop through all existing attributes until we find the one that we’re looking for and set its value based on its numeric index in the node’s attributes[]
array. The following snippet of JavaScript will get the job done:
// if the node’s class already exists, then replace its value if (p.getAttributeNode("class")) { for (var i = 0; i < p.attributes.length; i++) { if (p.attributes[i].name.toUpperCase() == 'CLASS') { p.attributes[i].value = className; } } // otherwise create a new attribute } else { p.setAttribute("class", LOG_CLASSES[level]); }
This example illustrates how logging can aid in developing JavaScripts that work in all browsers.