Javascript [Electronic resources] : The Definitive Guide (4th Edition) نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Javascript [Electronic resources] : The Definitive Guide (4th Edition) - نسخه متنی

David Flanagan

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید


17.2 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.


17.2.1 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>


17.2.2 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]

[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.

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");


17.2.3 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.

17.2.4 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]

[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.

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</h1>";
}
// 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="/image/library/english/10113_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>


17.2.5 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.


17.2.6 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="/image/library/english/10113_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.
Example 17-8 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;
}
}


17.2.7 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/DisplayEmployeeDatal?data.xml

DisplayEmployeeDatal 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: DisplayEmployeeDatal?data.xml.
-->
<body onload="loadXML(location.search.substring(1), makeTable)">
</body>

/ 844