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>

\$\endgroup\$
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

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.