Creating and destroying content

The main strength of the DOM is the option not only to read, but also to alter the content and the structure of the document at hand. For this we have several methods at our disposal.

Creating new content

createElement(element)
Creates a new element
createTextNode(string)
Creates a new text node with the value string.

Newly created elements are not added to the document immediately, they remain in limbo until we append them somewhere in the node tree. These functions have to be applied to the document object rather than to a node.

Javascript:
mynewparagraph=document.createElement('p');
mynewtext=document.createTextNode('this is a new paragraph');

Altering the existing content

setAttribute(attribute,value)
Adds a new attribute with the value to the object
appendChild(child)
Adds child as a childNode to the object. child needs to be an object, you cannot use a string.
cloneNode()
Copies the whole node with all childNodes.
hasChildNodes()
Checks if an object has childNodes, and returns true if that is the case.
insertBefore(newchild,oldchild)
Adds newchild before oldchild to the document tree.
removeChild(oldchild)
Removes the childnode oldchild.
replaceChild(newchild,oldchild)
Replaces oldchild with newchild.
removeAttribute(attribute)
Removes the attribute from the object.

The picture example

Let's say we have links to images, and they should open in a new window in browsers without Javascript, or below the links when Javascript is available.

HTML:
<ul id="imglist">
 <li><a href="home.gif" target="_blank">Home 
 (new window)</a></li>
 <li><a href="home_on.gif" target="_blank">Home active 
 (new window)</a></li>
 <li><a href="jscsshtml.gif" target="_blank">HTML-CSS-Javascript 
 (new window)</a></li>
</ul>

Now, when Javascript and DOM is available, we want to:

This function should

The first problem is not hard to solve:

Javascript:
function imgpop()
{
 var il,imga,imgatxt;

// get all LIs in imagelist, loop over them 
 il=document.getElementById('imglist').getElementsByTagName('li');
 for(i=0;i<il.length;i++)
 {

// grab first link in the LI
 imga=il[i].getElementsByTagName('a')[0];

// delete the wording (new window) in the link text 
// (which is the nodeValue of the first node)  
 imgatxt=imga.firstChild;
 imgatxt.nodeValue=imgatxt.nodeValue.replace(/ \(new window\)/,'');

// add the event handlers to call popw();
 imga.onclick=function(){return popw(this);}
 //imga.onkeypress=function(){return popw(this);}
 }
}

Now, for the function popw() we need to use some of the methods shown above:

Javascript:
function popw(o)
{
 var newimg;
// if there is already an image in the parentNode (li) 
 if(o.parentNode.getElementsByTagName('img').length>0)
 {
// delete it
  o.parentNode.removeChild(o.parentNode.getElementsByTagName('img')[0]);
 } else {
// else, create a new image and add a handler that removes it when you 
// click it
  newimg=document.createElement('img');
  newimg.style.display='block';
  newimg.onclick=function(){this.parentNode.removeChild(this);};
  newimg.src=o.href;
  o.parentNode.appendChild(newimg)
 }
 return false;
}
See this imagepop example in action

The datepicker link example

Let's say for example we have a form that has date fields and we want to offer users with Javascript enabled a date picker, others should simply enter the date by hand. Let's not discuss the date picker function now, but focus on how to call it.

We start with the necessary HTML. To see which element should get a datepicker link, we add classes with the name date to them.

HTML:
<h1>Flight booking</h1>
<form action="nosend.php" method="post" onsubmit="return check(this);">
<p>Step 1 of 4</p>
<h2>Please select your dates</h2>
<p>
 <label for="startdate">Start Date</label>
 <input type="text" class="date" id="startdate" name="startdate" />
</p>
<p>
 <label for="enddate34;>End Date</label>
 <input type="text" class="date" id="enddate" name="enddate" />
</p>
<p>
 <input type="submit" value="send" />
</p>
</form>

We loop through all inputs in the document, and check which one has a className that contains date (remember, elements can have more than one class in the class attribute!).

If that is the case, we create a new link, and a link text. We append the link text as a child of the link and add event handlers to call our picker script.

