The HTML Document Object Model

In this lesson of the Ajax tutorial, you will learn...
  1. To work with the HTML DOM.
  2. To access specific nodes.
  3. To access nodes by class name.
  4. To remove a node.
  5. To create a new node.
  6. To dynamically create an HTML page.
  7. To modify node styles.
  8. To modify node classes.
  9. To add events to nodes.

The HTML Document Object Model (DOM) is a W3C standard that defines a set of HTML objects and their methods and properties. JavaScript can be used to create and destroy these objects, to invoke their methods, and to manipulate their properties.

If you have done any JavaScript programming on the web then you have almost definitely worked with the HTML DOM. A subset of the object hierarchy is shown below.

This course assumes that you have some experience working with the DOM. This lesson is concerned with identifying and manipulating document nodes with the purpose of repainting a page with JavaScript. For now, we will be using data generated on the client. Later, we'll see how to repaint a page using data returned from the server.

Accessing Nodes

There are many types of nodes in an HTML document, but the three that you most often need to work with are element nodes, attribute nodes, and text nodes.

Accessing Element Nodes

Element nodes can be accessed by their tag name, id, or position within the HTML document hierarchy.

getElementsByTagName()

The getElementsByTagName() method of an element node retrieves all descendant elements of the specified tag name and stores them in a NodeList, which is exposed by JavaScript as an array. The following example illustrates how getElementsByTagName() works.

Code Sample: HTMLDOM/Demos/getElementsByTagName.html

<html>
<head>
<script type="text/javascript">
function getElements()
{
 var elems = document.getElementsByTagName("li");
 var msg="";
 for (var i=0; i < elems.length; i++)
 {
  msg += elems[i].innerHTML + "\n";
 }
 alert(msg);
}
</script>
<title>getElementsByTagName()</title>
</head>

<body onload="getElements();">
<h1>Rockbands</h1>
<h2>Beatles</h2>
<ol>
 <li>Paul</li>
 <li>John</li>
 <li>George</li>
 <li>Ringo</li>
</ol>
<h2>Rolling Stones</h2>
<ol>
 <li>Mick</li>
 <li>Keith</li>
 <li>Charlie</li>
 <li>Bill</li>
</ol>
</body>
</html>
Code Explanation

When this page loads, the following alert box will pop up:

As you can see, the method returns all the node's descendants, not just the direct children.

getElementById()

The getElementById() method of the document node returns a reference to the node with the specified id. The following example illustrates how getElementById() works.

Code Sample: HTMLDOM/Demos/getElementById.html

<html>
<head>
<script type="text/javascript">
function getElements(PARENTNODE,TAGNAME)
{
 var elems = PARENTNODE.getElementsByTagName(TAGNAME);
 var msg="";
 for (var i=0; i < elems.length; i++)
 {
  msg += elems[i].innerHTML + "\n";
 }
 alert(msg);
}
</script>
<title>getElementById()</title>
</head>

<body onload="getElements(document.getElementById('BeatleList'),'li');">
<h1>Rockbands</h1>
<h2>Beatles</h2>
<ol id="BeatleList">
 <li>Paul</li>
 <li>John</li>
 <li>George</li>
 <li>Ringo</li>
</ol>
<h2>Rolling Stones</h2>
<ol id="StonesList">
 <li>Mick</li>
 <li>Keith</li>
 <li>Charlie</li>
 <li>Bill</li>
</ol>
</body>
</html>
Code Explanation

When this page loads, the following alert box will pop up:

Accessing Element and Text Nodes Hierarchically

There are several node methods and properties that provide access to other nodes based on their hierarchical relationship. The most common and best supported of these are shown in the table below.

Properties for Accessing Element Nodes
Property Description
childNodes[] A nodeList containing all of a node's child nodes.
firstChild A reference to a node's first child node.
lastChild A reference to a node's last child node
nextSibling A reference to the next node at the same level in the document tree.
parentNode A reference to a node's parent node.
previousSibling A reference to the previous node at the same level in the document tree.

The this Object

The this keyword provides access to the current object. It references the object (or element) that it appears in.

