Mutually exclusive expandable panels (needs to work in IE9 and above) I have a function which essentially opens and closes content associated to a panel. Only one panel can be open at any time.

Here is my JavaScript, along with a typical HTML sample that it could work with:



ts = {};

// Panels
ts.panel = (function() {


  function panels() {


    'use strict';


      var panel   = document.querySelectorAll('.panels-item');
      var button  = document.querySelectorAll('.panels-button');
      var content = document.querySelectorAll('.panels-content');


      // Reset all states
      function reset() {

        for (var i = 0; i &lt; panel.length; i++) {
          button[i].setAttribute('aria-expanded', false);
          content[i].setAttribute('aria-hidden', true);      
          content[i].className = 'panels-content js-hidden';      
        }

      }


      Array.prototype.forEach.call(panel, function(el, i) {


        var button  = el.querySelector('.panels-button');
        var content = el.querySelector('.panels-content');


        // Buttons
        button.setAttribute('aria-expanded', false);
        button.setAttribute('aria-controls', content.getAttribute('id'));


        // Content
        content.setAttribute('aria-hidden', true);


        function togglePanel() {

          if (content.getAttribute('aria-hidden') === 'true') {

            reset();

            button.setAttribute('aria-expanded', true);
            content.setAttribute('aria-hidden', false);
            content.className = 'panels-content';

          } else {

            button.setAttribute('aria-expanded', false);
            content.setAttribute('aria-hidden', true);
            content.className = 'panels-content js-hidden';

          }

        }


        // Hook up the event listener
        button.addEventListener('click', togglePanel); 


      });


    }


    return {
      panels: panels
    };


})();

ts.panel.panels();
* {
  margin: 0; 
  padding: 0;
}

.panels {
  display: flex;
  flex-wrap: wrap;
}

.panels-item {
  border-radius: 24px;
  display: flex;
  flex-basis: 25%;
  flex-direction: column;
}

.panels-button {
  background-color: blue;
  border-radius: 24px;
  color: white;
  padding: 24px;
}

.panels-content {
  background-color: #ccc;
  padding: 24px;

}

.panels-title {
  color: blue;
  text-decoration: underline;
}

.js-hidden {
  display: none;
}
&lt;div class="panels"&gt;

&lt;div class="panels-item"&gt;
  &lt;button class="panels-button"&gt;1&lt;/button&gt;
  &lt;div class="panels-content js-hidden"&gt;1&lt;/div&gt;
&lt;/div&gt;

&lt;div class="panels-item"&gt;
  &lt;button class="panels-button"&gt;2&lt;/button&gt;
  &lt;div class="panels-content js-hidden"&gt;2&lt;/div&gt;
&lt;/div&gt;

&lt;div class="panels-item"&gt;
  &lt;button class="panels-button"&gt;3&lt;/button&gt;
  &lt;div class="panels-content js-hidden"&gt;3&lt;/div&gt;
&lt;/div&gt;

&lt;div class="panels-item"&gt;
  &lt;button class="panels-button"&gt;4&lt;/button&gt;
  &lt;div class="panels-content js-hidden"&gt;4&lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




It does work, but I'm not confident with it. It would be good to have some suggestions.
Overall, the code looks pretty good. Definitely much better than many other JS examples in CodeReview. A few minor points can be made though.

DRY - Don't repeat yourself

At the very minimum, you can reuse some code by encapsulating it into a function.

You can define setPanelVisibilityState which will be responsible for the details of showing/hiding a section. The decision on whether a button/panel should be visible or not remains in the control of the caller...

Variable naming

Similarly, I'm unsure if the selectors '.panels-button' and '.panels-content' more than in one place...

I personally am a big enemy of variables like i, j, c, and so on. It's not so difficult to spell it out: panelIndex.

IE is a beast

As far as IE page renderer goes, I can't provide much feedback because I don't deal with it.



function togglePanel() {
    const showContent = content.getAttribute('aria-hidden') === 'true';
    reset();
    setPanelVisibilityState(false, button, content);
}

function reset() {
    for (var panelIndex = 0; panelIndex &lt; panel.length; panelIndex++)
        setPanelVisibilityState(false, button[panelIndex], content[panelIndex]);
}

function setPanelVisibilityState(isVisible, button, content) {
    button.setAttribute('aria-expanded', isVisible);
    content.setAttribute('aria-hidden', !isVisible);
    content.className = isVisible ? 'panels-content' : 'panels-content js-hidden';
}