Once the link is created, we append it after the input field.

Javascript:
function addPickerLink()
{
 var inputs,pickLink,pickText;

// loop through all inputs
 inputs=document.getElementsByTagName('input');
 for(i=0;i<inputs.length;i++)
 {
  
// if the class contains 'date'
  if(/date/.test(inputs[i].className))
  {

// create a new link and a text
   pickLink=document.createElement('a');
   pickText=document.createTextNode('pick a date');

// add the text as a child of the link
   pickLink.appendChild(pickText);

// set the href to # and call picker when clicked or tabbed to  
   pickLink.setAttribute('href','#');
   pickLink.onclick=function(){picker(this);return false;};
   //pickLink.onkeypress=function(){picker(this);return false;};

// add the new link to the parent of the input field (the P)
   inputs[i].parentNode.appendChild(pickLink)
  }
 }
}
See the picker demo in action.

Now all fields with dates have a link after them that links to picker().

All we need now is to tell the picker function where to apply the return value to.

As we send the link itself as the object to picker(), we need to access its previous sibling, the INPUT.

Javascript:
function picker(o)
{
 alert('This is a simulation only.') // no real function today
 o.previousSibling.value='26/04/1975';		
}

Close, but not quite. As we appended the new link as the last child to the parent node of the input, it could very well be that the previousSibling of our new link is indeed not the INPUT but whitespace! Therefore, we need to loop through the previous siblings until we hit an element.

Javascript:
function picker(o)
{
 alert('This is a simulation only.') // no real function today
 while(o.previousSibling.nodeType!=1)
 {
  o=o.previousSibling;
 }
 o.previousSibling.value='26/04/1975';		
}

Looping is always hacky and could be rather slow. To avoid the loop we have to change our function.

Changing the addPickerLink() function

It is always easy to use appendChild(), but it makes us dependent on the markup. What happens for example if we need to add a SPAN with an asterisk next to the input to indicate a mandatory field later on?

The trick is use insertBefore() on the nextSibling of our input field.

Javascript:
function addPickerLink()
{
 var inputs,pickLink,pickText;

// loop through all inputs
 inputs=document.getElementsByTagName('input');
 for(i=0;i<inputs.length;i++)
 {
		
// if the class contains 'date'
   if(/date/.test(inputs[i].className))
   {

// create a new link and a text
    pickLink=document.createElement('a');
    pickText=document.createTextNode('pick a date');
	
// add the text as a child of the link
    pickLink.appendChild(pickText);

// set the href to # and call picker when clicked or tabbed to		
    pickLink.setAttribute('href','#'); 
    pickLink.onclick=function(){picker(this)};
    //pickLink.onkeypress=function(){picker(this)};

// add the new link directly after the input
    inputs[i].parentNode.appendChild(pickLink)
    inputs[i].parentNode.insertBefore(pickLink,inputs[i].nextSibling);
    }
  }
}
See the cleaner picker demo in action.

Things to remember

That's about it, with these tools we are able to access and alter any element of a document, and can enhance the user's experience without becoming dependent on Javascript.

It might be a bit confusing at first, but once you got your head around the DOM a bit, it gets easier every time you use it.

Some common obstacles are:

What about innerHTML?

When Internet Explorer 4 came to be, innerHTML was born, a fast way to generate and change content. It is a way to read the content of an element a lot easier than with the original W3C recommendations. This is especially the case, if an element contains childNodes which are elements themselves and we want to read the whole content. To do this with DOM only, you need to go through excrutiating exercises[1] of checking nodeTypes and reading the values of each child node. innerHTML is a lot easier to use, but has some drawbacks. For example you do not get any references back to the elements you create with it as the whole value is a string rather than objects. Furthermore, innerHTML is only HTML related, not XML, and DOM was meant to traverse any markup. For a comparison and browser support check the DOM section of Quirksmode.org[2] or the big discussion at Developer-x[3].

Links

Creative Commons License Unless otherwise expressly stated, all original material of whatever nature created by Christian Heilmann and included in this web site and any related pages is licensed under the Creative Commons License.