Search the Catalog
JavaScript: The Definitive Guide, 4th Edition

JavaScript: The Definitive Guide, 4th Edition

By David Flanagan
4th Edition November 2001
0-596-00048-0, Order Number: 0480
936 pages, $44.95

Chapter 17:
The Document Object Model

A document object model (DOM) is an application programming interface (API) for representing a document (such as an HTML document) and accessing and manipulating the various elements (such as HTML tags and strings of text) that make up that document. JavaScript-enabled web browsers have always defined a document object model; a web-browser DOM may specify, for example, that the forms in an HTML document are accessible through the forms[] array of the Document object.

In this chapter, we'll discuss the W3C DOM, a standard document object model defined by the World Wide Web Consortium and implemented (at least partially) by Netscape 6 and Internet Explorer 5 and 6. This DOM standard[1] is a full-featured superset of the traditional web-browser DOM. It represents HTML (and XML) documents in a tree structure and defines properties and methods for traversing the tree and examining and modifying its nodes. Other portions of the standard specify techniques for defining event handlers for the nodes of a document, working with the style sheets of a document, and manipulating contiguous ranges of a document.

This chapter begins with an overview of the DOM standard and then describes the core portions of the standard for working with HTML documents. The discussion of the core standard is followed by short sections that explain the DOM-like features of Internet Explorer 4 and Netscape 4. The chapter ends with an overview of two optional parts of the DOM standard that are closely related to the core. Later chapters cover advanced DOM features for working with style sheets and events.

An Overview of the DOM

The DOM API is not particularly complicated, but before we can begin our discussion of programming with the DOM, there are a number of things you should understand about the DOM architecture.

Representing Documents as Trees

HTML documents have a hierarchical structure that is represented in the DOM as a tree structure. The nodes of the tree represent the various types of content in a document. The tree representation of an HTML document primarily contains nodes representing elements or tags such as <body> and <p> and nodes representing strings of text. An HTML document may also contain nodes representing HTML comments.[2] Consider the following simple HTML document:

<html>
  <head>
    <title>Sample Document</title>
  </head>
  <body>
    <h1>An HTML Document</h2>
    <p>This is a <i>simple</i> document.
  </body>
</html>

The DOM representation of this document is the tree pictured in Figure 17-1.

Figure 17-1. The tree representation of an HTML document

 

If you are not already familiar with tree structures in computer programming, it is helpful to know that they borrow terminology from family trees. The node directly above a node is the parent of that node. The nodes one level directly below another node are the children of that node. Nodes at the same level, and with the same parent, are siblings. The set of nodes any number of levels below another node are the descendants of that node. And the parent, grandparent, and all other nodes above a node are the ancestors of that node.

Nodes

The DOM tree structure illustrated in Figure 17-1 is represented as a tree of various types of Node objects. The Node interface[3] defines properties and methods for traversing and manipulating the tree. The childNodes property of a Node object returns a list of children of the node, and the firstChild, lastChild, nextSibling, previousSibling, and parentNode properties provide a way to traverse the tree of nodes. Methods such as appendChild( ), removeChild( ), replaceChild( ), and insertBefore( ) enable you to add and remove nodes from the document tree. We'll see examples of the use of these properties and methods later in this chapter.

Types of nodes

Different types of nodes in the document tree are represented by specific subinterfaces of Node. Every Node object has a nodeType property that specifies what kind of node it is. If the nodeType property of a node equals the constant Node.ELEMENT_NODE, for example, you know the Node object is also an Element object and you can use all the methods and properties defined by the Element interface with it. Table 17-1 lists the node types commonly encountered in HTML documents and the nodeType value for each one.

Table 17-1: Common node types

Interface

nodeType constant

nodeType value

Element

Node.ELEMENT_NODE

1

Text

Node.TEXT_NODE

3

Document

Node.DOCUMENT_NODE

9

Comment

Node.COMMENT_NODE

8

DocumentFragment

Node.DOCUMENT_FRAGMENT_NODE

11

Attr

Node.ATTRIBUTE_NODE

2

The Node at the root of the DOM tree is a Document object. The documentElement property of this object refers to an Element object that represents the root element of the document. For HTML documents, this is the <html> tag that is either explicit or implicit in the document. (The Document node may have other children, such as Comment nodes, in addition to the root element.) The bulk of a DOM tree consists of Element objects, which represent tags such as <html> and <i>, and Text objects, which represent strings of text. If the document parser preserves comments, those comments are represented in the DOM tree by Comment objects. Figure 17-2 shows a partial class hierarchy for these and other core DOM interfaces.

Figure 17-2. A partial class hierarchy of the core DOM API

 

Attributes

The attributes of an element (such as the src and width attributes of an <img> tag) may be queried, set, and deleted using the getAttribute( ), setAttribute( ), and removeAttribute( ) methods of the Element interface.

Another, more awkward way to work with attributes is with the getAttributeNode( ) method, which returns an Attr object representing an attribute and its value. (One reason to use this more awkward technique is that the Attr interface defines a specified property that allows you to determine whether the attribute is literally specified in the document, or whether its value is a default value.) The Attr interface appears in Figure 17-2, and it is a type of node. Note, however, that Attr objects do not appear in the childNodes[] array of an element and are not directly part of the document tree in the way that Element and Text nodes are. The DOM specification allows Attr nodes to be accessed through the attributes[] array of the Node interface, but Microsoft's Internet Explorer defines a different and incompatible attributes[] array that makes it impossible to use this feature portably.

The DOM HTML API

The DOM standard was designed for use with both XML and HTML documents. The core DOM API -- the Node, Element, Document, and other interfaces -- are relatively generic and apply to both types of documents. The DOM standard also includes interfaces that are specific to HTML documents. As you can see from Figure 17-2, HTMLDocument is an HTML-specific subinterface of Document, and HTMLElement is an HTML-specific subinterface of Element. Furthermore, the DOM defines tag-specific interfaces for many HTML elements. These tag-specific interfaces, such as HTMLBodyElement and HTMLTitleElement, typically define a set of properties that mirror the HTML tag's attributes.

The HTMLDocument interface defines various document properties and methods that were supported by browsers prior to W3C standardization. These include the location property, forms[] array, and write( ) method, which are described in Chapters 13, 14, and 15.

The HTMLElement interface defines id, style, title, lang, dir, and className properties. These properties allow convenient access to the values of the id, style, title, lang, dir, and class attributes, which are allowed on all HTML tags. A number of HTML tags, listed in Table 17-2, accept no attributes other than these six, and so are fully represented by the HTMLElement interface.

Table 17-2: Simple HTML tags

<abbr>

<acronym>

<address>

<b>

<bdo>

<big>

<center>

<cite>

<code>

<dd>

<dfn>

<dt>

<em>

<i>

<kbd>

<noframes>

<noscript>

<s>

<samp>

<small>

<span>

<strike>

<strong>

<sub>

<sup>

<tt>

<u>

<var>

 

 

All other HTML tags have corresponding interfaces defined by the HTML portion of the DOM specification. For many HTML tags, these interfaces do nothing more than provide a set of properties that mirror their HTML attributes. For example, the <ul> tag has a corresponding HTMLUListElement interface, and the <body> tag has a corresponding HTMLBodyElement interface. Because these interfaces simply define properties that are standardized by the HTML standard, they are not documented in detail in this book. You can safely assume that the HTMLElement object that represents a particular HTML tag has properties for each of the standard attributes for that tag (but see the naming conventions described in the next section). Note that the DOM standard defines properties for HTML attributes as a "convenience" to script writers. The general (and possibly preferred) way to query and set attribute values is with the getAttribute( ) and setAttribute( ) methods of the Element object.

Some of the interfaces defined in the HTML DOM define additional properties or methods, other than those that mirror HTML attribute values. For example, the HTMLInputElement interface defines focus( ) and blur( ) methods, and the HTMLFormElement interface defines submit( ) and reset( ) methods and a length property. Methods and properties like these typically predate DOM standardization and have been made part of the DOM standard for backward compatibility with existing practice. Interfaces like these are documented in the DOM reference section. You can usually also find information about the "existing practice" portions of these interfaces in the client-side reference section, although this information is typically referenced under a name that also predates DOM standardization; for example, you can find information about HTMLFormElement and HTMLInputElement in the client-side reference section under "Form" and "Input."

HTML naming conventions

When working with the HTML-specific portions of the DOM standard, you should be aware of some simple naming conventions. Properties of the HTML-specific interfaces begin with lowercase letters. If the property name consists of multiple words, the first letters of the second and subsequent words are capitalized. Thus, the maxlength attribute of the <input> tag translates into the maxLength property of HTMLInputElement.

When an HTML attribute name conflicts with a JavaScript keyword, it is prefixed with the string "html" to avoid the conflict. Thus, the for attribute of the <label> tag translates to the htmlFor property of the HTMLLabelElement. An exception to this rule is the class attribute (which can be specified for any HTML element); it translates to the className property of HTMLElement.[4]

DOM Levels and Features

There are two versions, or "levels," of the DOM standard. DOM Level 1 was standardized in October, 1998. It defines the core DOM interfaces, such as Node, Element, Attr, and Document, and also defines various HTML-specific interfaces. DOM Level 2 was standardized in November, 2000.[5] In addition to some updates to the core interfaces, this new version of the DOM is greatly expanded to define standard APIs for working with document events and CSS style sheets and to provide additional tools for working with ranges of documents. As of this writing, the DOM Working Group at the W3C is working to standardize DOM Level 3. You may also sometimes see a reference to DOM Level 0. This term does not refer to any formal standard but is used to refer informally to the common features of the HTML document object models implemented by Netscape and Internet Explorer prior to W3C standardization.

