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)">
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);
});
});
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);
});
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 anysubmit
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 sameonkeydown
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
});
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 adata-escape="blur"
attribute to the textarea:<textarea name="description" data-escape="blur"></textarea>
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
No comments:
Post a Comment