Unobtrusive JavaScript

|   Sep 26, 2012

What it used to mean

The meaning of the phrase unobtrusive JavaScript has changed since jQuery came around. To understand this change, let’s first go back to when we used to write code like this:

<form class="validated_form" onsubmit="return validateForm(this)">

This is definitely obtrusive JavaScript, since the code is directly embedded in the HTML tags.

In order to make this unobtrusive, we pulled the JavaScript out of the HTML document and put into a separate JS file. But, as soon as we removed the JavaScript code from the onsubmit attribute, we had to write more code to accomplish the same thing. Before we could bind to the submit event, we first had to find that form tag, most likely from within an onload event-handler.

While separating the JavaScript behavior from the HTML is an improvement, we’re doing a lot more work to find the form when the page loads. This shift in where the JavaScript was located forced us to think about DOM traversal – figuring out how to find the elements that we wanted to bind the event to. However, the benefit of separating the JavaScript and HTML outweighed the performance hit of the DOM actions that were happening when the page loaded.

What it means today

In today’s world of JavaScript development, we can use jQuery to easily find elements in the DOM and attach event-handlers with a simple API. So the complex code in our separate JS file can now be written in jQuery like this:

$(function(){
  $('.validated_form').submit(function(){
    return validateForm(this);
  });
});

Although this looks innocent because of the simple jQuery syntax, it still has to search the DOM for the matching element when the page loads (DOM-ready). Essentially it’s doing same thing as our previous onload code, except that jQuery allows us to find the form element and bind events to it using much less code.

Because our code is dependent on how well jQuery performs the DOM traversal, I believe the term obtrusive now refers to the amount of work that has to be done before the user can interact with the page. Every new event-handler that needs to get bound to an element means another DOM scan, which means the user has to wait that much longer before they can use your page. The more DOM work and event-binding that occurs, the more obtrusive your JavaScript has become.

The solution

Fortunately, we can avoid this initial DOM searching by using event delegation. To make our JavaScript unobtrusive, we will essentially adopt an event-driven approach, by only binding a single event-handler to the document. In that case, nothing will happen until a submit event occurs, when it will then bubble up to the document, where the event-handler will respond. The jQuery changes slightly, to look like this:

$(document).on('submit', '.validated_form', function(){
  return validateForm(this);
});

At first glance, it doesn’t look like a whole lot has changed, but it’s actually doing something completely different. Binding event-handlers on the document is a significant change in how the JavaScript is written and how it behaves.

The jQuery on function was introduced in version 1.7, but the same thing can be accomplished in version >= 1.4.2 using the delegate function.

The foundation: event delegation

Since a reference to the document is immediately available, we don’t have to wait for an onload or DOM-ready event to attach an event-handler. As soon as the script executes, it will listen to any submit event that fires in the document and only run the callback if the triggering element matches the specified selector. When an event occurs, it will not only fire the event on the source element, but will also bubble up to its parent, and that element’s parent, firing the event along the way. It will keep bubbling up the DOM, firing the event on all ancestor elements until it reaches the document. At that point, our bound event-handler will run, but will still have access to the source element that fired the event. This process is called event delegation, since execution of the event-handler is delegated to an ancestor element. This makes our code unobtrusive because the work to determine which element triggered the event is deferred until the event fires.

Another benefit of this approach is that the submit event will also be executed for forms that don’t yet exist in the DOM. This means that if you add more forms to the document via AJAX, for example, you don’t have to worry about binding submit event-handlers to those forms. When the submit event occurs on those form elements, they’ll just bubble up to the document, where our existing event-handler will respond.

Any ancestor element can have events delegated to it, not just the document.

Creating an unobtrusive widget

Using event delegation, we can then identify common patterns and build a single reusable widget. I’ll use a keycapture widget as an example. After setting up many textareas in our application, we discovered that we kept writing the same onkeydown event-handlers, to limit input or respond to certain keystrokes. To avoid duplicating this, we created a single unobtrusive keycapture widget that listens to keydown events on the document. The basic setup looks like this:

$(document).on('keydown', 'textarea', function(e){
  var $textarea = $(this);
  // do something with this keydown event
});

Now that we can react to keydown events in a single location, how do we know what to do with that keydown event? We’ll look to the element for hints on what to do.

Markup-driven behavior

Each unobtrusive widget can be configured using HTML5 data- attributes on the element. The sole purpose of these attributes is to provide information to the JavaScript code. For now, let’s set up our keycapture widget to blur the textarea whenever the Escape key is pressed. All we have to do is add a data-escape="blur" attribute to the textarea:

<textarea name="description" data-escape="blur"></textarea>

With this embedded data on the element, the JavaScript code can now make a decision on what to do:

if ((e.which === 27) && ($textarea.data('escape') === 'blur')) {
  $textarea.blur();
}

As of version 1.4.3, jQuery automatically makes all data-* attributes available via the data function.

Now any textarea at any time can use this keycapture widget by simply adding the data-escape attribute to the tag – no additional JavaScript necessary!

Summary

