17.2. Using the Core DOM APINow 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 DocumentAs 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
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
17.2.2. Finding Specific Elements in a DocumentThe 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:
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.[59]
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:
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:
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:
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:
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:
17.2.3. Modifying a DocumentTraversing 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
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 reversedThere 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
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
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
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:
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:
17.2.4. Adding Content to a DocumentThe 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( ) functionThe 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.[60]
Example 17-7. Adding debugging output to a document
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:
17.2.5. Working with Document FragmentsThe 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:
Once you have created a DocumentFragment, you can use it with code like this:
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 ContentsThe 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 contentsIf 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:
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
17.2.7. Working with XML DocumentsWeb 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
Copyright © 2003 O'Reilly & Associates. All rights reserved. |
|
|