Home > Back-end >  How to add mutually exclusivity on toggling dropdown menu
How to add mutually exclusivity on toggling dropdown menu

Time:05-23

I have a menu with dropdown submenus. I'm trying to close an item when clicking on another, so that I don't have multiple items open at the same time. In a previous question: How to Apply .nextElementSibling to the next item of a dropdown menu a user suggested that I take a look at the function called mutually exclusivity. How can I add it to my menu?

var dropdownBtn = document.querySelectorAll('.menu-btn');

dropdownBtn.forEach(btn => btn.addEventListener('click', function() {
  var menuContent = this.nextElementSibling;
  menuContent.classList.toggle("show");
  
}));
.menu-btn {
  background: #e0e0e0;
  padding: 10px;
  margin: 5px 0px 0px 0px;
}

.menu-btn:hover {
  background: #000;
  color: #fff;
}


.drop_container {
   display: none;
   background-color: #017575;
   transition: 0.3s;
   opacity: 0;
}

.drop_container.show {
  display: contents;
  visibility: visible;
  opacity: 1;
}

.drop_container > .item {
  display: flex;
  flex-direction: column;
  margin-left: 10px;
  padding: 10px 0px 0px 0px;
}
<div >

<div >One</div>
<div >
  <a  href="#">Contact Us</a>
  <a  href="#">Visit Us</a>
</div>

<div >Two</div>
<div >
  <a  href="#">Contact Us</a>
  <a  href="#">Visit Us</a>
</div>

</div>

CodePudding user response:

Store previously clicked menu in a variable, and clear it's class if another menu was clicked

var dropdownBtn = document.querySelectorAll('.menu-btn'),
    lastOpened = null;

dropdownBtn.forEach(btn => btn.addEventListener('click', function() {
    var menuContent = this.nextElementSibling;
    menuContent.classList.toggle("show");

    if (lastOpened && lastOpened !== menuContent)
      lastOpened.classList.remove("show");

    lastOpened = menuContent;
  
}));
.menu-btn {
  background: #e0e0e0;
  padding: 10px;
  margin: 5px 0px 0px 0px;
}

.menu-btn:hover {
  background: #000;
  color: #fff;
}


.drop_container {
   display: none;
   background-color: #017575;
   transition: 0.3s;
   opacity: 0;
}

.drop_container.show {
  display: contents;
  visibility: visible;
  opacity: 1;
}

.drop_container > .item {
  display: flex;
  flex-direction: column;
  margin-left: 10px;
  padding: 10px 0px 0px 0px;
}
<div >

<div >One</div>
<div >
  <a  href="#">Contact Us</a>
  <a  href="#">Visit Us</a>
</div>

<div >Two</div>
<div >
  <a  href="#">Contact Us</a>
  <a  href="#">Visit Us</a>
</div>

</div>

CodePudding user response:

You could add a function that closes all menu's except the one you pass into it:

var dropdownBtn = document.querySelectorAll('.menu-btn');

dropdownBtn.forEach(btn => btn.addEventListener('click', function() {
  var menuContent = this.nextElementSibling;
  closeMenusExcept(menuContent);
  menuContent.classList.toggle("show");
}));

function closeMenusExcept(menuContent) {
  dropdownBtn.forEach((element) => {
    if (menuContent !== element.nextElementSibling) {
      element.nextElementSibling.classList.remove("show");
    }
  })
}
.menu-btn {
  background: #e0e0e0;
  padding: 10px;
  margin: 5px 0px 0px 0px;
}

.menu-btn:hover {
  background: #000;
  color: #fff;
}

.drop_container {
  display: none;
  background-color: #017575;
  transition: 0.3s;
  opacity: 0;
}

.drop_container.show {
  display: contents;
  visibility: visible;
  opacity: 1;
}

.drop_container>.item {
  display: flex;
  flex-direction: column;
  margin-left: 10px;
  padding: 10px 0px 0px 0px;
}
<div >

  <div >One</div>
  <div >
    <a  href="#">Contact Us</a>
    <a  href="#">Visit Us</a>
  </div>

  <div >Two</div>
  <div >
    <a  href="#">Contact Us</a>
    <a  href="#">Visit Us</a>
  </div>

</div>

CodePudding user response:

Note I changed the class names so they easier to type and read.

We'll use the programming paradigm call event delegation. Bind event to an ancestor tag (a tag that holds all of the tags you want to control)

Figure I

const menu = document.querySelector(".dropdown"); 
menu.addEventListener('click', //...

Next, design an event handler that only reacts when the right tags are clicked

Figure II

//...
  function(event) {
   // This is the tag the user clicked 
   const clicked = event.target;
   // Find .show 
   const current = document.querySelector('.show');
   // See if the clicked tag has .show class
   let state = clicked.matches('.show');
   // Only react if the clicked tag has .btn class
   if (clicked.matches('.btn')) {//...

Note: The .show class is now assigned to the .btn. See Figure IV

Figure III

//...
      // if there already is a .show    
      if (current) {
        // remove .show
        current.classList.remove('show');
      }
      // if the clicked tag did not have .show previously...
      if (!state) {
        // ...add .show to it
        clicked.classList.add("show");
      }
    }
  });

In CSS this ruleset uses the adjacent sibling combinator which is equivalent to .nextElementSibling

Figure IV

/* .btn.show   .list <=that's the next sibling */
.show .list {
  display: block;
} 

Removed visibility and opacity since original state is display:none which is a switch that inhibits any sort of transition (also removed). The display: content was changed to display: block. display: content doesn't have any standard behavior, when applied, the .items were black and white, but once replaced their original green returned. As a general rule dealing with CSS is if you don't see it being used in the examples, don't use it because there's probably a good reason why it isn't being used.

With this setup you never have to worry about how many button/items you have as long as it is inside the ancestor tag. Also, if you add any button/items dynamically, they do not need to be bound to the event. All you'll ever need is one event listener for each event you want to listen for.

const menu = document.querySelector(".dropdown");

const btns = document.querySelectorAll('.btn');

menu.addEventListener('click', function(event) {
  const clicked = event.target;
  const current = document.querySelector('.show');
  let state = clicked.matches('.show');
  if (clicked.matches('.btn')) {
    if (current) {
      current.classList.remove('show');
    }
    if (!state) {
      clicked.classList.add("show");
    }
  }
});
.btn {
  background: #e0e0e0;
  padding: 10px;
  margin: 5px 0px 0px 0px;
}

.btn:hover {
  background: #000;
  color: #fff;
}

.list {
  display: none;
  background-color: #017575;
}

.show .list {
  display: block;
}

.list>.item {
  display: flex;
  flex-direction: column;
  margin-left: 10px;
  padding: 10px 0px 0px 0px;
}
<div >

  <div >One</div>
  <div >
    <a  href="#">Contact Us</a>
    <a  href="#">Visit Us</a>
  </div>

  <div >Two</div>
  <div >
    <a  href="#">Contact Us</a>
    <a  href="#">Visit Us</a>
  </div>

  <div >Three</div>
  <div >
    <a  href="#">Contact Us</a>
    <a  href="#">Visit Us</a>
  </div>

</div>

  • Related