Creating reusable widgets with unobtrusive JavaScript techniques gives you the following benefits:

  • Avoids duplication of JS code by having a single event-handler on the document
  • Avoids expensive DOM searches when the page loads
  • Automatically handles elements that are dynamically added to the DOM
  • Separates HTML markup from JS behavior, but provides means to configure widgets via data-* attributes

Custom Events

In the next post, I’ll discuss how we can hook into these widgets even further, using custom events in jQuery.

 

Comments

  • Thanks for posting this. I’m not sure I like your example of using the data attribute in the example at the end of the article however. When you are specifying behavior (blur) through data attributes you are violating the original intent of unobtrusive JavaScript by mixing in the code with the markup. No different in the end than calling onblur=”function()”. Instead I think your data attributes should be descriptive of the form element itself. What necessitates the behavior? Is something like like data-validated=true more semantically meaningful?

    Thanks for contributing to the conversation!

    Commented on September 26, 2012 at 1:58 pm
  • Uriah, it’s perfectly fine for data-* attributes to dictate the type of behavior that will occur. The point is that the JavaScript has been removed from the markup, and a reusable widget has been created with some default behavior. When certain pre-defined values are supplied in the data attributes, it can change how the widget works. This is what makes it unobtrusive – to use a flexible widget without needing to write any additional JavaScript.

    Commented on September 26, 2012 at 2:40 pm
  • While eventing is currently a good way to bind javascript to the dom, onsubmit events are not a good choice. Focus on UI interaction like onclick or onscroll, etc. onsubmit allows for errors to occur and the form to submit anyway if every possible execution route is not returning the correct value. Instead, do your preprocessing THEN do a submit, without binding directly to the onsubmit.

    Commented on September 28, 2012 at 6:59 am
  • Everything is fine, but why most of the articles/posts have to always touch so simple stuff… maybe would be nice to say something about event class names or be aware of doubling event listeners… and more… sorry, its nothing personal, I’m just being flustrated looking at these “Weekly JS News” articles…

    Commented on September 28, 2012 at 7:35 am
  • Great article. I’m going to start binding my event handlers to the document.

    Commented on September 28, 2012 at 7:53 am
  • Nice article. I like the italicized comments about previous versions of jQuery. Interesting topic.

    As for the Javascript, I wonder how you would deal with the situation where you do not want to have the same action taken in response to the event? If I have one master click handler, for example, each probably has different consequences.

    One could specify the action in the button’s data attribute but, how is that different from using “onclick=”?

    I know. How about making a registration system for the master click handler? Controller X creates a button and a callback. It adds the callback to a list associated with the master click handler. The master click handler executes all the callbacks but the rules are that the callback only touches its own item. That way you keep the reference to the details of the button and it’s callback in one place (unobtrusively *not* the html) but only have one click handler.

    It has the advantage of not scanning the dom many times at startup. It could have the disadvantage of scanning many button callbacks (or whatever) every time you click. I guess that’s probably ok.

    Well, we now have two questions. What would you do? and What do you think?

    peace,
    tqii

    Commented on September 28, 2012 at 10:22 am
  • Or you can just use document.forms…and then wrap it as a jQuery object.

    Commented on October 1, 2012 at 3:47 am
  • Interesting point of view; thank you for sharing :)
    Binding to the document is especially useful when you have to bind the same behavior to a lot of similar elements in the page. I’ve a question, though: in a very large application, can binding all events to a single point (the document) become a performance bottleneck ? just wondering..

    As for the data attribute, I’m not a fan of it. I agree with Uriah in that he taints the markup, introducing tight coupling between the markup ant the javascript layer. Moreover, the dynamic nature of the behavior layer (Js) doesn’t conform to the static nature of the information contained in data attribute in the markup;
    Using the “data” attribute to mark an element as subject to validation (data-validate=true), for example, doesn’t hold up when your field validation requirements can change dynamically over time; you end up describing only a ‘starting state’ in the markup, that you’ll change as the application flows, thus managing the same state in 2 different places (the initial state in the markup and the dynamic state in the js behavior). I very much prefer to handle everything in the behavior layer and leave the markup only for the content/static informations (for example, I don’t see any issue in using the data attribute to describe a widget type, since this is something not supposed to change for a markup element during the application lifecycle).

    Commented on October 1, 2012 at 4:47 am
  • thank for posting this article about unobstructive javasript.
    By this article we knew about obstructive and unobstrutive javascript.

    Commented on January 21, 2014 at 11:03 am

Leave a comment

Your email address will not be published. Required fields are marked *

Connect with Facebook

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Sign up to receive email communications regarding events, webinars, and product news.

Author Spotlight

Jason Moon
Jason Moon Senior Software Engineer View full bio

What is Socialcast?

Socialcast by VMware (NYSE: VMW) is a social network for business uniting people, information, and applications with its real-time enterprise activity stream engine. Behind the firewall or in the cloud, Socialcast enables instant collaboration in a secure environment. Socialcast is headquartered in San Francisco, California. www.socialcast.com