Unobtrusive JavaScript made possible

Unobtrusive Javascript explained

The usual way of writing JavaScript in templates results in obtrusive code. For instance, the classing link_to_function() helper called in symfony like this:

<?php use_helper('Javascript') ?>
<?php echo link_to_function('see the image', 'showPopup('image.jpg')') ?>


The code appearing in the template is:

<a href="#" onclick="showPopup('image.jpg'); return false;">see the image</a>


While this code effectively provides the correct interaction, it contains an inline even handler (onclick) which will create problems with screen readers. Additionally, the CSS revolution taught us to separate content from presentation, and we soon understood the benefits this could bring (reusability of the presentation layer, better maintainability, etc). Following the same concept would naturally lead to separating behavior from content, and this means putting away JavaScript code. Accessibility and layer separation recently led to a new way of using JavaScript called unobtrusive scripting. The term comes from Stuart Langridge, who started the movement in 2002, rapidly followed by JavaScript gurus like Peter-Paul Koch.

The problem with unobtrusive JavaScript is that it is much longer to design and write. You must first deliver a Javascript-free XHTML content, which must be able to run without further addition, then execute JavaScript code on it to modify some elements, add event handlers, etc. This should sound familiar to those used to styling through CSS, but look at how long it takes:

<head>
  <script type="text/javascript" src="my_behaviors.js"></script>
</head>
<body>
  <a href="image.jpg" id="foobar">see the image</a>


// in my_behaviors.js
function initializePage()
{
  var x=document.getElementById('foobar');
  x.onlick = function () { showPopup('image.jpg'); return false };
}
window.onload = initializePage;


The link works even if JavaScript is disabled. But in order to modify if afterwards, we must add an id attribute to it, then we must register its onclick event handler in JavaScript, and then make sure that the JavaScript is launched when the DOM is ready, therefore executing it when the onload event fires. This is the correct way to achieve the same effect as the first listing in an unobtrusive way.

If you ever tried this, you probably thought just like me: the hell with unobtrusiveness, there is no way I'll spend the rest of my life writing ten lines instead of one just for the sake of accessibility and layer separation!

Fortunately, there are two tools that could make you change your mind.

The first is a JavaScript framework. The most well-known (because it's the official js framework of the most well-known web application framework) is Prototype, but you could as well consider jQuery or others. Doing unobtrusive JavaScript (ok, let's call it UJS until the end of this article) with a JavaScript framework is a lot more easier than with plain JavaScript. See how the previous listing could be reduced by using jQuery:

<head>
  <script type="text/javascript" src="jquery.js"></script>
  <script type="text/javascript" src="my_behaviors.js"></script>
</head>
<body>
  <a href="image.jpg" id="foobar">see the image</a>


// in my_behaviors.js
$().ready(function(){
  $('#foobar').click('showPopup('image.jpg')');
 })


The jQuery dollar function can use a CSS3 selector to find a DOM element, and the addition of a handler is greatly facilitated by the click method. Also, the ready method fires when the document is ready, but you can attach more than one funciton to it.

But that's not enough. This is still way longer to write than the obtrusive version, which was using a single call to a PHP helper. But why not use a helper to add UJS code? This could be very easily done. The syntax could look like this:

<?php use_helper('UJS') ?>
<a href="image.jpg" id="foobar">see the image</a>
<?php UJS_add_behaviour('#foobar', 'click', 'showPopup('image.jpg')') ?>


The UJS helper could very well manage storing the UJS code in the session and writing it in a separate file that would be called by the first template, transforming this listing into the previous one. In fact, I've written a symfony plugin called sfUJSPlugin that does exactly that.

It goes event beyond: If you use the regular symfony helpers, every mention of an event handler property gets automatically transformed into UJS code. So the same effect as before can be achieved with just:

<?php use_helper('UJS') ?>
<?php link_to('see the image', 'image.jpg', 'onclick=showPopup('image.jpg')') ?>


This is not longer than the first example, and that's unobtrusive. At last, we have the right tools to make the web both accessible and usable.

Possibly related posts (automatically generated):

12 Comments so far

  1. Dave Dash on February 28th, 2007

    So does the sfUJSPlugin use (or come bundled with) jQuery?

    I think the transformation code is phenomenal, btw. I don't think you would have sold me unless I saw that.

    P.S. Nice blog ;) You should enable the pretty URLs.

  2. François Zaninotto on February 28th, 2007

    Yes, the plugin comes bundled with jQuery.

    What transformation code are you referring to? Do you mean the filter that takes the regular helpers and turns them to unobtrusive ones?

    P.S.: Pretty URLs are now enabled, thanks.

  3. [...] Zaninotto has been talking about a new Unobtrusive JavaScript Plug-In for [...]

  4. [...] jquery symfony François Zaninotto has just released a symfony plugin that wraps up jQuery. Allows some very cool stuff and much nicer [...]

  5. Dave Dash on February 28th, 2007

    Yeah the filter that takes regular helpers and turns them into unobtrusive ones. It's one of the selling points of using frameworks is just the sheer ease-of-use aspect.

    This is well timed too, I was thinking about moving away from Prototype to jQuery, myself. YUI was nice, but... complex and now that Ext will support both, I think we can see something real interesting emerging in the world of Javascripting.

  6. Markus on March 1st, 2007

    hi,

    outputbuffers which are opened in UJS_block() are never closed.. they are only cleaned in UJS_end_block()

    Thank you, Markus

  7. François Zaninotto on March 1st, 2007

    Markus,

    Yes, UJS_block() and UJS_end_block() have to be used in pair. Just like slot() and end_slot() in symfony.

  8. [...] Zaninotto has been talking about a new Unobtrusive JavaScript Plug-In for [...]

  9. Markus on March 1st, 2007

    Hi Francois,

    the problem I see is, that the outputbuffers are never closed with ob_end()..

    Bye, Markus

  10. François Zaninotto on March 1st, 2007

    I use ob_get_clean(), which is a shortcut for ob_get_contents() and ob_end_clean(), so the outputbuffers are indeed closed.

  11. Markus on March 1st, 2007

    ahh sorry, you are right... didn't read carefully and also don't know this new function..

  12. Dave Dash on March 6th, 2007

    So I've been thinking about Javascript and read a little about UJS... and I'm almost thinking that even this isn't the optimal solution. This is nice on the lazy front, the same way that inline-css is easy too... but this article (hope you use the markdown plugin ;) ) almost suggests we should use javascript the way we use CSS....

    I'm wondering if we (the community) can brainstorm some smarter solutions that:

    1. seperate behavior from semantics
    2. preserve symfony's (or whatever framework's) ability to simplify these type of interactions.

    Because in the end, there's something to say about keeping your PHP code as clean as your outputted HTML/JS/CSS, etc.

    Or maybe I'm just crazy.