Ok, let's repeat the trinity of web standards: Markup is the structure,

Style Sheets are the presentation and Javascript is the behaviour. That much

should be common sense by now. New starters in web development have the

chance to follow these rules easily - no more annoying IE4s and Netscape

Communicators to worry about - modern browsers can do the CSS and xHTML dance

quite well. The separation of markup and presentation is practised a lot, and

loads of tutorials and templates help the developer to do so.

With Javascript, it is still a bit dodgy, as too many good resources get lost

in the mass of bad or outdated ones.

Look - new Javascript!

2004's state-of-the-art Javascript should be unobtrusive and easily maintainable.

In essence, this means:

The last item is what this article is about. Many a time,

you encounter scripts that are nice in themselves, but fail to completely

separate the presentation from the behaviour. We cannot assume that every web

developer knows Javascript - and its syntax. To avoid frustration using a

script, we need to find a way to allow the developer to skin the outcome of this behaviour with

CSS. This also makes maintenance easier, as you don't need to check in two

places, if you have to change the visiual outcome - all it needs is a change of

the CSS.

We want clickable headlines - now!

Let's take a look at an example. Say we want to make every second level headline

in a document clickable. What it should do is collapse and expand an adjacent element.

Headers as they are are not clickable elements, so we need to make the user

aware that ours are. We do this by adding a > and a hover state.

In the world of non-Internet Explorer, the hover could be done via CSS, but

in the real world, we do it via Javascript to ensure that the majority of users

will see the effect.

When the header was clicked, we want to give it a selected state.

Now, if we kept the design in the Javascript, this could be done like this:

// Define colours

var hovercolour='#ffc';

var normalcolour='#fff';

var highlight='#69c';

function init()

{

var h2s,i,tohide,tohideobj,isexpanded;

// grab all second level headlines and loop over them

h2s=document.getElementsByTagName('h2');

for (i=0;i<h2s.length;i++)

{

// get next sibling (the element to hide, check that it is an element

tohide=h2s[i].nextSibling;

while(tohide.nodeType!=1)

{

tohide=tohide.nextSibling;

}

h2s[i].tohideobj=tohide;

// add the hover function onmouseover and onmouseout

h2s[i].onmouseover=function(){hover(this,this.tohideobj,1);}

h2s[i].onmouseout=function(){hover(this,this.tohideobj,0);}

// hide next element and add onclick event to show and hide it

tohide.style.display='none';

h2s[i].onclick=function(){collapse(this,this.tohideobj);return false}

h2s[i].insertBefore(document.createTextNode('>'),h2s[i].firstChild);

}

}

// hover function, adds the hover colour

// unless the headline is an active trigger

function hover(o,ho,state)

{

if(ho.style.display=='none')

{

o.style.background=state==1?hovercolour:normalcolour;

}

}

// collapse function, shows and hides the element and sets the highlight

// colour of the headline

function collapse(o,ho)

{

ho.style.display=ho.style.display=='none'?'block':'none';

o.style.background=highlight;

}

window.onload=init;

It does work, but has some disadvantages:

Becoming classy

What to do? Easy - put all the presentation in style sheet classes, and

use the className attribute to change them.

This also means we won't have to add the > any longer, as that can be

done in a background image.

.hover{

padding-left:30px;

background:url(arrow.gif) 5px 5px no-repeat #ffc;

color:#000;

}

.highlight{

padding-left:30px;

background:url(arrowon.gif) 5px 5px no-repeat #69c;

color:#fff;

}

.normal{

padding-left:30px;

background:url(arrow.gif) 5px 5px no-repeat #fff;

color:#000;

}

.hidden{

display:none;

}

Then we change the functions accordingly:

function init()

{

[...]

// hide next element and add onclick event to show and hide it

tohide.className='hidden';

h2s[i].className='normal';

[...]

}

// hover function, adds the hover colour

// unless the headline is an active trigger

function hover(o,ho,state)

{

if(ho.className=='hidden')

{

o.className=state==1?'hover':'normal';

}

}

// collapse function, shows and hides the element and sets the highlight

// colour of the headline

function collapse(o,ho)

{

ho.className=ho.className=='hidden'?'':'hidden';

o.className='highlight';

}

Works a treat, but still has one problem:

If I already have a class on the headline, the script will remove that one,

which means once again Javascript meddling with presentation

Becoming even classier

One thing that is amazingly enough still not too commonly known:

Elements can have more than one class. Something like

is perfectly valid

HTML
and is supported by modern browsers.

Bearing this in mind, we can add our classes to the existing ones or remove

them. For this, we can use two small functions.

function juggleClass(o,c,s)

{

o.className=s==1?o.className+' '+c:o.className.replace(' '+c,'');

}

function checkClass(o,c)

{

var isClassInObj=o.className.indexOf(c)!=-1?true:false;

return isClassInObj;

}

juggleClass adds the class c to the object o when s is 1, otherwise it removes the class

c from the object o.

checkClass checks if the class c is one of the classes of the object o.

If we add that feature, our final function looks like this:

function init()

{

var h2s,i,tohide,tohideobj,isexpanded;

// grab all second level headlines and loop over them

h2s=document.getElementsByTagName('h2');

for (i=0;i<h2s.length;i++)

{

// get next sibling (the element to hide, check that it is an element

tohide=h2s[i].nextSibling;

while(tohide.nodeType!=1)

{

tohide=tohide.nextSibling;

}

h2s[i].tohideobj=tohide;

// add the hover function onmouseover and onmouseout

h2s[i].onmouseover=function(){hover(this,this.tohideobj,1);}

h2s[i].onmouseout=function(){hover(this,this.tohideobj,0);}

// hide next element and add onclick event to the header to show and hide it

juggleClass(tohide,'hidden',1);

juggleClass(h2s[i],'normal',1);

h2s[i].onclick=function(){collapse(this,this.tohideobj);return false}

}

}

// hover function, adds the hover colour

// unless the headline is an active trigger

function hover(o,ho,state)

{

if(checkClass(ho,'hidden'))

{

if(state==1)

{

juggleClass(o,'normal',0);

juggleClass(o,'hover',1);

} else {

juggleClass(o,'normal',1);

juggleClass(o,'hover',0);

}

}

}

// collapse function, shows and hides the element and sets the highlight

// colour of the headline

function collapse(o,ho)

{

if(checkClass(ho,'hidden'))

{

juggleClass(ho,'hidden',0);

juggleClass(o,'normal',0);

juggleClass(o,'highlight',1);

}else{

juggleClass(ho,'hidden',1);

juggleClass(o,'highlight',0);

juggleClass(o,'normal',1);

}

}

function juggleClass(o,c,s)

{

o.className=s==1?o.className+' '+c:o.className.replace(' '+c,'');

}

function checkClass(o,c)

{

var isClassInObj=o.className.indexOf(c)!=-1?true:false;

return isClassInObj;

}

window.onload=init;

Works the same, but now features a full separation of

behaviour and presentation. Storing the class names in global variables would

also enable us to change them easily.

If we want to be independent of other Javascripts, we cannot use window.onload

of course but should use a

more clever

function
instead that adds our function to the overall window.onload.