Problem
NOTE: the Javascript is all I’m concerned about, the HTML/CSS is just DEMO code. One caveat is ARIA states or attributes, if you have input on that, by all means, dive in.
I had the need for and unobtrusive extensible toggle that could be applied to a handful of items: tooltips, accordions, panels, dropdowns, etc.
With the accordions, there is a need for a sidebar that also has accordion-like functionality with sub-links that reveal once clicked, this event triggers the accordion as well, so had to make paired events that triggered the actual accordion, as those sidebar sub-links anchor/smooth scroll into the accordion panels.
We’re not utilizing babeljs so wrote this in ES5 compliant JS. Any recommendations to improve or areas that you see could pose an issue in compatibility, performance, maintainability, scalability, etc. feel free to offer advice. I am not a strong JS dev, know enough to get in trouble, so any advice on improvement is welcomed.
var toggleAttribute = function (el, attr) {
!el.hasAttribute(attr) ?
el.setAttribute(attr, '') :
el.removeAttribute(attr);
};
var toggleValue = function (el, attr, on, off) {
el.setAttribute(
attr,
el.getAttribute(attr) === off ? on : off);
};
var eventHandler = function eventHandler(event, node, callback) {
window.addEventListener(
event,
function (e) {
if (!e.target.matches(node)) return;
callback.call(this, e);
},
false
);
};
var toggleMethod = function (target, identifier, active, attr, on, off) {
var d = document;
target.classList.add(active);
toggleValue(target, attr, on, off);
toggleAttribute(target.nextElementSibling, 'hidden')
if (target.hasAttribute('data-controls')) {
var pairedTarget = d.querySelector('#' + target.getAttribute('data-controls'));
toggleValue(pairedTarget, attr, on, off);
toggleAttribute(pairedTarget.nextElementSibling, 'hidden')
}
var selectorList = d.querySelectorAll(identifier);
//for (var i = 0, len = selectorList.length; i < len; ++i)
Array.from(selectorList)
.forEach(function (selector) {
if (
selector !== target &&
selector !== pairedTarget
) {
selector.classList.remove(active);
selector.setAttribute(attr, off);
selector.nextElementSibling.hidden = true;
}
});
};
eventHandler('click', '.toggle', function (e) {
toggleMethod(e.target, '.toggle', 'active', 'aria-expanded', 'true', 'false');
});
@charset 'utf-8';
body {
margin: 0
}
body>div {
width: 49%;
display: inline-block;
}
.toggle {
display:block;
margin-right: 5px;
padding: 8px 12px 8px 8px;
}
.toggle:before {
content: '›';
margin-right: 8px;
display: inline-block;
}
.toggle[aria-expanded='true']:before {
transform: rotate(90deg);
}
<div>
<div>
<div>
<a href="#" aria-describedby="message-0" aria-expanded="false" class="toggle" data-controls="toggle-a" id="toggle-b">paired</a>
<div class="message" hidden id="message-0">
<p>paired a message</p>
</div>
</div>
<div>
<a href="#" aria-describedby="message-1" aria-expanded="false" class="toggle">isolated</a>
<div class="message" hidden="" id="message-1">
<p>isolated a message</p>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<button aria-describedby="message-2" aria-expanded="false" class="toggle" data-controls="toggle-b" id="toggle-a">paired</button>
<div class="message" hidden id="message-2">
<p>paired b message</p>
</div>
</div>
<div>
<button aria-describedby="message-3" aria-expanded="false" class="toggle">isolated</button>
<div class="message-3" hidden id="message-3">
<p>isolated b message</p>
</div>
</div>
</div>
</div>
Solution
The code looks pretty good. There are only a few suggestions I have about it.
While it doesn’t depend on Babeljs, it appears that Array.from()
is part of the Ecmascript 2015 (6th edition) Standard1. So if you really wanted to make this code ES-5 compliant, you would need to remove that. For example, this example suggests using .slice.call
:
So instead of
Array.from(selectorList)
.forEach(function (selector) {
You would need to do something like
[].slice.call(selectorList)
.forEach(function (selector) {
The naming of selectorList
is a little misleading, since querySelectorAll
returns a NodeList
. A more appropriate name would be selectedNodes
or something similar. Similarly, selector
is misleading, since it is typically passed a DOMNode
object.