0

I have 29 clickable drop down menus on a page. I need them to open when clicked on, then close when clicked on another one or anywhere else on the page. I got this code from w3schools.com.

Right now this code will open the first menu on the page. When I click on another menu however, it will just open the first menu again. I cannot open any other menu on the page. The only menu that will open, no matter what one is clicked is the first menu.

I played around with the code and was eventually able to get other menus to open, the problem was that the first menu would stay open. For example, I would click menu 1 and it would open. When I clicked menu 2 it would open, but menu 1 would also remain open. This was for every menu on the page.

I messed with the code enough that no menus work anymore and I am having to resort to code I began with.

/* When the user clicks on the button, 
toggle between hiding and showing the dropdown content */
function myFunction() {
  document.getElementById("myDropdown").classList.toggle("show");
}

// Close the dropdown menu if the user clicks outside of it
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');
      }
    }
  }
}
/* Dropdown Button */
.dropbtn {
  background-color: #3498DB;
  color: white;
  padding: 16px;
  font-size: 16px;
  border: none;
  cursor: pointer;
}

/* Dropdown button on hover & focus */
.dropbtn:hover, .dropbtn:focus {
  background-color: #2980B9;
}

/* The container <div> - needed to position the dropdown content */
.dropdown {
  position: relative;
  display: inline-block;
}

/* Dropdown Content (Hidden by Default) */
.dropdown-content {
  display: none;
  position: absolute;
  background-color: #f1f1f1;
  min-width: 160px;
  box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
  z-index: 1;
}

/* Links inside the dropdown */
.dropdown-content a {
  color: black;
  padding: 12px 16px;
  text-decoration: none;
  display: block;
}

/* Change color of dropdown links on hover */
.dropdown-content a:hover {background-color: #ddd}

/* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */
.show {display:block;}
<div class="dropdown">
  <button onclick="myFunction()" class="dropbtn">Dropdown</button>
  <div id="myDropdown" class="dropdown-content">
    <a href="#">Link 1</a>
    <a href="#">Link 2</a>
    <a href="#">Link 3</a>
  </div>
</div>

What I need is each menu to be able to be clicked on. Then, when I click on the page, or another menu, the first menu will close and open up the second menu. Only one menu open at a time.

Thank you!

Scott Marcus
  • 64,069
  • 6
  • 49
  • 71

1 Answers1

0

First, stay as far away as possible from W3Schools as it is widely known to have outdated, incomplete, or flat out wrong information. As such, many of the techniques in your code are outdated and should not be used, such as onXyz inline event attributes, JavaScript event properties and the use of getElementsByClassName(). The Mozilla Developer's Network is well-known to be an authoritative resource.

The overall solution here is to use event delegation and allow events to bubble up from their originating element (the event target), to the document, where they are handled. This allows us to create just one event handler instead of one for each menu. Then, no matter which menu was clicked, the event handling function collapses them all and then just expands the one that was clicked.

Note that in the following code, there is no JavaScript in the HTML, no elements have id attributes, and that the menus have a CSS class of hide applied to them by default instead of having display:none set in the element CSS class. This allows us to remove that class by itself that leave the rest of the CSS intact.

There is no reliance on ids, so when you add another dropdown structure, it will just work with no changes to the JavaScript needed.

// Get all the menus into an array, just once:
let menus = Array.prototype.slice.call(document.querySelectorAll(".dropdown-content"));

let openMenu = null;

/* When the user clicks on the button, 
toggle between hiding and showing the dropdown content */
function hideAllMenus() {
  // Get all the dropdowns into an array.
  menus.forEach(function(dropdown) {
    // If the element currently is not hidden
    if(!dropdown.classList.contains("hide")){
      openMenu = dropdown;
      dropdown.classList.add('hide'); // Hide it
    }
  });
}

// Close the dropdown menu if the user clicks outside of it
document.addEventListener("click", function(event) {

  hideAllMenus();      // Hide all the menus
  
  // If the clicked item was a menu
  if (event.target.classList.contains('dropbtn')) { 
    if(event.target.nextElementSibling === openMenu){
      event.target.nextElementSibling.classList.add("hide");
      openMenu = null;
    } else {
      // Go to the next element that is a sibling of the one that got clicked (the menu)
      // and toggle the use of the `hide` CSS class
      event.target.nextElementSibling.classList.remove("hide"); // Show the one that was clicked
      openMenu = event.target.nextElementSibling;
    }
  }
});
/* Dropdown Button */
.dropbtn {
  background-color: #3498DB;
  color: white;
  padding: 16px;
  font-size: 16px;
  border: none;
  cursor: pointer;
}

/* Dropdown button on hover & focus */
.dropbtn:hover, .dropbtn:focus {
  background-color: #2980B9;
}

/* The container <div> - needed to position the dropdown content */
.dropdown {
  position: relative;
  display: inline-block;
}

/* Dropdown Content (Hidden by Default) */
.dropdown-content {
  position: absolute;
  background-color: #f1f1f1;
  min-width: 160px;
  box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
  z-index: 1;
}

/* Links inside the dropdown */
.dropdown-content a {
  color: black;
  padding: 12px 16px;
  text-decoration: none;
  display: block;
}

/* Change color of dropdown links on hover */
.dropdown-content a:hover {background-color: #ddd}

/* Add or remove this to hide or show */
.hide {display:none;}
<div class="dropdown">
  <button class="dropbtn">Dropdown</button>
  <div class="dropdown-content hide">
    <a href="#">Link 1</a>
    <a href="#">Link 2</a>
    <a href="#">Link 3</a>
  </div>
</div>
<div class="dropdown">
  <button class="dropbtn">Dropdown</button>
  <div class="dropdown-content hide">
    <a href="#">Link 1</a>
    <a href="#">Link 2</a>
    <a href="#">Link 3</a>
  </div>
</div>
<div class="dropdown">
  <button class="dropbtn">Dropdown</button>
  <div class="dropdown-content hide">
    <a href="#">Link 1</a>
    <a href="#">Link 2</a>
    <a href="#">Link 3</a>
  </div>
</div>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • Also, my menu titles vary in length. Is there a way to make every menu box the same size, regardless of how long the title is? – Brett Brandeburg Apr 16 '19 at 22:01
  • sorry figured it out add a ```min-width: 200px;``` to the ```/* Dropdown Button */``` of the css file so it looks like this – Brett Brandeburg Apr 16 '19 at 22:07
  • Hey, sorry to come back for this, this code does not work well for mobile devices. You cannot clicks the screen or the selected menu and have it go away. Instead, the first menu clicked stays open and will not close unless you click another menu. I need it to open with a tap and then close with another tap on mobile. Do you have a fix for this at all? – Brett Brandeburg Apr 22 '19 at 17:12
  • @BrettBrandeburg It's not just mobile. You didn't explicitly mention that requirement, so I didn't build it it. I've updated the answer to address it. – Scott Marcus Apr 22 '19 at 18:44
  • I do very much appreciate it! I know I did not explicitly mention that, I did not think of it until I started playing around with it after I got everything set up so I thank you yet again! – Brett Brandeburg Apr 22 '19 at 18:48