« Back to home

The whys of Unobtrusive JavaScript

The HTML standards provide attributes on many elements, enabling you to set them up to call JavaScript when an event occurs. For example, a text field might use the onchange attribute to call some JavaScript to trim whitespace from the beginning and end of the value:

<input type="text" id="field1" 
       name="username" onchange="trim('field1');">

However, this method of working has gradually become unpopular. Instead, most now seem to recommend setting up all the events on a page using a JavaScript routine; a practice called unobtrusive JavaScript.

The thing is, none of the rationales provided for unobtrusive JavaScript ever made any sense to me, and it wasn’t until recently that I worked out some valid reasons for the practice.

Bogus reasons for unobtrusive JavaScript

Let me run through a few of the bogus reasons first:

“The old-fashioned method only lets you run one event.”

    <a onclick="event1(); event2();">

“The old-fashioned method makes event callbacks visible in the HTML.”

You say that like it’s a bad thing. If I’m editing the HTML, I need to know if an element has associated JavaScript functionality. Otherwise, I might break the JavaScript by changing the element.

Hiding the link between the script and the HTML means a person looking at the HTML has no idea it’s invoking some code somewhere else. Congratulations, you’ve just reinvented the COMEFROM statement.

“But we should keep behavior separate from layout.”

In that case, we should stop putting A elements in HTML, and instead parse the DOM and insert all the links later using JavaScript. And we should stop using features like :hover in CSS, and don’t even think about using the new CSS 3 behavior property. Right?

“The old-fashioned method is ugly.”

Old method:

    <a id="myid" onclick="event('myid');>

New method:

    <a id="myid">

and elsewhere

    document.getElementById('myid').addEventListener('onclick',  
      function () { event('myid'); });

Or the even newer way:

    <a id="myid">

and elsewhere

    document.getElementById('myid').addEventListener('onclick',
      event);
    
    var event = function (e) {
      if (window.event && window.event.srcElement)
        el = window.event.srcElement;
      if (e && e.target)        
        el = e.target;    
    ... 
    }

You’re telling me the first one is the ugly one? Really?

Face it, you’re not removing the ugliness. You’re making it worse, it’s just that you’re moving it out of the HTML and into the JavaScript.

“OK, but if you’ve got a dozen related elements to add behavior to, the onclick attributes get ugly.”

Sure, if you (say) want to add behavior to every image on the page, that’s a good use of the new technique. Scan through the DOM and add behavior to all the appropriate nodes, that’s the smart thing to do in that case. But that doesn’t justify a blanket statement that it’s always better to do it that way.

“But unobtrusive scripting is better for accessibility and graceful degradation.”

How, exactly? Browsers with JavaScript turned off will ignore onclick attributes, just like they’ll ignore your JavaScript-created event callbacks. In both cases the page needs to be developed in such a way that it still works without the script active. In both cases accessibility requires using appropriate markup to associate labels with actions. Where the callbacks get created makes no difference at all.

Good reasons for unobtrusive JavaScript

So spurious arguments dealt with, what do I think are the good reasons for unobtrusive JavaScript?

The first good reason is scope.

When you use the HTML method, the method you call has to be globally visible to the page. You can place all your callbacks in a single global object to reduce the problem (MyApp.nameClick(), MyApp.validate() and so on), but the name of the global object is then fixed by how the HTML refers to it. If you need to use some code which uses the same global, you’re stuck, unless you rewrite all the HTML to use a different global.

With the unobtrusive method, your callbacks don’t need a globally visible name. In fact, they don’t need a name at all — they can be anonymous functions.

The second good reason is performance. Page speed is critical to user experience, and a difference of just 1 second in the time taken to make the page readable can significantly increase the number of readers who bail on your site. For this reason, modern practice is to try to defer as much browser work as possible until after the page has been displayed.

With the old fashioned method, the browser attempts to set up all the JavaScript callbacks while it is parsing the page. With the new method, you can load your JavaScript file at the bottom of the page, and have it run after the initial render is complete. As an added bonus, you can write a single script that sets up callbacks for multiple pages, and it will be cached browser-side — and meanwhile, all those onclick="…" occurrences can be removed from every HTML page load.

There are also some other performance benefits, which I’ll consider in my next posting.