Web DevCenter    
 Published on Web DevCenter (http://www.oreillynet.com/javascript/)
 See this if you're having trouble printing code examples


Dynamic Content with DOM-2 (Part I of II)

by Scott Andrew LePera and Apple Developer Connection
08/17/2001

The relatively recent emergence of peer-to-peer distributed computing and the renewed interest in real-time data exchange have stoked the embers of a hot topic: displaying dynamic content over the Web. Unfortunately, the statelessness of HTTP and the limitations of the rendering components of different browsers present significant challenges to web developers wishing to get fresh information to the client without sending additional requests to a server.

Until recently, developers have addressed this challenge by falling back on JavaScript trickery, such as cleverly placed document.write() statements, framesets, and IFRAMEs, or the use of Internet Explorer's innerHTML() and insertAdjacentHTML() functionality to insert new HTML content. While these methods are successful to some degree, they are usually tied to a specific browser implementation. A specific drawback is that these techniques do little to address the structure of the document in question. For the most part, a string of HTML is simply inserted into the page over and over again, with little regard for what is actually being rendered.

Fortunately, with the advent of Internet Explorer 5 (IE5) and Netscape 6 (NS6), we now have a better approach. The Document Object Model Level 2 (DOM-2), supported in both Mac IE5 and NS6, provides an interface that enables developers to generate HTML on the fly, after the page has loaded. We accomplish this by calling DOM-2 methods to create HTML elements, defining the elements' attributes, and appending them to the document body or existing elements. This article explores some of the basic functionality of DOM-2 for generating dynamic content in the browser.

This article assumes you have a general knowledge of proper document structure and understand the concept of the document as a series of nested parent and child HTML objects.

Nodes and Elements

The DOM-2 specification identifies all items that compose a document as nodes. The node interface provides a set of common methods and properties that allow items within a document to be accessed and manipulated.

IE5 recognizes everything in your HTML document to be either an element or a text node, as does NS6 (and Mozilla). It's important to understand the fundamental differences between elements and text nodes. Elements are universally associated with angle-bracketed tags. In HTML, all tags are elements, such as <P>, <IMG> and <DIV>. Elements are also likely to have attributes and contain child nodes.

Comment on this articleThe DOM-2 has evolved from an exciting concept to a legitimate tool that is now supported in the major browsers. Do you think more developers will begin to take advantage of these tools, or will they continue to rely on old tricks?
Post your comments

Text nodes, on the other hand, represent a chunk of text. Unlike elements, text nodes have neither attributes nor children (although they inherit both the childNodes and attributes collections from the node interface.)

Consider the following example code:

<span id="weather">Partly cloudy, scattered showers, high 67</span>

The code above is comprised of two separate nodes. The <SPAN> tag is an element node, with a single ID attribute. The text inside the SPAN is in fact a separate text node. Being only text, it has no attributes or children.

Elements and text nodes share some common properties:

For a complete list of node properties and methods, you can refer to the W3C DOM-2 specification.

Creating Elements and Text

The creation of new nodes is made possible by a set of methods available on the document object. These methods are:

createElement

Related Reading

JavaScript: The Definitive Guide, 4th EditionJavaScript: The Definitive Guide, 4th Edition
By David Flanagan
Table of Contents
Index
Sample Chapter
Full Description

Creating a new element node is deceptively simple. Just call the createElement method with the type of element you wish to create. It's a good idea to assign the return value of the method -- a reference to the newly created element -- to a variable if you wish to perform further operations with that element.

The following code creates a new empty paragraph element:

var newPara = document.createElement("p");

The variable newPara is now a reference to the new paragraph element.

createTextNode

You can create text nodes in a similar fashion, with the createTextNode method, by passing it the string of text you want in the node. As with creating elements, assigning the return value to a variable ensures that you can refer to the node later in your code.

var newText = document.createTextNode("This is a new sentence.");

The preceding code created two new nodes, but neither of them can be seen in the browser because they have not been inserted into the document. Newly created nodes float freely in the JavaScript until you assign them to a parent object or to the document body.

Every object that inherits the node interface has a method called appendChild, which allows that node to have other nodes inserted into it. For example, you can insert the new text node newText into the new paragraph element newPara using the appendChild method:

var newPara = document.createElement("p");
var newText = document.createTextNode("This is a new sentence.");
newPara.appendChild(newText);

Now all you need to do is insert the paragraph element into the document body. To do this, you need a reference to the BODY element of the document. There are several ways to get this reference. Most newer browsers (IE5 and NS6) allow you to use document.body, e.g. document.body.appendChild(newPara);,but this is a non-standard convenience property which may not work in future versions.

A more standards-compliant way is to use the document object's getElementsByTagName method to get a reference to the first BODY element found in the document.

var bodyRef = document.getElementsByTagName("body").item(0);
bodyRef.appendChild(newPara);

The item(0) method is necessary because getElementsByTagName returns a collection of elements it finds with the specified tag name. Since there is usually only one BODY tag in an HTML page, you use the item method to get the first item in the collection.

Another standards-compliant way to reference the document body is to use the getElementById method. This assumes, however, that you have bothered to assign an ID attribute to the <BODY> tag of your HTML page.

<body id="docBody">
var bodyRef = document.getElementById("docBody");
bodyRef.appendChild(newPara);

You can also use getElementsByTagName and getElementById to obtain references to page objects other than the body. For example, clicking this link will invoke a script that uses getElementById to get a reference this very paragraph element (which has been given an ID attribute of "example1"). The script then inserts a new text node into this element with the appendChild method.

The code used to do this is as follows:

<script type="text/javascript">
    function insertNewText()
{
    var newText = document.createTextNode(" DOM methods are cool, are they not?");
    var para = document.getElementById("example1");
    para.appendChild(newText);
}
</script>

Since a paragraph element with an ID of "example1" already resides within the BODY of the document, there is no need to reference the body at all. This is a simple example, but the concepts demonstrated could be used to achieve some complicated DOM manipulations.

There are some restrictions imposed on the creation of new nodes. One to consider is that a newly created node can only be inserted in the document that created it. This means that if you're using a series of frames, you cannot create an element in one frame and append it into the document body of another frame. Its also not possible to pass an element from one window to another.

It's also worth noting that you are restricted to the logical hierarchy of the document and the types of nodes you are manipulating. You may get errors if you try things like inserting a node into itself, into one of its own children, or into another node that does not allow children of that type.

Whither innerHTML?

You may be wondering why it's worth using these seemingly complex, overlong JavaScript statements to create a single bit of text or an HTML element when you could use innerHTML and be done with it.

innerHTML was a convenience property introduced by Microsoft as a means of reading and writing the HTML content of an element. For example, you could easily create a multi-celled table structure and insert it into your page with innerHTML:

var str = '<table border="1">';
str += '<tr><td>Cell One</td><td>Cell Two</td><td>Cell Three</td></tr>';
str += '</table>';
document.all["paragraphOne"].innerHTML = str;

innerHTML was incorporated into the Mozilla codebase and is now a part of NS6. So why not use it? The primary reason is that innerHTML does not return any references to the nodes it creates. If at some point you needed to change the text inside the second TD element of this new table, you would have to somehow find a reference to that second cell--something you would already have if you had used createElement(), which always returns a node reference. You could use document.all or even document.getElementById() to retrieve a reference, but that assumes that you have properly assigned unique IDs to each element in your string. At that point, it's easier to simply rewrite the entire string and reinsert in into paragraphOne.

Ultimately, the approach you choose depends on what you intend to accomplish. If you are simply displaying text or HTML and have no need to access the new elements being created, use innerHTML. If, however, you plan on manipulating specific items within a larger dynamically generated structure, take the extra time to construct it with DOM document methods.

More Node Manipulations

You can remove existing nodes as well as add new ones. The removeChild method allows any node to remove one of its child nodes. Simply pass a reference to the node you wish to remove. Any text or elements within the node being removed will be removed along with it.

The text between these bold tags will disappear when the B element is removed from the paragraph.

<script type="text/javascript">
function removeBElm(){
    var para = document.getElementById("example2");
    var boldElm = document.getElementById("example2B");
    var removed = para.removeChild(boldElm);
}
</script>

Click this link to see the above script in action.

Nodes that are removed are not destroyed. removeChild returns a reference to the node that was removed so additional manipulations can be made with it later. In the example above, notice that we've assigned the removed B element to the removed variable, thus maintaining a reference to it.

Depending on the browser implementation, you may get errors if you try to remove a non-existent node or one that has been marked as read-only.

Replacing Nodes

In addition to removing nodes, the DOM offers a way to replace one node with another. The replaceChild method accomplishes this. As with removeChild above, replaceChild needs to be called from the element that contains the node you wish to replace.

replaceChild takes two arguments: a reference to the new node, and another to the node being replaced. The following example creates a new SPAN element containing a text node, and uses it to replace an existing SPAN.

<script type="text/javascript">
function replaceSpan(){

    var newSpan = document.createElement("span");
    var newText = document.createTextNode("on top of the astounded zebra");
    newSpan.appendChild(newText);

    var para = document.getElementById("example3");
    var spanElm = document.getElementById("ex3Span");
    var replaced = para.replaceChild(newSpan,spanElm);
}
</script>

You can try this example here:

The quick brown fox jumps over the lazy dog.


Inserting Nodes in a Sequence

You may have noticed in the previous examples that appendChild always places the newly appended node as the very last child of the parent node. There are times when it is desirable to insert a node before or in-between existing child nodes.

The insertBefore node method provides a way of accomplishing this. insertBefore takes two arguments: the new node to insert and the node you wish to have prepended. The following example uses insertBefore to insert a new TD element before the third existing TD in an HTML table.

<table border="1">
  <tr id="example4">
    <td>TD One</td>
    <td>TD Two</td>
    <td>TD Three</td>
  </tr>
</table>

<script type="text/javascript">

var TDCount = 0;

function insertTD(){

  var newTD = document.createElement("td");
  var newText = document.createTextNode("New Cell " + (TDCount++));
  newTD.appendChild(newText);

  var trElm = document.getElementById("example4");
  var refTD = trElm.getElementsByTagName("td").item(2);
  trElm.insertBefore(newTD,refTD);
  
}
</script>

You can try this example here:

TD One TD Two TD Three


The Road Ahead

We've covered how DOM-2 makes generating on-the-fly HTML and manipulating page elements extremely simple, but you may be looking for more power. This article doesn't cover how to affect attributes of elements, like table cell widths, background colors, and so forth. In the next article, you'll see how DOM-2 can be used to manipulate the attributes of elements and create dynamic effects. You will also see some additional methods and properties of the node and element interfaces that provide easier references to objects in the DOM tree.

Scott Andrew LePera lives in San Francisco, where he ekes out a schizoid existence as both a web applications developer for KnowNow and a frustrated urban folk singer.


Return to the JavaScript and CSS DevCenter.

Copyright © 2009 O'Reilly Media, Inc.