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 .item
s 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>