Introduction
I tend to write my own cross browser UI components and always strive to reduce the number of elements rendered in a page whilst also ensuring the mark-up validates. In situations where I need a key value pair but do not want to insert hidden fields, I use a custom CSS selector such as:
<div id="mydiv" class="data-usedialogs-true">
prefix-key-value
I then have a script that executes when the page loads and goes through all (or selected) elements extracting the key value pairs and binding them to the actual element within the DOM.
This allows me to read/test the value using JavaScript without any manual parsing:
var mydiv = document.getElementById("mydiv");
if (mydiv.prefix.property=="true")
{
}
This method is my W3C compliant (pre HTML5) alternative to the new HTML5 data attribute, whereby you can add custom data attributes to elements, e.g.:
<div id="mydiv" data-usedialogs="true">
The good thing about this method is you can change the attribute, so you could have:
<div id="mydiv" class="settings-autohide-true">
resulting in:
var mydiv = document.getElementById("mydiv");
if (mydiv.settings.autohide=="true")
{
}
How does the script work?
When the script executes, it looks through the DOM for all elements with a class name matching the regular expression prefix-[A-Za-z0-9]{1,}- [A-Za-z0-9]{1,}. Once it finds an element, it then creates a custom attribute on that element and assigns an array containing the keys and values found within the matching selector(s).
This means you can have multiple key value pairs within the same element:
<div id="mydiv" class="data-usedialogs-true data-autohide-true">
Allowing you to read the values back in JavaScript as:
var mydiv = document.getElementById("mydiv");
if (mydiv.data.usedialogs=="true")
{
}
and:
var mydiv = document.getElementById("mydiv");
if (mydiv.data.autohide=="true")
{
}
The script
function bindCssData(t , p )
{
p = p || "data";
t = t || "*";
var els = document.getElementsByTagName(t);
var reg = new RegExp("^(" + p + ")-([A-Z0-9]{1,})-([A-Z0-9]{1,})$", "i");
for (var i=0, l=els.length; i<l; i++)
{
var cssNames = (els[i].className != "") ?
new String(els[i].className).split(' ') : [];
if (cssNames.length<=0) continue;
for (var ii=0, ll=cssNames.length; ii<ll; ii++)
{
var m = reg.exec(cssNames[ii]);
if (m && m.length >= 2) {
els[i][p] = els[i][p] || new Array();
els[i][p][m[2]] = m[3];
}
}
}
}
To use the script, call bindCssData()
from within your window.onload
function. If you do not pass any parameters to the function, it will parse all elements looking for a prefix of data. To make this faster, consider calling the function with only the element types (tagName) you want to affect:
bindCssData("span");
or
bindCssData("span", "settings");
Anticipated backlash -> Auto response
Of course, there will be many designers/developers out there that will disagree with the concept of using CSS selectors to carry data to client side scripts. However, in my defense and with progressive enhancement in mind, this is the cleanest solution I could come up with without inserting unnecessary bloat into the page. In addition, the ability to define your own prefix reduces the potential of a conflict with actual page styles.
For example, you could use an unnatural CSS selector such as __cssdata-key-value.
Feedback
I am always stupidly busy and don't get much time to knock-up these articles so there might be areas lacking explanation. If you feel I need to expand/correct anything, please leave a comment and I will update it as soon as I can.