Unobtrusive JavaScript — Use It!

I’m sure you’ve heard of unobtrusive JavaScript before. But do you use it? I admit that in my early days I hadn’t and only started to once I began using Mootools.

Update: Some people aren’t reading the link about unobtrusive JS so I’ll summarize the benefits. Its main goal is not to make JS shorter but easier to maintain, very much like CSS makes styling easier to maintain (and I really hope you’re using CSS). It separates the behavior from the structure like CSS separates style from structure. If you’ve only got one or two things that use JS on your page, then yes, inline JS would be the simplest. But if you’re making something more complex, then unobtrusive JS will make your life easier in the long run.

It is possible to create unobtrusive JavaScript without a library, but you’re better off using something like DOM Assistant if you’re not already using a library. Even with all the modules, it’s only 10 kb compressed. That should allow you to easily do all the things you could with a full-fledged library.

It the examples below, I’ll be using Mootools. Look for equivalents for your library.

Link Actions

Don’t

<a href="#" onClick="doSomething(); return false;">Some link</a>
 
<a href="javascript: doSomething();">Some other link</a>

A benefit of separating behavior and structure is that it degrades gracefully. The two links above don’t. They’re broken if the user doesn’t have JavaScript. If you don’t want non-JavaScript users to see something, create it in JavaScript.

Do

function doSomething(e) {
  // some stuff
  new Event(e).stop();
}
window.addEvent('load', function(){
  $$('.linkClass', '#linkID').addEvent('click', doSomething);
 
  // or if you don't want non-javascript users seeing it
  new Element('div', {
    'class': 'button',
    'events': {
      'click': doSomething
    }
  }).injectInside('some_elemet_id');
});

The function doSomething() receives the event object which is extended by Mootools and then stopped. This prevents links from going anywhere in Mootools (in version 1.2 events will be extended automatically, and you can just return false like you’re used to). In the first example, I grab any links with a certain class or id, and attach a click event to them. In the second example, I create a new element and give it a click event, then inject it inside whatever existing element I want.

From here, you can do a lot of interesting things. Something I like to do to maintain the same functionality with or without JavaScript is to use GET requests and AJAX. You can have a normal link that performs just fine without JavaScript, but with JavaScript, it’ll be asynchronous. Here’s the HTML:

<a id="action1" href="action1.php?key1=value1&key2=value2">Perform Action1</a>

And the JavaScript:

var xhrReq = new XHR('method': 'get', 'onSuccess': function(){/* handle the response */});
 
window.addEvent('load', function(){
  $('action1').addEvent('click', function(){
    xhrReq.send(this.getProperty('href'), '_ajax=true');
  })
});

With just that little bit of code, I can attach an AJAX call to a link. It’s up to the server-side script to determine what to return based on the presence of _ajax or not, but other than that, it’s the same functionality with or without JavaScript.

Form Actions

Capturing form submissions is pretty similar to capturing link clicks.

Don’t

<form id="form1" action="action2.php" method="post" onSubmit="doSomething()">
...
</form>

Do

function doSomething(e) {
  // some stuff
  new Event(e).stop();
}
window.addEvent('load', function(){
  $('form1').addEvent('submit', doSomething);
});

Again, your function for form submission has to stop the default event actions, otherwise it’ll submit the form to action2.php. And similar to the tweak above to get the same functionality with or without JavaScript, you can send your form data using AJAX and just grab the path to send it to from the form’s action property. In fact, with Mootools, all you have to do is $('form1').send().

Styling

It’s also important to keep style out of behavior.

Don’t

function error(id) {
  $(id).addStyles({
    'color': '#f00',
    'font-size': 24
  });
}

Here, the styles are located within the JavaScript. Why do this when it’s a lot easier to do the following?

Do

/* in your css file */
.error {
  color: #f00;
  font-size: 24px;
}
function error(id) {
  $(id).addClass('error');
}

Conclusion

That’s it! Now your JavaScript should be a lot more maintainable and readable, and I won’t puke when I see it!


