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:

  • Get rid of the "(new window)" wording in the links.
  • Add an event handler to call a function popw()

This function should

  • Show the linked image below the link when it is not there already.
  • Remove the image, if it is already there (to avoid the link to add the image over and over again).
  • Make the image disappear when users click on it.

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:

  • Ensure to check for elements before you try to access them. Some browsers are happy to check for object.nextSibling.nodeName and return false when there is no next sibling or if it is a text node, others however throw an error stating that you try to access an attribute of a non-existant element.
  • Make sure not to rely on the markup too much, as linebreaks might be read as a new node, or the markup might change, and you don't want to change your script every time the markup changes.
  • Reading the content of an element is done by reading the values of its childNodes, not the value of the element itself! document.getElementsByTagName('h2')[0].nodeValue is empty, document.getElementsByTagName('h2')[0].firstChild.nodeValue isn't.
  • When checking for nodeNames and attributes, make
    sure to stay case insensitive, as some browsers render elements as uppercase, others as lowercase.
  • DOM generated HTML is in most of the cases not well-formed, if you want to reuse HTML from a browser-generated page, you'd have to tidy it up.
  • Avoid too much looping, if you have the chance to create the markup you need to work with, go for IDs instead.
  • Know your syntax. Many a time a getElementsById can cause a lot of rescripting.
  • Know your Javascript objects and HTML attributes, it is no use checking for an attribute that is not meant to be there to start with.
  • Don't assume your own ways of marking up to be commonly used. An example is to check if the className contains your string rather than is your string, as some developers like using multiple classes.

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.
counter hit make

AltStyle によって変換されたページ (->オリジナル) /