Skip to page content or Skip to Accesskey List.

Work

Main Page Content

A Touch Of Class Skinable Javascript

Rated 4.15 (Ratings: 5)

Want more?

  • More articles in Code
 
Picture of codepo8

Chris Heilmann

Member info

User since: 29 Jul 2002

Articles written: 17

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:

  • Only apply itself when it is possible to do so.
  • Use the markup as it is without the need to add inline event calls.
  • Be independent of the input device (insofar this is possible).
  • Play along well with other Javascripts (no common global variables, no event hijacking).
  • Keep the visuals to the CSS, to avoid double maintenance.

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:

  • We need to know javascript (at least how to set variables) to change

    the look and feel.
  • We can only change backgrounds, if we also wanted to change the colour,

    we'd need to set more variables and extend the hover and the collapse

    functions.

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.

Currently employed in London as a Lead Front End Developer, Chris has been bouncing around the globe working for several agencies and companies. A web enthusiast from 1997 on workplaces include Munich, London, Santa Monica and San Francisco. More of Chris' writings can be found at http://icant.co.uk and he blogs at http://wait-till-i.com

The access keys for this page are: ALT (Control on a Mac) plus:

evolt.org Evolt.org is an all-volunteer resource for web developers made up of a discussion list, a browser archive, and member-submitted articles. This article is the property of its author, please do not redistribute or use elsewhere without checking with the author.