The dropdown menu should open when the button is clicked.
And if you click outside the dropdown it should close again.
I tried this by adding an event listener to the body when the dropdown menu is active and then checking if the click happened outside the dropdown.
How can I write the following code without timeout function and make the dropdown close when clicked outside and not inside?
<div class="dropdown dropdown__open">
Open Dropdown
<div class="dropdown__window">
<a class="dropdown__link">Link Text</a>
<a class="dropdown__link">Link Text</a>
<a class="dropdown__link">Link Text</a>
<a class="dropdown__link">Link Text</a>
</div>
</div>
.dropdown {
position: relative;
cursor: pointer;
user-select: none;
&__window {
position: absolute;
background: $background-alt;
width: 100%;
max-width: 260px;
top: 45px;
z-index: 12;
overflow: hidden;
display: none;
visibility: hidden;
&--active {
display: block;
visibility: visible;
}
> * {
width: 100%;
display: block;
color: $white;
white-space: nowrap;
&:hover {
background: $background;
color: currentColor;
}
}
}
}
let dropdownBtn = document.querySelectorAll('.dropdown__open')
dropdownBtn.forEach((el) => {
let dropdownWindow = el.querySelector('.dropdown__window')
el.addEventListener('click', function () {
dropdownWindow.classList.toggle('dropdown__window--active')
window.setTimeout(function () {
if (dropdownWindow.classList.contains('dropdown__window--active')) {
let clickListener = document.body.addEventListener(
'click',
function (event) {
if (event.target.classList != 'dropdown__link') {
dropdownWindow.classList.remove('dropdown__window--active')
// Buggy clickListener.removeEventListener()
}
}
)
}
}, 1000)
})
})
OR https://codepen.io/Salala2/pen/RwxjOzW
You can use onBlur(). But in order to do that you need to add tabindex="0" to the invoking element i.e the element which is invoking dropdown to display.
const dropdown = document.querySelector('.dropdown');
const dropdownWindow = document.querySelector('.dropdown__window')
dropdown.addEventListener('click', (event) => {
dropdownWindow.classList.toggle('dropdown__window--active');
});
dropdown.addEventListener('blur', (event) => {
dropdownWindow.classList.remove('dropdown__window--active');
});
.dropdown {
position: relative;
cursor: pointer;
user-select: none;
max-width: 260px;
}
.dropdown__window {
position: absolute;
background: #ccc;
box-shadow: 0 10px 40px #1d2021;
width: 100%;
max-width: 260px;
top: 45px;
z-index: 12;
overflow: hidden;
display: none;
visibility: hidden;
}
.dropdown__window--active {
display: block;
visibility: visible;
}
.dropdown__window>* {
font-size: 14px;
cursor: pointer;
display: block;
padding: 1rem 0 1rem 1rem;
font-weight: 400;
text-align: left;
line-height: 1.42857143;
color: #fff;
white-space: nowrap;
}
<div class="dropdown dropdown__open" tabindex="0">
Open Dropdown
<div class="dropdown__window">
<a class="dropdown__link">Link Text</a>
<a class="dropdown__link">Link Text</a>
<a class="dropdown__link">Link Text</a>
<a class="dropdown__link">Link Text</a>
</div>
</div>
I was searching for a solution for this problem and got to this post.
I have found a solution that might be usefull for someone else as well.
In my case, i am using JSX where I only render the dropdown if a boolean is true, "opened" and my input field has a reference called inputRef.
The solution:
window.addEventListener("click", (evt)=>{
const eventPath = evt.composedPath();
this.opened = eventPath.includes(this.inputRef);
});
The event composed path will return the array list of elements that are targets of the click event, if my input field is part of that list, I keep the menu open, if not, it will not be rendered at all.
Keep in mind that if you are using vanilla JS you will need to query select your input element and create its instance like in the comments above.
More about event composed path at https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With