Search code examples
javascripttoggleaccordioncustom-data-attributeselectors-api

JavaScript Accordion Menu with querySelectorAll()


I'm attempting to build an accordion menu using querySelectorAll() but unsure what the best method would be to check if the clicked list item's children (.toggleContent and .toggleIcon) belong to it's clicked parent toggle_li[i].

Correct me if I am wrong, but I assume that controlling this within the onclick function will be more flexible than impacting the toggleDataAttr function?

I'm still new to querySelector so any guidance is appreciated.

codepen: http://codepen.io/seejaeger/pen/qdqxGy

// data attribute toggle
var toggleDataAttr = function (toggleElem, opt1, opt2, dataAttr) {
  //
  // ? belongs to clicked element (parent toggle_li[i])?
  //
  var toggleElem = document.querySelector(toggleElem);

  toggleElem.setAttribute(dataAttr,
  toggleElem.getAttribute(dataAttr) === opt1 ? opt2 : opt1);
};

// declare toggle onclick element
var toggle_li = document.querySelectorAll('li');

// iterate query and listen for clicks
for (var i = 0; i < toggle_li.length; i++) {

  toggle_li[i].onclick = function() {
    //
    // ? belongs to clicked element (parent toggle_li[i])?
    //
    toggleDataAttr('.toggleContent', 'closed', 'open', 'data-state');
    toggleDataAttr('.toggleIcon', 'plus', 'minus', 'data-icon');
    };
} 

Solution

  • Here is what I think you should do:

    • Update your toggleDataAttr function to receive one more parameter parentElem.
    • Use this new parentElem for querySelector instead of document inside toggleDataAttr.
    • And then in your loop, pass this as parameter to be used as parentElem.

    Snippet:

    var toggleDataAttr = function(parentElem, toggleElem, opt1, opt2, dataAttr) {
      var toggleElem = parentElem.querySelector(toggleElem);
      toggleElem.setAttribute(dataAttr, toggleElem.getAttribute(dataAttr) === opt1 ? opt2 : opt1);
    };
    
    var toggle_li = document.querySelectorAll('li');
    
    for (var i = 0; i < toggle_li.length; i++) {
      toggle_li[i].onclick = function() {
        toggleDataAttr(this, '.toggleContent', 'closed', 'open', 'data-state');
        toggleDataAttr(this, '.toggleIcon', 'plus', 'minus', 'data-icon');
      };
    }
    body {
      background: #034;
      opacity: 0.9;
      font-family: sans-serif;
      font-size: 24px;
      font-weight: 300;
      letter-spacing: 2px;
    }
    ul {
      list-style: none;
      padding: 0 24px;
      width: 30%;
      overflow: hidden;
      color: #333;
    }
    li {
      background: #eee;
      padding: 0px;
      border-bottom: 2px solid #aaa;
    }
    i {
      font-style: normal;
    }
    .li-label {
      padding: 18px;
    }
    .toggleContent {
      padding: 18px 14px;
      border-top: 2px solid #bac;
      background: #334;
      color: #eee;
    }
    .toggleContent[data-state=closed] {
      display: none;
    }
    .toggleContent[data-state=open] {
      display: block;
    }
    .toggleIcon[data-icon=plus]:after {
      content: '+';
      float: right;
    }
    .toggleIcon[data-icon=minus]:after {
      content: '-';
      float: right;
    }
    <ul>
      <li>
        <div class="li-label">
          list item one <i class="toggleIcon" data-icon="plus"></i>
        </div>
        <div class="toggleContent" data-state="closed">toggle content one</div>
      </li>
      <li>
        <div class="li-label">
          list item two <i class="toggleIcon" data-icon="plus"></i>
        </div>
        <div class="toggleContent" data-state="closed">toggle content two</div>
      </li>
      <li>
        <div class="li-label">
          list item three <i class="toggleIcon" data-icon="plus"></i>
        </div>
        <div class="toggleContent" data-state="closed">toggle content three</div>
      </li>
    </ul>

    Hope it helps.