Javascript: DOM e innerHTML

http://slayeroffice.com/articles/innerHTML_alternatives/

Introduction

The focus of this article will be to show you ways of doing things commonly reserved for innerHTML with only DOM methods. In each example, the innerHTML method will be shown, followed by its DOM based alternative.

What’s wrong with innerHTML?

A few things:

  • It’s not a standard. It’s a proprietary property that Microsoft introduced (along with the less popular outerHTML) that the other browser makers picked up.
  • Since it’s not a standard, it isn’t terribly future proof. It’s not supposed to work under the application/xhtml+xml MIME type that XHTML documents are supposed to be served under. (Firefox 1.5 changed this by allowing it for some reason)
  • innerHTML is a string. The DOM is not a string, it’s a hierarchal object structure. Shoving a string into an object is impure and similar to wrapping a spaghetti noodle around an orange and calling it lunch.
  • It makes for some nearly illegible code in a lot of instances, with escaped quotes and plus signs all over the place appending data to the string, which in my opinion makes it difficult to maintain.

Ok…anything good about it?

Sure. Don’t get me wrong, I had a long and productive love affair with innerHTML, using it in everything from favelets to games.

  • It’s faster than DOM methods. By a lot. [ http://www.quirksmode.org/dom/innerhtml.html ]
  • It’s less verbose than DOM methods.
  • It allows you to take arbitrary chunks of markup and drop them into a document without having to parse them.

It was when my favelets stopped working for people when they were invoked on application/xhtml+xml sites that I decided to wash my hands of it (and do a lot of re-coding).

Example One

[1a] Creating an element.

This example introduces three DOM methods. The first is createElement, a method of the document object. It does exactly that – creates an element. It’s argument is the name of the element you want created.

The second is the setAttribute method. This method is the equivalent of object.attributeName = “value”;.

The last is the appendChild method. This method will append your newly created element to the element it is invoked on. The element will be the newest child of the parent element, and appear last in the element.

innerHTML

document.getElementById("mContainer").innerHTML = <div id=\"myDiv\"></div>";

DOM

// create a DIV element, using the variable eDIV as a reference to it
eDIV = document.createElement("div");
//use the setAttribute method to assign it an id
eDIV.setAttribute("id","myDiv");
// append your newly created DIV element to an already existing element.
document.getElementById("mContainer").appendChild(eDIV);

[1b] Creating an Element with Text

In this example you’ll use the createTextNode method of the document object. This method will — you guessed it — create a text node. It’s important to note that a text node is just that: text. Any markup that you pass to createTextNode will be rendered as text, not as HTML.

innerHTML

document.getElementById("mContainer").innerHTML = "<div id=\"myDiv\">hello world</div>";

DOM

// create a DIV element, using the variable eDIV as a reference to it
eDIV = document.createElement("div");
//use the setAttribute method to assign it an id
eDIV.setAttribute("id","myDiv");
// add the text "hello world" to the div with createTextNode
eDIV.appendChild(document.createTextNode("hello world"));
// append your newly created DIV element to an already existing element.
document.getElementById("mContainer").appendChild(eDIV);

[1c] Creating an Element Containing Hyperlinked Text

In this example, you’ll create two elements. The div element as you did before, in addition to an anchor element. You’ll then append the text node (“hello world”) to the anchor element rather than the div element since that’s the text you want hyperlinked (the text node is a child of the anchor) and finally you’ll append the anchor to the div.

innerHTML

document.getElementById("mContainer").innerHTML = "<div id=\"myDiv\"><a href=\"http://slayeroffice.com\">hello world</a></div>";

DOM

// create a DIV element, using the variable eDIV as a reference to it
eDIV = document.createElement("div");
//use the setAttribute method to assign it an id
eDIV.setAttribute("id","myDiv");
// create your anchor element
eAnchor = document.createElement("a");
// set its href attribute with the setAttribute method
eAnchor.setAttribute("href","http://slayeroffice.com");
// add the text "hello world" to the anchor element
eAnchor.appendChild(document.createTextNode("hello world"));
// append your newly created anchor element to the div
eDIV.appendChild(eAnchor);
// append your newly created DIV element to an already existing element.
document.getElementById("mContainer").appendChild(eDIV);

[1d] Creating an Element Containing Hyperlinked Text and an Event Handler

innerHTML

document.getElementById("mContainer").innerHTML = "<div id=\"myDiv\"><a href=\"http://slayeroffice.com\" onmouseover="doStuff();">hello world</a></div>";

DOM

// create a DIV element, using the variable eDIV as a reference to it
eDIV = document.createElement("div");
//use the setAttribute method to assign it an id
eDIV.setAttribute("id","myDiv");
// create your anchor element
eAnchor = document.createElement("a");
// set its href attribute with the setAttribute method
eAnchor.setAttribute("href","http://slayeroffice.com");
// add our event handler to the anchors mouseover event
eAnchor.onmouseover = doStuff;
// add the text "hello world" to the anchor element
eAnchor.appendChild(document.createTextNode("hello world"));
// append your newly created anchor element to the div
eDIV.appendChild(eAnchor);
// append your newly created DIV element to an already existing element.
document.getElementById("mContainer").appendChild(eDIV);

[1e] Creating an Element Containing Hyperlinked Text and an Event Handler that Takes an Argument

innerHTML

document.getElementById("mContainer").innerHTML = "<div id=\"myDiv\"><a href=\"http://slayeroffice.com\" onmouseover=\"doStuff('foo');\">hello world</a></div>";

DOM

// create a DIV element, using the variable eDIV as a reference to it
eDIV = document.createElement("div");
//use the setAttribute method to assign it an id
eDIV.setAttribute("id","myDiv");
// create your anchor element
eAnchor = document.createElement("a");
// set its href attribute with the setAttribute method eAnchor.setAttribute("href","http://slayeroffice.com");
// add our event handler to the anchors mouseover event
eAnchor.onmouseover = function() { doStuff("foo") };
// add the text "hello world" to the anchor element
eAnchor.appendChild(document.createTextNode("hello world"));
// append your newly created anchor element to the div
eDIV.appendChild(eAnchor);
// append your newly created DIV element to an already existing element.
document.getElementById("mContainer").appendChild(eDIV);

Note:Simon Willison points out that the above example is a circular reference that will cause memory leaks in Internet Explorer. See this article by Mark Wubben for one solution, and be sure to unattach your event handlers when your document unloads.

By now you’ve no doubt noticed the verbosity of using DOM methods for object creation. Fear not, we’ll cover shortcuts at the end of the article!

Example Two

[2a] Getting the Text Value of an Element

The examples in this section introduce several DOM methods and properties:

firstChild
The first child node of the given node. For example, if the first node in a div element is a span, firstChild will return a reference to that span element.
childNodes
Returns a collection of child nodes belonging to the element. This includes element nodes, text nodes, and other types of nodes.
nodeValue
The value of the node. If the node is a text or comment node, the text. Null otherwise.
nodeType
The kind of node we’re dealing with. Text nodes, for example, have a nodeType of “3”, while element nodes are “1”. See the link above for more nodeType values.

innerHTML

mText = document.getElementById("mContainer").innerHTML

DOM

mText = document.getElementById("mContainer").firstChild.nodeValue;

It’s important to note in the above example that this will only work so long as the first child of the element is in fact the text node we’re after. If there is another element wrapping that text, say a span, that will return null. Of course, you’d have the same problem using innerHTML, only you’d have to use a regular expression to remove the extraneous elements.

[2b] Getting the Text Value of an Element that May Have Extraneous Nodes

In the last example, you knew that the only node in your element was the text node you wanted. In this next example, the element’s text is wrapped in a span element, which you aren’t interested in. Here is how, using the DOM, we can extract all of the text nodes from an element.

innerHTML

mHTML = document.getElementById("mContainer").innerHTML; mText = mHTML.replace(/<[^>]*>/g,"");

DOM

function so_getText(obj) {
    // return the data of obj if its a text node
    if (obj.nodeType == 3)
        return obj.nodeValue;
    var txt = new Array(),i=0;
    // loop over the children of obj and recursively pass them back to this function
    while(obj.childNodes[i]) {
        txt[txt.length] = so_getText(obj.childNodes[i]);
        i++;
    }
    // return the array as a string
    return txt.join(""); }
// set the text strings found in mContainer to a variable called mText
mText = so_getText(document.getElementById("mContainer"));

Important!

The “var” declarations within the function are necessary in this example since the function calls itself recursively – “var” privately scopes them for each iteration. Without them, the loop won’t end and your browser will lock up.

Example Three

Clearing the Contents of an Element

With innerHTML, updating the contents of an element is as simple as overwriting it. With DOM methods, we must first clear the contents of that element before updating it with new content.

The following examples introduce several DOM methods:

removeChild
Removes the specified child node from the parent node.
insertBefore
Inserts a new node before the specified node.
cloneNode
Makes a copy of the specified node.
innerHTML

document.getElementById("mContainer").innerHTML = newContent;

DOM

function so_clearInnerHTML(obj) {
    // so long as obj has children, remove them
    while(obj.firstChild) obj.removeChild(obj.firstChild);
}
// call your function to remove all the children from your element
so_clearInnerHTML(document.getElementById("mContainer"));
// update the contents of the element with a new node
document.getElementById("mContainer").appendChild(newContent);

It most likely goes without saying, but the variable “newContent” in the innerHTML example is a string, and in the DOM example it is a node object.

Clearing the Contents of an Element, Take Two

Another way of clearing the contents of an element is to do a shallow clone of the element. This idea was posted by Mr. Stephen Clay in response to this post.

DOM

function so_clearInnerHTML(obj) {
    // perform a shallow clone on obj
    nObj = obj.cloneNode(false);
    // insert the cloned object into the DOM before the original one
    obj.parentNode.insertBefore(nObj,obj);
    // remove the original object obj.parentNode.removeChild(obj);
}
so_clearInnerHTML(document.getElementById("mContainer"));

The example uses the cloneNode method with “false” as its “depth” argument. When this is passed as “true”, all of the element’s child nodes are cloned as well. Since we aren’t interested in the child nodes (outside of getting rid of them completely) we pass “false”. This give us an empty clone of the element we want to clear, and we are then able to remove the original element and replace it with the clone.

This means of clearing the contents of an element is always faster than the means employed in the first example since it doesn’t have to loop over the child nodes, as evident in this benchmark.

Note: Stuart Langridge points out a caveat for this method:

“The quick cloneNode approach to emptying an element’s content is neat, but it will invalidate any object references you have to the element. In your example, you’re explicitly looking up the element in the call [so_clearInnerHTML(document.getElementById(“mContainer”));] but if you were just calling so_clearInnerHTML(myobj) then myobj will point to an element in limbo when you finish the call.”

This example also introduced the parentNode property. This property returns an object reference to the element that your element descends from. For example, if used on an LI element, parentNode will most likely return a reference to an OL or UL.

Example Four

[4] Copying the Contents of One Element to Another

In this example, you’ll use the previously mentioned cloneNode method, only this time you’ll pass “true” instead of “false” to copy all of the elements child nodes as well.

innerHTML

document.getElementById("mContainer").innerHTML = document.getElementById("hiddenContent").innerHTML;

DOM

// clone the "hiddenContent" element and assign it to the "newContent"
variable newContent = document.getElementById("hiddenContent").cloneNode(true);
// clear the contents of your destination element.
so_clearInnerHTML(document.getElementById("mContainer"));
// append the cloned element to the destination element
document.getElementById("mContainer").appendChild(newContent);

Using cloneNode in this instance has a significant advantage over innerHTML: you’ll get an accurate representation of the contents of the element you are cloning. innerHTML has a nasty habit of returning to you the markup that the browser used to render the page, not the markup you wrote. A good example of this is alerting the innerHTML property of a table element in MSIE. There will be all sorts of things in the markup that you didn’t put there.

One difference between the innerHTML example and the DOM example bears note: the innerHTML example copies only the children of hiddenContent, while the DOM example copies hiddenContent and it’s children.

Example Five

[5a] Creating Multiple Elements

One very common use of innerHTML is looping over an array and populating a list with the items found in the array.

innerHTML
data = new Array("one","two","three");
mHTML = "<ul>";
for(i=0;i<data.length;i++) {
mHTML+="<li>" + data[i] + "</li>";
}
mHTML+="</ul>";
document.getElementById("mContainer").innerHTML = mHTML;

DOM

data = new Array("one","two","three");
// create the UL element that our LI elements will descend from
eUL = document.createElement("ul");
// loop over the length of the "data" array
for(i=0;i<data.length;i++) {
    // create an LI
    eLI = document.createElement("li");
    // append the value of data[i] to the LI as a text node
    eLI.appendChild(document.createTextNode(data[i]));
    // append the LI to the UL
    eUL.appendChild(eLI);
}
// append the UL to the "mContainer" element.
document.getElementById("mContainer").appendChild(eUL);

5[b] Creating Multiple Elements as Part of an Array

One of my favorite benefits of the DOM based means of creating elements is that you can assign these newly created elements to variable names. In the next example, we’ll create the same list again, only this time we’ll create the list items as part of an array.

DOM

data = new Array("one","two","three");
// create the UL element that our LI elements will descend from
eUL = document.createElement("ul");
// initialize the eLI variable as an array
eLI = new Array();
// loop over the length of the "data" array
for(i=0;i<data.length;i++) {
    // create an LI, this time as a member of your array
    eLI[i] = document.createElement("li");
    // append the value of data[i] to the LI as a text node
    eLI[i].appendChild(document.createTextNode(data[i]));
    // append the LI to the UL eUL.appendChild(eLI[i]);
}
// append the UL to the "mContainer" element.
document.getElementById("mContainer").appendChild(eUL);

Now we have an array that we can use wherever we want in our script to reference those list items, rather than giving them unique id attributes or referencing them as children of the <ul> (meaning we also don’t have to identify the <ul>).For example…

eLI[0].style.color = "#FF0000";

…will change the text color of the first <li> we created to red, providing an alternative to this:

document.getElementsByTagName("ul")[0].getElementsByTagName("li")[0].style.color = "red";

[5c] Using the documentFragment Object

The document fragment acts as a lightweight document object that you can use to append objects to, and is created with the createDocumentFragment method of the document object. Think of it as an offscreen buffer for object creation.

Document fragments can come in especially handy when you are creating many, many elements at once. The benefits of using a document fragment became apparent to me when writing the Cross Browser Imageless Gradients experiment. In this project, thousands of DIV elements are created and appended to a document fragment, which is then appended to a parent element. Without the document fragment, the script took upwards of five seconds to execute. With the fragment, it is nearly instantaneous.

// create your document fragment
fragment = document.createDocumentFragment();
// create one thousand DIV elements for the hell of it
for(i=0;i<1000;i++) {
    eDIV = document.createElement("div");
    eDIV.appendChild(document.createTextNode("I am one of many DIV elements."));
    // append the DIV element to the document fragment rather than
    // an element that already exists in the DOM
    fragment.appendChild(eDIV);
}
// finally, append the fragment to the "mContainer" element
document.getElementById("mContainer").appendChild(fragment);

Example Six

[6a] Dynamically Inserted Content

Many people are accustomed to inserting HTML snippets into their documents as a means of dynamic content updates. One way this can be done is with an XMLHttpRequest and setting the innerHTML of the element to be updated to the responseText of the request.

Sadly, this can’t be done with straight DOM methods. However, before you get irritated about having read through this entire thing only to find that the thing you use innerHTML most for isn’t possible with the DOM, allow me to talk a bit about alternative architectures that will allow the same functionality.

First, take a moment to read this article by Peter Paul Koch on preferences for response formats for AJAX requests.

My personal preference is end-use agnostic XML. I believe data should be transported in a format that allows the recipient to do what they will with it. Rather than using the responseText of the XMLHttpRequest object and then innerHTML’ing that content into the document, I would use the responseXML which returns an XML DOM and walk the tree grabbing the data I need and using DOM methods to insert that content. For example, see my obscenely large collection of video games here: http://slayeroffice.com/tools/gameData/.

All of the above data is stored in an xml file found here: http://slayeroffice.com/tools/gameData/games.xml

The table of data is created with DOM methods, and the data it is built from could be used for anything. I could import it into a spreadsheet, parse it with a server side language and spit it out in a different format, or even use an XML style sheet to make the XML document itself look nice — pretty much whatever I want. If I was AJAX’ing that content in as an HTML table I’d be much more limited, or at least more put out.

Shortcuts

As you have seen, DOM methods carry a certain verbosity. This can make for some repetitive and lengthy (though easy to read) code. Below are some tips for shortening them.

Set the Document to a Globally Scoped Reference Variable.

// at the top of your script, define "d" as document.
// You can then reference the document as "d" throughout your script.
var d = document;

Use Alias Functions for Object Creation Methods.

// instead of document.createElement:
function dce(obj) { return d.createElement(obj); }
// called like this:
eDIV = dce("div");
function dct(str) { return d.createTextNode(str); }
// called like this:
eTxtNode = dct("Hello World!")
function ac(pNode,cNode) { pNode.appendChild(cNode); }
// called like this:
ac(eUL,eLI);
function sa(obj,attr,val) { obj.setAttribute(attr,val); }
// called like this:
sa(eDIV,"id","myDiv");
function ge(objID) { return d.getElementById(objID); }
// called like this:
ge("mContainer"); 

Using these shortcuts, example 1e would look like this:

eDIV = dce("div");
sa(eDIV,"id","myDiv");
eAnchor = dce("a");
sa(eAnchor,"href","http://slayeroffice.com");
eAnchor.onmouseover = function() { doStuff("foo") };
ac(eAnchor,dct("hello world"));
ac(eDiv,eAnchor);
ac(ge("mContainer"),eDIV);

While still not as small byte-wise as its innerHTML counterpart, it remains more legible.

Some functions that you may find handy:

Resources & Related Links

Lascia un commento

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...