The tech behind the Internet evolves pretty quickly these days and HTML5 is the latest acronym you should have in your toolbox. I keep it right next to the latest trusty version of jQuery, my favorite “do more, write less” JavaScript library.
Some folks have a hard time seeing how the two complement each other. Sometimes it seems like HTML5 was designed to make tools like jQuery unnecessary. Not so, courageous reader! Let me show how HTML5 gives jQuery a whole new layer of awesome.
So how does it work?
HTML5 allows us to create our own custom attributes to store data. Custom attributes can be called anything we like, like variable names, but they need to be prepended with the word ‘data’, and words are separated by dashes. You could add “foo”, “bar”, and “foo bar” data attributes to a paragraph like this:
[code lang=”html”]
<p id="para" data-foo="bar" data-bar="baz" data-foo-bar="true">
This is dummy text, dummy.
</p>
[/code]
With just JavaScript to fetch the values, use either the getAttribute()
method or the dataset property, which inflects dashed-names into camelCase:
[code lang=”javascript”]
<script type="text/javascript">
var para = document.getElementByID(‘para’);
console.log(para.getAttribute(‘data-foo’));
// Expect string "bar"
console.log(para.dataset.bar);
// Grabs "data-bar"
// Expect string "baz"
console.log(para.dataset.fooBar);
// Grabs "data-foo-bar" ↑↑↑↑↑↑ Here be magic!
// Expect string "true"
</script>
[/code]
jQuery’s data()
method allows us to store and retrieve arbitrary data about an element in a memory-efficient manner. Like getAttribute()
, jQuery’s data()
method can read HTML5 data attributes, but it also does something that getAttribute()
doesn’t:
[code lang=”html”]
<section data-role="page" data-page-num="42" data-hidden="true">
<!– Imagine a bunch of page-type content here… –>
</section>
[/code]
[code lang=”javascript”]
<script type="text/javascript">
console.log($(‘section’).data(‘role’));
// Expect string "page"
console.log($(‘section’).data(‘pageNum’);
// Expect 42, an integer…! ↑↑↑↑↑↑↑↑ Here be magic!
console.log($(‘section’).data(‘hidden’);
// Expect the boolean TRUE!
</script>
[/code]
Yes, our good friend jQuery inflects the name of the data attribute – a la dataset
– and converts the value into the appropriate type. This even works for more complex types, like arrays and objects:
[code language=”html”]
<a href="#datepicker" data-options="{‘hidden’: true}">
<em>March 15, 2012</em>
<span data-icons="[‘calendar’, ‘arrow-dn’]"></span>
</a>
[/code]
[code language=”javascript”]
<script type="text/javascript">
console.log($(‘a’).data(‘options’));
// Expect {‘hidden’: true}
console.log($(‘a > span’).data(‘icons’));
// Expect [‘calendar’, ‘arrow-dn’];
</script>
[/code]
A Tale of Two Drop-downs: A Real-World Example
On my last freelancing assignment, I was working for a company whose primary business was staffing and recruiting. I was leading the development to replace their current business software with something shiny and new, and what could be shinier or newer than HTML5?
It wasn’t long before we ran into one of those “problem areas” that all the in-house folks had struggled with. They were using two standard select
elements to produce drop-down menus for selecting personnel and positions. Those select
elements were probably sufficient two or three years ago when the application was first built and the drop-downs had fewer options, but several thousand active personnel and positions later…
We decided to replace those horribly dysfunctional drop-downs with autocomplete widgets courtesy of jQuery UI. That way, the users pick from a much shorter list by typing a person’s name or employee number. We wired up some routes for personnel and positions that would accept a search term and return matching results as JSON. After that, we just had to add the widgets:
[code lang=”html”]
<dl>
<dt><label for="personnel_id">Personnel</label></dt>
<dd>
<input type="text" name="personnel_id"
id="personnel_id" class="autocomplete" />
</dd>
<dt><label for="position_id">Position</label></dt>
<dd>
<input type="text" name="position_id"
id="position_id" class="autocomplete" />
</dd>
</dl>
[/code]
[code lang=”javascript”]
<script type="text/javascript">
(function($){ // closure
$(function(){
/**
* First, store the appropriate "source" url
* on each element with data() for use below.
*/
$(‘input#personnel_id’).data(
‘autocompleteSource’, ‘/personnel.json’
);
$(‘input#position_id’).data(
‘autocompleteSource’, ‘/positions.json’
);
/**
* Using each() allows us access to the <input>
* element as "this", which we need below…
*/
$(‘input.autocomplete’).each(function(){
/**
* The initialization for each element is
* basically identical, except for the
* "source" option, which we’ll fetch from
* the element with the data() method.
*/
$(this).autocomplete({
source:$(this).data(‘autocompleteSource’)
});
}); // END each
}); // END document.ready
})(jQuery); // END closure
</script>
[/code]
Let’s Make Better Mistakes Tomorrow
Well, that looks good enough. It certainly works, but I’m one of those compulsive, perfectionist Agilistas who Refactor Mercilessly. Instead of tacking the source URLs on with jQuery, why not put them on the element directly? Without changing the Javascript, we can just throw some data attributes onto the input
elements:
[code lang=”html”]
<dl>
<dt><label for="personnel_id">Personnel</label></dt>
<dd>
<input type="text" name="personnel_id"
id="personnel_id" class="autocomplete"
data-autocomplete-source="/personnel.json" />
</dd>
<dt><label for="position_id">Position</label></dt>
<dd>
<input type="text" name="position_id"
id="position_id" class="autocomplete"
data-autocomplete-source="/positions.json" />
</dd>
</dl>
[/code]
Finishing Touches
Since jQuery will happily convert those complex data types for us, we can get away with even more magic. The autocomplete widget is expecting an object packed with options, so let’s give it one:
[code lang=”html”]
<!– This is just an abbreviated example… –>
<input type="text" name="personnel_id"
id="personnel_id" class="autocomplete"
data-autocomplete-options="{‘source’:’/personnel.json’,’delay’:1000}" />
[/code]
[code lang=”javascript”]
<script type="text/javascript">
(function($){ // closure
$(function(){ // document.ready
$(‘input.autocomplete’).each(function(){
/**
* Initialize the autocomplete widget
* with the options from the element,
* defaulting to an empty object if
* the data attribute isn’t set.
*/
$(this).autocomplete($(this).data(
‘autocompleteOptions’
) || { });
}); // END each
}); // END document.ready
)(jQuery); // END closure
</script>
[/code]
Well, it’s not really magic; it’s just good technique. Just keep in mind that without HTML5 data attributes, all this convenience wouldn’t be possible and we’d have to resort to our sloppier first pass or use an HTML5 shim.
What have we learned?
There are a ton of new features baked into the HTML5 spec and current browser technology, but that doesn’t render our “oldies but goodies” – like jQuery – useless. If anything, jQuery has become even more useful with the addition of all those new tricks. Using jQuery to leverage HTML5 data attributes, you can “do more, write less” and keep your code nicely organized and clean.
Also, jQuery Mobile is veeeeeery big on data attributes.
Very cool. I did not know .data() would autoconvert types, that’ll be super handy.
Putting the source and options into the HTML element seems neat, but I’ve been using classes for my autocomplete widgets, so they are more easily reusable. The web app I’m working on uses the same autocomplete box (with the same source and visual effects) in many places, so copying the options into each of those elements would be a bit of a maintenance headache. But a cool technique, nonetheless!
It also may be worth pointing out that while data attributes are in the HTML5 spec, from what I’ve seen so far they will still work in older browsers. Though they won’t validate, getAttribute() will still be able to retrieve them.
Quick note. Under “Finishing Touches”, the jQuery IIFE (Immediately Invoked Function Expression) is not closed properly. Minor over site, but could screw up a newbie. Should be…
(function( $ ) {
// Do your awesome jQuery stuff here
})( jQuery );
Thanks, @JD, and good catch. I’ll get with the UMS guys to update the code sample in the article.
Hopefully this helps someone, but the following would be interpreted by jQuery as a string, not an array:
data-icons=”[‘calendar’, ‘arrow-dn’]”
Which has been driving me crazy. I found that if you reverse the orders of the quotation marks or use eval(), jQuery will recognize that as an array.
The valid JSON syntax is,
– whole attribute value is in single quotes
– key/value data pair(s), or array elements are in double quotes
Easy to make a mistake.
Thanks for this nice introduction.
Hi,
I just started learning html5 and jquery, Thank you for this tutorial, I will appreciated if you can post or send me the source code for this example with sample json.
Thanks
This is a very useful function. You should be aware that when you use .data() to add data to a node, that it doesn’t change the DOM, so if there is a function or piece of code elsewhere relying on the attribute, don’t expect it to be available if you’re adding it dynamically using jquery.