A tiny, low-level, helper library to extract one or multiple attributes from a DOM element, based on a structured naming scheme.
Allows your elements to declare attributes in the form of data-attribute
, data-scope-attribute
, data-parentscope-scope-attribute
,... and helps you gather them from most specific to least specific.
It is built to support a core set of features (see right after) and exposes all its internals for you to extend.
Let's take the following button, meant to trigger some action and show some feedback to users in response.
<button
data-action="do-something"
data-feedback='{"type": "toast"}'
data-success-feedback='{"message": "Oh yeah!"}'
data-error-feedback='{"type": "Whoops! Little hiccup."}'>
The scopedAttributes
function is a foundation for building an accessor that'll collect the feedback settings
to notify users of either a successful or failed operation and merge them as common defaults.
const element = document.querySelector('button');
// Say we want the success attributes
const attributes = scopedAttributes(element, ['success', 'feedback']);
// At that point we've only collected an array of strings
// ['{"message": "Oh yeah!"}', '{"type": "toast"}']
// We can then parse and merge them to get a final value
const settings = Object.assign( // Shallow merge on top of each other
attributes
.map(JSON.parse)
.reverse() // So we merge more specific settings on top of least specific ones
);
What to do with the collected array is up to you. You can decide to only pick the most specific
(first) value for example, or parse the strings with parseInt
for example.
You'll likely want to encapsulate the parsing and processing in your own function, that you can reuse elements after elements for consistency. For example:
function scopedObjectAttributes(element, scopes) {
const attributes = scopedAttributes(element, scopes);
return Object.assign(
attributes
.map(JSON.parse)
.reverse()
);
}
function scopedIntAttributes(element, scopes) {
const attributes = scopedAttributes(element, scopes);
return parseInt(attributes[0]);
}
- The function filters out any empty value (or any falsy value, if you're using custom parsing)
- The function can also accept a single attribute name
The library is published on NPM
npm install scoped-attributes
Or for Yarn
yarn add scoped-attributes
Using with bundlers
The package provides an ESM module which should get picked up by your bundler of choice when just importing scoped-attributes
:
import { scoped-attributes } from 'scoped-attributes';
scopedAttributes(element, scopes);
Loading directly in the browser
An ES module can be loaded directly in the browser with:
<script type="module">
import {scoped-attributes} from "./node_modules/scoped-attributes/dist/scoped-attributes.esm.js"
scopedAttributes(element, scopes);
</script>
For older browsers, the package also provides and ES5 UMD build to support older browsers:
<script src="./node_modules/scoped-attributes/dist/scoped-attributes.es5.js" defer></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
scopedAttributes(element, scopes);
});
</script>
Both have a minified counterpart with a .min.js
extension, with an associated sourcemap.
The scopedAttributes
function allows you to provide options passing an Object
as third optional argument.
This will let you configure:
- how the attribute name is built from a given list of scopes
allowing you to set a prefix, or read other attributes than
data-attributes
- how attributes are read, allowing you to pre-process the attribute as it is read, for example if you already have functions for reading attributes as specific type of data
Little reminder that the scope of the function is only to gather the attribute values. Picking the right one/merging them is left to you (like in the Features example)
Passing a custom function as attributeName
option lets you take over the computation
of the attribute name. The function will receive the reduced list of scopes as list of arguments for the current attribute being looked up.
For example, going through ['action','success','feedback']
, it'll get called 3 times with:
['action', 'success', 'feedback']
['success','feedback']
['feedback]
Up to you to turn those into an attribute name. You could decide to:
- prefix the attribute name, to separate them from other libraries attributes:
const PREFIX = 'fwk';
function attributeName(...scopes) {
`data-${PREFIX}-${scopes.join('-')}`
}
const attributes = scopedAttributes(element, scopes, {attributeName});
- or not combine the scopes at all and use the first one only, which would let you access non-data attributes
function attributeName(scope) {
return scope;
}
const attributes = scopedAttributes(element, scopes, {attributeName});
To customise the parsing, you can provide a custom getAttribute
function.
It'll receive the element
and the attributeName
currently being looked up,
letting you parse it, sanitize it or whatnot.
Returning a falsy value will get the attribute filtered out from the result.
- another way to get JSON attributes, for example:
function getAttribute(element, attributeName) {
try {
return JSON.parse(element.getAttribute(attributeName));
} catch {
// We just want to ignore unparseable values
}
}
const attributes = scopedAttributes(element, scopes, {getAttribute});
- or a way to parse dates
function getAttribute(element, attributeName) {
try {
return Date.parse(element.getAttribute(attributeName));
} catch {
// We just want to ignore unparseable values
}
}
const attributes = scopedAttributes(element, scopes, {getAttribute});