3
\$\begingroup\$

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>

Sᴀᴍ Onᴇᴌᴀ
29.6k16 gold badges45 silver badges203 bronze badges
asked Oct 10, 2018 at 14:17
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

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.

1https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Specifications

answered Jan 10, 2019 at 22:35
\$\endgroup\$
1
  • \$\begingroup\$ Note, Node.prototype.forEach() is supported at several modern browsers \$\endgroup\$ Commented Jan 11, 2019 at 2:39

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.