How to reach what we want to change

For the inexperienced Javascript developers, HTML is their playground.

HTML:
<a href="index.html" 
onmouseover="image1.src='1on.gif'" 
onmouseout="image1.src='1off.gif'">
<img src="1off.gif" name="image1" border="0" height="150" width="150" 
alt="home"></a> 

or if they are a bit more advanced:

HTML:
<a href="index.html" 
onmouseover="roll('home',1)" 
onmouseout="roll('home',0)">
<img src="home.gif" name="home" border="0" 
height="150" width="150" 
alt="home"></a> 
Javascript:
// preloading image
homeoff = new Image(); 
homeoff.src = 'home.gif';
homeon = new Image(); 
homeon.src = 'homeoff.gif';
function roll(imgName,a) 
{
 imgState=a==0?eval(imgName + 'on.src'):eval(imgName + 'off.src');
 document.images[imgName].src = imgState;
}

In any case, all the event calls are in the HTML, and if the function name changes, we'd have to change each document. Furthermore, each rollover means a lot of markup which adds to the overall page weight.

Out! Out! - you demons of inline event calls

Let's forget for the moment, that almost every rollover effect these days can be achieved via CSS rather than Javascript - let's say we want to use the following markup and create a rollover for the image:

HTML:
<a href="index.html"><img src="home.gif" id="home" alt="home"></a>

Now, how to change that when the mouse is over it?

Climbing the branches of the node tree

Each XML (and that includes HTML) document is a node tree. A node is a part of this tree (think of a file or a folder in windows explorer when you navigate around your harddisk). A node can be twelve different things[1] - for HTML, only three are really interesting: element, TextNode and AttributeNode.

Our climbing equipment

Let's see which functions and attributes we can use to navigate the node tree of a document, and how to jump from one element to another.

Functions to reach an element in the page

getElementById('elementID')
returns the element with the id elementID as an object.
getElementsByTagName('tag')
returns all elements with the name tag as an array.

Of course you can mix and match these two. Some examples:

document.getElementById('navigation').getElementsByTagName('a')[3];
	returns the fourth link inside the element with the ID 'navigation'
document.getElementsByTagName('div')[2].getElementsByTagName('p')[0];
	returns the first paragraph inside the third div in the document.

Tools to navigate from a certain element

childNodes
returns an array of all the nodes inside the current one. There is also firstChild and lastChild, which are shorter versions for childNodes[0] and childNodes[this.childNodes.length-1].
parentNode
The element containing this one
nextSibling
the next element on the same level in the document tree
previousSibling
the previous element on the same level in the document tree

All of these can be mixed at will and need.

Javascript:
var other=document.getElementById('nav').childNodes[3].firstChild;
	returns the 4th element's first sub element inside the element 
	with the ID nav.
var prevlink=o.parentNode.previousSibling.firstChild.childnodes[2];
	returns the third node inside the previous element 
	that is on the same level as the parent element of o.

Attributes and functions for elements

attributes
returns an array of all the attributes of this element. Does not work with Internet Explorer below version 6.
data
returns or sets the textual data of the node
nodeName
returns the name of the node (the HTML element name)
nodeType
returns the type of the node — 1 is an element node, 2 attribute and 3 text.
nodeValue
returns or sets the value of the node. This value is the text when the node is a textnode, the attribute if it is an attribute or null if it is an element.
getAttribute(attribute)
returns the value of the attribute attribute.
Javascript:
var other=document.getElementById('nav').firstChild;
if(other.nodeType==3)
{
 other.data='newtext';
} 
if(other.nodeType==1)
{
 other.firstChild.data='newtext';
} 

Now to reach the image in our example, we can use either getElementsByTagName or getElementById.

HTML:
<a href="index.html"><img src="home.gif" id="home" alt="home"></a>
Javascript:
function findimg()
{
 var image;
 image=document.getElementById('home');
 if (image) 
  {
   image.style.border='3px dashed #ccc';
  }
}
or:
function findimg()
{
 var imgs,i;
 imgs=document.getElementsByTagName('img');
 for(i in imgs)
 {
  if(/home.gif/.test(imgs[i].src))
   {
    imgs[i].style.border='3px dashed #ccc';
   }
  }
}