31 Responses to “Unobtrusive JavaScript — Use It!”

  1. Ryan Says:

    Wow thanks Chris! I’ll admit when I do use Javascript I had been using the ‘obtrusive’ kind. I’ll stop for your sake :)

  2. cschneid Says:

    Or simply use a non-js Fallback directly in your tag:
    Some link

  3. cschneid Says:

    Oops, didn’t realize I have to quote HTML :-)

    I meant to suggest the simpler
    <a href=”action1.php” onclick=”dosomething(); return false”>Some link</a>

  4. Chris Says:

    Yeah, good idea. That’s similar to my GET and AJAX example. Using a valid url that performs an equivalent action is always a good idea. But the point of unobtrusive JS is to keep JS out of HTML so it’s easier to maintain.

  5. anonymous Says:

    good stuff, though now a days most sites require javascript to be on. It would interesting to know how many people actually browse the net with javascript turned off.

  6. Hank Says:

    For what it’s worth, I browse exclusively using Firefox + NoScript, and deny/block all Javascript by default. It’s safer (no drive-by malware infections), and breaks the most annoying ads as a bonus. When I run into a site that requires JS (or some function of a site I’m already using) I’ll either stop visiting that site, or turn on JS only temporarily, turning it off again as soon as I’m done with the specific thing I wanted to see/do. There are only a handful of sites for which I’ve permanently whitelisted JS. I definitely appreciate “Unobtrusive JavaScript”, and wish more people practiced these techniques.

  7. Brandon Bowman Says:

    I am with Hank on this one, I use NoScript and only white list hesitantly. I don’t find it impedes my browsing and I enjoy the fact I know what is going on.

  8. bobg Says:

    Hank, browser javascript does not, can not, install malware. It certainly can put up a billion annoying pop-ups but you can avoid that with your pop-up blocker. Browser side javascript has no access to local files, registry or even memory outside the browser. It can write cookies but so can server side code. Active X can install malware so definitely mistrust that.

    As for unobtrusive javascript, it is certainly a great idea to separate javascript from static html. Unfortunately there are aspects of DOM manipulation that vary greatly across browsers (elem.setAttribute for instance, doesn’t work well in IE).

    And don’t get me started on Javascript libraries. In general though, this seems like a nice non-programming way of separating code and markup. Thanks.

  9. Chris Says:

    bobq:

    If your Javascript engine is completely free of security bugs, then you’d be right. However, the real world is more complex than that. There are certain drive-by attacks that affect Firefox, despite the improved safety given by not supporting ActiveX.

    Back to the topic at hand though, this looks like a good way to do things. Maintainability is everything!

  10. Chris Says:

    bobg, it is pretty difficult to overcome all of the browser inconsistencies, which is why I think you almost have to use a library.

    I’m not sure what you’ve got against JS libraries, but if it’s bloat, DOM Assistant is modular, and even with everything included, it’s only 10kb compressed. I haven’t looked at it too closely, but it sure looks like it takes all the trouble out of DOM manipulation for little overhead compared to most other libraries.

  11. strayneuron Says:

    something to keep in mind -> i have found the “unobtrusive javascript” quite error-prone in applications maintained by multiple developers

    html developers can change html|css|div-names|form-names w/o anybody being the wiser and suddenly your JS never fires

  12. Neosapience Says:

    Wow, paranoia can really make your life miserable. If you’re that worried about your data, store everything important on a non-networked computer.

    I’ve been online for 13 years and not once have I had malware installed on my computer via javascript. Heck, I think I’ve had maybe a handful of viruses EVER, most of which I caught before they infected anything. I’ve never lost a singe important file, ever.

    Of course, I can’t say that I’m an average computer user. But I sure as heck don’t waste my time worrying about javascript security, and I browse dozens of different sites every day.

  13. Sean O Says:

    It’s its.

  14. Chris Says:

    Thanks for the catch.

  15. russ Says:

    I don’t see the difference. The whole big deal about decoupling style from content with HTML/CSS is a fake in the same way this is a ake. To make a decent web page you constantly have to jiggle the HTML/CSS together (unless your page is just made up of every possible DIV and SPAN opportunity just like cssbeauty).

    Anyway, it’s nice to aim high and move the JS out of content as much as possible so I applaud your efforts.

  16. bobg Says:

    Yes, Chris, there may indeed be security holes in FF’s javascript engine but Hank was talking about malware. I maintain that there is no way to install malware via javascript. It is a non-issue. Javascript certainly can facilitate things like sql injection attacks but I don’t see that as an issue for a user, just for the site maintainer.

    As for libraries, the issue is not bloat, it is the language. Javascript is a deep and complex language. Adding syntactical sugar like $ just confuses the issue and prevents programmers, especially new ones, for really understanding the language and the DOM. I use YUI because it keeps the language intact and is easily readable. Personally, I think:

    window.addEvent(’load’, function(){
    $(’action1′).addEvent(’click’, function(){
    xhrReq.send(this.getProperty(’href’), ‘_ajax=true’);
    })
    });

    is close to unreadable. Reminds me of those evil C++ nested ternaries ;)

  17. fluminis Says:

    For me, the unobstrusive way is totaly unreadable.

    It will be very difficult to maintain if your site have a lot of links with a lot of different javascript functions.

    Of course it’s not a good solution to do complexe js algorithm in onclick or onsubmit properties of our html tags. calling a js function to do that is enough (for me) to separate js from html…

  18. cmo Says:

    Check out Behaviour.js, basically a library to do just this that also takes care of some cross browser related issues.

  19. Chris Says:

    bobg:

    I’ve personally seen a machine compromised by malware installed through a remote code execution vulnerability in Firefox’s javascript engine. So don’t say its not possible.

    –Chris

  20. cwillu Says:

    I’m happy to see that aspect oriented programming may finally become mainstream.

  21. Chris Says:

    This definitely is heading in the direction of aspect oriented programming, but with it will come all the classic AOP issues.

    bobg: You can’t eschew syntactic sugar and then say that your code is hard to read. Choose one side or the other.

  22. Tom Sieron Says:

    Everything you’ve described here is well known good practice in writing JS for small to medium sized websites. Once you get to writing fairly complex sites or web apps, the time needed for all the $$(’…’) and $(’..’) selectors may become a performance issue, especially if you need to bind a few dozen events or more, not just 5 or 6 :).

  23. bobg Says:

    Chris, in this case, the “sugar” sweetens your coding time and lengthens my code reading time. I believe that is fairly consistent. However, it was more the nested functions that made for hard reading than the $ in this case.

    Now I’ve explained why Javascript cannot install malware. You say you’ve seen it happen. Could you please explain how the script did it? I understand if you do not want to publish the method and if so, please send me a private email. I really do not see how it could be done with browser restrictions on javascript and my search through comp.lang.javascript bears me out. But hey, prove me wrong.

    Bob

  24. olivier Says:

    in you event functions, you wrote:

    // some stuff
    new Event(e).stop();

    Shouldn’t it be the oposite:

    new Event(e).stop();
    // some stuff

    Stoping the event should be the first thing to do before executing any more code, especially when you want to override an anchor url or a form submission with an ajax call

    my two cents

  25. Chris Says:

    olivier, that’s a good suggestion, but I myself have never noticed a difference either way.

  1. tgpo.org » Blog Archive » JavaScript & HTML: A marriage made in Heaven or Hell?
    Pingback on October 23rd, 2007 at 8:57 am
  2. hosthg » Blog Archive » Unobtrusive JavaScript — Use It!
    Pingback on October 23rd, 2007 at 3:10 pm
  3. links for 2007-10-24 at Link Right 2
    Pingback on October 24th, 2007 at 5:23 am
  4. napyfab:blog» Blog Archive » links for 2007-10-24
    Pingback on October 24th, 2007 at 4:41 pm
  5. All in a days work…
    Pingback on October 25th, 2007 at 3:46 am
  6. Liste de liens webdesign, css, javascript intéressants
    Pingback on October 27th, 2007 at 10:33 pm

Leave a Reply