The following example shows a page (HTMLDOM/Demos/Hierarchy.html) on which a mouse click on any element will pop up an alert box giving information about that element's position in the object hierarchy.

When you click on the middle list item, for example, you get an alert that looks like this:

This shows that the list item's parent is an unordered list (UL), its first child is a text node ("List Item 2"), its last child is the same as it only has one child, its next and previous siblings are both text nodes, and it has only one child node (the text node). The alert above was generated in Firefox. As we'll see later, the result will be slightly different in Internet Explorer.

Attaching Events

It is possible to attach all sorts of events, such as clicks, mouseovers, mouseouts, etc., to elements on the page. The simplest, standard cross-browser method for adding events is to assign an event handler property to a node. The syntax is shown below:

node.onevent = DoSomething;

In HTMLDOM/Demos/DOM.js, there is a cross-browser AttachEvent() function shown below:

/*
 Function Name: AttachEvent
 Arguments: OBJ,EVT,FNCT
 Action: cross browser: attaches event to passed in object.  FNCT will be the event's callback function.
 Returns: true on success
*/
function AttachEvent(OBJ,EVT,FNCT)
{
 if (OBJ.addEventListener)
 {
  OBJ.addEventListener(EVT,FNCT,true);
  return true;
 } 
 else if (OBJ.attachEvent) 
 {
  return OBJ.attachEvent("on"+EVT,FNCT);
 }
}

The addEventListener() node method is part of the DOM 2 specification. It is supported by Mozilla, but not by Internet Explorer. Internet Explorer has its own proprietary method, attachEvent(), which essentially works the same way. Our function above attaches events to nodes. Throughout the course, you'll find this in the DOM.js library; however, if you prefer to use the simple node.onevent style, in most cases it will work just as well. However, our AttachEvent() function has a big advantage: it allows for multiple callback functions to be attached to a single event. The following example illustrates this.

Code Sample: HTMLDOM/Demos/Events.html

<html>
<head>
<title>Events</title>
<script type="text/javascript" src="DOM.js"></script>
<script type="text/javascript">
 function SetEvents1()
 {
  var trigger = document.getElementById("Trigger");
  trigger.onclick=sayHi;
  trigger.onclick=sayBye;
 }
 
 function SetEvents2()
 {
  var trigger = document.getElementById("Trigger");
  AttachEvent(trigger,"click",sayHi);
  AttachEvent(trigger,"click",sayBye);
 }
 
 function sayHi()
 {
  alert("Hi!");
 }
 
 function sayBye()
 {
  alert("Bye!");
 }
</script>
</head>
<body>
 <a href="javascript:SetEvents1()">Attach events using node.onevent syntax.</a><br/>
 <a href="javascript:SetEvents2()">Attach events using AttachEvent() function.</a>
 <hr/>
 <div id="Trigger" style="border:1px solid blue; cursor:pointer;">
  Click to trigger events.
 </div>
</body>
</html>
Code Explanation

When the page loads, the "Trigger" div has no events attached to it.

  1. Click on the link that reads "Attach events using node.onevent syntax." Then click on the "Trigger" div. A single "Bye" alert pops up. This is because the call to sayHi() was replaced by the call to sayBye().
  2. Refresh the page and then click on the link that reads "Attach events using AttachEvent() function." Now click on the "Trigger" div again. Both the "Hi" and "Bye" alerts pop up. The order in which they pop up may depend on which browser you are using.

Being able to have multiple callback functions associated with a single object event can be useful. As an example, you might have a calculation() function that is triggered when a button is clicked. You may later want to make that same action (the click of the button) cause another part of the page to be updated. As these are two separate tasks, you would separate them into different functions, both of which can be tied to the same event.

Accessing Attribute Nodes

getAttribute

The getAttribute() method of an element node returns the value of the specified attribute. According to the DOM specification, the value should be returned as a string; however, Internet Explorer may return the attribute value as a string, number or boolean. The syntax is shown below.

myNode.getAttribute("AttName");

attributes[]

