Behavioural Separation
This article first appeared in issue 218 of A List Apart magazine. Breaking up is hard to do. But in web design, separation can be a good thing. Content, style and behaviour all deserve their own space.
Style guide
One of the greatest advantages to designing with Cascading Style Sheets is the potential for separation of style and content.
You could style an entire website using nothing but inline style declarations:
<p style="font-weight: bold; color: red;">This text is important</p>
But then you might as well use <font>
tags. It makes more sense to keep style information in a separate file and add semantically sensible hooks into your markup:
Markup: <p class="important">This text is important</p>
CSS: p.important { font-weight: bold; color: red; }
That makes everybody’s life easier. It’s easy for the designer to tweak the styles — like changing important text from red to blue. It’s easier for the writer to edit the text without wading through inline style declarations. It’s easier for the visitor who doesn’t have to download unnecessarily bloated pages.
What’s good for CSS is good for JavaScript. Unfortunately, a lot of JavaScript functionality is found embedded within markup.
Everything is superficially intertwingled
Suppose you want to add some behaviour to a particular link. You could use what’s known as the javascript:
pseudo-protocol:
<a href="javascript: doSomething();">click for fun</a>
That’s a short-sighted approach. User agents that don’t support JavaScript will choke on that href
value. A more common approach is to use event handlers in combination with a meaningless href
value:
<a href="#" onclick="doSomething();">click for fun</a>
That’s slightly better but it’s semantically meaningless. Why mark up a piece of text as a link unless it goes somewhere?
<a href="arealpage.html" onclick="doSomething(); return false;">click for fun</a>
Now we’re getting somewhere. The link makes sense even if JavaScript isn’t enabled (and, for the first time, the link is spiderable). But the problem remains that the event handler is mixed in with the markup. Inline event handlers are the JavaScript equivalent of inline style declarations. Fortunately, the event handlers can be removed and placed in an external file, just as you would do with CSS. And, just as with CSS, you can add hooks to your markup to target the elements you want to play with:
<a href="arealpage.html" class="fun">click for fun</a>
In an external JavaScript file you could now write a script to find all the links that have a className of "fun" and have them execute a function — doSomething()
— when they are clicked.
Separating out behaviour from markup like this is called unobtrusive JavaScript.
Enough theory. I’m going to apply this technique to a real-world example.
Rogue’s gallery
Two years ago, I wrote an article for A List Apart on building a JavaScript image gallery. The JavaScript in that article works fine, but take a look at the markup:
<ul>
<li><a onclick="return showPic(this)" href="images/bananas.jpg" title="A bunch of bananas on a table">some bananas</a></li>
<li><a onclick="return showPic(this)" href="images/condiments.jpg" title="Condiments in a Chinese restaurant">two bottles</a></li>
<li><a onclick="return showPic(this)" href="images/shells.jpg" title="Seashells on a table">some shells</a></li>
</ul>
Those repetitive event handlers are intrusive. I could replace them with class
attributes:
<ul>
<li><a class="gallerypic" href="images/bananas.jpg" title="A bunch of bananas on a table">some bananas</a></li>
<li><a class="gallerypic" href="images/condiments.jpg" title="Condiments in a Chinese restaurant">two bottles</a></li>
<li><a class="gallerypic" href="images/shells.jpg" title="Seashells on a table">some shells</a></li>
</ul>
But that would be equally repetitive. I wouldn’t do that if I wanted to style every item in the list, so there’s no need to do it when I want to add behaviour to every item in the list. Instead, I can simply add a unique id to the list:
<ul id="imagegallery">
<li><a href="images/bananas.jpg" title="A bunch of bananas on a table">some bananas</a></li>
<li><a href="images/condiments.jpg" title="Condiments in a Chinese restaurant">two bottles</a></li>
<li><a href="images/shells.jpg" title="Seashells on a table">some shells</a></li>
</ul>
Now I need to write a function to add the event handlers externally.
Separation anxiety
I’ll write a function called prepareGallery
. This function will use the Document Object Model to find the links in the document I want. This is possible with the methods getElementById
and getElementsByTagName
:
document.getElementById("imagegallery").getElementsByTagName("a");
Those methods work like selectors in CSS. It’s the equivalent of combining ID and element selectors:
#imagegallery a
Here’s what I want to do:
- Make sure that the browser understands the DOM methods I will be using.
- Make sure that there is an element with the ID "imagegallery".
- Get the list with the ID "imagegallery".
- Get all the links in the list and loop through them all.
- Add the
onclick
event handler to each link, pointing it to theshowPic
function.
Here’s how that translates into JavaScript:
function prepareGallery() {
if (document.getElementById && document.getElementsByTagName) {
if (document.getElementById("imagegallery")) {
var gallery = document.getElementById("imagegallery");
var links = gallery.getElementsByTagName("a");
for (var i=0; i < links.length; i++) {
links[i].onclick = function() {
return showPic(this);
}
}
}
}
}
Now that I’ve written my function, all I have to do is run it. But there’s a problem. I can’t simply execute the function like this:
prepareGallery();
If I do that, the function — which is in an external file or in the <head>
of my document — will run before the rest of the document has finished loading. The DOM methods won’t work because there won’t be any Document Object Model. Without a complete document, there can’t be a corresponding model.
I need to wait until the entire document has finished loading. Fortunately, the loading of the document triggers an event, namely the load
event of the window
object. I can use the corresponding onload
event handler, window.onload
, to assign the prepareGallery
function to this event:
window.onload = prepareGallery;
Rather than hogging the onload
event for this one function, it’s better to use something like Simon Willison’s excellent addLoadEvent
function which allows you to queue up functions that you want to trigger when the document finishes loading:
addLoadEvent(prepareGallery);
Trial separation
Unobtrusive JavaScript is a relatively new idea. Try it out for yourself.
- Begin with your content,
- give it structure with semantically descriptive markup,
- apply a presentation layer using CSS, and finally,
- add a behaviour layer with DOM Scripting.
Just make sure they all keep their distance.