Using getElementById is a lot easier, as we don't have to loop through all elements and find a unique identifier. In this example, we checked if the src attribute of the image object contains 'home.gif'. A more common way is to check for a special class.

HTML:
<a href="index.html"><img src="home.gif" class="roll" alt="home"></a>
Javascript:
function findimg()
{
 var imgs,i;
 imgs=document.getElementsByTagName('img');
 for(i in imgs)
  {
  if(/roll/.test(imgs[i].className))
   {
    imgs[i].style.border='3px dashed #ccc';
   }
  }
}

Now, to add a roll-over effect, all we need to do is to add a function that does the switching of the image source, and adds the event handlers to the image.

function findimg()
{
 var imgs,i;
// loop through all images of the document
 imgs=document.getElementsByTagName('img');
 for(i=0;i<imgs.length;i++)
 {
// test if the class 'roll' exists
  if(/roll/.test(imgs[i].className))
  {
// add the function roll to the image onmouseover and onmouseout and send
// the image itself as an object
   imgs[i].onmouseover=function(){roll(this);};
   imgs[i].onmouseout=function(){roll(this);};
  }
 }
}
function roll(o)
{
 var src,ftype,newsrc;
// get the src of the image, and find out the file extension
 src = o.src;
 ftype = src.substring(src.lastIndexOf('.'), src.length);
// check if the src already has an _on and delete it, if that is the case 
 if(/_on/.test(src))
 {
  newsrc = src.replace('_on','');
 }else{
// else, add the _on to the src 
  newsrc = src.replace(ftype, '_on'+ftype);
 }
 o.src=newsrc;
}
window.onload=function(){
 findimg();
}
Test this example here.

Good for the moment, but we forgot one thing: Albeit being merely eye candy, a rollover should work without a mouse, too. To achieve this, we need to check if the link around the image gets the focus or not, as the image itself is not reachable via the keyboard when embedded in a link.

To do this, we need to get the element that contains the image, in this case the link. We do this via parentNode command. As this also changes the object that gets sent as a parameter to the function roll(), we need to find the image again. Hence we loop through the childNodes of the link and check which one is an element and an image by checking nodeType and nodeName. This is necessary, as some browsers see whitespace in the source as an own node, whereas others don't.

function findimg()
{
 var imgs,i;
// Loop through all images, check if they contain the class roll
 imgs=document.getElementsByTagName('img');
 for(i=0;i<imgs.length;i++)
 {
  if(/roll/.test(imgs[i].className))
  {
// add the function roll to the parent Element of the image
  imgs[i].parentNode.onmouseover=function(){roll(this);};
  imgs[i].parentNode.onmouseout=function(){roll(this);};
  imgs[i].parentNode.onfocus=function(){roll(this);};
  imgs[i].parentNode.onblur=function(){roll(this);};
  }
 }
}
function roll(o)
{
 var i,isnode,src,ftype,newsrc,nownode;
// loop through all childNodes
 for (i=0;i<o.childNodes.length;i++)
 {
  nownode=o.childNodes[i];
// if the node is an element and an IMG set the variable and exit the loop
  if(nownode.nodeType==1 && /img/i.test(nownode.nodeName))
  {
   isnode=i;
   break;
  }
 }
// check src and do the rollover
 src = o.childNodes[isnode].src;
 ftype = src.substring(src.lastIndexOf('.'), src.length);
 if(/_on/.test(src))
 {
  newsrc = src.replace('_on','');
 }else{
  newsrc = src.replace(ftype, '_on'+ftype);
 }
 o.childNodes[isnode].src=newsrc;
}

window.onload=function(){
 findimg();
}
Test this mouse independent example here.

Why don't you try it?

Simply download the demo HTML and try one of the following tasks. Follow the solution links to see one possible solution. The solutions have the Javascript inline as a demonstration, not in an extra document where they should be. This was done to help you see the necessary markup in one document rather than two.

  1. Change the colour of every H2 headline to blue. Solution colourchange.
  2. Outline every second paragraph with a black border. Solution p-border.
  3. Check which of the links in the document is external (by checking that the href attribute does not contain window.location.hostname) and add the href of the link as a text in parenthesis after the link. Until you learn how to do that propely, use the innerHTML attribute of the link object to change its content. Solution external links.
  4. Add an onclick handler to each link with the attribute target that opens a 400x400 pixels pop-up window. Solution pop-up.

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.