Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

Nikos Papapetrou
Nikos Papapetrou
6,305 Points

How do you close one dropdown menu when you open another, or clicking everywhere else in the document to close them all?

I have created a dropdown menu for more with classList and toggle. But you have to click again so it close. My codenpen: https://codepen.io/nikospapapetrou/pen/yLOVPoZ

I want to create one dropdown menu for more and another when you click on search (open the search form). I found a way in W3schools and tried to adjust it without result. What methods in general websites uses? I tried to search a tutorial or something but I couldn't find any good resource. Any hint is welcome.

Thanks!

Steven Parker
Steven Parker
231,007 Points

As soon as the codepen is opened, it generates a "ResizeObserver loop limit exceeded" error. (Open the console to see it). You'll need to fix that before the menu code can be demonstrated.

2 Answers

Steven Parker
Steven Parker
231,007 Points

Odd, that error occurs very consistently when I visit the link. But just looking at the code, I notice these issues:

  • the window event will catch any click, including one one that opens the submenu
  • when both handlers run, the closing would happen before the opening can be seen
  • the "match" function requires a selector not just a class (so ".dropdown" with a period)
  • the UL is not likely to be a click target anyway, it will most likely be an A (or maybe LI)
  • the loop variable "i" should be declared

Here's a potential alternative to replace both the original handlers:

window.addEventListener("click", event => {
  // start by closing every dropdown
  for (let dd of dropdown) dd.classList.toggle("open", false);
  // then if a link was clicked, open the associated dropdown
  if (event.target.matches(".mb-menu a")) {
    event.target.nextElementSibling.classList.toggle("open");
  }
});

Note: this is rough example and doesn't cover the case where the LI or a nested link is clicked.

Nikos Papapetrou
Nikos Papapetrou
6,305 Points

Thanks Steven Parker.

Yes, in the codepen sometimes occurs this thing (ResizeObserver loop limit exceeded), but as I use Firefox (when coding with Visual studio code) doesn't show anything. I can't find much information about what that means.

As for the second part I can't figure out how it should be done. "the window event should catch any click, including one one that opens the submenu". How can I achieve that? By adding a the first addEventListener with the toggle as argument in the second. Like this:

target.addEventListener(type, listener);

I found this code in stackoverflow.

function myFunction(event, dropDownName) {
  //Pass in your dropdownName which is the dropdown  
  var dropDownHandler = document.getElementById(dropDownName);

  dropDownHandler.classList.toggle("show");
  // Get the trigger element of the dropdown
  var menuHandler = event.currentTarget;

  if (dropDownHandler.classList.contains("show")) {
    //Attach only when the dropdown is active,
    //to ensure onclick isn't called always
    document.addEventListener("click", function(docEvent) {
      documentHandler(docEvent, menuHandler)
    });
  } else {
    dropDownHandler.classList.toggle("show");
    // If is closed, remove the handler
    document.removeEventListener("click", documentHandler);
  }

  function documentHandler(event, menuHandler) {
    if (menuHandler.contains(event.target)) {
      dropDownHandler.classList.add("show");
    } else {
      dropDownHandler.classList.remove("show");
    }
  }
}
Steven Parker
Steven Parker
231,007 Points

What I meant was that the window listener does catch every click, including the one caught by the mb_menu listener. So they both happen and one cancels out the other.

My suggested replacement is for both of them, so the one new handler does both jobs of closing everything first, then opening the one clicked.

I can't comment on the stackoverflow code as it is just part of a solution that would include more JavaScript and some HTML, which might have a different structure from your own.

Nikos Papapetrou
Nikos Papapetrou
6,305 Points

Yes, I finally understood it. Your first answer with the code was very enlightening. Unlikely I can't make the search form open (second dropdown). I am sure I am doing something wrong. Also when I re-click the dropdown menus (to close them) doesn't close. Should I add another function inside the event listener to achieve the result I want?

Steven Parker
Steven Parker
231,007 Points

You can easily add a check if the menu was already open, and if so just skip the opening later:

window.addEventListener("click", event => {
  // first check if it was already open
  let wasOpen = event.target.matches(".mb-menu a") &&
    event.target.nextElementSibling.classList.contains("open");
  // next close every dropdown
  for (let dd of dropdown) dd.classList.toggle("open", false);
  // then if a link was clicked, open the associated dropdown, BUT only if not open before
  if (!wasOpen && event.target.matches(".mb-menu a")) {
    event.target.nextElementSibling.classList.toggle("open");
  }
});

This code won't work with "search" since it doesn't follow the same HTML structure (has no "mb-menu" classs and contains no list with "dropdown" class). You could rebuild it to fit the same pattern, or handle it with different code.

Nikos Papapetrou
Nikos Papapetrou
6,305 Points

Yes, I see. The difficult is to apply all this logic and turn it into code. You make it so simple with so little code. I couldn't figure it out, I can't imagine how I would had to deal with more complicated stuff. lol. It needs a lot of practice.

Nikos Papapetrou
Nikos Papapetrou
6,305 Points

Steven Parker, I have checked the console for errors but I don't get any. Also the resizeObserver seems to work the way I want.