Beginning DOM Manipulation
by
Ben on 01/01/06
Questions on this tutorial? We get something wrong? Email Ben
The Document Object Model (abbreviated DOM) is a tree-like representation of the HTML in the page. For example:
<p>
Using <em>javascript</em> is fun.
</p>
This page (well, wouldn't be valid :)), but it has the root element, <p>. Inside <p>, there are three nodes, an element node, then a text node, then another element node. The first text node for <p> has the text "Using ". Then an element node <em>, which has a text node inside that which contains the text "javascript". The last text node in the <p> element says " is fun".
Now to start with the Javascript. There are two functions you need to know about to access the DOM. They are getElementById and getElementsByTagName. In this tutorial though, we will cover a few different ways to use the DOM, including: getElementById, getElementsByTagName, childNodes, firstChild, lastChild, and nodeValue.
The getElementById function accesses element nodes by their id attribute. Since none of our elements have an id at the moment, we'll go to getElementsByTagName, which returns an array of all the elements with the tag name you provide. So to start, let's count the amount of elements in the <p> element.
Example can be found here
<html>
<head><title>Using the DOM</title></head><body>
<p>
Using <em>javascript</em> is fun.
</p>
<script type="text/javascript">
var p = document.getElementsByTagName("p")[0];
var p_children = p.childNodes;
alert(p_children.length);
</script>
</body></html>
Aside from the page elements that I added, there's everything in between the <script> tags.
The first thing I do is get all of the elements with the tag name of "p." Notice that I have to have document.getElementsById. Otherwise the javascript interpreter will look for a function named "getElementsById". Now also notice that I must appened [0] onto the end of the document.getElementsByTagName. This is because that function will return an array, so since we already know there is only one "p" element, we can just access the first one. To cycle all of them, we could have used a for loop:
Example can be found here.
var text = "Hey! "
var p = document.getElementsByTagName("p");
for ( x = 0; x < p.length; x++ ){
text = text + p[x].firstChild.nodeValue
text = text + p[x].lastChild.nodeValue
}
alert(text)//don't laugh at the output, totally unintentional
This uses another three concepts: firstChild, lastChild, and nodeValue. As you would think, firstChild and lastChild are pretty self-explanatory, fetching the first and last child of the element that was the parent. Then nodeValue returns the value of the node.
Now you could just as easily set the values of the element with nodeValue as well. This time let's make the text say something totally not true.
Example can be found here.
<html>
<head><title>Using the DOM</title></head><body>
<p>
Using <em>javascript</em> is fun.
</p>
<script type="text/javascript">
var text = "Hey! "
var p = document.getElementsByTagName("p")[0];
p.lastChild.nodeValue = " is not fun";
</script>
</body></html>
The output of the page will change "Using javascript is fun" to "Using javascript is not fun". Even though the file says different, the javascript changes the html to say what it wants. Now this is kind of boring, so we can attach it to a function for the last thing to do. And let's also change <p> to have an id to illustrate documentgetElementsById..
Example can be found here.
<html>
<head><title>Using the DOM</title>
<script type="text/javascript">
function change(){
var p = document.getElementById("paragraph")
p.lastChild.nodeValue = " is not fun."
}
</script></head><body>
<p id="paragraph">
Using <em>javascript</em> is fun.
</p>
<a href="#" onclick="change()">click me</a>
</body></html>
First thing to notice is that we added an id to the <p> element. IDs are used to define CSS rules as well as used in the DOM. Then there's the new link. The link has the href value of "#", meaning that it won't go anywhere. Now the onclick value is change(), meaning that the javascript function change() will be invoked.
The next item of interest is that I moved the script block into the head section of the document. This is because you can't define an element that does not exist yet, so if you put the script block above the paragraph in the body, it would present an error. The easy way to do this is to put the script in the head. This is more useful when you develop more advanced scripts.
The function change() is very simple. It has no arguments, just defines p as the paragraph. Notice that I didn't need to access any elements, because it returns one object. An id should be unique throughout the document. Then I can just find the last child's node value and change it to " is not fun" once the function is executed by clicking the link.
That's basically all for now. I will put out another article very soon on how to manipulate the DOM with some more advanced techniques.
Also, this javascript does not degrade very gracefully. Using the href="#" is not a good idea, because if a browser doesn't have javascript enabled, then the functionality is pointless. Now this was fine for this demonstration, but for more advanced projects, make sure that your website works without javascript enabled.
In the last article, we covered how to begin to manipulate page elements using Javascript and the Document Object Model. In this tutorial, we'll cover how to get and set attributes of elements, using the easy DOM conventions, and using getAttribute, and setAttribute.
Let's start with a real-world application of the DOM. Let's try an image gallery. Cliche, yes, but it's good for our purposes. We need some pictures, a few links, and maybe a placeholder.
<html>
<head><title>Using more DOM</title></head>
<body>
<img src="placeholder.jpg" /><br />
<ul>
<li>
<a href="images/treedom.jpg"><img src="images/treedom_thumb.jpg" /></a>
</li>
<li>
<a href="images/treeplant.jpg"><img src="images/treeplant_thumb.jpg" /></a>
</li>
</ul>
</body></html>
Very basic. Two pictures that will be thumbnails, and a larger placeholder to be replaced with the pictures later. Now to start getting it DOM ready, let's add an id attribute to the placeholder to be easily accessed, so we'll put: <img src="placeholder.jpg" id="placeholder" />. That'll make it much easier to replace with the image.
Now let's start an easy function to see this image.
This will be in script.js for our application.
function changeImage(pic){
if (!document.getElementById) return false;
var placeholder = document.getElementById("placeholder");
var picture = pic.getAttribute("href");
placeholder.setAttribute("src", picture);
}
The first line of code is the function declaration, which takes one argument, the picture to use. This is followed by a quick compatibility check to make sure our code is accessible to those with javacript off. Then we define a variable, placeholder, to the element with the id of "placeholder", which, if you remember, is our placeholder image. Then we get the location the link will go to in our picture variable. The getAttribute function takes one argument, which is the name of the attribute; it then returns the value of said attribute. Then we set our placeholder picture's src, or source image to the href value of the link, which would have made our browsers go to that location.
Now all you need to do is implement it in the correct way. Our finished code could look something like this:
<html>
<head><title>Using more DOM</title>
<script type="text/javascript" src="script.js"></script>
</head>
<body>
<img src="placeholder.jpg" /><br />
<ul>
<li>
<a href="images/treedom.jpg" onclick="changeImage(this); return false;"><img src="images/treedom_thumb.jpg" /></a>
</li>
<li>
<a href="images/treeplant.jpg" onclick="changeImage(this); return false;"><img src="images/treeplant_thumb.jpg" /></a>
</li>
</ul>
</body></html>
It's just a matter of adding the onclick attribute to the links. The return false; part ensures the browser won't go anywhere, otherwise the image would change, but the browser would go to the picture anyways.
This would look done. But we could add a bit more to this, using the DOM conventions to access attributes. Instead of putting:
element.getAttribute("att")
We could have just put:
element.att
Same with setting.
element.att = "value"
As opposed to:
element.setAttribute("att", "value")
So knowing this, for our onclick attributes, we could have easily put:
<a href="images/treeplant.jpg" onclick="changeImage(this.href); return false;">
Which would change our script to being something like:
function changeImage(pic){
if (!document.getElementById) return false;
var placeholder = document.getElementById("placeholder");
placeholder.src = pic
}
Much easier to write.
And that about sums up getAttribute and setAttribute. The only thing left to understand in the next tutorial would be the innerHTML property, and playing God with the DOM tree, namely creating and destroying nodes from within the DOM tree.
The last and final installation to this trilogy of a tree, will involve directly influencing the Document Object Model, metaphorically playing God with the DOM tree, creating and destroying nodes inside the DOM tree using createElement, createTextNode, removeChild, appendChild, and insertBefore. We'll also cover the innerHTML property.
So to quickly start here, let's think of what real-world application we could have here. How about one of those cool interactive forms we always see. Where selecting something from a drop down list immediately adds an info box.
Example can be found here
<html>
<head><title>DOM equals greatness</title>
<script type="text/javascript">
function displayInfo(whatinfo){
var info = document.createElement("p");
document.body.appendChild(info);
switch(whatinfo){
case "attributes":
var text = "You have getAttribute and setAttribute. getAttribute takes one argument, setAttribute takes two.";
break;
case "tree":
var text = "There are two functions: getElementsByTagName, which returns an array, and getElementById, which returns a single element based on the id attribute.";
break;
case "god":
var text = "There are many functions. createElement, appendChild, insertBefore, createTextNode, and removeChild.";
break;
}
info.innerHTML = text;
}
</script></head><body>
<form action="formaction" method="post">
Methods in the DOM: <select name="selection">
<option value="attributes" onclick="displayInfo(this.value)">Attributes</option>
<option value="tree" onclick="displayInfo(this.value)">Traversing the Tree</option>
<option value="god" onclick="displayInfo(this.value)">Creating and destroying nodes</option>
</select>
</form>
</body></html>
Notice it's a bit crass, not anything refined at the moment, we'll build up to that. First things first, we have a pointless form. the action and method didn't really matter. Then the selection element, pretty basic. Now the options are where we have some magic. They each have the onclick attribute which will invoke the displayInfo() function with the argument of their value which one is selected. Pretty easy.
The script is also very straightforward. When you create an element using document.createElement("elementtype"), it's stuck in a sort of limbo area. It can't actually be seen until it's inserted into the document using either insertBefore, or appendChild. We'll use the function appendChild just for now. We could just as easily have used insertBefore, but I wanted the info to appear after the list.
Next it's just a simple matter of a switch statement to set the text to be what you want. The innerHTML property of any element is like carefully trying to pound a tiny nail into the wall with a sledgehammer. You're effectively destroying any text within the element before. You can also return all the HTML within the item. Instead of returning text as you usually would with a text node's value, it would return everything as a string, even HTML elements, like "Hello, I <em>love</em> Javascript", as opposed to "Hello, I " like a text node's value would be.
Anyways, I'm getting off track. The annoying part of this script is that I can create new paragraphs, even if the paragraphs I created already existed. So we can easily solve this by using the removeNode ability. Also, instead of using innerHTML, let's say you wanted to create a text node instead using createTextNode. Note that createTextNode behaves like createElement: they both are suspended in a "limbo" until they are assigned to a parent.
So here is our updated script.
Example can be found here
function displayInfo(whatinfo){
if(test = document.getElementById("paragraph")) {
document.body.removeChild(test);
}
var info = document.createElement("p");
info.setAttribute("id", "paragraph");
switch(whatinfo){
case "attributes":
var text = document.createTextNode("You have getAttribute and setAttribute. getAttribute takes one argument, setAttribute takes two.");
break;
case "tree":
var text = document.createTextNode("There are two functions: getElementsByTagName, which returns an array, and getElementById, which returns a single element based on the id attribute.");
break;
case "god":
var text = document.createTextNode("There are many functions. createElement, appendChild, insertBefore, createTextNode, and removeChild.");
break;
}
info.appendChild(text);
document.body.appendChild(info);
}
Now the first thing I added was to see if you could find an element with the id paragraph and assign it to the variable test. If so, we had to remove it, just to keep things clean. The next thing was normal, to create the element. Then I set the new id attribute to paragraph. Also notice that I don't add the info element until the very end. This is just for some readability. The switch statement hasn't changed, except now instead of making the variable text a string, I assigned a new text node to it. The function for createTextNode takes one argument: a string. Then at the end, I added the text node to the info element, and finally added the info element into the tree.
So now if you test it, it works flawlessly. It'll insert a paragraph, then take it out and insert a new one. We could style these easy using CSS, since the id attribute is set on it. Easy to style, easy to use, makes for a great user experience.