salamander
salamander

Reputation: 191

Closing dropdown by clicking outside in Javascript (tutorial clarification)

I have attempted to implement the method of opening and closing a drop-down using Javascript via this tutorial on w3schools.com. While the function to "show" the drop-down works, the one to close it does not. Furthermore, there is no explanation alongside this code to explain why it should work, making it difficult to debug.

/* 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');
      }
    }
  }
}

My questions are, therefore,

1) whether the code in the tutorial should work for the purpose of closing the drop-down. (ANSWERED)

2) Could someone please clarify how/why this should work, for the sake of clarity for myself and future newbies who make come across the same tutorial and issue? (UNANSWERED)

Edit (MY ATTEMPT):

HTML:

<div class="sharedown">     
    <p onclick="shareVis()" class="sharebtn">&nbsp Share</p>
    <div id="mySharedown" class="sharedown-content">
        <a href="#">Self</a>
        <p>User</p><input type="text" name="user-name" placeholder="Share to">
        <a href="#">Community</a>
    </div> 
</div>

JS:

function shareVis() {
    document.getElementById("mySharedown").className = "show";
}

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

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

CSS:

/* Share dropdown menu */

p.sharebtn {

    color: darkgrey;
    font-family:calibri;
    padding: 0px;
    margin: 0px;
    font-size: 12;
    border: none;
    cursor: pointer;
    display:    inline; 
}

/* Dropdown button on hover & focus */
p.sharebtn:hover, p.sharebtn:focus {
    color: grey;

}

/* The container <div> - needed to position the dropdown content */

.sharedown {
    position: relative;
    display:    inline-block;   

}

/* Dropdown Content (Hidden by Default) */
.sharedown-content {
    display: none;
    position: absolute;
    background-color:   #f1f1f1;
    min-width:  100px;
    box-shadow: 0 2px 4px 1px #C4E3F5;
    z-index:1; /* place dropdown infront of everything else*/
}

.sharedown-content a { 
color: black;
padding: 5px 5px;
text-decoration: none;
display: block;
}

/* 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;
    position: absolute;
    background-color:   #f1f1f1;
    min-width:  100px;
    box-shadow: 0 2px 4px 1px #C4E3F5;
    opacity: 1;
    z-index:1;}

Upvotes: 9

Views: 57039

Answers (4)

Muneeb Ejaz
Muneeb Ejaz

Reputation: 884

Update 2022 Vanilla Javascript now contains a mehtod called Node.closest(Node) to check if the event matches the node in the upper hierarchy. below is an example to open the dropdown menu on click and hide it again on click and if clicking outside the document will also hide the dropdown menu.

const list = document.querySelector('.list')
const btn = document.querySelector('.btn')

btn.addEventListener('click', (e)=> {
  
 list.classList.toggle('hidden')
  e.stopPropagation()
})

document.addEventListener('click', (e)=> {
  if(e.target.closest('.list')) return 
  
  list.classList.add('hidden')

})
.hidden {
  display:none
}
ul {
  background-color: blue;
  
}
<button class="btn">open</button>
<ul class="list hidden">
    <li class="item1">Item 1</li>
    <li class="item2">Item 2</li>
    <li class="item3">Item 3</li>
</ul>

Upvotes: 7

shontek
shontek

Reputation: 9

The example is fully functional and should work. Copy the following code below:

/* 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 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');
          }
        }
      }
    }
 .dropbtn {
        background-color: #3498DB;
        color: white;
        padding: 16px;
        font-size: 16px;
        border: none;
        cursor: pointer;
    }
    
    .dropbtn:hover, .dropbtn:focus {
        background-color: #2980B9;
    }
    
    .dropdown {
        position: relative;
        display: inline-block;
    }
    
    .dropdown-content {
        display: none;
        position: absolute;
        background-color: #f1f1f1;
        min-width: 160px;
        overflow: auto;
        box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
        z-index: 1;
    }
    
    .dropdown-content a {
        color: black;
        padding: 12px 16px;
        text-decoration: none;
        display: block;
    }
    
    .dropdown a:hover {background-color: #ddd}
    
    .show {display:block;}
    
    <h2>Clickable Dropdown</h2>
    <p>Click on the button to open the dropdown menu.</p>
    
    <div class="dropdown">
    <button onclick="myFunction()" class="dropbtn">Dropdown</button>
      <div id="myDropdown" class="dropdown-content">
        <a href="#home">Home</a>
        <a href="#about">About</a>
        <a href="#contact">Contact</a>
      </div>
    </div>

Upvotes: 0

Emmanuel
Emmanuel

Reputation: 33

Here I leave another "short" example that I implemented to my own code, but is easy to understand.

.tw-hidden is a class "display: none"

window.onclick = function(event) {
        let customDropdownsEl = document.querySelectorAll(".custom-dropdown");
        let liContainerEl = event.target.querySelector(".custom-dropdown");
    
        customDropdownsEl.forEach(el => el.parentNode !== event.target && !el.classList.contains("tw-hidden") && el.classList.add("tw-hidden"));
        event.target.matches('.custom-dropdown-container') && liContainerEl.classList.toggle("tw-hidden");
}

Upvotes: 0

Thum Choon Tat
Thum Choon Tat

Reputation: 3090

The issue lies in shareVis function. Here

document.getElementById("mySharedown").className = "show";

you are replacing #mySharedown class name to show. Then in window.onclick

var sharedowns = document.getElementsByClassName("sharedown-content");

you are not getting any sharedowns as you already replaced the class name to show.


You can either add show class into classList

document.getElementById("mySharedown").classList.add("show");

or replace the class name with sharedown-content show

document.getElementById("mySharedown").className = "sharedown-content show";

Working solution below:

function shareVis() {
    //document.getElementById("mySharedown").className = "sharedown-content show";
    document.getElementById("mySharedown").classList.add("show");
}

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

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

document.getElementById("mySharedown").addEventListener('click',function(event){
    event.stopPropagation();
});
#mySharedown{
  display: none;
  border: 1px solid black;
}

#mySharedown.show {
  display: block;
}
<div class="sharedown">     
    <p onclick="shareVis()" class="sharebtn">&nbsp Share</p>
    <div id="mySharedown" class="sharedown-content">
        <a href="#">Self</a>
        <p>User</p><input type="text" name="user-name" placeholder="Share to">
        <a href="#">Community</a>
    </div> 
</div>

Update

To prevent the second click within #mySharedown from hiding #mySharedown, you should add another click event for #mySharedown and prevent it from bubbling up, like this

document.getElementById("mySharedown").addEventListener('click',function(event){
    event.stopPropagation();
});

Updates are included in the working solution

Upvotes: 11

Related Questions