Separation of CSS and Javascript
You might have noticed in the previous examples that we are not exactly practising what we preach.
Our mistake
True, we separated structure from behaviour, but we also defined presentation via Javascript, which is just as bad from a semantic point of view.
We defined presentation by accessing the style property of elements:
if(!document.getElementById('errormsg')){
var em=document.createElement('p');
em.id='errormsg';
em.style.border='2px solid #c00';
em.style.padding='5px';
em.style.width='20em';
em.appendChild(document.createTextNode('Please enter
or change the fields marked with a '))
i=document.createElement('img');
i.src='img/alert.gif';
i.alt='Error';
i.title='This field has an error!';
em.appendChild(i);
}
This means that whenever we want to change the look of our enhancement, we'd have to change the Javascript, and that is neither practical nor clever.
There are several ways to apply presentation defined in
the CSS to an element via Javascript. One would be
to apply an ID to the element by changing its ID attribute. A much more
versatile way is to apply a class to the element, especially as elements can have more than one class.
Multiple class syntax
For an element to have more than one class, we simply separate the class names with a space:
<div class="special highlight kids">. This is supported by most modern browsers,
some of which show some bugs, though. IE on a Mac does not like several classes when one class contains
the name of the other, and behaves badly when the class attribute starts or ends with a space.
Applying classes via Javascript
To add a class to a certain element, we change its className attribute. So if we want
to change a navigation only when Javascript and DOM is available, we can do this:
HTML:
<ul id="nav">
[...]
</ul>
Javascript:
if(document.getElementById && document.createTextNode)
{
if(document.getElementById('nav'))
{
document.getElementById('nav').className='isDOM';
}
}
This allows us to define two different states in our CSS:
ul#nav{
[...]
}
ul#nav.isDOM{
[...]
}
This would overwrite any class applied to the element though. That is why we need to check if there is already a class applied and add the new one preceeded by a space if that is the case:
if(document.getElementById && document.createTextNode)
{
var n=document.getElementById('nav');
if(n)
{
n.className+=n.className?' isDOM':'isDOM';
}
}
The same applies when we want to remove classes that have been added dynamically, as some
browsers don't allow for something like class="foo bar ". This can be
pretty annoying, and it is easier to re-use a function that does this for us (<- denotes a linewrap):
function jscss(a,o,c1,c2)
{
switch (a){
case 'swap':
o.className=!jscss('check',o,c1)?o.className.replace(c2,c1): <-
o.className.replace(c1,c2);
break;
case 'add':
if(!jscss('check',o,c1)){o.className+=o.className?' '+c1:c1;}
break;
case 'remove':
var rep=o.className.match(' '+c1)?' '+c1:c1;
o.className=o.className.replace(rep,'');
break;
case 'check':
return new RegExp('\\b'+c1+'\\b').test(o.className)
break;
}
}
This example function takes four parameters:
a- defines the action you want the function to perform.
o- the object in question.
c1- the name of the first class
c2- the name of the second class
Possible actions are:
swap- replaces class
c1with classc2in objecto. add- adds class
c1to the objecto. remove- removes class
c1from the objecto. check- test if class
c1is already applied to objectoand returnstrueorfalse.
Let's have an example how to use it. We want all headlines of the second level expand and collapse their next sibling element. A class should be applied to the headline to indicate that it is a dynamic element and to the next sibling to hide it. Once the headline gets activated, it should get another style and the sibling element should get shown. To make things more interesting, we also want a rollover effect on the headline.
CSS:
.hidden{
display:none;
}
.shown{
display:block;
}
.trigger{
background:#ccf;
}
.open{
background:#66f;
}
.hover{
background:#99c;
}
JS:
function collapse()
{
// check if DOM is available, return if not
if(!document.createTextNode){return;}
// create new paragraph explaining that the headlines
// are clickable
var p=document.createElement('p');
p.appendChild(document.createTextNode('Click on the headlines to
collapse and expand the section'));
// loop over all headlines of the second level
var heads=document.getElementsByTagName('h2');
for(var i=0;i<heads.length;i++)
{
// grab the next sibling (the loop is needed because
// of whitespace issues
var tohide=heads[i].nextSibling;
while(tohide.nodeType!=1)
{
tohide=tohide.nextSibling;
}
// hide the sibling by applying the class 'hidden' and
// show that the headline is clickable by applying
// the class 'trigger'
cssjs('add',tohide,'hidden')
cssjs('add',heads[i],'trigger')
// store the element to be hidden in an attribute
heads[i].tohide=tohide;
// add the class 'hover' when the mouse touches the headline
heads[i].onmouseover=function()
{
cssjs('add',this,'hover');
}
// remove the class 'hover' when the mouse leaves the headline
heads[i].onmouseout=function()
{
cssjs('remove',this,'hover');
}
// if the user activates the headline
heads[i].onclick=function()
{
// test if the class 'hidden' is already applied to the
// next sibling
if(cssjs('check',this.tohide,'hidden'))
{
// if that is the case, replace it with shown and
// the headline class with open
cssjs('swap',this,'trigger','open');
cssjs('swap',this.tohide,'hidden','shown');
} else {
// and vice versa
cssjs('swap',this,'open','trigger');
cssjs('swap',this.tohide,'shown','hidden');
}
}
// insert the new paragraph before the first h2.
document.body.insertBefore(p,document.getElementsByTagName('h2')[0]);
}
function cssjs(a,o,c1,c2)
{
[...]
}
}
window.onload=collapse;
See it in action here.
That way we successfully separated structure, presentation and behaviour and created a rather complex effect. Maintenance of this effect is easy, and does not require any Javascript knowledge.
Save css.js for your own use by saving the following link's target to your computer. css.js
