0

I'm making a popup menu. The user clicks on it to show the menu, then if they click outside the popup menu I want to hide it.

I can find many solutions (most popular is here: How do I detect a click outside an element?) but they all seem to have the same issue.

They rely on handling clicks that bubble up to the window element.

Their Logic:

All clicks bubble up to window element. Handle those clicks - if menu is open, then close it. Also call preventDefault to stop any links being followed (let's just say that the user happens to click on a link when they are clicking outside the menu - we don't want to follow that link)

$(window).click(function(e) {

        if (!e.isDefaultPrevented()) {

            if($('.mainNav').hasClass('menuVisible')){

                //stop any other actions happening (e.g. following a link)
                e.preventDefault();

                //Hide the menus
                $('.mainNav').removeClass('menuVisible');
            }
        }
    });

The issue

If the thing the user clicks on happens to have an onclick event itself then that code still gets fired. Elements lower down the tree get the click even first, so I cannot use preventDefault or stopPropagation to stop these events..

Any ideas how to fix it? My only idea is to put a transparent div across the whole screen on top of everything to catch the clicks first?

Brian Mains
  • 50,520
  • 35
  • 148
  • 257
Phil Teare
  • 417
  • 1
  • 6
  • 14

3 Answers3

1

You need to use addEventListener() and the useCapture property. the useCapture property allows events from object higher in the DOM tree to be triggered first. You can then prevent your normal click behaviour from occurring:

var button = document.getElementById("myButton");
var response = document.getElementById("myResponse");
var windowClick = function (evt) {
    response.innerHTML += "<p>The Window</p>";
    evt.stopPropagation ();
}
var buttonClick = function (evt) {
    response.innerHTML += "<p>The Button</p>";
    evt.stopPropagation ();
}
button.addEventListener("click", buttonClick);
// If true, the window event fires first, if false, the button fires first.
var useCapture = true;
window.addEventListener("click", windowClick, useCapture); 
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<button id="myButton">Hello!</button>
<div id="myResponse">Who Clicked?</div>
</body>
</html>
PilotInPyjamas
  • 897
  • 6
  • 7
1

Updated

I originally misunderstood that we were trying to stop inline onclick events from firing. I found a potential solution from another StackOverflow question, you can see it here.

Otherwise, take a look at this:

 $('button[onclick]').each(function(){
        $(this).data('onclick', this.onclick);

        this.onclick = function(event) {
        if($('.mainNav').hasClass('menuVisible')) {
            return false;
        };

        $(this).data('onclick').call(this, event || window.event);
    };
 });

It overrides the elements click handler. I've updated your jsFiddle to show it in action.

Jeramiah Harland
  • 854
  • 7
  • 15
  • In your example, if you had put an event listener on "Link, the hero of our time" it still would have fired with the menu open, so in this case, your solution doesn't work. – PilotInPyjamas Aug 17 '17 at 14:19
  • the e.preventDefault stops a link being fired, but it does not stop an onclick event being fired. fiddle here: https://jsfiddle.net/evp9je7w/6/ – Phil Teare Aug 17 '17 at 14:33
  • I've updated my answer but can't really take any credit if it works out for you. – Jeramiah Harland Aug 17 '17 at 16:51
0

you can add a class to the body when menu is opened, and attach an event listener to the click event of body which will hide the menu and remove the listener

when showing the menu

$('body').addClass('menu-open');
$('body.menu-open').one('click', function(e) {
    e.preventDefault();
    // code to hide your menu goes here
    $('body').removeClass('menu-open');
});

Note the usage of .one for attaching the event handler. It automatically removes the event handler after it is executed once.

Ref https://api.jquery.com/one/

sid-m
  • 1,504
  • 11
  • 19