As of Level 2, the DOM standard has been "modularized." The core module, which defines the basic tree structure of a document with the Document, Node, Element, and Text interfaces (among others), is the only required module. All other modules are optional and may or may not be supported, depending on the needs of the implementation. The DOM implementation of a web browser would obviously support the HTML module, since web documents are written in HTML. Browsers that support CSS style sheets typically support the StyleSheets and CSS modules, because (as we'll see in Chapter 18) CSS styles play a crucial role in Dynamic HTML programming. Similarly, since almost all interesting client-side JavaScript programming requires event-handling capabilities, you would expect web browsers to support the Events module of the DOM specification. Unfortunately, the Events module was only recently defined by the DOM Level 2 specification and is not yet widely supported at the time of this writing. We'll see a complete list of DOM Level 2 modules in the next section.

DOM Conformance

At the time of this writing, no browser is completely conformant to the DOM standard. Recent releases of Mozilla come closest, and complete DOM Level 2 conformance is a goal of the Mozilla project. Netscape 6.1 does a good job of conforming to the most important Level 2 modules, and Netscape 6.0 does an adequate job but has gaps in its coverage. Internet Explorer 6 is mostly compliant (with at least one annoying exception) with the Level 1 DOM, but does not support many of the Level 2 modules -- most notably the Events module, which is the topic of Chapter 19. Internet Explorer 5 and 5.5 have substantial gaps in their conformance but support key DOM Level 1 methods well enough to run most of the examples in this chapter. The Macintosh version of IE 5 has considerably better support for the DOM than the Windows version of IE 5.

In addition to Mozilla, Netscape, and Internet Explorer, several other browsers offer at least partial support for the DOM. The number of available browsers has become too large, and the rate of change in the area of standards support has grown too fast, for this book to even attempt to provide definitive statements about which browsers support which particular DOM features. Therefore, you'll have to rely on other information sources to determine exactly how conformant the DOM implementation in any particular web browser is.

One source for conformance information is the implementation itself. In a conformant implementation, the implementation property of the Document object refers to a DOMImplementation object that defines a method named hasFeature( ). You can use this method (if it exists) to ask an implementation whether it supports a specific feature (or module) of the DOM standard. For example, to determine whether the DOM implementation in a web browser supports the basic DOM Level 1 interfaces for working with HTML documents, you could use the following code:

if (document.implementation &&
    document.implementation.hasFeature &&
    document.implementation.hasFeature("html", "1.0")) {
    // The browser claims to support Level 1 Core and HTML interfaces
} 

The hasFeature( ) method takes two arguments: the first is the name of the feature to check, and the second is a version number, expressed as a string. It returns true if the specified version of the specified feature is supported. Table 17-3 lists the feature name/version number pairs that are defined by the DOM Level 1 and Level 2 standards. Note that the feature names are case-insensitive: you can capitalize them any way you choose. The fourth column of the table specifies what other features are required for support of a feature and are therefore implied by a return value of true. For example, if hasFeature( ) indicates that the MouseEvents module is supported, this implies that UIEvents is also supported, which in turn implies that the Events, Views, and Core modules are supported.

Table 17-3: Features that can be tested with hasFeature( )

Feature name

Version

Description

Implies

HTML

1.0

Level 1 Core and HTML interfaces

 

XML

1.0

Level 1 Core and XML interfaces

 

Core

2.0

Level 2 Core interfaces

 

HTML

2.0

Level 2 HTML interfaces

Core

XML

2.0

Level 2 XML-specific interfaces

Core

Views

2.0

AbstractView interface

Core

StyleSheets

2.0

Generic style-sheet traversal

Core

CSS

2.0

CSS styles

Core, Views

CSS2

2.0

CSS2Properties interface

CSS

Events

2.0

Event-handling infrastructure

Core

UIEvents

2.0

User-interface events (plus Events and Views)

Events, Views

MouseEvents

2.0

Mouse events

UIEvents

HTMLEvents

2.0

HTML events

Events

MutationEvents

2.0

Document mutation events

Events

Range

2.0

Document range interfaces

Core

Traversal

2.0

Document traversal interfaces

Core

In Internet Explorer 6 (on Windows), hasFeature( ) returns true only for the feature HTML and Version 1.0. It does not report compliance to any of the other features listed in Table 17-3 (although, as we'll see in Chapter 18, it supports the most common uses of the CSS2 module.) In Netscape 6.1, hasFeature( ) returns true for most feature names and version numbers, with the notable exceptions of the Traversal and MutationEvents features. It returns false for the Core and CSS2 features with Version 2.0, indicating incomplete support (even though support for these features is quite good).

This book documents the interfaces that make up all of the DOM modules listed in Table 17-3. The Core, HTML, Traversal, and Range modules are covered in this chapter. The StyleSheets, CSS, and CSS2 modules are covered in Chapter 18, and the various Event modules (except MutationEvents) are covered in Chapter 19. The DOM reference section includes complete coverage of all modules.

The hasFeature( ) method is not always perfectly reliable. As previously noted, IE 6 reports Level 1 compliance to HTML features even though there are some problems with its compliance. On the other hand, Netscape 6.1 reports noncompliance to the Level 2 Core feature even though it is mostly compliant. In both cases, you need more detailed information about exactly what is and is not compliant. This is exactly the type of information that is too voluminous and volatile to include in a printed book.

If you are an active web developer, you undoubtedly already know or will discover many browser-specific support details on your own. There are also resources on the Web that can help you. Most importantly, the W3C (in collaboration with the U.S. National Institute of Standards and Technology) is working on developing an open source test suite for DOM implementations. At the time of this writing, the test suite effort is just getting off the ground, but it ought to prove to be an invaluable resource for fine-grained compliance testing of DOM implementations. See http://www.w3c.org/DOM/Test/ for details.

The Mozilla organization has a set of test suites for a variety of standards, including DOM Level 1 (available at http://www.mozilla.org/quality/browser_sc.html). Netscape has published a test suite that includes some DOM Level 2 tests (available at http://developer.netscape.com/evangelism/tools/testsuites/). Netscape has also published a partisan (and dated) comparison of DOM compliance of an early Mozilla release versus IE 5.5 (available at http://home.netscape.com/browsers/future/standards.html). Finally, you can also find compatibility and compliance information at independent sites on the Web. One notable site is published by Peter-Paul Koch. You can find a link to his DOM Compatibility Table from his main JavaScript page (http://www.xs4all.nl/~ppk/js/).

DOM conformance in Internet Explorer

Because IE is the most widely used web browser, a few special notes about its compliance to the DOM specifications are appropriate here. IE 5 and later versions support the Level 1 Core and HTML features well enough to run the examples in this chapter, and they support the key Level 2 CSS features well enough to run most of the examples in Chapter 18. Unfortunately, IE 5, 5.5, and 6 do not support the DOM Level 2 Events module, even though Microsoft participated in the definition of this module and had ample time to implement it for IE 6. As we'll see in Chapter 19, event handling is crucial for client-side event handling, and IE's lack of support for the standard event model impedes the development of advanced client-side web applications.

Although IE 6 claims (through its hasFeature( ) method) to support the Core and HTML interfaces of the DOM Level 1 standard, this support is actually incomplete. The most egregious problem, and the one you are most likely to encounter, is a minor but annoying one: IE does not support the node-type constants defined by the Node interface. Recall that each node in a document has a nodeType property that specifies what type of node it is. The DOM specification also says that the Node interface defines constants that represent each of the defined node types. For example, the constant Node.ELEMENT_NODE represents an Element node. In IE (at least as high as version 6), these constants simply do not exist.

The examples in this chapter have been modified to work around this problem by using integer literals instead of the corresponding symbolic constants. For example, you'll see code like this:

if (n.nodeType == 1 /*Node.ELEMENT_NODE*/)  // Check if n is an Element

It is good programming style to use constants instead of hardcoded integer literals in your code, and if you'd like to do this portably, you can include the following code in your programs to define these constants if they are missing:

if (!window.Node) {
    var Node = {            // If there is no Node object, define one
        ELEMENT_NODE: 1,    // with the following properties and values.
        ATTRIBUTE_NODE: 2,  // Note that these are HTML node types only.
        TEXT_NODE: 3,       // For XML-specific nodes, you need to add
        COMMENT_NODE: 8,    // other constants here.
        DOCUMENT_NODE: 9,
        DOCUMENT_FRAGMENT_NODE: 11
    }
} 

Language-Independent DOM Interfaces

Although the DOM standard grew out of a desire to have a common API for dynamic HTML programming, the DOM is not of interest only to web scripters. In fact, the standard is currently most heavily used by server-side Java and C++ programs that parse and manipulate XML documents. Because of its many uses, the DOM standard is defined to be language-independent. This book describes only the JavaScript binding of the DOM API, but you should be aware of a few other points. First, note that object properties in the JavaScript binding are typically mapped to pairs of get/set methods in other language bindings. Thus, when a Java programmer asks you about the getFirstChild( ) method of the Node interface, you need to understand that the JavaScript binding of the Node API doesn't define a getFirstChild( ) method. Instead, it simply defines a firstChild property, and reading the value of this property in JavaScript is equal to calling getFirstChild( ) in Java.

Another important feature of the JavaScript binding of the DOM API is that certain DOM objects behave like JavaScript arrays. If an interface defines a method named item( ), objects that implement that interface behave like read-only numerical arrays. For example, suppose you've obtained a NodeList object by reading the childNodes property of a node. You can obtain the individual Node objects in the list by passing the desired node number to the item( ) method, or, more simply, you can simply treat the NodeList object as an array and index it directly. The following code illustrates these two options:

var n = document.documentElement;  // This is a Node object.
var children = n.childNodes;       // This is a NodeList object.
var head = children.item(0);       // Here is one way to use a NodeList.
var body = children[1];            // But this way is easier!

Similarly, if a DOM object has a namedItem( ) method, passing a string to this method is the same as using the string as an array index for the object. For example, the following lines of code are all equivalent ways to access a form element:

var f = document.forms.namedItem("myform");
var g = document.forms["myform"];
var h = document.forms.myform;

Because the DOM standard may be used in a variety of ways, the architects of the standard were careful to define the DOM API in a way that would not restrict the ability of others to implement the API as they saw fit. Specifically, the DOM standard defines interfaces instead of classes. In object-oriented programming, a class is a fixed data type that must be implemented exactly as specified. An interface, on the other hand, is a collection of methods and properties that must be implemented together. Thus, an implementation of the DOM is free to define whatever classes it sees fit, but those classes must define the methods and properties of the various DOM interfaces.

This architecture has a couple of important implications. First, the class names used in an implementation might not correspond directly to the interface names used in the DOM standard (and in this book). Second, a single class may implement more than one interface. For example, consider the Document object. This object is an instance of some class defined by the web browser implementation. We don't know what the specific class is, but we do know that it implements the Document interface; that is, all methods and properties defined by Document are available to us through the Document object. Since web browsers work with HTML documents, we also know that the Document object implements the HTMLDocument interface and that all methods and properties defined by that interface are available to us as well. Furthermore, if a web browser supports CSS style sheets and implements the DOM CSS module, the Document object also implements the DocumentStyle and DocumentCSS DOM interfaces. And if the web browser supports the Events and Views modules, Document implements the DocumentEvent and DocumentView interfaces as well.

Because the DOM is broken into independent modules, it defines a number of minor add-on interfaces, such as DocumentStyle, DocumentEvent, and DocumentView, that define only one or two methods each. Interfaces such as these are never implemented independently of the core Document interface, and for this reason, I do not document them independently. When you look up the Document interface in the DOM reference section, you'll find that it also lists the methods and properties of its various add-on interfaces. Similarly, if you look up one of the add-on interfaces, you'll simply find a cross-reference to the core interface with which it is associated. The exception to this rule is when the add-on interface is a complex one. For example, the HTMLDocument interface is always implemented by the same object that implements the Document object, but because it adds substantial new functionality, I have given it a reference page of its own.

Another important fact you need to understand is that since the DOM standard defines interfaces instead of classes, it does not define any constructor methods. If you want to create a new Text object to insert into a document, for example, you cannot simply say:

var t = new Text("this is a new text node");  // No such constructor! 

Since it cannot define constructors, the DOM standard instead defines a number of useful factory methods for creating objects in the Document interface. So, to create a new Text node for a document, you would write the following:

var t = document.createTextNode("this is a new text node"); 

Factory methods defined by the DOM have names that begin with the word "create". In addition to the factory methods defined by Document, a few others are defined by DOMImplementation and available as document.implementation.

Using the Core DOM API

Now that we've studied the tree structure of documents and seen how the tree is composed of Node objects, we can move on to study the Node object and document trees in more detail. As I noted previously, the core DOM API is not terribly complex. The following sections contain examples that demonstrate how you can use it to accomplish common tasks.

Traversing a Document

As we've already discussed, the DOM represents an HTML document as a tree of Node objects. With any tree structure, one of the most common things to do is traverse the tree, examining each node of the tree in turn. Example 17-1 shows one way to do this. It is a JavaScript function that recursively examines a node and all its children, adding up the number of HTML tags (i.e., Element nodes) it encounters in the course of the traversal. Note the use of the childNodes property of a node. The value of this property is a NodeList object, which behaves (in JavaScript) like an array of Node objects. Thus, the function can enumerate all the children of a given node by looping through the elements of the childNodes[] array. By recursing, the function enumerates not just all children of a given node, but all nodes in the tree of nodes. Note that this function also demonstrates the use of the nodeType property to determine the type of each node.

Example 17-1: Traversing the nodes of a document

<head>
<script>
// This function is passed a DOM Node object and checks to see if that node 
// represents an HTML tag; i.e., if the node is an Element object. It
// recursively calls itself on each of the children of the node, testing
// them in the same way. It returns the total number of Element objects
// it encounters. If you invoke this function by passing it the
// Document object, it traverses the entire DOM tree.
function countTags(n) {                         // n is a Node 
    var numtags = 0;                            // Initialize the tag counter
    if (n.nodeType == 1 /*Node.ELEMENT_NODE*/)  // Check if n is an Element
        numtags++;                              // Increment the counter if so
    var children = n.childNodes;                // Now get all children of n
    for(var i=0; i < children.length; i++) {    // Loop through the children
        numtags += countTags(children[i]);      // Recurse on each one
    }
    return numtags;                             // Return the total number of tags
}
</script>
</head>
<!-- Here's an example of how the countTags(  ) function might be used -->
<body onload="alert('This document has ' + countTags(document) + ' tags')">
This is a <i>sample</i> document.
</body>

Another point to notice about Example 17-1 is that the countTags( ) function it defines is invoked from the onload event handler, so that it is not called until the document is completely loaded. This is a general requirement when working with the DOM: you cannot traverse or manipulate the document tree until the document has been fully loaded.

In addition to the childNodes property, the Node interface defines a few other useful properties. firstChild and lastChild refer to the first and last children of a node, and nextSibling and previousSibling refer to adjacent siblings of a node. (Two nodes are siblings if they have the same parent node.) These properties provide another way to loop through the children of a node, demonstrated in Example 17-2. This example counts the number of characters in all the Text nodes within the <body> of the document. Notice the way the countCharacters( ) function uses the firstChild and nextSibling properties to loop through the children of a node.

Example 17-2: Another way to traverse a document

<head>
<script>
// This function is passed a DOM Node object and checks to see if that node 
// represents a string of text; i.e., if the node is a Text object. If
// so, it returns the length of the string. If not, it recursively calls
// itself on each of the children of the node and adds up the total length
// of the text it finds. Note that it enumerates the children of a node
// using the firstChild and nextSibling properties. Note also that the 
// function does not recurse when it finds a Text node, because Text nodes 
// never have children.
function countCharacters(n) {                // n is a Node 
    if (n.nodeType == 3 /*Node.TEXT_NODE*/)  // Check if n is a Text object
        return n.length;                     // If so, return its length
    // Otherwise, n may have children whose characters we need to count
    var numchars = 0;  // Used to hold total characters of the children
    // Instead of using the childNodes property, this loop examines the
    // children of n using the firstChild and nextSibling properties.
    for(var m = n.firstChild; m != null; m = m.nextSibling) {
        numchars += countCharacters(m);  // Add up total characters found
    }
    return numchars;                     // Return total characters
}
</script>
</head>
<!-- 
  The onload event handler is an example of how the countCharacters(  )
  function might be used. Note that we want to count only the characters
  in the <body> of the document, so we pass document.body to the function.
-->
<body onload="alert('Document length: ' + countCharacters(document.body))">
This is a sample document.<p>How long is it?
</body>

Finding Specific Elements in a Document

The ability to traverse all nodes in a document tree gives us the power to find specific nodes. When programming with the DOM API, it is quite common to need a particular node within the document or a list of nodes of a specific type within the document. Fortunately, the DOM API provides functions that make this easy for us.

In Example 17-2, we referred to the <body> element of an HTML document with the JavaScript expression document.body. The body property of the Document object is a convenient special-case property and is the preferred way to refer to the <body> tag of an HTML document. If this convenience property did not exist, however, we could also refer to the <body> tag like this:

document.getElementsByTagName("body")[0] 

This expression calls the Document object's getElementsByTagName( ) method and selects the first element of the returned array. The call to getElementsByTagName( ) returns an array of all <body> elements within the document. Since HTML documents can have only one <body>, we know that we're interested in the first element of the returned array.[6]

You can use getElementsByTagName( ) to obtain a list of any type of HTML element. For example, to find all the tables within a document, you'd do this:

var tables = document.getElementsByTagName("table");
alert("This document contains " + tables.length + " tables"); 

Note that since HTML tags are not case-sensitive, the strings passed to getElementsByTagName( ) are also not case-sensitive. That is, the previous code finds <table> tags even if they are coded as <TABLE>. getElementsByTagName( ) returns elements in the order in which they appear in the document. Finally, if you pass the special string "*" to getElementsByTagName( ), it returns a list of all the elements in the document, in the order in which they appear. (This special usage is not supported in IE 5 and IE 5.5. See instead the IE-specific Document.all[] array in the client-side reference section.)

Sometimes you don't want a list of elements but instead want to operate on a single specific element of a document. If you know a lot about the structure of the document, you may be able to use getElementsByTagName( ). For example, if you want to do something to the fourth paragraph in a document, you might use this code:

var myParagraph = document.getElementsByTagName("p")[3]; 

This typically is not the best (nor the most efficient) technique, however, because it depends so heavily on the structure of the document; a new paragraph inserted at the beginning of the document would break the code. Instead, when you need to manipulate specific elements of a document, it is best to give those elements an id attribute that specifies a unique (within the document) name for the element. Then you can look up your desired element by its ID. For example, you might code the special fourth paragraph of your document with a tag like this:

<p id="specialParagraph"> 

You can then look up the node for that paragraph with JavaScript code like this:

var myParagraph = document.getElementById("specialParagraph"); 

Note that the getElementById( ) method does not return an array of elements like getElementsByTagName( ) does. Because the value of every id attribute is (or is supposed to be) unique, getElementById( ) returns only the single element with the matching id attribute. getElementById( ) is an important method, and its use is quite common in DOM programming.

getElementById( ) and getElementsByTagName( ) are both methods of the Document object. Element objects also define a getElementsByTagName( ) method, however. This method of the Element object behaves just like the method of the Document object, except that it returns only elements that are descendants of the element on which it is invoked. Instead of searching the entire document for elements of a specific type, it searches only within the given element. This makes it possible, for example, to use getElementById( ) to find a specific element and then to use getElementsByTagName( ) to find all descendants of a given type within that specific tag. For example:

// Find a specific Table element within a document and count its rows
var tableOfContents = document.getElementById("TOC");
var rows = tableOfContents.getElementsByTagName("tr");
var numrows = rows.length;

Finally, note that for HTML documents, the HTMLDocument object also defines a getElementsByName( ) method. This method is like getElementById( ), but it looks at the name attribute of elements rather than the id attribute. Also, because the name attribute is not expected to be unique within a document (for example, radio buttons within HTML forms usually have the same name), getElementsByName( ) returns an array of elements rather than a single element. For example:

// Find <a name="top">
var link = document.getElementsByName("top")[0];
// Find all <input type="radio" name="shippingMethod"> elements
var choices = document.getElementsByName("shippingMethod");

Modifying a Document

Traversing the nodes of a document can be useful, but the real power of the core DOM API lies in the features that allow you to use JavaScript to dynamically modify documents. The following examples demonstrate the basic techniques of modifying documents and illustrate some of the possibilities.

Example 17-3 includes a JavaScript function named reverse( ), a sample document, and an HTML button that, when pressed, calls the reverse( ) function, passing it the node that represents the <body> element of the document. (Note the use of getElementsByTagName( ) within the button's event handler to find the <body> element.) The reverse( ) function loops backward through the children of the supplied node and uses the removeChild( ) and appendChild( ) methods of the Node object to reverse the order of those children.

Example 17-3: Reversing the nodes of a document

<head><title>Reverse</title>
<script>
function reverse(n) {           // Reverse the order of the children of Node n 
    var kids = n.childNodes;    // Get the list of children
    var numkids = kids.length;  // Figure out how many children there are
    for(var i = numkids-1; i >= 0; i--) {  // Loop backward through the children
        var c = n.removeChild(kids[i]);    // Remove a child
        n.appendChild(c);                  // Put it back at its new position
    }
}
</script>
</head>
<body>
<p>paragraph #1<p>paragraph #2<p>paragraph #3  <!-- A sample document -->
<p>                                    <!-- A button to call reverse(  )-->
<button onclick="reverse(document.body);"
>Click Me to Reverse</button>
</body>

The result of Example 17-3, illustrated in Figure 17-3, is that when the user clicks the button, the order of the paragraphs and of the button are reversed.

Figure 17-3. A document before and after being reversed

 

There are a couple of points worth noting about Example 17-3. First, if you pass a node that is already part of the document to appendChild( ) it first removes it, so we could have simplified our code by omitting the call to removeChild( ). Second, keep in mind that the childNodes property (like all NodeList objects) is "live": when the document is modified, the modifications are immediately visible through the NodeList. This is an important features of the NodeList interface, but it can actually make some code trickier to write. A call to removeChild( ), for example, changes the index of all the siblings that follow that child, so if you want to iterate through a NodeList and delete some of the nodes, you must write your looping code carefully.

Example 17-4 shows a variation on the reverse( ) function of the previous example. This one uses recursion to reverse not only the children of a specified node, but also all the node's descendants. In addition, when it encounters a Text node, it reverses the order of the characters within that node. Example 17-4 shows only the JavaScript code for this new reverse( ) function. It could easily be used in an HTML document like the one shown in Example 17-3, however.

Example 17-4: A recursive node-reversal function

// Recursively reverse all nodes beneath Node n and reverse Text nodes
function reverse(n) { 
    if (n.nodeType == 3 /*Node.TEXT_NODE*/) {    // Reverse Text nodes
        var text = n.data;                       // Get content of node
        var reversed = "";
        for(var i = text.length-1; i >= 0; i--)  // Reverse it
            reversed += text.charAt(i);
        n.data = reversed;                       // Store reversed text
    }
    else {  // For non-Text nodes, recursively reverse the order of the children
        var kids = n.childNodes;
        var numkids = kids.length;
        for(var i = numkids-1; i >= 0; i--) {       // Loop through kids
            reverse(kids[i]);                       // Recurse to reverse kid
            n.appendChild(n.removeChild(kids[i]));  // Move kid to new position
        }
    }
}

Example 17-4 shows one way to change the text displayed in a document: simply set the data field of the appropriate Text node. Example 17-5 shows another way. This example defines a function, uppercase( ), that recursively traverses the children of a given node. When it finds a Text node, the function replaces that node with a new Text node containing the text of the original node, converted to uppercase. Note the use of the document.createTextNode( ) method to create the new Text node and the use of Node's replaceChild( ) method to replace the original Text node with the newly created one. Note also that replaceChild( ) is invoked on the parent of the node to be replaced, not on the node itself. The uppercase( ) function uses Node's parentNode property to determine the parent of the Text node it replaces.

In addition to defining the uppercase( ) function, Example 17-5 includes two HTML paragraphs and a button. When the user clicks the button, one of the paragraphs is converted to uppercase. Each paragraph is identified with a unique name, specified with the id attribute of the <p> tag. The event handler on the button uses the getElementById( ) method to get the Element object that represents the desired paragraph.

Example 17-5: Replacing nodes with their uppercase equivalents

<script>
// This function recursively looks at Node n and its descendants, 
// replacing all Text nodes with their uppercase equivalents.
function uppercase(n) {
    if (n.nodeType == 3 /*Node.TEXT_NODE*/) {
        // If the node is a Text node, create a new Text node that
        // holds the uppercase version of the node's text, and use the
        // replaceChild(  ) method of the parent node to replace the
        // original node with the new uppercase node.
        var newNode = document.createTextNode(n.data.toUpperCase(  ));
        var parent = n.parentNode;
        parent.replaceChild(newNode, n);
    }
    else {
        // If the node is not a Text node, loop through its children
        // and recursively call this function on each child.
        var kids = n.childNodes;
        for(var i = 0; i < kids.length; i++) uppercase(kids[i]);
    }
}
</script>
 
<!-- Here is some sample text. Note that the <p> tags have id attributes. -->
<p id="p1">This <i>is</i> paragraph 1.</p>
<p id="p2">This <i>is</i> paragraph 2.</p>
 
<!-- Here is a button that invokes the uppercase(  ) function defined above. -->
<!-- Note the call to document.getElementById(  ) to find the desired node. -->
<button onclick="uppercase(document.getElementById('p1'));">Click Me</button> 

The previous two examples show how to modify document content by replacing the text contained within a Text node and by replacing one Text node with an entirely new Text node. It is also possible to append, insert, delete, or replace text within a Text node with the appendData( ), insertData( ), deleteData( ), and replaceData( ) methods. These methods are not directly defined by the Text interface, but instead are inherited by Text from CharacterData. You can find more information about them under "CharacterData" in the DOM reference section.

In the node-reversal examples, we saw how we could use the removeChild( ) and appendChild( ) methods to reorder the children of a Node. Note, however, that we are not restricted to changing the order of nodes within their parent node; the DOM API allows nodes in the document tree to be moved freely within the tree (only within the same document, however). Example 17-6 demonstrates this by defining a function named embolden( ) that replaces a specified node with a new element (created with the createElement( ) method of the Document object) that represents an HTML <b> tag and "reparents" the original node as a child of the new <b> node. In an HTML document, this causes any text within the node or its descendants to be displayed in boldface.

Example 17-6: Reparenting a node to a <b> element

<script>
// This function takes a Node n, replaces it in the tree with an Element node
// that represents an HTML <b> tag, and then makes the original node the
// child of the new <b> element.
function embolden(node) {
    var bold = document.createElement("b");  // Create a new <b> element
    var parent = node.parentNode;            // Get the parent of the node
    parent.replaceChild(bold, node);         // Replace the node with the <b> tag
    bold.appendChild(node);                  // Make the node a child of the <b> tag
}
</script>
 
<!-- A couple of sample paragraphs -->
<p id="p1">This <i>is</i> paragraph #1.</p>
<p id="p2">This <i>is</i> paragraph #2.</p>
 
<!-- A button that invokes the embolden(  ) function on the first paragraph -->
<button onclick="embolden(document.getElementById('p1'));">Embolden</button>

In addition to modifying documents by inserting, deleting, reparenting, and otherwise rearranging nodes, it is also possible to make substantial changes to a document simply by setting attribute values on document elements. One way to do this is with the element.setAttribute( ) method. For example:

var headline = document.getElementById("headline");  // Find named element
headline.setAttribute("align", "center");            // Set align='center' 

The DOM elements that represent HTML attributes define JavaScript properties that correspond to each of their standard attributes (even deprecated attributes such as align), so you can also achieve the same effect with this code:

var headline = document.getElementById("headline");
headline.align = "center";  // Set alignment attribute. 

Adding Content to a Document

The previous two examples showed how the contents of a Text node can be changed to uppercase and how a node can be reparented to be a child of a newly created <b> node. The embolden( ) function showed that it is possible to create new nodes and add them to a document. You can add arbitrary content to a document by creating the necessary Element nodes and Text nodes with document.createElement( ) and document.createTextNode( ) and by adding them appropriately to the document. This is demonstrated in Example 17-7, which defines a function named debug( ). This function provides a convenient way to insert debugging messages into a program, and it serves as a useful alternative to using the built-in alert( ) function. A sample use of this debug( ) function is illustrated in Figure 17-4.

Figure 17-4. Output of the debug( ) function

 

The first time debug( ) is called, it uses the DOM API to create a <div> element and insert it at the end of the document. The debugging messages passed to debug( ) on this first call and all subsequent calls are then inserted into this <div> element. Each debugging message is displayed by creating a Text node within a <p> element and inserting that <p> element at the end of the <div> element.

Example 17-7 also demonstrates a convenient but nonstandard way to add new content to a document. The <div> element that contains the debugging messages displays a large, centered title. This title could be created and added to the document in the way that other content is, but in this example we instead use the innerHTML property of the <div> element. Setting this property of any element to a string of HTML text causes that HTML to be parsed and inserted as the content of the element. Although this property is not part of the DOM API, it is a useful shortcut that is supported by Internet Explorer 4 and later and Netscape 6. Although it is not standard, it is in common use and is included in this example for completeness.[7]

Example 17-7: Adding debugging output to a document

/**
 * This debug function displays plain-text debugging messages in a
 * special box at the end of a document. It is a useful alternative
 * to using alert(  ) to display debugging messages.
 **/
function debug(msg) {
    // If we haven't already created a box within which to display
    // our debugging messages, then do so now. Note that to avoid
    // using another global variable, we store the box node as
    // a proprty of this function.
    if (!debug.box) {
        // Create a new <div> element
        debug.box = document.createElement("div");
        // Specify what it looks like using CSS style attributes
        debug.box.setAttribute("style", 
                               "background-color: white; " +
                               "font-family: monospace; " +
                               "border: solid black 3px; " +
                               "padding: 10px;");
        
        // Append our new <div> element to the end of the document
        document.body.appendChild(debug.box);
 
        // Now add a title to our <div>. Note that the innerHTML property is
        // used to parse a fragment of HTML and insert it into the document.
        // innerHTML is not part of the W3C DOM standard, but it is supported
        // by Netscape 6 and Internet Explorer 4 and later. We can avoid 
        // the use of innerHTML by explicitly creating the <h1> element,
        // setting its style attribute, adding a Text node to it, and 
        // inserting it into the document, but this is a nice shortcut.
        debug.box.innerHTML = "<h1 style='text-align:center'>Debugging Output</h2>";
    }
 
    // When we get here, debug.box refers to a <div> element into which
    // we can insert our debugging message.
    // First create a <p> node to hold the message.
    var p = document.createElement("p");
    // Now create a text node containing the message, and add it to the <p>
    p.appendChild(document.createTextNode(msg));
    // And append the <p> node to the <div> that holds the debugging output
    debug.box.appendChild(p);
}

The debug( ) method listed in Example 17-7 can be used in HTML documents like the following, which is the document that was used to generate Figure 17-4:

<script src="Debug.js"></script>  <!-- Include the debug(  ) function -->
<script>var numtimes=0;</script>  <!-- Define a global variable -->
<!-- Now use the debug(  ) function in an event handler -->
<button onclick="debug('clicked: ' + numtimes++);">press me</button>

Working with Document Fragments

The core DOM API defines the DocumentFragment object as a convenient way of working with groups of Document nodes. A DocumentFragment is a special type of node that does not appear in a document itself but serves as a temporary container for a sequential collection of nodes and allows those nodes to be manipulated as a single object. When a DocumentFragment is inserted into a document (using any of the appendChild( ), insertBefore( ), or replaceChild( ) methods of the Node object), it is not the DocumentFragment itself that is inserted, but each of its children.

As an example, you can use a DocumentFragment to rewrite the reverse( ) method of Example 17-3 like this:

function reverse(n) {  // Reverses the order of the children of Node n
    var f = document.createDocumentFragment(  );  // Get an empty DocumentFragment
    while(n.lastChild)                 // Loop backward through the children,
          f.appendChild(n.lastChild);  // moving each one to the DocumentFragment
    n.appendChild(f);                  // Then move them back (in their new order)
}

Once you have created a DocumentFragment, you can use it with code like this:

document.getElementsByTagName("p")[0].appendChild(fragment); 

Note that when you insert a DocumentFragment into a document, the child nodes of the fragment are moved from the fragment into the document. After the insertion, the fragment is empty and cannot be reused unless you first add new children to it. We'll see the DocumentFragment object again later in this chapter, when we examine the DOM Range API.

Example: A Dynamically Created Table of Contents

The previous sections showed how you can use the core DOM API to traverse, modify, and add content to a document. Example 17-8, at the end of this section, puts all these pieces together into a single longer example. The example defines a single method, maketoc( ), which expects a Document node as its single argument. maketoc( ) traverses the document, creates a table of contents (TOC) for it, and replaces the specified node with the newly created TOC. The TOC is generated by looking for <h1>, <h2>, <h3>, <h4>, <h5>, and <h6> tags within the document and assuming that these tags mark the beginnings of important sections within the document. In addition to creating a TOC, the maketoc( ) function inserts named anchors (<a> elements with the name attribute set instead of the href attribute) before each section heading so that the TOC can link directly to each section. Finally, maketoc( ) also inserts links at the beginning of each section back to the TOC; when the reader reaches a new section, she can either read that section or follow the link back to the TOC and choose a new section. Figure 17-5 shows what a TOC generated by the maketoc( ) function looks like.

Figure 17-5. A dynamically created table of contents

 

If you maintain and revise long documents that are broken into sections with <h1>, <h2>, and related tags, the maketoc( ) function may be of interest to you. TOCs are quite useful in long documents, but when you frequently revise a document it can be difficult to keep the TOC in sync with the document itself. The TOC for this book was automatically created by postprocessing the content of the book. maketoc( ) allows you to do something similar for your web documents. You can use the function in an HTML document like this one:

<script src="TOC.js"></script>  <!-- Load the maketoc(  ) function -->
<!-- Call the maketoc(  ) function when the document is fully loaded -->
<body onload="maketoc(document.getElementById('placeholder'))">
<!-- This span element will be replaced by the generated TOC -->
<span id="placeholder">Table Of Contents</span>
// ... rest of document goes here ... 

Another way to use the maketoc( ) function is to generate the TOC only when the reader requests it. You can do this by including a link (or button) that replaces itself with the generated TOC when the user clicks on it:

<a href="#" onclick="maketoc(this); return false;">Show Table Of Contents</a>

The code for the maketoc( ) function follows. This example is long, but it is well commented and uses techniques that have already been demonstrated. It is worth studying as a practical example of the power of the DOM API. Note that the maketoc( ) function relies on two helper functions. For modularity, these helper functions are defined inside maketoc( ) itself. This prevents the addition of extra unnecessary functions to the global namespace.

Example 17-8: Automatically generating a table of contents

/**
 * Create a table of contents for this document, and insert the TOC into
 * the document by replacing the node specified by the replace argument.
 **/
function maketoc(replace) {
    // Create a <div> element that is the root of the TOC tree
    var toc = document.createElement("div");
 
    // Set a background color and font for the TOC. We'll learn about
    // the style property in the next chapter.
    toc.style.backgroundColor = "white";
    toc.style.fontFamily = "sans-serif";
 
    // Start the TOC with an anchor so we can link back to it
    var anchor = document.createElement("a");  // Create an <a> node
    anchor.setAttribute("name", "TOC");        // Give it a name
    toc.appendChild(anchor);                   // Insert it
 
    // Make the body of the anchor the title of the TOC
    anchor.appendChild(document.createTextNode("Table Of Contents"));
 
    // Create a <table> element that will hold the TOC and add it 
    var table = document.createElement("table");
    toc.appendChild(table);
 
    // Create a <tbody> element that holds the rows of the TOC
    var tbody = document.createElement("tbody");
    table.appendChild(tbody);
 
    // Initialize an array that keeps track of section numbers
    var sectionNumbers = [0,0,0,0,0,0];

    // Recursively traverse the body of the document, looking for sections
    // sections marked with <h1>, <h2>, ... tags, and use them to create 
    // the TOC by adding rows to the table
    addSections(document.body, tbody, sectionNumbers);
 
    // Finally, insert the TOC into the document by replacing the node
    // specified by the replace argument with the TOC subtree
    replace.parentNode.replaceChild(toc, replace);

    // This method recursively traverses the tree rooted at Node n, looking
    // looking for <h1> through <h6> tags, and uses the content of these tags
    // to build the table of contents by adding rows to the HTML table specified
    // by the toc argument. It uses the sectionNumbers array to keep track of
    // the current section number.
    // This function is defined inside of maketoc(  ) so that it is not
    // visible from the outside. maketoc(  ) is the only function exported
    // by this JavaScript module.
    function addSections(n, toc, sectionNumbers) {
        // Loop through all the children of n
        for(var m = n.firstChild; m != null; m = m.nextSibling) {
            // Check whether m is a heading element. It would be nice if we
            // could just use (m instanceof HTMLHeadingElement), but this is
            // not required by the specification and it does not work in IE.
            // Therefore, we must check the tagname to see if it is H1-H6.
            if ((m.nodeType == 1) &&  /* Node.ELEMENT_NODE */ 
                (m.tagName.length == 2) && (m.tagName.charAt(0) == "H")) {
                // Figure out what level heading it is
                var level = parseInt(m.tagName.charAt(1));
                if (!isNaN(level) && (level >= 1) && (level <= 6)) {
                    // Increment the section number for this heading level
                    sectionNumbers[level-1]++;
                    // And reset all lower heading-level numbers to zero
                    for(var i = level; i < 6; i++) sectionNumbers[i] = 0;
                    // Now combine section numbers for all heading levels
                    // to produce a section number like "2.3.1"
                    var sectionNumber = "";
                    for(var i = 0; i < level; i++) {
                        sectionNumber += sectionNumbers[i];
                        if (i < level-1) sectionNumber += ".";
                    }
 
                    // Create an anchor to mark the beginning of this section
                    // This will be the target of a link we add to the TOC
                    var anchor = document.createElement("a");
                    anchor.setAttribute("name", "SECT"+sectionNumber);
 
                    // Create a link back to the TOC and make it a
                    // child of the anchor
                    var backlink = document.createElement("a");
                    backlink.setAttribute("href", "#TOC");
                    backlink.appendChild(document.createTextNode("Contents"));
                    anchor.appendChild(backlink);
 
                    // Insert the anchor into the document right before the
                    // section header
                    n.insertBefore(anchor, m);
 
                    // Now create a link to this section. It will be added
                    // to the TOC below.
                    var link = document.createElement("a");
                    link.setAttribute("href", "#SECT" + sectionNumber);
                    // Get the heading text using a function defined below
                    var sectionTitle = getTextContent(m);
                    // Use the heading text as the content of the link
                    link.appendChild(document.createTextNode(sectionTitle));
 
                    // Create a new row for the TOC
                    var row = document.createElement("tr");
                    // Create two columns for the row
                    var col1 = document.createElement("td");
                    var col2 = document.createElement("td");
                    // Make the first column right-aligned and put the section
                    // number in it
                    col1.setAttribute("align", "right");
                    col1.appendChild(document.createTextNode(sectionNumber));
                    // Put a link to the section in the second column
                    col2.appendChild(link);
                    // Add the columns to the row, and the row to the table
                    row.appendChild(col1);
                    row.appendChild(col2);
                    toc.appendChild(row);
 
                    // Modify the section header element itself to add
                    // the section number as part of the section title
                    m.insertBefore(document.createTextNode(sectionNumber+": "),
                                   m.firstChild);
                }
            }
            else {  // Otherwise, this is not a heading element, so recurse
                addSections(m, toc, sectionNumbers);
            }
        }
    }
 
    // This utility function traverses Node n, returning the content of
    // all Text nodes found and discarding any HTML tags. This is also
    // defined as a nested function, so it is private to this module.
    function getTextContent(n) {
        var s = '';
        var children = n.childNodes;
        for(var i = 0; i < children.length; i++) {
            var child = children[i];
            if (child.nodeType == 3 /*Node.TEXT_NODE*/) s += child.data;
            else s += getTextContent(child);
        }
        return s;
    }
}

Working with XML Documents

Web browsers display HTML documents, but XML documents are becoming more and more important as sources of data. Since the DOM allows us to traverse and manipulate both HTML and XML documents, we can use DOM methods to load an XML document, extract information from it, and dynamically create an HTML version of that information for display in a web browser. Example 17-9 shows how this can be done in Netscape 6.1 and Internet Explorer 6. It is an HTML file that consists mostly of JavaScript code. The file expects to be loaded through a URL that uses the URL query string to specify the relative URL of the data file to load. For example, you might invoke this example file with a URL like this:

file://C:/javascript/DisplayEmployeeData.html?data.xml 

DisplayEmployeeData.html is the name of the example file, and data.xml is the name of the XML file it uses. The XML file must contain data formatted like this:

<employees>
  <employee name="J. Doe"><job>Programmer</job><salary>32768</salary></employee>
  <employee name="A. Baker"><job>Sales</job><salary>70000</salary></employee>
  <employee name="Big Cheese"><job>CEO</job><salary>1000000</salary></employee>
</employees> 

The example contains two JavaScript functions. The first, loadXML( ), is a generic function for loading any XML file. It contains standard DOM Level 2 code to load the XML document and also code that uses a proprietary Microsoft API to accomplish the same thing. The only really new thing in this example is the creation of a new Document object with the DOMImplementation.createDocument( ) method and the call to the load( ) method of that Document object. An important thing to notice here is that documents do not load instantaneously, so the call to loadXML( ) returns before the document is loaded. For this reason, we pass loadXML( ) a reference to another function that it should call when the document has finished loading.

The other function in the example is makeTable( ). This is the function that we pass to loadXML( ). When the XML file finishes loading, it passes the Document object representing the XML file and the URL of the file to makeTable( ). makeTable( ) uses DOM methods we've seen before to extract information from the XML document and insert it into a table in the HTML document displayed by the browser. This function also illustrates the use of some table-related convenience methods defined by HTMLTableElement, HTMLTableRowElement, and related interfaces. See the DOM reference section for complete details about these table-specific interfaces and their methods. Although the DOM methods and properties used in this function are all straightforward, they are used in dense combinations. Study the code carefully and you should have no difficulty understanding it.

Example 17-9: Loading and reading data from an XML document

<head><title>Employee Data</title>
<script>
// This function loads the XML document from the specified URL and, when
// it is fully loaded, passes that document and the URL to the specified
// handler function. This function works with any XML document.
function loadXML(url, handler) {
    // Use the standard DOM Level 2 technique, if it is supported
    if (document.implementation && document.implementation.createDocument) {
        // Create a new Document object
        var xmldoc = document.implementation.createDocument("", "", null);
        // Specify what should happen when it finishes loading
        xmldoc.onload = function(  ) { handler(xmldoc, url); }
        // And tell it what URL to load
        xmldoc.load(url);
    }
    // Otherwise, use Microsoft's proprietary API for Internet Explorer
    else if (window.ActiveXObject) { 
        var xmldoc = new ActiveXObject("Microsoft.XMLDOM");   // Create doc
        xmldoc.onreadystatechange = function(  ) {              // Specify onload
            if (xmldoc.readyState == 4) handler(xmldoc, url);
        }
        xmldoc.load(url);                                     // Start loading!
    }
}
 
// This function builds an HTML table of employees from data it reads from
// the XML document it is passed
function makeTable(xmldoc, url) {
    // Create a <table> object and insert it into the document
    var table = document.createElement("table");
    table.setAttribute("border", "1");
    document.body.appendChild(table);
 
    // Use convenience methods of HTMLTableElement and related interfaces
    // to define a table caption and a header that gives a name to each column
    var caption = "Employee Data from " + url;
    table.createCaption(  ).appendChild(document.createTextNode(caption));
    var header = table.createTHead(  );
    var headerrow = header.insertRow(0);
    headerrow.insertCell(0).appendChild(document.createTextNode("Name"));
    headerrow.insertCell(1).appendChild(document.createTextNode("Job"));
    headerrow.insertCell(2).appendChild(document.createTextNode("Salary"));
    
    // Now find all <employee> elements in our xmldoc document
    var employees = xmldoc.getElementsByTagName("employee");
 
    // Loop through these <employee> elements
    for(var i = 0; i < employees.length; i++) {
        // For each employee, get name, job, and salary data using standard DOM
        // methods. The name comes from an attribute. The other values are
        // in Text nodes within <job> and <salary> tags.
        var e = employees[i];
        var name = e.getAttribute("name");
        var job = e.getElementsByTagName("job")[0].firstChild.data;
        var salary = e.getElementsByTagName("salary")[0].firstChild.data;
 
        // Now that we have the employee data, use methods of the table to
        // create a new row and then use the methods of the row to create
        // new cells containing the data as Text nodes
        var row = table.insertRow(i+1);
        row.insertCell(0).appendChild(document.createTextNode(name));
        row.insertCell(1).appendChild(document.createTextNode(job));
        row.insertCell(2).appendChild(document.createTextNode(salary));
    }
}
</script>
</head>
<!-- 
The body of the document contains no static text; everything is dynamically
generated by the makeTable(  ) function. The onload event handler starts
things off by calling loadXML(  ) to load the XML data file. Note the use of
location.search to encode the name of the XML file in the query string. Load
this HTML file with a URL like this: DisplayEmployeeData.html?data.xml.
-->
<body onload="loadXML(location.search.substring(1), makeTable)">
</body>

DOM Compatibility with Internet Explorer 4

Although IE 4 is not DOM-compliant, it has features that are similar to the core DOM APIs. These features are not part of the DOM standard and are not compatible with Netscape, but they are compatible with later versions of IE. The features are summarized here; consult the client-side reference section of this book for more details.

Traversing a Document

The DOM standard specifies that all Node objects, which includes both the Document object and all Element objects, have a childNodes[] array that contains the children of that node. IE 4 does not support childNodes[], but it provides a very similar children[] array on its Document and HTMLElement objects. Thus, it is easy to write a recursive function like the one shown in Example 17-1 to traverse the complete set of HTML elements within an IE 4 document.

There is one substantial difference between IE 4's children[] array and the standard DOM childNodes[] array, however. IE 4 does not have a Text node type and does not consider strings of text to be children. Thus, a <p> tag that contains only plain text with no markup has an empty children[] array in IE 4. As we'll see shortly, however, the textual content of a <p> tag is available through the IE 4 innerText property.

Finding Document Elements

IE 4 does not support the getElementById( ) and getElementsByTagName( ) methods of the Document object. Instead, the Document object and all document elements have an array property named all[]. As the name suggests, this array represents all the elements in a document or all the elements contained within another element. Note that all[] does not simply represent the children of the document or the element--it represents all descendants, no matter how deeply nested.

The all[] array can be used in several ways. If you index it with an integer n, it returns the n+1th element of the document or the parent element. For example:

var e1 = document.all[0];  // The first element of the document
var e2 = e1.all[4];        // The fifth element of element 1 

Elements are numbered in the order in which they appear in the document source. Note the one big difference between the IE 4 API and the DOM standard: IE does not have a notion of Text nodes, so the all[] array contains only document elements, not the text that appears within them.

It is usually much more useful to be able to refer to document elements by name rather than number. The IE 4 equivalent to getElementbyId( ) is to index the all[] array with a string rather than a number. When you do this, IE 4 returns the element whose id or name attribute has the specified value. If there is more than one such element (which can happen, since it is common to have multiple form elements, such as radioboxes, with the same name attribute), the result is an array of those elements. For example:

var specialParagraph = document.all["special"];
var radioboxes = form.all["shippingMethod"];  // May return an array 

JavaScript also allows us to write these expressions by expressing the array index as a property name:

var specialParagraph = document.all.special;
var radioboxes = form.all.shippingMethod;

Using the all[] array in this way provides the same basic functionality as getElementById( ) and getElementsByName( ). The main difference is that the all[] array combines the features of these two methods, which can cause problems if you inadvertently use the same values for the id and name attributes of unrelated elements.

The all[] array has an unusual quirk: a tags( ) method that can be used to obtain an array of elements by tag name. For example:

var lists = document.all.tags("UL");  // Find all <ul> tags in the document
var items = lists[0].all.tags("LI");  // Find all <li> tags in the first <ul> 

This IE 4 syntax provides essentially the same functionality as the DOM Document and Element objects' getElementsByTagName( ) method. Note that in IE 4, the tag name should be specified using all capital letters.

Modifying Documents

Like the DOM standard, IE 4 exposes the attributes of HTML tags as properties of the corresponding HTMLElement objects. Thus, it is possible to modify a document displayed in IE 4 by dynamically changing its HTML attributes. If an attribute modification changes the size of any element, the document "reflows" to accommodate its new size. The IE 4 HTMLElement object defines setAttribute( ), getAttribute( ), and removeAttribute( ) methods as well. These are similar to the methods of the same name defined by the Element object in the standard DOM API.

The DOM standard defines an API that makes it possible to create new nodes, insert nodes into the document tree, reparent nodes, and move nodes within the tree. IE 4 cannot do this. Instead, however, all HTMLElement objects in IE 4 define an innerHTML property. Setting this property to a string of HTML text allows you to replace the content of an element with whatever you want. Because this innerHTML property is so powerful, it has been implemented by Netscape 6 (and the Mozilla browser from which it is derived), even though it is not part of the DOM standard. innerHTML was demonstrated in Example 17-7.

IE 4 also defines several related properties and methods. The outerHTML property replaces an element's content and the entire element itself with a specified string of HTML text. The innerText and outerText properties are similar to innerHTML and outerHTML, except that they treat the string as plain text and do not parse it as HTML. Finally, the insertAdjacentHTML( ) and insertAdjacentText( ) methods leave the content of an element alone but insert new HTML or plain-text content near (before or after, inside or outside) it. These properties and functions are not as commonly used as innerHTML and have not been implemented by Netscape 6. For further details, see "HTMLElement" in the client-side reference section.

DOM Compatibility with Netscape 4

Netscape 4 does not even come close to implementing the DOM standard. In particular, Netscape 4 provides no way to access or set attributes on arbitrary elements of a document. Netscape 4 supports the Level 0 DOM API, of course, so elements such as forms and links can be accessed through the forms[] and links[] arrays, but there is no general way to traverse the children of these elements or set arbitrary attributes on them. Furthermore, Netscape 4 does not have the ability to "reflow" document content in response to changes in element size.

Despite these restrictions, Netscape 4 does provide an API that allows access to and manipulation of the crucial "dynamic elements" used to implement DHTML effects. In the Netscape 4 API, these elements are known as layers; they float above the rest of the document and can be moved, resized, and modified independently of the other elements of the document. Layers are typically implemented using CSS style sheets, and the Netscape 4 Layer API is discussed in detail in Chapter 18.

What follows is simply an overview that explains how you can create, access, and modify the content of individual layer elements within a document. Although Netscape 4 does not support anything like the DOM standard, its Layer API allows you to achieve some of the same dynamic effects that are possible with the standard API. Note that the Layer API was submitted to the W3C for consideration as part of the DOM standard, but no part of this API was ever standardized. Because Netscape 6 is based on a complete rewrite of Netscape 4, the Layer API has been abandoned and is not supported in Netscape 6 (or in Mozilla).

Layers can be created in a document using the <layer> tag, a proprietary Netscape extension to HTML. More commonly, however, you create a layer in a Netscape 4 document using standard CSS positioning attributes (which will be explained in detail in Chapter 18). Any element made dynamic with CSS style attributes is treated as a layer by Netscape 4 and can be manipulated using the Layer API. (Note, though, that Netscape 4 does not allow all elements to be made dynamic. To be safe, a <div> wrapper element is usually used around any element that is to be dynamic.) JavaScript can also dynamically create layers using the Layer( ) constructor, which you can read about in the client-side reference section of this book.

Once you've created dynamic elements, or layers, in your document, Netscape 4 allows you to access them via a simple extension of the Level 0 DOM API. Just as you access form elements through a forms[] array and image elements through an images[] array, so do you access layers through a layers[] array of the Document object. If the first layer in a document has a name attribute of "layer1", you can refer to that layer element with any of the following expressions:

document.layers[0]         // Index the array with a number
document.layers['layer1']  // Index the array with an element name
document.layer1            // Named layers become a document property 

If a layer has no name attribute but has an id attribute, the value of this attribute is used as the layer name instead.

Layers in your documents are represented by Layer objects that define a number of useful properties and methods you can use to move, resize, show, hide, and set the stacking order of the layer. These properties and methods are related to CSS style attributes and will be discussed in Chapter 18. The most interesting thing about the Layer object is that it contains a Document object of its own: the content of a layer is treated as an entirely separate document from the document that contains the layer. This allows you to modify the content displayed by a layer by dynamically rewriting the content of the layer using the document.write( ) and document.close( ) methods. You can also dynamically load documents into a layer using Layer's load( ) method. Finally, note that layers may themselves contain layers, and you can refer to such nested layers with expressions like this:

// The second layer nested within the layer named "mylayer"
document.mylayer.document.layers[1];

Convenience Methods: The Traversal and Range APIs

So far in this chapter, we've discussed the core DOM API, which provides basic methods for document traversal and manipulation. The DOM standard also defines other optional API modules, the most important of which will be discussed in the chapters that follow. Two of the optional modules are essentially convenience APIs built on top of the core API. The Traversal API defines advanced techniques for traversing a document and filtering out nodes that are not of interest. The Range API defines methods for manipulating contiguous ranges of document content, even when that content does not begin or end at a node boundary. The Traversal and Range APIs are briefly introduced in the sections that follow. See the DOM reference section for complete documentation. The Range API is implemented by Netscape 6.1 (and partially implemented by Netscape 6), and the Traversal API is expected to be fully supported by Mozilla 1.0, which means that a future release of Netscape will support it. At the time of this writing, IE does not support either of these APIs.

The DOM Traversal API

At the beginning of this chapter, we saw techniques for traversing the document tree by recursively examining each node in turn. This is an important technique, but it is often overkill; we do not typically want to examine every node of a document. We instead might want to examine only the <img> elements in a document, or to traverse only the subtrees of <table> elements. The Traversal API provides advanced techniques for this kind of selective document traversal. As noted previously, the Traversal API is optional and, at the time of this writing, is not implemented in major sixth-generation browsers. You can test whether it is supported by a DOM-compliant browser with the following:

document.implementation.hasFeature("Traversal", 2.0)  // True if supported

NodeIterator and TreeWalker

The Traversal API consists of two key objects, each of which provides a different filtered view of a document. The NodeIterator object provides a "flattened" sequential view of the nodes in a document and supports filtering. You could define a NodeIterator that filters out all document content except <img> tags and presents those image elements to you as a list. The nextNode( ) and previousNode( ) methods of the Node-Iterator object allow you to move forward and backward through the list. Note that NodeIterator allows you to traverse selected parts of a document without recursion; you can simply use a NodeIterator within a loop, calling nextNode( ) repeatedly until you find the node or nodes in which you are interested, or until it returns null, indicating that it has reached the end of the document.

The other key object in the Traversal API is TreeWalker. This object also provides a filtered view of a document and allows you to traverse the filtered document by calling nextNode( ) and previousNode( ), but it does not flatten the document tree. TreeWalker retains the tree structure of the document (although this tree structure may be dramatically modified by node filtering) and allows you to navigate the tree with the firstChild( ), lastChild( ), nextSibling( ), previousSibling( ), and parentNode( ) methods. You would use a TreeWalker instead of a NodeIterator when you want to traverse the filtered tree yourself, instead of simply calling nextNode( ) to iterate through it, or when you want to perform a more sophisticated traversal, skipping, for example, some subtrees.

The Document object defines createNodeIterator( ) and createTreeWalker( ) methods for creating NodeIterator and TreeWalker objects. A practical way to check whether a browser supports the Traversal API is to test for the existence of these methods:

if (document.createNodeIterator && document.createTreeWalker) {
    /* Safe to use Traversal API */
} 

Both createNodeIterator( ) and createTreeWalker( ) are passed the same four arguments and differ only in the type of object they return. The first argument is the node at which the traversal is to begin. This should be the Document object if you want to traverse or iterate through the entire document, or any other node if you want to traverse only a subtree of the document. The second argument is a number that indicates the types of nodes NodeIterator or TreeWalker should return. This argument is formed by taking the sum of one or more of the SHOW_ constants defined by the NodeFilter object (discussed in the next section). The third argument to both methods is an optional function used to specify a more complex filter than simply including or rejecting nodes based on their type (again, see the next section). The final argument is a boolean value that specifies whether entity reference nodes in the document should be expanded during the traversal. This option can be useful when you're working with XML documents, but web programmers working with HTML documents can ignore it and pass false.

Filtering

One of the most important features of NodeIterator and TreeWalker is their selectivity, their ability to filter out nodes you don't care about. As described previously, you specify the nodes you are interested in with the second and (optionally) third arguments to createNodeIterator( ) and createTreeWalker( ). These arguments specify two levels of filtering. The first level simply accepts or rejects nodes based on their type. The NodeFilter object defines a numeric constant for each type of node, and you specify the types of nodes you are interested in by adding together (or by using the | bitwise OR operator on) the appropriate constants.

For example, if you are interested in only the Element and Text nodes of a document, you can use the following expression as the second argument:

NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT 

If you are interested in only Element nodes, use:

NodeFilter.SHOW_ELEMENT 

If you are interested in all nodes or do not want to reject any nodes simply on the basis of their types, use the special constant:

NodeFilter.SHOW_ALL 

And if you are interested in all types of nodes except for comments, use:

~NodeFilter.SHOW_COMMENT 

(See Chapter 5 if you've forgotten the meaning of the ~ operator.) Note that this first level of filtering applies to individual nodes but not to their children. If the second argument is NodeFilter.SHOW_TEXT, your NodeIterator or TreeWalker does not return element nodes to you, but it does not discard them entirely; it still traverses the subtree beneath the Element nodes to find the Text nodes you are interested in.

Any nodes that pass this type-based filtration may be put through a second level of filtering. This second filter is implemented by a function you define and can therefore perform arbitrarily complex filtering. If you do not need this kind of filtering, you can simply specify null as the value of the third argument to create-NodeIterator( ) or createTreeWalker( ). But if you do want this kind of filtering, you must pass a function as the third argument.

The function should expect a single node argument, and it should evaluate the node and return a value that indicates whether the node should be filtered out. There are three possible return values, defined by three NodeFilter constants. If your filter function returns NodeFilter.FILTER_ACCEPT, the node is returned by the NodeIterator or TreeWalker. If your function returns NodeFilter.FILTER_SKIP, the node is filtered out and is not returned by the NodeIterator or TreeWalker. The children of the node are still traversed, however. If you are working with a TreeWalker, your filter function may also return the value NodeFilter.FILTER_REJECT, which specifies that the node should not be returned and that it should not even be traversed.

Example 17-10 demonstrates the creation and use of a NodeIterator and should clarify the previous discussion. Note, however, that at the time of this writing none of the major web browsers support the Traversal API, so this example is untested!

Example 17-10: Creating and using a NodeIterator

// Define a NodeFilter function to accept only <img> elements
function imgfilter(n) {
    if (n.tagName == 'IMG') return NodeFilter.FILTER_ACCEPT;
    else return NodeFilter.FILTER_SKIP;
}
 
// Create a NodeIterator to find <img> tags
var images = document.createNodeIterator(document,  // Traverse entire document
    /* Look only at Element nodes */     NodeFilter.SHOW_ELEMENT,
    /* Filter out all but <img> */       imgfilter,
    /* Unused in HTML documents */       false);
 
// Use the iterator to loop through all images and do something with them
var image;
while((image = images.nextNode(  )) != null) {
    image.style.visibility = "hidden";  // Process the image here
}

The DOM Range API

The DOM Range API consists of a single interface, Range. A Range object represents a contiguous range[8] of document content, contained between a specified start position and a specified end position. Many applications that display text and documents allow the user to select a portion of the document by dragging with the mouse. Such a selected portion of a document is conceptually equivalent to a range.[9] When a node of a document tree falls within a range, we often say that the node is "selected," even though the Range object may not have anything to do with a selection action initiated by the end user. When the start and end positions of a range are the same, we say that the range is "collapsed." In this case, the Range object represents a single position or insertion point within a document.

The Range object provides methods for defining the start and end positions of a range, copying and deleting the contents of a range, and inserting nodes at the start position of a range. Support for the Range API is optional. At the time of this writing, it is supported by Netscape 6.1. IE 5 supports a proprietary API that is similar to, but not compatible with, the Range API. You can test for Range support with this code:

document.implementation.hasFeature("Range", "2.0");  // True if Range is supported 

Start and end positions

The start and end positions of a range are each specified by two values. The first value is a document node, typically a Document, Element, or Text object. The second value is a number that represents a position within that node. When the node is a document or element, the number represents a position between the children of the document or the element. An offset of 0, for example, represents the position immediately before the first child of the node. An offset of 1 represents the position after the first child and before the second. When the specified node is a Text node (or another text-based node type, such as Comment), the number represents a position between the characters of text. An offset of 0 specifies the position before the first character of text, an offset of 1 specifies the position between the first and second characters, and so on. With start and end positions specified in this way, a range represents all nodes and/or characters between the start and end positions. The real power of the Range interface is that the start and end positions may fall within different nodes of the document, and therefore a range may span multiple (and fractional) Element and Text nodes.

To demonstrate the action of the various range-manipulation methods, I'm going to adopt the notation used in the DOM specification for illustrating the document content represented by a range. Document contents are shown in the form of HTML source code, with the contents of a range in bold. For example, the following line represents a range that begins at position 0 within the <body> node and continues to position 8 within the Text node contained within the <h1> node:

<body><h1>Document Title</h2><body>

To create a Range object, call the createRange( ) method of the Document object:

var r = document.createRange(  ); 

Newly created ranges have both start and end points initialized to position 0 within the Document object. Before you can do anything interesting with a range, you must set the start and end positions to specify the desired document range. There are several ways you can do this. The most general way is to call the setStart( ) and setEnd( ) methods to specify the start and end points. Each is passed a node and a position within the node.

A higher-level technique for setting a start and/or end position is to call setStartBefore( ), setStartAfter( ), setEndBefore( ), or setEndAfter( ). These methods each take a single node as their argument. They set the start or end position of the Range to the position before or after the specified node within the parent of that node.

Finally, if you want to define a Range that represents a single Node or subtree of a document, you can use the selectNode( ) or selectNodeContent( ) method. Both methods take a single node argument. selectNode( ) sets the start and end positions before and after the specified node within its parent, defining a range that includes the node and all of its children. selectNodeContent( ) sets the start of the range to the position before the first child of the node and sets the end of the range to the position after the last child of the node. The resulting range contains all the children of the specified node, but not the node itself.

Manipulating ranges

Once you've defined a range, there are a number of interesting things you can do with it. To delete the document content within a range, simply call the deleteContents( ) method of the Range object. When a range includes partially selected Text nodes, the deletion operation is a little tricky. Consider the following range:

<p>This is <i>only</i> a test 

After a call to deleteContents( ), the affected portion of the document looks like this:

<p>This<i>ly</i> a test 

Even though the <i> element was included (partially) in the Range, that element remains (with modified content) in the document tree after the deletion.

If you want to remove the content of a range from a document but also want to save the extracted content (for reinsertion as part of a paste operation, perhaps), you should use extractContents( ) instead of deleteContents( ). This method removes nodes from the document tree and inserts them into a DocumentFragment (introduced earlier in this chapter), which it returns. When a range includes a partially selected node, that node remains in the document tree and has its content modified as needed. A clone of the node (see Node.cloneNode( )) is made (and modified) to insert into the DocumentFragment. Consider the previous example again. If extractContents( ) is called instead of deleteContents( ), the effect on the document is the same as shown previously, and the returned DocumentFragment contains:

is <i>on</i>

extractContents( ) works when you want to perform the equivalent of a cut operation on the document. If instead you want to do a copy operation and extract content without deleting it from the document, use cloneContents( ) instead of extractContents( ).[10]

In addition to specifying the boundaries of text to be deleted or cloned, the start position of a range can be used to indicate an insertion point within a document. The insertNode( ) method of a range inserts the specified node (and all of its children) into the document at the start position of the range. If the specified node is already part of the document tree, it is moved from its current location and reinserted at the position specified by the range. If the specified node is a DocumentFragment, all the children of the node are inserted instead of the node itself.

Another useful method of the Range object is surroundContents( ). This method reparents the contents of a range to the specified node and inserts that node into the document tree at the position of the range. For example, by passing a newly created <i> node to surroundContents( ), you could transform this range:

This is only a test 

into:

This is <i>only</i> a test 

Note that because opening and closing tags must be properly nested in HTML files, surroundContents( ) cannot be used (and will throw an exception) for ranges that partially select any nodes other than Text nodes. The range used earlier to illustrate the deleteContents( ) method could not be used with surroundContents( ), for example.

The Range object has various other features as well. You can compare the boundaries of two different ranges with compareBoundaryPoints( ), clone a range with cloneRange( ), and extract a plain-text copy of the content of a range (not including any markup) with toString( ). The start and end positions of a range are accessible through the read-only properties startContainer, startOffset, endContainer, and endOffset. The start and end points of all valid ranges share a common ancestor somewhere in the document tree, even if it is the Document object at the root of the tree. You can find out what this common ancestor is with the commonAncestorContainer property of the range.


1. Technically, the W3C issues "recommendations." These recommendations serve the same purpose and carry the same weight as international standards do, however, and are called "standards" in this book.

2. The DOM can also be used to represent XML documents, which have a more complex syntax than HTML documents, and the tree representation of such a document may contain nodes that represent XML entity references, processing instructions, CDATA sections, and so on. Most client-side JavaScript programmers do not need to use the DOM with XML documents, and although the XML-specific features of the DOM are covered in the DOM reference section, they are not emphasized in this chapter.

3. The DOM standard defines interfaces, not classes. If you are not familiar with the term interface in object-oriented programming, you can think of it as an abstract kind of class. We'll describe the difference in more detail later in this DOM overview.

4. The name className is misleading, because in addition to specifying a single class name, this property (and the HTML attribute it represents) can also specify a space-separated list of class names.

5. Except for the HTML-specific portions of the standard, which at the time of this writing are still at the "working draft" stage. Fortunately, however, the current draft standard is hardly changed from the HTML-specific portions of the Level 1 standard, so the lack of Level 2 standardization is not a serious problem.

6. Technically, the DOM API specifies that getElementsByTagName( ) returns a NodeList object. In the JavaScript binding of the DOM, NodeList objects behave like arrays and are typically used that way.

7. The innerHTML property is particularly useful when you want to insert large or complex chunks of HTML text into a document. For simple fragments of HTML, using DOM methods is more efficient because no HTML parser is required. Note that appending bits of text to the innerHTML property with the += operator is usually not efficient.

8. That is, a logically contiguous range. In bidirectional languages such as Arabic and Hebrew, a logically contiguous range of a document may be visually discontiguous when displayed.

9. Although web browsers typically allow the user to select document content, the current DOM Level 2 standard does not make the contents of those ranges available to JavaScript, so there is no standard way to obtain a Range object that corresponds to a user's desired selection.

10. Implementing word processor-style cut, copy, and paste operations is actually more complex than this. Simple range operations on a complex document tree do not always produce the desired cut-and-paste behavior in the linear view of the document.

Back to: JavaScript: The Definitive Guide, 4th Edition


oreilly.com Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies | Privacy Policy

© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com