Custom attributes - Yea or nay?
HTML 5 explicitly allows custom attributes that begin with data
. So, for example, <p data-date-changed="Jan 24 5:23 p.m.">Hello</p>
is valid. Since it's officially supported by a standard, I think this is the best option for custom attributes. And it doesn't require you to overload other attributes with hacks, so your HTML can stay semantic.
Source: http://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes
Here's a technique I've been using recently:
<div id="someelement"> <!-- { someRandomData: {a:1,b:2}, someString: "Foo" } --> <div>... other regular content...</div></div>
The comment-object ties to the parent element (i.e. #someelement).
Here's the parser: http://pastie.org/511358
To get the data for any particular element simply call parseData
with a reference to that element passed as the only argument:
var myElem = document.getElementById('someelement');var data = parseData( myElem );data.someRandomData.a; // <= Access the object staight away
It can be more succinct than that:
<li id="foo"> <!--{specialID:245}--> ... content ...</li>
Access it:
parseData( document.getElementById('foo') ).specialID; // <= 245
The only disadvantage of using this is that it cannot be used with self-closing elements (e.g. <img/>
), since the comments must be within the element to be considered as that element's data.
EDIT:
Notable benefits of this technique:
- Easy to implement
- Does not invalidate HTML/XHTML
- Easy to use/understand (basic JSON notation)
- Unobtrusive and semantically cleaner than most alternatives
Here's the parser code (copied from the http://pastie.org/511358 hyperlink above, in case it ever becomes unavailable on pastie.org):
var parseData = (function(){ var getAllComments = function(context) { var ret = [], node = context.firstChild; if (!node) { return ret; } do { if (node.nodeType === 8) { ret[ret.length] = node; } if (node.nodeType === 1) { ret = ret.concat( getAllComments(node) ); } } while( node = node.nextSibling ); return ret; }, cache = [0], expando = 'data' + +new Date(), data = function(node) { var cacheIndex = node[expando], nextCacheIndex = cache.length; if(!cacheIndex) { cacheIndex = node[expando] = nextCacheIndex; cache[cacheIndex] = {}; } return cache[cacheIndex]; }; return function(context) { context = context || document.documentElement; if ( data(context) && data(context).commentJSON ) { return data(context).commentJSON; } var comments = getAllComments(context), len = comments.length, comment, cData; while (len--) { comment = comments[len]; cData = comment.data.replace(/\n|\r\n/g, ''); if ( /^\s*?\{.+\}\s*?$/.test(cData) ) { try { data(comment.parentNode).commentJSON = (new Function('return ' + cData + ';'))(); } catch(e) {} } } return data(context).commentJSON || true; };})();
You can create any attribute if you specify a schema for your page.
For example:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:addthis="http://www.addthis.com/help/api-spec">...<a addthis:title="" addthis:url="" ...>
Facebook (even tags)
<html xmlns:og="http://opengraphprotocol.org/schema/" xmlns:fb="http://www.facebook.com/2008/fbml">...<fb:like href="http://developers.facebook.com/" width="450" height="80"/>