Search code examples
javascripthtmlcssmenuonclick

Close elements by clicking anywhere on the page using pure Javascript


I have a menu which opens a sub-navigation on clicking a header which I am trying to get to close by clicking anywhere on the page except an open element.

My Code Snippet is as follows:

function showSubMenu(show, hide1, hide2, hide3, hide4) {
  document.getElementById(show).className = "subNavShow";
  document.getElementById(hide1).className = "subNavHide";
  document.getElementById(hide2).className = "subNavHide";
  document.getElementById(hide3).className = "subNavHide";
  document.getElementById(hide4).className = "subNavHide";
}
.subNavHide {
  display: none;
}

.subNavShow {
  display: block;
}
<ul class="topnavList" id="siteTopnavList">
  <li>
    <a onclick="showSubMenu('text1','text2','text3','text4','text5')" href="javascript:void(0);">Nav 1</a>
    <article id="text1" class="subNavHide">
      <ul>
        <li><a href="#">Sub Nav 1</a></li>
      </ul>
    </article>
  </li>
  <li>
    <a onclick="showSubMenu('text2','text1','text3','text4','text5')" href="javascript:void(0);">Nav 2</a>
    <article id="text2" class="subNavHide"> text2 </article>
  </li>
  <li>
    <a onclick="showSubMenu('text3','text1','text2','text4','text5')" href="javascript:void(0);">Nav 3</a>
    <article id="text3" class="subNavHide"> text3 </article>
  </li>
  <li>
    <a onclick="showSubMenu('text4','text1','text2','text3','text5')" href="javascript:void(0);">Nav 4</a>
    <article id="text4" class="subNavHide"> text4 </article>
  </li>
  <li>
    <a onclick="showSubMenu('text5','text1','text2','text3','text4')" href="javascript:void(0);">Nav 5</a>
    <article id="text5" class="subNavHide"> text5 </article>
  </li>
</ul>

Ideally I would like to use pure Javascript for this but if Jquery is absolutely necessary then I would be OK with that too


Solution

  • The easiest way to do this with your current implementation, in my opinion, is to add a click event listener to the document and use .closest to determine if the element clicked is the element open:

    document.addEventListener(`click`, hideSubMenus);
    
    function hideSubMenus(event) {
        if (!event.target.closest(`.topnavList li a, .subNavShow`)) {
            document.getElementById(`text1`).className = `subNavHide`;
            document.getElementById(`text2`).className = `subNavHide`;
            document.getElementById(`text3`).className = `subNavHide`;
            document.getElementById(`text4`).className = `subNavHide`;
            document.getElementById(`text5`).className = `subNavHide`;
        }
    }
    

    closest is however not compatible with older browsers: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest

    But I would probably add classes to the links and add event listeners to them instead of using the "onclick" attribute. That way, for example, if you add the "subNavLink" class to each link, you can use a loop to deal with the links, instead of repeating the same line for each link:

    let links, i, n;
    links = document.getElementsByClassName(`subNavLink`);
    for (i = 0, n = links.length; i < n; i++) {
        links[i].addEventListener(`click`, showSubMenu);
    }
    
    function showSubMenu(event) {
        let currentLink, i, link, n;
        currentLink = event.currentTarget;
        for (i = 0, n = links.length; i < n; i++) {
            link = links[i];
            if (link === currentLink) {
                // this link was clicked, so we have to show its submenu
                link.nextElementSibling.className = `subNavShow`;
            } else {
                // this link was not clicked, so we have to hide its submenu
                link.nextElementSibling.className = `subNavHide`;
            }
        }
    }
    

    By doing this you can change the hideSubMenus function to:

    function hideSubMenus(event) {
        let i, n;
        if (!event.target.closest(`.subNavLink, .subNavShow`)) {
            for (i = 0, n = links.length; i < n; i++) {
                links[i].nextElementSibling.className = `subNavHide`;
            }
        }
    }