JavaScript and the Document Object Model
Last Updated: 2005-12-10
By Nicholas Chase - 2004-03-10
Use DOM in your Web pages
While the Document Object Model (DOM) is perhaps best known in its role as a foundation for working with XML, variations on the theme actually started in browsers with HTML. Now DOM has come full circle as newer browsers implement the W3C Document Object Model through client-side scripting, such as JavaScript. This article looks at the JavaScript approach to DOM and chronicles the building of a Web page to which the user can add notes and edit note content.
Note:This article uses a DOM-compatible browser such as Netscape 6.x, Internet Explorer 6, or Mozilla 1.0. Familiarity with the Document Object Model in Java technology or other languages, is helpful, but not required. You should, however, be familiar with JavaScript in general.
Why use DOM instead of DHTML?
In this article, I look at the structure of a Web page from a Document Object Model point of view, examining children and parents and adding nodes to and editing nodes within an existing document. To illustrate this, I build a page where the user can create a series of notes to the page and edit their content. (The page won't get too fancy; I'll leave saving the content, moving them around, resizing them and such to you.) As this is something that could be accomplished without much trouble using DHTML techniques, why bother with DOM?
One of the driving forces behind the development of XML was the fragmentation of HTML, and DHTML follows that trend. Some things have become standard, but a significant divergence in how browsers handle various aspects still exists. DOM is a standard set of interfaces that can be implemented across the board, letting programmers write just one version of their pages.
Besides, Web pages are moving towards XML anyway. XHTML 1.0 is a reformulation of HTML 4.0.1 into XML, and XML is increasingly used to generate Web pages. Ultimately, DOM will be the main technique for accessing the elements and text within these pages. (You can also use it in XSLT style sheets.)
The basic page
Let's start with the basic page, which includes a work area and form indicating how many notes are already created:
Listing 1. The basic page
<html>
<head>
<title>Getting Sticky</title>
<style type="text/css">
* {font-family: sans-serif}
a {font-size: 6pt}
.editButton {font-size:6pt}
</style>
</head>
<body>
<div id="mainDiv" style="height:95%; width:95%; border:3px solid red;
padding: 10px;">
<h1>Getting Sticky</h1>
<form id="noteForm">
Current number of notes:
<input type="text" name="total" value="0" size="3"/>
<input type="button" value="Add a new note"/>
</form>
</div>
</body>
</html>
Lisitng 1 shows a basic page with a couple of styles (just to pretty it up a little). The body of the page includes a single div element, which includes a heading element (h1) and a form element. Even in pre-DOM browsers, accessing the information within the form wasn't a problem.
The DHTML way
Using DHTML, you can access the information in a form field, or even change it. For example, you can create a script that increments the current form value by 1, then tell the page to execute the script when the user presses the button:
Listing 2. Incrementing the current number
<html><head>
<title>Getting Sticky</title>
<style type="text/css">
* {font-family: sans-serif}
a {font-size: 6pt}
.editButton {font-size:6pt}
</style>
<script type="text/javascript">
function incrementCurrent() {
current = parseInt(document.forms["noteForm"].total.value);
document.forms["noteForm"].total.value = current + 1;
}
</script>
</head><body>
<div id="mainDiv" style="height:95%; width:95%; border:3px solid red;
padding: 10px;">
<h1>Getting Sticky</h1>
<form id="noteForm">
Current number of notes:
<input type="text" name="total" value="0" size="3"/>
<input type="button" value="Add a new note"
onclick="incrementCurrent()"/>
</form>
</div>
</body></html>
In Listing 2, the incrementCurrent() function takes the document object, and then pulls the noteForm object out of the array of forms within the page. From the noteForm object, the function gets the form field named total and retrieves the value. It then updates this value within the page.
These kinds of changes are live. If you make the changes to the page, reload, and press the button repeatedly, you'll see that the text field is updated each time.
Similarly, you can retrieve the text of the div element using DHTML properties. Because it has an id of mainDiv, you could use the property innerHTML, as in:
theText = mainDiv.innerHTML;
In this case, you see two DHTML techniques: the forms array, and calling elements by names based on attribute values, rather than by element names or by overall structure. The problem here is that it doesn't lend itself well to generalization. Yes, you can use a variable for the name of the form, but what if an alternate presentation doesn't actually use a form?
DOM and JavaScript
If one principle defined the Document Object Model, it would be that information is arranged as a parent-child hierarchy. For example, the following XML:
<parentElement><childElement>My Text Node</childElement></parentElement>
has three nodes: the root node is parentElement. It has one child, the childElement. The childElement element has as its parent the parentElement, and as its child the text node with a value of "My Text Node". The text node has childElement as its parent. Two nodes that share a parent are considered siblings. Notice that the text is its own node. Elements don't actually have a value, they simply have text node children.
You may be familiar with the idea of reading the structure of an XML document using Java technology or another language. When you do so, you use an API, with well-defined functions and object properties. For example, this document has an html element as its root element, which has two children, head and body. (I've removed the white space that would normally appear between these elements to simplify matters; browsers are not yet consistent on how they handle this white space.) To access the body element using Java technology, you could use several different expressions, assuming that you've named the Document object document.
The first approach is to get a list of all of the element's children, then choose a specific item from the list, as in:
bodyElement = document.getChildNodes().item(0).getChildNodes().item(1);
Here, you first get the html element, which is the first child of the document, then get its second child, the body. (getChildNodes() is zero-based.) An alternative approach is to access the html element as first child directly, then move to its first child (head), and then to that element's next sibling (the second child, body):
bodyElement = document.getFirstChild().getFirstChild().getNextSibling();
From there you can get the type of node:
typeInt = bodyElement.getNodeType()
Node types are returned as integers, and allow you to handle each node appropriately; an element (type 1) has a name, but no value, whereas a text node (type 3) has a value but no name.
Once you know what you have, you can retrieve the name of the element:
elementName = bodyElement.getNodeName();
or its text contents:
elementContent = bodyElement.getFirstChild().getNodeValue();
(Remember, the text of an element is a node unto itself, with the element as its parent.)
When you move these functions over to JavaScript, the same basic API is in place, but it's handled slightly differently. For example, properties are accessed directly, rather than through get and set methods, so the same statements in JavaScript would be represented as
bodyElement = document.childNodes.item(0).childNodes.item(1);
bodyElement = document.firstChild.firstChild.nextSibling;
From there you can get the type and name of the element, as well as its contents:
ElementType = bodyElement.nodeType;
elementName = bodyElement.nodeName;
elementContent = bodyElement.firstChild.nodeValue;
Be aware, however, that only properties undergo this name change. Functions that return objects remain constant. For example, you can retrieve a specific object based on its ID, as in:
formElement = document.getElementById("noteForm");
That's the basic idea. Let's see it in action.
Adding a new note
The actual note itself is just a small box with standard text and a link that enables the user to edit the text later.
This is the equivalent of the following HTML:
Listing 3. The target HTML
<div id="note1" style="width: 100; height:100; border: 1px solid blue;
background-color: yellow; position: absolute;
top: 150; left: 135">
<a href="javascript:editNote('note1')">edit</a>
New note
</div>
Adding the actual element uses standard DOM techniques, as seen in Listing 4:
Listing 4. Adding the new note element
<html>
<head>
<title>Getting Sticky</title>
<style type="text/css">
* {font-family: sans-serif}
a {font-size: 6pt}
.editButton {font-size:6pt}
</style>
<script type="text/javascript">
...
function getCurrentNumber() {
formElement = document.getElementById("noteForm");
return formElement.childNodes.item(1).value;
}
function makeNewNote(){
mainDivElement = document.getElementById("mainDiv");
newNote = document.createElement("div");
newNote.setAttribute("id", "note"+getCurrentNumber());
mainDivElement.appendChild(newNote);
incrementCurrent();
}
</script>
</head>
<body>
<div id="mainDiv" style="height:85%; width:85%; border:3px solid red;
padding: 10px; z-index: -100" >
<h1>Getting Sticky</h1>
<form id="noteForm">
Current number of notes <input type="text" name="total" value="0"
size="3"/>
<input type="button" value="Add a new note"
onclick="makeNewNote()"/>
</form>
</div>
</body>
</html>
In this case, you've changed the button so that it causes the execution of makeNewNote() rather than incrementCurrent(), though that function is still in use within makeNewNote(). First, you use getElementById() to get a reference to the main div element that ultimately will contain the note. You then use the document object to create a new element, just as you would do in any other language, with the name of div. To set the id attribute, you simply use the setAttribute() method on the new element.
Each note will have a unique id attribute, so you need to know what the current total is. To get this information, you start at the level of the form element itself. From there, you retrieve the list of children. The first (with an index of 0) is the text, and the second (with an index of 1) is the actual input element.
But what about .value? Isn't that a typo? Shouldn't it be nodeValue?
Actually, no. Remember, elements don't have values. At first glance, it might look as though I'm mixing DOM and DHTML properties, but in actuality, the element retrieved here is not just an implementation of org.w3c.dom.Element, it's an implementation of org.w3c.dom.html.HTMLInputElement, which also includes a value property that represents the value of the form field. In this way, DOM mimics some (though not all) of the properties that were available through DHTML.
Once the attribute is set, you simply append the new div element to the mainDiv element, where it will appear. Or at least, it would if it had any presentation properties or text.
To add the style information, you will actually use the DHTML style object:
Listing 5. Adding style information
...
function makeNewNote(){
mainDivElement = document.getElementById("mainDiv");
newNote = document.createElement("div");
newNote.setAttribute("id", "note"+getCurrentNumber());
newNote.style.width="100";
newNote.style.height="100";
newNote.style.border="1px solid blue";
newNote.style.backgroundColor="yellow";
newNote.style.position="absolute";
newNote.style.top=(150);
newNote.style.left=(25 + 110*getCurrentNumber());
mainDivElement.appendChild(newNote);
incrementCurrent();
}
...
The result is a small yellow box with a blue border.
Notice that the left property of the note depends on the current number of notes, which increments after each note is added. This way, you can add a series of boxes.
Adding the content
Adding the content of the div presents a bit of a problem. I could use the innerHTML property:
newNote.innerHTML = "<a href=\"javascript:editNote('note"
+getCurrentNumber()+")\">edit</a>
New note";
but how could I do it using straight DOM methods? The first thought would be to simply set the value of the text node child of the div element:
noteText = document.createTextNode(
"<a href=\"javascript:editNote('note"+getCurrentNumber()+")\">"+
"edit</a>
New note");
newNote.appendChild(noteText);
The text does indeed get added, but the results might not quite be what you expect.
The problem is that you're not really adding text, but rather mixed content, consisting of text and elements. The browser assumes that you mean this as CDATA, which is taken literally, and the elements are not created. Rather than simply adding all of the content in one block, you need to actually add each element:
Listing 6. Adding the content
...
function makeNewNote(){
mainDivElement = document.getElementById("mainDiv");
newNote = document.createElement("div");
newNote.setAttribute("id", "note"+getCurrentNumber());
...
editLink = getEditLink("note"+getCurrentNumber());
newNote.appendChild(editLink);
newNote.appendChild(document.createElement("br"));
noteText = document.createTextNode("New Form");
newNote.appendChild(noteText);
mainDivElement.appendChild(newNote);
incrementCurrent();
}
function getEditLink(thisId){
editLink = document.createElement("a");
linkText = document.createTextNode("edit");
editLink.setAttribute("href", "javascript:editNote('"+thisId+"')");
editLink.appendChild(linkText);
return editLink;
}
...
First, you've created a new function, getEditLink, which returns an object. That object is the a element, which you created using standard DOM methods. Next, you add a standard break tag, br, and finally, the node that contains the actual note text.
The result is the completed note, with elements intact and ready for use.
Changing existing nodes
Now you have the content, but how can you change it? Because you added elements separately, you can just edit the single text node that represents the text of the note. You can do this three ways. First, you can remove the offending node and add a new one:
Listing 7. Removing the node and adding a replacement
...
function editNote(editLink){
theDiv = document.getElementById(editLink);
newText = prompt("What should the note say?");
oldNode = theDiv.firstChild.nextSibling.nextSibling;
theDiv.removeChild(oldNode);
newNode = document.createTextNode(newText);
theDiv.appendChild(newNode);
}
...
Another option is to simply replace the existing node:
Listing 8. Replacing a node
...
function editNote(editLink){
theDiv = document.getElementById(editLink);
newText = prompt("What should the note say?");
oldNode = theDiv.firstChild.nextSibling.nextSibling;
newNode = document.createTextNode(newText);
theDiv.replaceChild(newNode, oldNode);
}
...
In this case, the oldNode is replaced with the newNode, and the document changes accordingly.
Finally, you could simply change the text of the existing node:
Listing 9. Changing the existing node
...
function editNote(editLink){
theDiv = document.getElementById(editLink);
newText = prompt("What should the note say?");
theDiv.firstChild.nextSibling.nextSibling.nodeValue=newText;
}
...
Because editNote takes the id value of the appropriate div, the same function can be used for any note
Summary and Resources
In this article, you've taken a very basic look at the use of DOM in the JavaScript of a Web page. You can use the same principles anywhere JavaScript is in use, such as within an XSLT style sheet.
The Document Object Model represents elements, text, and other types of nodes within an XML document as a series of parent-child relationships. By manipulating these individual nodes, you can affect the page itself. In addition to DOM Core methods, an XHTML page can also expose properties and methods that are part of the DOM HTML module, which attempts to integrate many of the DHTML properties that programmers have been using for years.
Resources
* Check out the DOM Level 2 Core (http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/) and DOM Level 2 HTML Recommendations from the W3C(http://www.w3.org/TR/2002/CR-DOM-Level-2-HTML-20020605/).
* Look at the JavaScript binding for the DOM Level 2 Core(http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html).
* Read the Understanding DOM (http://www-106.ibm.com/developerworks/edu/x-dw-xudom-i.html) tutorial (developerWorks, August 2001).
* Find more XML resources on the developerWorks XML zone(http://www-106.ibm.com/developerworks/xml?article=xmlr).
Source: http://www.opensourcetutorials.com
Originally Posted by Skylinux @ 2005-12-10 10:12:38
No Comments yet .....
Add Your Comment:
Note: All posts require administrator approval. Please allow 24 hours for message approval.