Thousands of developers interact with this API on a daily basis yet most forget it is just… an API! Of course, I’m talking about the notorious “Document Object Model” (a.k.a the DOM). With the rise of “Rich Internet Applications” interaction with the DOM has sky-rocketed and as a result it’s being heavily abused!
Have you ever needed to do this? –
// E.g. 1 element.addEventListener('click', function(e){ this.rel += ' clicked' + '(' + e.clientX + ' ,' + e.clientY + ')'; }); // E.g. 2 jQuery('a').mouseover(function(){ var color = jQuery(this).css('color'); jQuery(this).data('beforeColor', color); changeColor(this); }); |
If you have then (in my humble opinion) you’re abusing the DOM!
The first example is obviously bad practice; using HTML attributes to track changes unrelated to the HTML content is a crime of sorts and should never be done! The second example probably doesn’t seem as bad to most developers. jQuery’s “data” method allows you to store expando-like data about an element without obtrusively defining it as a direct property. This technique of storing element data is far better than using the HTML rel
attribute. Or is it?
I was an advocate of this particular technique but I’ve come to realise that it is, in most situations, no better than using any random HTML attribute. If we put the unobtrusive-vs-obtrusive argument aside, both techniques are effectively the same. Plus, jQuery’s “data” technique is not really unobtrusive – jQuery still uses an expando property to tie the element to a particular property in a JavaScript object; I’m not saying there’s anything wrong with this, I’m just pointing out the common misconception that using jQuery’s data
method is somehow cleaner than using a random or made-up HTML attribute (/DOM property).
The main reason I am now so sceptical about its usage is because, over time, I’ve seen a lot of cases that don’t really require it at all and could quite easily be achieved with a well-thought-out use of JavaScript closures. Obviously it’s not all bad; there are a few valid uses – for example, jQuery currently uses its own “data” method to tie events to elements/objects; a central part of the its event dispatch system.
Magical closures
The nature of JavaScript closures allows us to use regular variables to store progress or states and then access those variables from within nested functions. I’ve put together a short example; this is an outline of a drag-and-drop script:
// Library neutral/agnostic draggableElements.forEach(function(elem){ var down = false, y = 0, x = 0; elem.bind('mousedown', function(e){ down = true; x = e.pageX - getOffset(elem).left; y = e.pageY - getOffset(elem).top; }); document.bind('mousemove', function(e){ if (down) { elem.applyCSS({ left: e.pageX - x, top: e.pageY - y }); } }); document.bind('mouseup', function(e){ down = false; }); }); |
Every “draggable” element is given its very own closure (through forEach
) within which we can define numerous variables accessible in each of the nested “handler” functions (‘mousemove’, ‘mousedown’, etc.). No expando properties are required, nada!
I haven’t got much more to say about this; the above code pretty much sums up my point!
I’m interested in valid uses of jQuery’s data()
method (other than those used in the core); someone please enlighten me!
Thanks for reading! Please share your thoughts with me on Twitter. Have a great day!
I’m not sure if jQuery’s data() method uses this, but HTML5 has ‘data’ attributes, which are attributes named anything prefixed with ‘data-‘ and can be used at the developer’s whim.
Typical use of them is data-remote=true for specifying AJAX usage, and data-method for hyperlinks that should run a command onclick instead of visiting their usual URL, thus promoting unobtrusive JavaScript.
I use this in my Prototype-based web apps (Rails 3 will include this functionality as standard):
Where I think the data() method is useful is when you need to associate a value with a DOM element that needs to be publicly available. For example, you’re developing two companion plug-ins that need to work with each other. In this case you need some mechanism for sharing data between the two plug-ins.
Not that this is something that I truly every think about, but I would think that by using the data() method, it allows us to create one set of functions for the entire set of target elements, rather than a single closure for each element in the target set. Data(), essentially, allows us to be slightly more memory sensitive as it cuts down on the number of methods that are defined.
I agree also with where Dan is coming from. I am working on some data-table stuff right now that uses plugins and I am using the data() method to bind record-specific data to each tbody element such that other plugins used in the record initialization will know how to access record-specific data.
@Ryan, looks interesting; I haden’t heard of those HTML5 data attributes. One question about your usage of it: I’m not quite sure why the method is being stored as a string and then eval’d. Why can’t it just be stored as an *actual* method so that it can be called like any other method? Maybe I’m missing the point…
@Dan & @Ben, So it seems that
data()
is mostly used to create “linkers” between different abstractions; notably between jQuery plugins and the code that makes use of them (or, other jQuery plugins). A unified abstraction would not require these linkers.@Ben, is it not possible for you to have that extra information for each table row existing somewhere else, like in a JavaScript object/array (one with a structure similar to the table?). Also, I don’t see how using
data()
cuts down on the amount of functions/methods required? Even with my example, the anonymousforEach
function is only being created once; a new closure is created every time it runs.Thanks for the comments, I really appreciate the insight! 🙂
@James,
Yeah, I suppose a unified solution would cut down on a need for data. However, for something that controls elements in which the HTML itself is dynamic (ex. a paging data-grid), I think there is something quite elegant about keeping the meta-data with the record itself such that when the new HTML is injected / replaced, the old meta-data is stripped out along with it.
The way libraries are doing this is pretty solid: e.g The custom attribute in jquery is ‘jQuery’ + timestamp with an integer value attached.
That’s certainly safer in memory management terms than attaching a indefinite ammount of objects and functions to elements. And unique enough to be considered unobtrusive imo.
I think you’re right though that storing data on elements can usually be avoided, and should be a second choice.
The way MooTools handles it, using Element#store and Element#retrieve, those functions were defined in a closure, with an object called
storage
.All Elements are given a UID when returned from $ or $$, and that UID is used as a key for the
storage
object. So they essentially use what you suggested to Ben for you.