Namespacing data attributes
HTML data attributes are a very useful way of conveying information to JavaScript that is running on the page and can be used to separate out functionality from presentation. As these techniques become more widely adopted, especially in framework and library code, there is a risk that some attribute names are used for more than one thing.
For example, an attribute with a such as data-length
might conceivably be used in a number of cases which conflict and, if the current trend of libraries increasingly using data attributes continues, these conflicts are only likely to become more common. In fact this situation was foreseen by the W3C spec for data attributes which contains the following:
JavaScript libraries may use the custom data attributes, as they are considered to be part of the page on which they are used. Authors of libraries that are reused by many authors are encouraged to include their name in the attribute names, to reduce the risk of clashes. Where it makes sense, library authors are also encouraged to make the exact name used in the attribute names customizable, so that libraries whose authors unknowingly picked the same name can be used on the same page, and so that multiple versions of a particular library can be used on the same page even when those versions are not mutually compatible.
Whilst the W3C do recommend making the names configurable that's probably overkill to do for each attribute so we can add a prefix to our attributes to namespace them.
Namespaced attributes
If we take the example of the data-length
attribute without prefix this would be marked up as follows:
<div data-length="3">
And, when this div has been assigned to the variable element
it would be accessed in JavaScript as follows:
element.dataset.length
In this case I'm going to use the prefix dc
for my library. This would now be marked up as follows:
<div data-dc-length="3">
And accessed in JavaScript as follows (note capitalisation):
element.dataset.dcLength
Configuring namespaces
Whilst it's much less likely that a namespaced data attribute will conflict, it's still possible that another library might choose the same prefix as ours. Alternatively we might have different versions of a library that use the same name space. In this case we can make the prefix a configurable value.
In this case accessing our attributes would be accessed in JavaScript like this:
var data_prefix = 'dc';
element.dataset[data_prefix + 'Length'];
Whilst this is the basic formulation it's not a great implementation as it requires you to add the prefix every time you access an attribute and will fail if the prefix is set to a null string (as the attribute capitalisation is now wrong). To overcome this we could add a helper functions for common data attribute use cases:
var data_prefix = 'dc';
/**
* Builds a data attribute name using the configured prefix.
*
* @param attribute {string} Attribute name you wish to use.
*
* @returns {string} Attribute name with configured prefix.
*/
function getDataAttributeName(attribute) {
if (data_prefix.length) {
attribute = data_prefix + attribute.slice(0, 1).toUpperCase() + attribute.slice(1);
}
return attribute;
}
/**
* Gets a data attribute value.
*
* @param element {element} DOM element from which to get the data.
* @param attribute {element} Name of attribute to get.
*
* @returns {mixed} Value of data attribute.
*/
function getDataAttribute(element, attribute) {
return element.dataset[getDataAttributeName(attribute)];
}
/**
* Sets a data attribute value.
*
* @param element {element} DOM element on which to set the data.
* @param attribute {element} Name of attribute to set.
* @param value {mixed} Data to set on attribute.
*/
function setDataAttribute(element, attribute, value) {
element.dataset[getDataAttributeName(attribute)] = value;
}
/**
* Selects elements based on a data attribute with an optional value.
*
* @param attribute {string} Attribute name to select.
* @param value {string} Value of attribute to use in selector. (Optional)
* @param parent {element} DOM element on which to base selection. (Optional, defaults to `document`.)
*
* @returns {NodeList} Elements that match the selector.
*/
function queryElementByAttributeAll(attribute, value, parent) {
parent = parent || document;
var selector = '[data';
if (data_prefix.length) {
selector += '-' + data_prefix;
}
selector += '-' + attribute;
if (typeof value !== 'undefined') {
selector += '="' + value + '"';
}
selector += ']';
return parent.querySelectorAll(selector);
}
There is a version of this code with unit tests on GitHub.
Conclusion
The complexity of configurable namespaces will not be required for all projects but if you are expecting your code to run alongside other libraries then you should use some form of namespacing of your data attributes to prevent unforeseen conflicts.