Looking for the previous guiStuff?

It's still here, the content didn't go anywhere. You may want to check out this new guiStuff though -- It's rather informative.

References/Tutorials:


Intro Documents:


guiStuff:






::Stuff for the multi-spec coder;

Coding, formats, standards, and other practical things.

 Home  //  JavaScript  //  The DOM: Part 2 - Modifications 

<!-- JavaScript

The DOM: Part 2 - Modifications

In part 1, we learned how to traverse the DOM using various functions and methods to glean information about the document. This article will expand on how to modify and manipulate the DOM by changing, adding, and removing nodes.

We're going to use the same page template we used in Part 1, which included the loadTrigget() function which is called by the onload of the BODY element. The document is also defined as an XHTML document, and contains some content in its BODY which we can play with. Lastly, we'll define the node_types Array to be used in conjunction with the nodeType method.

The basic page should look like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" > <head> <title>Page Title</title> <script type="text/javascript"> var node_types = new Array("??", "ELEMENT_NODE", "ATTRIBUTE_NODE", "TEXT_NODE", "CDATA_SECTION_NODE", "ENTITY_REFERENCE_NODE", "ENTITY_NODE", "PROCESSING_INSTRUCTION_NODE", "COMMENT_NODE", "DOCUMENT_NODE", "DOCUMENT_TYPE_NODE", "DOCUMENT_FRAGMENT_NODE", "NOTATION_NODE"); function loadTrigget(){ //...DOM Scripts go here... } </script> </head> <body onload="loadTrigget()"> <h1>This is the Page Heading</h1> <p align="left" id="para1">This is some text placed on the page that's contained <i>within</i> a paragraph.</p> <p>This is a second paragraph.</p> </body> </html>

Note that you do not have to use XHTML to make use of the DOM. However, the DOM's Core functions are based on XML, which means that when you're using DOM functions on HTML (which is based on SGML), you're letting the browser interpret your meaning, even if your markup is valid. When you're using XHTML, valid markup means that the DOM functions act as intended, and require no 'interpretation of meaning' on the side of the browser. This is much more important when you're modifying the DOM than when you're just reading it, since you're going to be altering the document tree using functions that were designed to modify an XML-based document.
Eventually the decision will be up to you, but considering the power that the DOM provides you, and the relatively subtle changes you'll have to make in order to convert your pages to valid XHTML, there are few reasons not to make the change.

Creating Nodes

The first thing that comes to mind when you think of making changes to the DOM is how would one go about creating nodes? Among the nodes we've seen so far were Element nodes and Text nodes, so we'll start by learning how to create them: createElement() is defined for creating element nodes, and createTextNode() is defined for creating text nodes.

In the document we're working with, there are 2 P elements. Let's add another. We'll start by creating the nodes necessary:

var objP = document.createElement("p"); var objText1 = document.createTextNode("Third Paragraph.");

Running this script will actually do nothing. At least, it will do nothing visually. Two nodes were created here, and the methods that created them were both of the document. The document now has two new nodes, but they weren't attached anywhere. You won't find them on the document tree, they're just nodes of the document, they haven't been attached to the document yet.

There are several ways to insert nodes into the document tree, and the simplest is the appendChild() method. Actually, appendChild() is a method of Node, and you'll soon see why this is important. The appendChild() method inserts a node as the last child of the caller. Think of it as appending an element to the childNodes array.
Now consider that we have two nodes, the newly created P element, and the text node. They don't yet have any relationship between them, which is why it's a good thing that appendChild() is a method of Node:

var objP = document.createElement("p"); var objText1 = document.createTextNode("Third Paragraph."); objP.appendChild(objText1);

By doing this, we've tied the two new nodes together and created a relationship between them. We now have a P element which contains a text node (as a child) in objP. Of course, this still didn't effect the document tree, and running this script still won't do anything. So here's the last piece of the puzzle:

var objP = document.createElement("p"); var objText1 = document.createTextNode("Third Paragraph."); objP.appendChild(objText1); document.body.appendChild(objP);

If you run the script now, you'll see that your document is the proud new owner of a third paragraph. The appendChild() method was first used to attach the text node as a child of the P element, and then used to attach the P element as the last child of the BODY element.

Of course, nobody said that you had to attach elements only to document.body. We've learned in part 1 how to locate elements within the page, so let's locate the first paragraph, and attach our new paragraph as its child:

var objP = document.createElement("p"); var objText1 = document.createTextNode("Third Paragraph."); objP.appendChild(objText1); var paras = document.getElementsByTagName("p"); paras[0].appendChild(objP);

If you load the page now, you'll see the words "Third Paragraph" in the second paragraph's place. Actually, you've added the third paragraph as a child of the first paragraph, not after it. Remember, appendChild() adds the new node as the last child of the calling node, and since the first P element was the calling node, then the new paragraph was appended as its last child.

Now suppose you'd like to attach your new node somewhere within the document that isn't the last the last child of another element. To do this, you'd use insertBefore(). The insertBefore() method takes two arguments: The first is the new node you'd like to insert, and the second is the node before which you'd like to insert it. Here's a common way to insert a node as the first child of the BODY:

