Unobtrusive Javascript – the stillbreathing way

It’s been a while since I posted a decent code sample in JavaScript, so here’s a quick idea that I’ve been formulating for a while. It’s to make JavaScript unobtrusive – which means it doesn’t clomp it’s heavy boots all over your nice flowers. Sound too good to be true? Not really.

For this example I’ll be using the Prototype library (take a look at their shiny new website, too) as it’s my framework of choice. There are plenty of other frameworks available any one of which will work with this technique, so don’t worry if you’ve got a hankerin’ for another flavour of framework.

OK, onto the article. The problem is that I want to attach a JavaScript command to certain elements, in this case a series of links, the command in question will load content from an external resource into the page at a specified point in an AJAXy manner. Well, actually it will be an AJAHy manner, as we’re using HTML rather than XML as the return data type. What does that mean to you? That you don’t have to do any Javascript transformations of XML data. Vive la innerHTML.

So, first things first. We slip a little onClick goodness into our links … er, hang on. No we don’t – that would be extremely intrusive. So what can we do? Let’s think about what we need to do with this link:

  1. Make the browser aware it’s a special type of link so that we can access it with our Javascript.
  2. Somehow make the link set the Javascript content loader function to access the right server-side script. Hmm.
  3. Somehow make the link tell the Javascript content loader function to put the content returned from the server-side script into a certain element. Hmm again.
  4. Not be shown if the user doesn’t have Javascript enabled/available.

The first thing we do is group all the special links together by giving them a special CSS class name. In this case as our links are going to be refreshing parts of the page we’ll use a CSS class of ‘refresher’. That makes our links Refresher links, and everyone likes refreshment, don’t they?

So we’ve got our class set on all our Refresher links, which means we can use the Prototype DOM function getElementsByClassName to access them all. But what about hiding the links when Javascript isn’t available.

Simple. Let’s set some CSS in our stylesheet to hide the Refresher links by default:

.refresher
{
  display: none;
}

And they’re gone! Now we just need to turn them on again using Javascript – that means only people with Javascript will see them. Here’s the CSS:

.refresheractive
{
  display: inline;
}

And here’s the Javascript:

for (var i=0; i<document.getElementsByClassName('refresher').length; i++)
{
  document.getElementsByClassName('refresher')[i].removeClassName('refresher');
  document.getElementsByClassName('refresher')[i].addClassName('refresheractive');
}

So now those links will only show for people with Javascript. Great. But what about the other two bits of information we need to pass from the link – the processing server-side script and the target element? Well, here’s where we dust off some little-used attributes. Namely rel and rev

I should say at this point that rel and rev do have specific semantic meanings, denoting the documents’ relationship with the target of the link. They are used quite a lot for microformats, and rightly so, but I don’t think this technique is stepping too far away from correct use of those attributes. And at least this way is better than making up non-standard attributes (I’m looking at you, Spry) or forcing convoluted encoded strings into the links id attribute.

So, how do we do it? Easy, like this:

<a href="#targetElement" id="MyFirstRefresherLink" rel="updater.php" rev="targetElement">Click to update</a>

See? Easy. A bit of Javascript magic and we’re up and running.

for (var i=0; i<document.getElementsByClassName('refresheractive').length; i++)
{
  var targetElement = document.getElementsByClassName('refresheractive')[i].getAttribute('rev');
  var processingScript = document.getElementsByClassName('refresheractive')[i].getAttribute('rel');
  // do the updater thingy here
}

That technique works great for me, so I hope it’s useful for you.