-1

I have looked around at similar posts and seem to have hit a snag in how the javascript around this implementation works. I have a dropdown with a filter where I click a button to initiate the dropdown. This works fine. However, it seems the only way to dismiss this dropdown is to click the original button. I'm trying to dismiss when clicking anywhere else on the screen other than the dropdown. I'm not sure if the way I have approached this is a quirk with javascript, but the following code sets out my approach and on a conceptual level I think this should work fine:

Code:

function myFunction() {
  document.getElementById("myDropdown").classList.toggle("show");
}

window.onclick = function(event) {
  if (!event.target.matches('.dropbtn')) {

    var dropdowns = document.getElementsByClassName("dropdown-content");
    var i;
    for (i = 0; i < dropdowns.length; i++) {
      var openDropdown = dropdowns[i];
      if (openDropdown.classList.contains('show')) {
        openDropdown.classList.remove('show');
      }
    }
  }
}

function filterFunction() {
  var input, filter, ul, li, a, i;
  input = document.getElementById("myInput");
  filter = input.value.toUpperCase();
  div = document.getElementById("myDropdown");
  a = div.getElementsByTagName("a");
  for (i = 0; i < a.length; i++) {
    if (a[i].innerHTML.toUpperCase().indexOf(filter) > -1) {
      a[i].style.display = "";
    } else {
      a[i].style.display = "none";
    }
  }
}
<div class="dropdown">
  <button onclick="myFunction()" class="dropbtn"><img src="bread.png" height="50" width="50" style="vertical-align:middle" align="left"><a>Testing</a></button>
  <div id="myDropdown" class="dropdown-content">
    <input type="text" placeholder="Search.." id="myInput" onkeyup="filterFunction()">
    <a> Dropdown Content 1 </a>
    <a> Dropdown Content 2 </a>
    <a> Dropdown Content 3 </a>
  </div>
</div>

Thanks in advance and have a great day out there!

Barmar
  • 741,623
  • 53
  • 500
  • 612
NickvR
  • 75
  • 1
  • 9

3 Answers3

1

I have recently written a blog post - Evolution of Drop Down Menus and Exiting Them about this techique of targeting the whole page click, by introducing a faux mask that stays on the background.

I am using jQuery in this just for the event listeners, but the main concept is the mask. This is the logic behind for closing and opening as well:

click() {  
  if (other_dropdowns_open) {
    close_others;
    open_current;
  } else {
    if (current_dropdown_open)
      close_current;
    else
      open_current;
  }
}

The code is here:

