Link :http://www.ibm.com/developerworks/web/library/wa-jsdom/
Listing 1. The basic page
Figure 1. The basic page
Listing 2. Incrementing the current number
Figure 2. The new note
Listing 3. The target HTML
Listing 4. Adding the new note element
Listing 5. Adding style information
Figure 3. The empty box
Figure 4. The text in the box
Listing 6. Adding the content
Listing 7. Removing the node and adding a replacement
Listing 8. Replacing a node
Listing 9. Changing the existing node
Figure 5. The final page
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.)
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.Figure 1. The basic page
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?
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
objectdocument
.
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 asbodyElement = 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.
The actual note itself is just a small box with standard text and a link that enables the user to edit the text later, as seen in Figure 2.
Figure 2. The new note
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> <br /> 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, as shown in Figure 3.
Figure 3. The empty box
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, as shown in Figure 3.
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><br />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><br />New note"); newNote.appendChild(noteText); |
The text does indeed get added, but the results might not quite be what you expect, as shown in Figure 4.
Figure 4. The text in the box
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.
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, as seen in Figure 5.Figure 5. The final page
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.
No comments:
Post a Comment