var objP = document.createElement("p"); var objText1 = document.createTextNode("Third Paragraph."); objP.appendChild(objText1); document.body.insertBefore(objP, document.body.firstChild);

This will insert the new paragraph as the first element within the BODY. You may have noticed that the calling node was document.body, not document -- this is highly relevant. The calling node must be the parent of the second argument node. At first, this may seem like odd structuring for a method, but think of it this way: A ParentNode calls insertBefore() so that a NewChild (the first argument) can be inserted before ThisChild (the second argument).

For example, say you wanted to insert the new paragraph within the first paragraph:

var objP = document.createElement("p"); var objText1 = document.createTextNode("Third Paragraph."); objP.appendChild(objText1); var paras = document.getElementsByTagName("p"); paras[0].insertBefore(objP, paras[0].childNodes[1]);

If you run this script, you'll actually be presented with four lines. This is because insertBefore() was told to insert the new paragraph before the second child (childNodes[1]) of the first paragraph. This caused it to insert the new paragraph just before the I element, which means it inserted a P element in the middle of a line, 'breaking' it in two. Once again, you can see the relation between the calling node, and the second argument -- the second argument is the child of the calling node. This is easy to achieve by using firstChild, lastChild, and childNodes in the second argument, or by using parentNode in the calling node. For example:

var objP = document.createElement("p"); var objText1 = document.createTextNode("Third Paragraph."); objP.appendChild(objText1); var paras = document.getElementsByTagName("p"); paras[0].parentNode.insertBefore(objP, paras[0]);

Here, we know for certain that the parent-child relationship is maintained not because the second argument is made a child of the calling node, but because the calling node is made the parent of the second argument. Either way, you're not just handing over two arbitrary nodes, but rather making sure that the relationship between them is parent-child.

removeChild()

Of course, if you have the ability to add nodes, you also have the ability to remove them. The removeChild() method takes one argument, which is the child of the calling node that is to be removed. Here, again, there is a relationship between the argument and the calling node, and again, it's a parent-child relationship. The node which calls removeChild() must be the parent of the child that is to be removed.

For example:

var paras = document.getElementsByTagName("p"); paras[0].parentNode.removeChild(paras[0]);

This will remove the first paragraph from the document. The parent-child relationship was maintained using the parentNode property on the calling node, but as mentioned above, there are several ways to make sure that the parent-child relationship is maintained.

While the example above didn't show this, the removeChild() method actually returns the node which it has removed. You can assign that node to a variable, and re-place it into the document using appendChild() or insertBefore(). Here's an example of how this is done:

var paras = document.getElementsByTagName("p"); var container = paras[0].parentNode.removeChild(paras[0]); document.body.appendChild(container);

Here, the paragraph that was removed was placed in a variable named container. This variable now contained the node that was removed, along with all of its descendants. At this point, appendChild() was called by document.body, with the container variable as its argument. The result is that if you loaded the page, you saw the second paragraph appear before the first (because the first paragraph was removed and then re-inserted as the last child of the BODY element).

This is quite useful when you want to move elements around the page in real-time. If, for example, you wanted to display a list of items where the higher an item is placed on the list, the higher its priority is, you could use removeChild() to 'cut' items from the list and 'paste' them into a new position. This can also be used to move elements around the page that may have had their contents change since the page was first loaded.

replaceChild()

The replaceChild() method is in a way a combination of the removeChild() method and one of the node-adding methods. The replaceChild() method takes two arguments -- the new child node, and the old child node. As with insertBefore() and removeChild(), there's a relationship to maintain: This time, the calling node must be the parent of the old child node.

Here's how it's used:

var objP = document.createElement("p"); var objText1 = document.createTextNode("Third Paragraph."); objP.appendChild(objText1); var paras = document.getElementsByTagName("p"); paras[0].parentNode.replaceChild(objP, paras[0]);

As you may expect, this replaced the first paragraph with the new paragraph. Replacing nodes becomes very important when you're constructing web applications that use the DOM heavily, since it allows you to burrow down the document tree to replace text nodes and make subtle modifications in one action.

Unique Nodes and Cloning

You may have wondered what would happen if you gave one of the methods which inserted a node into the document tree, a node which was already in the document tree as an argument. This will not produce an error.

Let's give it a try:

var paras = document.getElementsByTagName("p"); document.body.appendChild(paras[0]);

After running the script, was the result what you expected? Basically, all the script did was take the first paragraph in the page, and append it as the last child of the BODY element. However, since the argument given to appendChild() was a node which was already part of the document tree, it was first removed from the document tree, and then re-inserted in the new position. This is true for all methods which accept a node that will be inserted into a new location in the tree: If the node already exists in the tree, it will not be copied, but rather moved.

Here's another way of going about it:

var paras = document.getElementsByTagName("p"); var buffer = paras[0]; document.body.appendChild(buffer);

Variables only contain references to nodes, not the nodes themselves. Even when you create new nodes, remember that the node that calls, for example, the createElement() method is the document, which means that the variable still only contains a reference to the node, not the node itself. This is actually quite advantageous, since it allows you to use any of the methods that place a node anywhere in the document tree, to also move nodes around the document tree. It also shows you why it's so useful that removeChild() returns the node that it removes -- you can store that reference and use it later, or maybe modify it before re-inserting it into the document.