$(function () {
  $(".dd-trigger").click(function (e) {
    e.preventDefault();
    $("body").addClass("dd-open");
    $(this).closest(".dd").toggleClass("open");
  });
  $(".dd-mask").click(function () {
    $("body").removeClass("dd-open");
    $(".open").removeClass("open");
  });
});
.dd {display: inline-block; position: relative;}
.dd .dd-trigger {display: block; text-decoration: none; border: 1px solid #ccf; padding: 5px; border-radius: 5px; color: #000; min-width: 100px;}
.dd .dd-trigger img {margin-top: 5px; float: right;}
.dd .dd-menu,
.dd .dd-menu li {margin: 0; padding: 0; list-style: none;}
.dd .dd-menu {display: none; position: absolute; z-index: 2; background-color: #f5f5f5; border: 1px solid #ccf; left: 0; right: 0; margin-top: -3px; border-top: 0;}
.dd .dd-menu a {display: block; text-decoration: none; line-height: 1; padding: 5px; color: #000; margin: 2px 0;}
.dd .dd-menu a:hover {background-color: #fff;}
.dd.open .dd-menu {display: block;}
.dd-mask {position: fixed; z-index: 1; left: 0; top: 0; bottom: 0; right: 0; display: none;}
.dd-open .dd-mask {display: block;}
input {width: 90%; margin: 5px; border: 1px solid #ccc; padding: 3px 5px;}
li:first-child input {margin-bottom: 0;}
<script src="https://code.jquery.com/jquery-2.2.4.js"></script>
<div class="dd-mask"></div>
<div class="dd">
  <a href="#" class="dd-trigger">
    Account
    <img src="https://www.mydemenageur.com/images/caret_open.png" alt="Drop Down" />
  </a>
  <ul class="dd-menu">
    <li><a href="">Profile</a></li>
    <li><a href="">Settings</a></li>
    <li><a href="">Logout</a></li>
  </ul>
</div>
<div class="dd">
  <a href="#" class="dd-trigger">
    Help
    <img src="https://www.mydemenageur.com/images/caret_open.png" alt="Drop Down" />
  </a>
  <ul class="dd-menu">
    <li><a>Empty Clicker</a></li>
    <li><a href="">Questions</a></li>
    <li><a href="">Support</a></li>
  </ul>
</div>
<div class="dd">
  <a href="#" class="dd-trigger">
    Search
    <img src="https://www.mydemenageur.com/images/caret_open.png" alt="Drop Down" />
  </a>
  <ul class="dd-menu">
    <li><label><input type="search" /></label></li>
    <li><input type="submit" value="Search" /></li>
  </ul>
</div>

Note: I am using jQuery only for the adding or removing of classes. See below on how to do it using pure JavaScript:

element.classList.add("className");     // Adding
element.classList.remove("className");  // Removing

Have a quick look in the original post and you might be able to understand how can the CSS be implemented.

Bonus: My article explains exactly how to avoid Search Button click and dismissing the menu issue too! Check out the demo above to see it in action and see my article to learn it too.

Praveen Kumar Purushothaman
  • 164,888
  • 24
  • 203
  • 252
1

The problem you're running into is event bubbling. When you click on the button, the event bubbles out to the window object, and then the function that closes the menu runs.

You can call event.stopPropagation() in myFunction() to prevent this.

I've also changed the window.onclick function to use document.querySelectorAll() so it just loops over the dropdowns that are showing, instead of calling classList.contains() in the loop.

function myFunction(event) {
  document.getElementById("myDropdown").classList.toggle("show");
  event.stopPropagation();
}

window.onclick = function(event) {
  if (!event.target.matches('.dropbtn')) {

    var dropdowns = document.querySelectorAll(".dropdown-content.show");
    for (var i = 0; i < dropdowns.length; i++) {
      var openDropdown = dropdowns[i];
      openDropdown.classList.remove('show');
    }
  }
}

function filterFunction() {
  var input, filter, ul, li, a, i;
  input = document.getElementById("myInput");
  filter = input.value.toUpperCase();
  div = document.getElementById("myDropdown");
  a = div.getElementsByTagName("a");
  for (i = 0; i < a.length; i++) {
    if (a[i].innerHTML.toUpperCase().indexOf(filter) > -1) {
      a[i].style.display = "";
    } else {
      a[i].style.display = "none";
    }
  }
}
.dropdown-content {
  display: none;
}

.dropdown-content.show {
  display: block;
}
<div class="dropdown">
  <button onclick="myFunction(event)" class="dropbtn"><img src="bread.png" height="50" width="50" style="vertical-align:middle" align="left"><a>Testing</a></button>
  <div id="myDropdown" class="dropdown-content">
    <input type="text" placeholder="Search.." id="myInput" onkeyup="filterFunction()">
    <a> Dropdown Content 1 </a>
    <a> Dropdown Content 2 </a>
    <a> Dropdown Content 3 </a>
  </div>
</div>
Barmar
  • 741,623
  • 53
  • 500
  • 612
0

I have added two onclick event. window.onclick recognises the click is not on button.

var button = document.getElementById("myDropdown");

window.onclick = function(event) {
  if (event.target.tagName !== "BUTTON") {
    if (button.style.display != "none") {
      button.style.display = "none";
    }
  }
}

function myFunction() {
  window.click = false;
  if (button.style.display != "none") {
    button.style.display = "none";
  } else {
    button.style.display = "";
  }
}
<div class="dropdown">
  <button onclick="myFunction()" class="dropbtn">
    <img src="bread.png" height="50" width="50" style="vertical-align:middle" align="left">
    <a>Testing</a></button>
  <div id="myDropdown" class="dropdown-content">
    <input type="text" placeholder="Search.." id="myInput" onkeyup="filterFunction()">
    <a> Dropdown Content 1 </a>
    <a> Dropdown Content 2 </a>
    <a> Dropdown Content 3 </a>
  </div>
</div>