The attributes[] property references the collection of a node's attributes. On the face of it, such a collection could be very useful; however, the browsers handle the attributes collection differently, making use of it a bit risky. Internet Explorer includes all possible attributes of a node in its attributes collection, whereas Firefox includes only those attributes currently used by the node. In practice, it's better to access attributes with the getAttribute() method.

Accessing Nodes by Type, Name or Value

nodeType

Every node has a nodeType property, which contains an integer corresponding to a specific type. For example, 1 is an element node, 2 is an attribute node, 3 is a text node, etc. The W3C DOM specifies a set of constants that correspond to these integers. These constants are properties of every node. Unfortunately, Internet Explorer's DOM doesn't support these constants, so it's a good idea to add them to your DOM library. The constants are listed below:

Node Type Constants
Constant Integer
ELEMENT_NODE 1
ATTRIBUTE_NODE 2
TEXT_NODE 3
CDATA_SECTION_NODE 4
ENTITY_REFERENCE_NODE 5
ENTITY_NODE 6
PROCESSING_INSTRUCTION_NODE 7
COMMENT_NODE 8
DOCUMENT_NODE 9
DOCUMENT_TYPE_NODE 10
DOCUMENT_FRAGMENT_NODE 11
NOTATION_NODE 12

To add these to a script library, simply include the following code at the top of the script:

var ELEMENT_NODE = 1; 
var ATTRIBUTE_NODE = 2; 
var TEXT_NODE = 3; 
var CDATA_SECTION_NODE = 4; 
var ENTITY_REFERENCE_NODE = 5;
var ENTITY_NODE = 6;
var PROCESSING_INSTRUCTION_NODE = 7;
var COMMENT_NODE = 8;
var DOCUMENT_NODE = 9;
var DOCUMENT_TYPE_NODE = 10;
var DOCUMENT_FRAGMENT_NODE = 11;
var NOTATION_NODE = 12;

To find all of a node's child element nodes, you would loop through its childNodes collection checking each node one by one:

for (var i=0; i<node.childNodes.length; i++)
{
 if (node.childNodes[i].nodeType==ELEMENT_NODE)
 {
  alert("This is an element node.");
 }
}

nodeName

