Home > Back-end >  Reset all button states on click
Reset all button states on click

Time:12-03

I'm building an assessable nav. Each menu item has a button which toggles the sub-menu.

I've got this working, but I'm having trouble removing the previous state when another button is clicked.

For example, if you click the "Food" button followed by the "Drink" button, the "Food" sub-menu remains visible.

As you can see, I've added a for loop at the start of my function. I was expecting this to remove any previous classes.

What am I doing wrong here and how can I get this working to reset the aria attribute too?

function dropdownButton() {
    var buttons = document.querySelectorAll('.header__button');
    for (var i = 0; i < buttons.length; i  ) {
        buttons[i].addEventListener('click', function () {
            // Remove class
            var removeClass = document.querySelectorAll('.header__button');
            for (var j = 0; j < removeClass.length; j  ) {
                removeClass[j].classList.remove('.header__button--toggled');
            }
            // Toggle button class
            this.classList.toggle('header__button--toggled');
            // Toggle class on next sibling (.header__sub-menu)
            this.nextElementSibling.classList.toggle('header__sub-menu--toggled');
            // Toggle Aria
            var ariaExpanded = this.getAttribute('aria-expanded');
            ariaExpanded === 'true' ? ariaExpanded = 'false' : ariaExpanded = 'true';
            this.setAttribute('aria-expanded', ariaExpanded);
        });
    }
}
dropdownButton();
.header__nav-items {
  display: grid;
  grid-auto-flow: column;
  justify-content: start;
  column-gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.header__nav-item {
  position: relative;
}

.header__sub-menu {
  display: none;
  position: absolute;
  top: 24px;
  left: 0;
  background-color: #fff;
  border: 1px solid #dedede;
  padding: 12px;
  list-style: none;
}

.header__sub-menu--toggled {
  display: block;
}
<header class="header">
  <nav>
    <ul class="header__nav-items">
      <li class="header__nav-item">
        <button aria-expanded="false" aria-controls="food" class="header__button">Food</button>
        <ul id="food" class="header__sub-menu">
          <li><a href="/pizza">Pizza</a></li>
          <li><a href="/fries">Fries</a></li>
          <li><a href="/hamburger">Hamburger</a></li>
          <li><a href="/waffles">Waffles</a></li>
        </ul>
      </li>
      <li class="header__nav-item">
        <button aria-expanded="false" aria-controls="drink" class="header__button">Drink</button>
        <ul id="drink" class="header__sub-menu">
          <li><a href="/coke">Coke</a></li>
          <li><a href="/lemonade">Lemonade</a></li>
          <li><a href="/beer">Beer</a></li>
          <li><a href="/water">Water</a></li>
        </ul>
      </li>
    </ul>
  </nav>
</header>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

As Kinglish mentioned you need to remove the . inside removeClass[j].classList.remove('.header__button--toggled');

and also add

removeClass[j].nextElementSibling.classList.remove('header__sub-menu--toggled');

after it in the loop

you also dont need to use toggle function since you already removed the class names, you can use add class name instead

function dropdownButton() {
    var buttons = document.querySelectorAll('.header__button');
    for (var i = 0; i < buttons.length; i  ) {
        buttons[i].addEventListener('click', function () {
            // Remove class
            var removeClass = document.querySelectorAll('.header__button');
            for (var j = 0; j < removeClass.length; j  ) {
                removeClass[j].classList.remove('header__button--toggled');
                removeClass[j].nextElementSibling.classList.remove('header__sub-menu--toggled');
            }
            // Toggle button class
            this.classList.add('header__button--toggled');
            // Toggle class on next sibling (.header__sub-menu)
            this.nextElementSibling.classList.add('header__sub-menu--toggled');
            // Toggle Aria
            var ariaExpanded = this.getAttribute('aria-expanded');
            ariaExpanded === 'true' ? ariaExpanded = 'false' : ariaExpanded = 'true';
            this.setAttribute('aria-expanded', ariaExpanded);
        });
    }
}
dropdownButton();
.header__nav-items {
  display: grid;
  grid-auto-flow: column;
  justify-content: start;
  column-gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.header__nav-item {
  position: relative;
}

.header__sub-menu {
  display: none;
  position: absolute;
  top: 24px;
  left: 0;
  background-color: #fff;
  border: 1px solid #dedede;
  padding: 12px;
  list-style: none;
}

.header__sub-menu--toggled {
  display: block;
}
<header class="header">
  <nav>
    <ul class="header__nav-items">
      <li class="header__nav-item">
        <button aria-expanded="false" aria-controls="food" class="header__button">Food</button>
        <ul id="food" class="header__sub-menu">
          <li><a href="/pizza">Pizza</a></li>
          <li><a href="/fries">Fries</a></li>
          <li><a href="/hamburger">Hamburger</a></li>
          <li><a href="/waffles">Waffles</a></li>
        </ul>
      </li>
      <li class="header__nav-item">
        <button aria-expanded="false" aria-controls="drink" class="header__button">Drink</button>
        <ul id="drink" class="header__sub-menu">
          <li><a href="/coke">Coke</a></li>
          <li><a href="/lemonade">Lemonade</a></li>
          <li><a href="/beer">Beer</a></li>
          <li><a href="/water">Water</a></li>
        </ul>
      </li>
    </ul>
  </nav>
</header>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

based on your comment request if you want to keep the toggle functionality you can modify your code to check if the current expaned button is same as the clicked button. If so to add the class to it

let me know if the modifcation is not clear but basically by var ariaExpanded = this.getAttribute('aria-expanded') == 'true'; we know if the current button is already expaned so that we can remove the expaned property from it as well otherwise we add to it in the if statment below

function dropdownButton() {
    var buttons = document.querySelectorAll('.header__button');
   
    for (var i = 0; i < buttons.length; i  ) {
        buttons[i].addEventListener('click', function () {
            // Remove class
           var ariaExpanded = this.getAttribute('aria-expanded') == 'true';
            var removeClass = document.querySelectorAll('.header__button');
            for (var j = 0; j < removeClass.length; j  ) {
                removeClass[j].classList.remove('header__button--toggled');
                removeClass[j].nextElementSibling.classList.remove('header__sub-menu--toggled');
                  removeClass[j].setAttribute('aria-expanded', 'false');
                
            }
            
            if(!ariaExpanded){
              // Toggle button class
              this.classList.add('header__button--toggled');
              // Toggle class on next sibling (.header__sub-menu)
              this.nextElementSibling.classList.add('header__sub-menu--toggled');
              ariaExpanded = 'true';
            }else{
              ariaExpanded = 'false'
            }
           
            this.setAttribute('aria-expanded', ariaExpanded);
        });
    }
}
dropdownButton();
.header__nav-items {
  display: grid;
  grid-auto-flow: column;
  justify-content: start;
  column-gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.header__nav-item {
  position: relative;
}

.header__sub-menu {
  display: none;
  position: absolute;
  top: 24px;
  left: 0;
  background-color: #fff;
  border: 1px solid #dedede;
  padding: 12px;
  list-style: none;
}

.header__sub-menu--toggled {
  display: block;
}
<header class="header">
  <nav>
    <ul class="header__nav-items">
      <li class="header__nav-item">
        <button aria-expanded="false" aria-controls="food" class="header__button">Food</button>
        <ul id="food" class="header__sub-menu">
          <li><a href="/pizza">Pizza</a></li>
          <li><a href="/fries">Fries</a></li>
          <li><a href="/hamburger">Hamburger</a></li>
          <li><a href="/waffles">Waffles</a></li>
        </ul>
      </li>
      <li class="header__nav-item">
        <button aria-expanded="false" aria-controls="drink" class="header__button">Drink</button>
        <ul id="drink" class="header__sub-menu">
          <li><a href="/coke">Coke</a></li>
          <li><a href="/lemonade">Lemonade</a></li>
          <li><a href="/beer">Beer</a></li>
          <li><a href="/water">Water</a></li>
        </ul>
      </li>
    </ul>
  </nav>
</header>
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

Very quick post; based on your requirement and the code.

  • Fix the selectors to have periods when needed
  • Fix the aria/remove if not the button clicked
  • Aria adjust the code to simplify the condition to toggle the true/false strings
  • Smaller number of variables using .forEach() - not a performance issue with this small a number

function setup() {
  const subMenu = 'header__sub-menu--toggled';
  const buttonToggle = 'header__button--toggled';
  document.querySelectorAll('.header__button')
    .forEach(function(button) {
      button.addEventListener('click', function() {
        // is it me?
        let hasClass = this.classList.contains(buttonToggle);
        if (!hasClass) {
          // Remove class
          document.querySelector(".header__nav-items")
            .querySelectorAll('.'   buttonToggle)
            .forEach(function(navItem) {
              navItem.nextElementSibling.classList.remove(subMenu);
              navItem.setAttribute('aria-expanded', "false");
              navItem.classList.remove(buttonToggle);
            });
        }
        // Toggle button class
        this.classList.toggle(buttonToggle);
        // Toggle class on next sibling - subMenu
        this.nextElementSibling.classList.toggle(subMenu);
        // Toggle Aria
        const ariaExpanded = this.getAttribute('aria-expanded') === 'true' ? 'false' : 'true';
        this.setAttribute('aria-expanded', ariaExpanded);
      });
    });
}
setup();
.header__nav-items {
  display: grid;
  grid-auto-flow: column;
  justify-content: start;
  column-gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.header__nav-item {
  position: relative;
}

.header__sub-menu {
  display: none;
  position: absolute;
  top: 24px;
  left: 0;
  background-color: #fff;
  border: 1px solid #dedede;
  padding: 12px;
  list-style: none;
}

.header__sub-menu--toggled {
  display: block;
}
<header class="header">
  <nav>
    <ul class="header__nav-items">
      <li class="header__nav-item">
        <button aria-expanded="false" aria-controls="food" class="header__button">Food</button>
        <ul id="food" class="header__sub-menu">
          <li><a href="/pizza">Pizza</a></li>
          <li><a href="/fries">Fries</a></li>
          <li><a href="/hamburger">Hamburger</a></li>
          <li><a href="/waffles">Waffles</a></li>
        </ul>
      </li>
      <li class="header__nav-item">
        <button aria-expanded="false" aria-controls="drink" class="header__button">Drink</button>
        <ul id="drink" class="header__sub-menu">
          <li><a href="/coke">Coke</a></li>
          <li><a href="/lemonade">Lemonade</a></li>
          <li><a href="/beer">Beer</a></li>
          <li><a href="/water">Water</a></li>
        </ul>
      </li>
    </ul>
  </nav>
</header>
<iframe name="sif4" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related