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?
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>
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.
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/
Related
Repro: https://jsfiddle.net/ssabc/cL6qxn1r/13/
I have a background element and a foreground element (you can think of it as a dialog popup on top of a canvas). When the user right-clicks on the foreground I would like to prevent the context menu from appearing in the background.
I tried binding a handler for the foreground's context menu and returning false from it to no avail:
document.getElementById('above').oncontextmenu = function(e) {
e.preventDefault();
return false;
}
As you can see in the JSFiddle, the oncontextmenu-event triggers on both elements. Here's a screenshot showing event firing in the background no matter which element is right-clicked
Is there any way to prevent the background event from firing?
You just need to add
e.stopPropagation();
to your child element right click event handler. With the change, it would look like this:
document.getElementById('above').oncontextmenu = function(e) {
e.preventDefault();
e.stopPropagation(); // <=== add this
getResultP().innerHTML += '<li>Dialog oncontextmenu called</li>';
return false;
}
This prevents the event from bubbling up the DOM tree. Read more about it here.
I've tried searching for an explanation to my issue below but it's possible the keywords I used are to ambiguous.. so I turn to you.
I'm trying to implement a click outside menu to close based on this code here which is:
$('#menuscontainer').click(function(event) {
//your code that shows the menus fully
//now set up an event listener so that clicking anywhere outside will close the menu
$('html').click(function(event) {
//check up the tree of the click target to check whether user has clicked outside of menu
if ($(event.target).parents('#menuscontainer').length==0) {
// your code to hide menu
//this event listener has done its job so we can unbind it.
$(this).unbind(event);
}
})
});
However it seems the function in $('html').click... gets executed when clicking on the #menucontainer so it just open and immediately closes.
I've simplified it to:
$('.trig').click(function () {
$('.menu').show();
$('body').click( function () {
alert('boo');
});
});
Here's the JSFiddle link.
As you can see alert gets executed when .trig is clicked on. So instead of just creating the even listener, it seems to already be listening.
Can someone please explain this to me? Thanks.
Events bubble to parents also.
You can prevent this by using event.stopPropagation()
$('.trig').click(function (e) {
e.stopPropagation();
$('.menu').show();
$('body').click( function () {
alert('boo');
});
});
Since you are adding a click handler to the body before the event is over it is also being triggered on the body
Reference: https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation
DEMO
I'm trying to implement the following functionality and am having some trouble. What I want is when a user clicks a certain image, a popup div will appear containing some information about that image. Then if the user were to click anywhere on the page outside of that popup div, it would simply hide and then remove the popup.
What I am trying to do is register an eventListener after the popUp div is added to the page. Tried with both jquery and without and am after the same issue. (I included both below but only one is active in the code at a time.)
createProfilePopUpEventListener: function(){
$('body').on('click', function(){
$('.profile_pop_up').fadeOut('fast').remove();
});
},
createProfilePopUpEventListener: function(){
var el = document.getElementsByTagName('body')[0];
el.addEventListener("click", $('.profile_pop_up').fadeOut('fast').remove();
},
showPopUp: function(e){
//creates popUp and adds it to the DOM
this.createProfilePopUpEventListener();
}
What seems to be happening is that the event is being triggered right away on the initial click to show the popup and thus it is never displayed. How can I create an eventListener that only starts listening for those clicks at a certain time?
I guess your problem is event propagation. Your image that is used as the trigger to open the popup bubbles your event up the whole DOM, eventually to the body. Thus the fadeout/remove event is triggered at the same time as your open event.
You will need to stop the propagation of that in such a fashion (using :
$('#popup_trigger').on('click', function(event){
event.stopPropagation();
$('.profile_pop_up').fadeIn();
});
I have a div that has an onclick that hides the div.
If I have a link inside the div, the link does not work, because the onclick steals the click.
The desired result: if a click is made anywhere inside the div BUT the link, that the div gets hidden. If a click is made ON the link, then I want the link to open in a _blank window, and the div to remain visible.
Is there a way to deal with this with javascript?
document.getElementById('yourlinksid').onclick = function(e){
// ... pop your window ...
/* the following prevents the event from "bubbling up"
to the div's event handler */
if(!e.stopPropagation) {
e.cancelBubble = true;
return;
}
e.stopPropagation();
};
Verification:
http://jsfiddle.net/kSTNT/4/
DEMO
Inside the click handler for the link, you'll want to call event.stopPropagation, or set e.cancelBubble to true—whichever your browser prefers. This will prevent the event from bubbling to your div.
document.getElementById("thelink").onclick = function (e) {
window.open();
if (e.stopPropogation)
e.stopPropogation();
if (e.cancelBubble != null)
e.cancelBubble = true;
};
If you control the links, the easiest way is to add onclick="event.stopPropagation();" (or whatever the cross-browser version of that is) to each link, which stops the div from seeing the event without preventing the default effect of the link from taking place.
Alternatively, if the links only contain text, then you can check the tag name of the event's target in the div's click event to see whether it is a link, but if you have something more complex then you need to walk the dom tree from the event target to the div to verify that no link exists.
for arcane reasons I need to be able to cancel the click event via the mousedown event.
Briefly; I am creating a context menu in the mousedown event, however, when the user clicks on the page the context menu should disappear.
I am not able to use the mousedown event over the click in that scenario as I want the user to be able to click links inside the menu ( a full click would never travel to the <a> based menu elements ).
If it is any help, jQuery can be applied.
I would like to either be able to prevent the click event from happening from within the initial mousedown, or be able to pass information to the click event (via originalEvent or otherwise).
TIA
Seems to be impossible, neither FF nor Opera didnt cancel upcoming click when prevented in mousedown and/or mouseup (as side note: click is dispatched after mouseup if certain conditions met). testcase: http://jsfiddle.net/ksaeU/
I have just had the exact same problem. I fixed my context menu by closing it on mousedown and eating the mousedown event on the menu so that I can still receive clicks on the menu, like so:
$(document).one('mousedown.ct', null, function() { cmenu.hide(); return false; });
cmenu.bind('mousedown', function(e) { e.stopImmediatePropagation(); });
And in the hide() function I unbind the mousedown.ct again, in case it was closed due to a click on an item.
Hey, I think this is what you are trying to do with your code. If not, I apologize, I may have misunderstood the question. I used jQuery to get it done: http://jsfiddle.net/jackrugile/KArRD/
$('a').bind({
mousedown: function(){
// Do stuff
},
click: function(e){
e.preventDefault();
}
});