Every node also has a nodeName property. For elements and attributes, the nodeName property returns the tag name and attribute name, respectively. For other node types, the nodeName property returns a string beginning with a pound sign (#) and indicating the node type (e.g, #text or #comment).

nodeValue

The nodeValue is usually used with text nodes and it simply returns the text value of the node.

Accessing Nodes by Class Name

There is no built in method for getting an element's child nodes by class name, but we can find the class of an element using its className property. The function shown in the file below loops through all elements in a document checking each element's className. It returns an array containing all the matched elements.

Code Sample: HTMLDOM/Demos/getElementsByClassName.html

---- Code Omitted ----
<script type="text/javascript"> function GetElementsByClassName(CLASS,NODE) { var startNode = NODE || document; var AllTags = startNode.getElementsByTagName("*"); if (AllTags.length == 0) AllTags = startNode.all; //for IE var Elems = new Array(); var re = new RegExp("(^|\\s+)" + CLASS + "(\\s+|$)"); for (var i=0; i<AllTags.length; i++) { if (re.test(AllTags[i].className)) Elems[Elems.length] = AllTags[i]; } return Elems; } function init() { var MenuOptions = GetElementsByClassName("MenuOptions"); alert(MenuOptions.length); } </script> <title>Menus</title> </head> <body onload="init();"> <div style="position:absolute; z-index:1000; left:0px; top:0px;" id="Menus"> <div class="Menu" id="Menu1"> <div class="MenuHead">Beatles</div> <div class="MenuOptions"> <a href="http://www.paulmccartney.com">Paul</a> <a href="http://www.johnlennon.com">John</a> <a href="http://www.georgeharrison.com">George</a> <a href="http://www.ringostarr.com">Ringo</a> </div> </div> <div class="Menu" id="Menu2"> <div class="MenuHead">Rolling Stones</div> <div class="MenuOptions"> <a href="http://www.mickjagger.com">Mick</a> <a href="http://www.keithrichards.com">Keith</a> </div> </div> </div>
---- Code Omitted ----
Code Explanation

The W3C DOM allows for passing the wildcard "*" to an element's getElementsByTagName() method, which stores a nodeList containing all descendant element nodes in the AllTags variable. Unfortunately, Internet Explorer doesn't support this and will return an empty nodeList. To handle this, we set the variable AllTags to document.all, which accomplishes the same thing in Internet Explorer.

A regular expression is created to hold a pattern that checks that the class name passed in to the function is either at the beginning or end of the element's class name or is surrounded by spaces. This is necessary because of the possibility of a compound class name (e.g, class="bright important").

We then loop through AllTags storing each match in the Elems array. The function then returns the Elems array.

Prototype's getElementsByClassName() Method

Many JavaScript frameworks have functionality for getting elements by class name. Prototype extends the document object with a getElementsByClassName() method, which takes two parameters: the class name and the element to search in (optional). If the second parameter is not specified, it searches the entire document body. The following sample does the same thing as the previous one using Prototype's document.getElementsByClassName() method.

Code Sample: HTMLDOM/Demos/getElementsByClassName_prototype.html

<html>
<head>
<link rel="stylesheet" type="text/css" href="Menus.css">
<script type="text/javascript" src="../../prototype.js"></script>
<script type="text/javascript">
 function init()
 {
  var MenuOptions = document.getElementsByClassName("MenuOptions");
  alert(MenuOptions.length);
 }
 
</script>
<title>Menus</title>
</head>

<body onload="init();">
 <div style="position: absolute; z-index: 1000; left: 0px; top: 0px;" id="Menus">
  <div class="Menu" id="Menu1">
   <div class="MenuHead">Beatles</div>
   <div class="MenuOptions">
    <a href="http://www.paulmccartney.com">Paul</a>
    <a href="http://www.johnlennon.com">John</a>
    <a href="http://www.georgeharrison.com">George</a>
    <a href="http://www.ringostarr.com">Ringo</a>
   </div>
  </div>
  <div class="Menu" id="Menu2">
   <div class="MenuHead">Rolling Stones</div>
   <div class="MenuOptions">
    <a href="http://www.mickjagger.com">Mick</a>
    <a href="http://www.keithrichards.com">Keith</a>
   </div>
  </div>
 </div>
 <div id="divBody">Page</div>
</body>
</html>

Removing Nodes from the DOM

Element nodes have a removeChild() method, which takes a single parameter: the child node to be removed. There is no W3C method for a node to remove itself, but the following function will do the trick:

function RemoveElement(ELEM)
{
 return ELEM.parentNode.removeChild(ELEM);
}

Sometimes it's useful to remove all of a node's children in one fell sweep. The function below will handle this.

function RemoveAllChildren(PARENT)
{ 
 while (PARENT.hasChildNodes())
 {
  PARENT.removeChild(PARENT.childNodes[0]);
 }
}

Both of these functions are in our DOM.js library at HTMLDOM/Demos/DOM.js. We'll examine some other functions in this library in a bit and we'll also add some new ones.

DOM Differences: The Whitespace Problem

The W3C specification is not entirely clear on how certain whitespace should be treated in the DOM. The whitespace in question is any that occurs between a close tag and an open tag (e.g, </tr> <tr>), between two open tags (e.g, <tr> <td>), or between two close tags (e.g, </td> </tr>). The question is: should this whitespace be preserved or ignored in the DOM. The answer, it turns out, is unclear ; however, the way a browser treats this space is very important. To illustrate, open HTMLDOM/Demos/Hierarchy.html in Internet Explorer and click on the middle list item. The alert that pops up is shown below:

Notice that the nextSibling and previousSibling properties both reference list item nodes, whereas they referenced text nodes in Firefox. If you were to click on the UL element itself, you would find that Internet Explorer considers it to have three child nodes, whereas Firefox considers it to have seven. Firefox counts the whitespace-only text nodes. Internet Explorer does not.

Creating New Nodes

The document node has separate methods for creating element nodes and creating text nodes: createElement() and createTextNode(). These methods each create a node in memory that then has to be placed somewhere in the object hierarchy. A new node can be inserted as a child to an existing node with that node's appendChild() and insertBefore() methods. These methods and some others are described in the table below.

Methods for Inserting Nodes
Method Description
appendChild() Takes a single parameter: the node to insert, and inserts that node after the last child node.
insertBefore() Takes two parameters: the node to insert and the child node that it should precede. The new child node is inserted before the referenced child node.
replaceChild() Takes two parameters: the new node and the node to be replaced. It replaces the old node with the new node and returns the old node.
setAttribute() Takes two parameters: the attribute name and value. If the attribute already exists, it replaces it with the new value. If it doesn't exist, it creates it.

The sample below illustrates how these methods work.

Code Sample: HTMLDOM/Demos/InsertingNodes.html

<html>
<head>
---- Code Omitted ----
<script type="text/javascript"> function AppendNewListItem() { var li = document.createElement("li"); var liText = document.createTextNode("New List Item"); li.appendChild(liText); List.appendChild(li); } function PrependNewListItem() { var li = document.createElement("li"); var liText = document.createTextNode("New List Item"); li.appendChild(liText); List.insertBefore(li,List.firstChild); } function ChangeStartNum() { var start; if (start=List.getAttribute("start")) start++; else start=2; List.setAttribute("start",start); } function ReplaceOlWithUl() { List.parentNode.replaceChild(List2,List); List = List2; } var List, List2; function init() { List = document.getElementById("TheList"); List2 = document.getElementById("TheList2"); } </script> <title>Inserting Nodes</title> </head> <body onload="init()"> <div onclick="AppendNewListItem()">Append List Item</div> <div onclick="PrependNewListItem()">Prepend List Item</div> <div onclick="ChangeStartNum()">Change Start Number</div> <div onclick="ReplaceOlWithUl()">Replace ordered list with bulleted list</div> <ol id="TheList"> <li>List Item</li> <li>List Item</li> <li>List Item</li> <li>List Item</li> </ol> <ul id="TheList2"> <li>List Item</li> <li>List Item</li> <li>List Item</li> <li>List Item</li> </ul> </body> </html>
Code Explanation

The page is shown below in a browser. Click on any of the menu items to see the methods in action.

A Note on the setAttribute() Method

You can use the setAttribute() method to change the value of all attributes by name as you would expect with one exception. In Internet Explorer, the class attribute must be referrred to as "className" in the setAttribute() method. This means that you have to branch your code when using setAttribute() for setting the class:

var isIE = (navigator.appName == "Microsoft Internet Explorer");
if (isIE)
{
 var classTerm = "className";
}
else
{
 var classTerm = "class";
}
node.setAttribute(classTerm,"warning");

Rather than go through all this work, it is easier to use the className property to set the class attribute. This will work in both browsers:

node.className = "warning";

The HTML Document Object Model Conclusion

In this lesson of the Ajax tutorial, you have learned to work with the HTML DOM to create and modify HTML page elements dynamically with JavaScript.

To continue to learn Ajax go to the top of this page and click on the next lesson in this Ajax Tutorial's Table of Contents.

Use of this website implies agreement to the following:

Copyright Information

All pages and graphics on this Web site are the property of Webucator, Inc. unless otherwise specified.

None of the content on this website may be redistributed or reproduced in any way, shape, or form without written permission from Webucator, Inc.

No Printing or saving of web pages

This content may not be printed or saved. It is for online use only.


Linking to this website

You may link to any of the pages on this website; however, you may not include the content in a frame or iframe without written permission from Webucator, Inc.


Warranties

This website is provided without warranty of any kind. There are no guarantees that use of the site will not be subject to interruptions. All direct or indirect risk related to use of the site is borne entirely by the user. All code and explanations provided on this site are provided without warranties to correctness, performance, fitness, merchantability, and/or any other warranty (whether expressed or implied).

For individual private use only

You agree not to use this online manual to deliver or receive training. If you are delivering or attending a class that is making use of this online manual, you are in violation of our terms of service. Please report any abuse to courseware@webucator.com. If you would like to deliver or receive training using this manual, please fill out the form at http://www.webucator.com/Contact.cfm.