Nathanael Nathanael - 1 month ago 11
CSS Question

close dropdown by clicking other areas including other dropdowns

I am trying to make a dropdown menu where the .dropdown-content shows up when .dropbtn is clicked, and disappears if the user clicks any other part of the page, including another .dropbtn. Most solutions I have found on here require detecting a click anywhere on the page and stopping propagation within the .dropdown class. The problem is I have multiple buttons with .dropdown class, and I don't think I need a solution that complicated. I think I just need help revising my existing javascript.

CSS

<style>
ul.topnav .dropdown {display: inline-block;}
ul.topnav .dropbtn {background-color: black;}
ul.topnav .dropbtn:after {/*inject down caret*/}
ul.topnav .dropbtn.active:after {/*inject up caret*/}
ul.topnav .dropdown-content {display: none;}
ul.topnav .dropdown-content.show {display:block;}
</style>


HTML

<ul class="topnav" id="myTopnav">
<li><a href="#">Home</a></li>
<li class="dropdown">
<a href="#" class="dropbtn">Locations</a>
<div class="dropdown-content">
<a href="#">Location 1</a>
<a href="#">Location 2</a>
</div>
</li>
<li class="dropdown">
<a href="#" class="dropbtn">Photos</a>
<div class="dropdown-content">
<a href="#">Folder 1</a>
<a href="#">Folder 2</a>
<a href="#">Folder 3</a>
</div>
</li>
<li><a href="#about">About Us</a></li>
</ul>


JAVASCRIPT

<script>
/*controls topnav accordion*/
var acc = document.getElementsByClassName("dropbtn");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].onclick = function(){
this.classList.toggle("active");
this.nextElementSibling.classList.toggle("show");
}
}
</script>


This much gets the .dropdown-content to appear and disappear when its parent .dropbtn is clicked, but I can still open multiple dropdown menus at once, and none closes when I click on another or click on the page itself. I have tried to write a piece of javascript which toggles .dropbtn.active and .dropdown-content.show when I click anywhere but the currently active .dropbtn, but it doesn't work. Here is my broken attempt:

<script>
window.onclick = function(e) {
if (!e.target.matches('.dropbtn.active')) {
this.classList.toggle("active");
this.nextElementSibling.classList.toggle("show”);
}
</script>


I suspect there is a simple fix, but I am new to javascript. Again, I want all open dropdown menus to close when I click a new dropdown button, and that dropdown to close when I click anywhere outside of the dropdown. Maybe this code and the above (working) javascript can be combined? Please help.

Answer

Try this example using for loop to toggle only clicked element and remove .show class for others using e.target.nextElementSibling vs e.target.previousElementSibling

<style>
  ul.topnav .dropdown {
    display: inline-block;
  }
  ul.topnav .dropbtn {
    background-color: black;
  }
  ul.topnav .dropbtn:after {
    /*inject down caret*/
  }
  ul.topnav .dropbtn.active:after {
    /*inject up caret*/
  }
  ul.topnav .dropdown-content {
    display: none;
  }
  ul.topnav .dropdown-content.show {
    display: block;
  }
</style>

<ul class="topnav" id="myTopnav">
  <li><a href="#">Home</a>
  </li>
  <li class="dropdown">
    <a href="#" class="dropbtn">Locations</a>
    <div class="dropdown-content">
      <a href="#">Location 1</a>
      <a href="#">Location 2</a>
    </div>
  </li>
  <li class="dropdown">
    <a href="#" class="dropbtn">Photos</a>
    <div class="dropdown-content">
      <a href="#">Folder 1</a>
      <a href="#">Folder 2</a>
      <a href="#">Folder 3</a>
    </div>
  </li>
  <li><a href="#about">About Us</a>
  </li>
</ul>
<script>
  window.onclick = function(e) {
    var targ = e.target;
    var drp = document.getElementsByClassName("dropdown-content");
    for (var i = 0; i < drp.length; i++) {
      if (drp[i].previousElementSibling == targ) {
        targ.nextElementSibling.classList.toggle("active");
        targ.nextElementSibling.classList.toggle("show");
      } else {
        drp[i].className = drp[i].className.replace(/\bshow\b/, '');
      }
    }

  }
</script>