Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Close Dropdown Menu when clicking outside

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

like image 374
badg Avatar asked Oct 23 '25 11:10

badg


2 Answers

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>
like image 193
Pedam Avatar answered Oct 25 '25 01:10

Pedam


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

like image 40
Nuno Rodrigues Avatar answered Oct 25 '25 01:10

Nuno Rodrigues



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!