
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
c1
with classc2
in objecto
. add
- adds class
c1
to the objecto
. remove
- removes class
c1
from the objecto
. check
- test if class
c1
is already applied to objecto
and returnstrue
orfalse
.
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