All this of course leaves us with a new problem: Suppose there's content on the page that we do want to copy. How would we do that? That's a job for...

cloneNode()

This method allows you to make a perfect copy of a node, and, optionally, all of its descendants. The cloneNode() method takes one argument: A boolean True, or False. This argument tells cloneNode() whether or not to clone the node along with all of its descendants. The reason for not cloning a node's descendants is because cloneNode() also clones all of an element's attributes, which may be the only thing you want, rather than everything that the node contains along with it. Note, however, that event-listener functions are not cloned.

Here's how you would use cloneNode():

var paras = document.getElementsByTagName("p"); var buffer = paras[0].cloneNode(1); document.body.appendChild(buffer);

This time around, the variable named buffer was pointed to a clone of the first paragraph, not the first paragraph itself. This allowed the appendChild() method to duplicate this paragraph within the document tree. There was no actual need to "make a stop" within a variable -- it would have worked just the same like this:

var paras = document.getElementsByTagName("p"); document.body.appendChild(paras[0].cloneNode(1));

Needless to say, if you create new elements which you intend to insert over and over again, create a "master copy" and clone it every time you want to insert it into your document:

var objP = document.createElement("p"); var objText1 = document.createTextNode("Third Paragraph."); objP.appendChild(objText1); document.body.appendChild(objP.cloneNode(1)); document.body.appendChild(objP.cloneNode(1)); document.body.appendChild(objP.cloneNode(1));

The Live NodeList and childNodes

The two pseudo-arrays that are returned by DOM methods are the NodeList array and the childNodes array. In practice, you're going to loop through them quite often and use conditions to find the nodes you're looking for. What you're going to have to keep in mind is that both of these pseudo-arrays are 'live arrays', not snapshots taken when you assign them to a variable. This is something that will change how you design your scripts, since you'll have to keep in mind that if you modify the contents of the document tree, the pseudo-arrays will change to reflect this modification in real-time.

This is best illustrated using an example. Add the following markup to the end of the page (the end of the BODY) we're currently working with:

<span>This is SPAN 1<br /></span> <span>This is SPAN 2<br /></span> <span>This is SPAN 3<br /></span> <span>This is SPAN 4<br /></span> <span>This is SPAN 5<br /></span> <span>This is SPAN 6<br /></span> <span>This is SPAN 7<br /></span> <span>This is SPAN 8<br /></span> <span>This is SPAN 9<br /></span> <span>This is SPAN 10<br /></span>

After you've added the above markup, run the following script:

var spans = document.getElementsByTagName("span"); for (var i=0; i<spans.length; i++){ alert(i); spans[0].parentNode.removeChild(spans[i]); }

Notice anything interesting? Every time a SPAN was removed, the list got shorter by 1. At the same time, i was incremented by 1. That meant that by the time i reached 5, there were 5 items on the list, and the loop was complete. Every time the loop alerts which element on the list it is going to remove, and each time this means something else on the list, since the list was changed since the last time it was addressed.

Here's one way to remedy this particular problem:

var spans = document.getElementsByTagName("span"); for (var i=spans.length-1; i>=0; i--){ alert(i); spans[0].parentNode.removeChild(spans[i]); }

By removing elements from the end of the array, rather than from the beginning, you're making sure that the array isn't being modified in a way that will effect you. Remember, however, that if you were to be removing elements arbitrarily, this wouldn't solve anything. If you're modifying elements in a non-ordered manner, you may have to reset the loop every time you modify the array.

Setting Attributes

When you have an element node in a variable, you can get or set an attribute using either the DOM's setAttribute(), or simply by addressing the attribute's value. For example:

var paras = document.getElementsByTagName("p"); paras[0].align = "right"; // does the same as: paras[0].setAttribute("align", "right");

Usually, there are three attributes which you should use setAttribute() to set: id, class, and name. This is mainly for when you've just created a new element and you're assigning it new attributes. This isn't set in stone, however, and you should always check your scripts in the various browsers, which should be common practice anyway. I should note that there isn't a specific problem with this aspect of setting attributes, it's just that the 'old way' of setting attributes dates back quite a bit, and so when implementing new scripts developers often check various scripts using both methods to see if the same effect is achieved in every browser.

That's it for part 2. Needless to say, scripting the DOM is a very broad subject, and there's a lot more to cover, but if you understood the bulk of this two-part intro then you actually have 90% of the foundation covered. Experimentation is crucial here, as well as digging into other people's source code! Remember when looking through source code to look for annotations, you'll often find URL's as a reference of where a particular script was found, where you can find more of its kind.
If you're ready for more material right away, you can move on to Part 3, which covers styling element nodes and implementing a Search and Augment script (and if you liked what you've seen so far, you'll love that one...).

Happy Document Object Modelling!





Return to the JavaScript section, or go the to Main page.





Looking for the old guiStuff?

It's still here, the old content didn't go anywhere.