In-Depth
Working with the HTML5 Data Attributes Using jQuery
jQuery support for the new HTML5 data attributes may not be everything a developer could want -- but it's very close. And, more important, it's the perfect solution for handling transactional data.
You've always been able to add your own attributes to HTML elements (browsers simply ignore any attributes they don't recognize). HTML5 includes a standard for regularizing these custom attributes by prefixing them with the string "data-" -- a feature that lets you store data about an entity with the part of the entity being displayed. For instance, in displaying a set of employees, I might not want to display the employee ID, status or salary on the page. However, I can still keep that data with the rest of employee data by inventing attributes called data-empid, data-salary and data-status, as in this example:
<table>
<tr data-empid="A123" data-salary="120" data-status="drone">
<td>Peter</td><td>Vogel</td><td>Principal</td>
</tr>
<tr data-empid="B456" data-salary="130" data-status="worker">
<td>Jan</td><td>Vogel</td><td>General Manager</td>
</tr>
<tr data-empid="C789" data-salary="110" data-status="worker">
<td>Jason</td><td>van de Velde</td><td>Company nurse</td>
</tr>
</table>
I'll start with the bad news. It would be great if jQuery gave you a dedicated selector for accessing elements by the value of these data attributes. However, that's not the case -- to extract the row for a particular employee using data-empid, I'd have to use the jQuery generic attribute selector (the square brackets, []). This example, for instance, finds the first row whose data-empid is set to B456:
var emp = $('tr[data-empid="B456"]').first();
That being the case, I might as well use the id attribute to define my table's rows because jQuery selectors do support the id attribute. That gives me a table like this:
<table>
<tr id="A123" data-salary="120" data-status="drone">
<td>Peter</td><td>Vogel</td><td>Principal</td>
</tr>
<tr id="B456" data-salary="130" data-status="worker">
<td>Jan</td><td>Vogel</td><td>General Manager</td>
</tr>
<tr id="C789" data-salary="110" data-status="worker">
<td>Jason</td><td>van de Velde</td><td>Company Nurse</td>
</tr>
</table>
And now I can find my employee with this selector:
var emp = $('#B456');
But other than that piece of wishful thinking (and I've seen a couple of jQuery extensions that do support selecting by data values), jQuery provides extensive -- and useful! -- support for data elements through its data function.
Adding and Removing Data
For instance, in retrieving an element, I can retrieve the value of data attributes on it without using the "data-" prefix: I just pass the name of the data attribute to the jQuery data function. This example retrieves the value of the data-salary attribute for employee B456, assuming that the data-empid attribute is still in place:
var empSalary = $('tr[data-empid="B456"]').data("salary");
alert(empSalary);
There's a key difference between this code and my previous example that also used the attribute selector with data-empid: the first function is gone from this example. While the attribute selector ([data-empid="B456"]) returns a collection, the data function retrieves the value of only the first item in the collection (a jQuery convention). There would be nothing wrong with continuing to use the first function (and it might even make the code more obvious), but it wouldn't gain you anything, either.
As is also common with jQuery functions, if you pass the data function two parameters, the second parameter is used to update the data attribute. I can give employee B456 a raise with this code (and I'm assuming that I've done the right thing and moved the employee ID out of the data-empid attribute and into the id attribute):
$('#B456').data("salary",150);
However, the behavior of the data function is different during updates: The data function updates every item that the selector retrieves, not just the first item (again, a jQuery convention). Fortunately, this code uses the "#" selector, which ensures that I retrieve only the first element with a matching id attribute. However, if I did want to give a raise to all the employees with a status of "worker," I could use this code:
$('tr[data-status="worker"]').data("salary",150);
You can also use the data function to add new data attributes to an element without defining those attributes in your HTML. This example, for instance, effectively adds an attribute called data-transactionStatus to the employee's element and sets it to the string "approved":
$('#B456').data("transactionStatus", "approved");
Working with Objects
As the number of attributes starts to grow -- and especially if your data attributes represent several different kinds of data -- you might want to organize those attributes into groups. For that, you can store JSON objects in your data attributes.
Rewriting my table to store a JSON object with multiple properties in a data attribute called emp gives me this table:
<table>
<tr id="A123" data-emp='{"salary":120, "status":"drone"}' >
<td>Peter</td><td>Vogel</td><td>Principal</td>
</tr>
<tr id="B456" data-emp='{"salary":120, "status":"worker"}'>
<td>Jan</td><td>Vogel</td><td>General Manager</td>
</tr>
<tr id="C789" data-emp='{"salary":120, "status":"worker"}'>
<td>Jason</td><td>van de Velde</td><td>Nurse</td>
</tr>
</table>
I can now retrieve the salary property with code like this:
var empSalary = $('#B456').data("emp").salary;
To update a JSON object you must use the JSON syntax (though with fewer quotation marks than required in the HTML). Where your JSON code references a property that already exists, the property is updated; where you reference a property that doesn't already exist, the property is added with the new value.
This example updates the existing salary property on employee B456 and adds the raised property, setting the property to true:
$('#B456').data("emp", {salary:200, raised:true});
Three things to watch out for: jQuery itself stores data under the names "events" and "handles"; jQuery also keeps the right to use data attribute names beginning with an underscore. You should avoid those names. And two bonuses: You can store anything you want in a data attribute, including functions (though I haven't done so, so I'm taking that feature on faith); you can also wire up read and set events to have code execute when the data function is used (again, not something I've needed to do).
Using the Data Function
I'm a big fan of the Model-View-ViewModel (MVVM) pattern and, unless my client tells me otherwise, lately I've been using Knockout to implement the pattern in JavaScript. In that scenario, I've ended up with three sets of data: the data in the objects that I retrieve from the server, the subset of that data that I bind to elements in my page, and "transactional" data -- temporary data that only exists while the user is working with the page.
I can further divide that transactional data. Some of that data is associated with the page as a whole, but some of it's associated with individual objects that I've retrieved. I've found the data function most useful as a way to store that object-related, transactional data: I store it as data on the elements to which my retrieved objects are bound. It really is the perfect solution for that kind of data.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.