Layered elements with oncontextmenu bindings - javascript

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.

Related

How to invert Javascript element bubble order?

I have created a custom Slider component that works perfectly except for one issue.
If it is present in a modal, then the mouseUp event for it fires on it for all events below it (z-index-wise) before on the element itself.
This is because to have a nice slider you need to attach the mouseMove and mouseUp events to the document instead of the slider itself so that you can still move the thumb and capture mouse up events even when the mouse moves off the slider while sliding the thumb.
As a result, I am now having issues where the mouseUp event causes the click events for elements below it to run before the slider element. This causes issues like buttons below it clicking and the modal closing (since these are both click events).
A potential solution is to just add checks to all the potential event handlers that might be intercepted (something like if (thisModalOpened) { return false; } to all the various other elements), but that solution seems a bit messy.
Is there any better way? A way to invert the bubbling order seems like it could work, because then I could just call stopPropagation in the mouseUp for the Slider component. However, I'm not sure if that can be done, because currently the event listener is registered in the document so that it can be detected outside of the element itself.
Some code snippets:
componentDidMount() {
document.addEventListener("pointermove", this.onThumbMove, false);
document.addEventListener("pointerup", this.onThumbUp, false);
}
onThumbDown = e => {
this.setState({ dragging: true, startX: this.getX(e) });
}
onThumbMove = e => {
if (this.state.dragging) {
this.setState({ position: e.clientX });
}
}
onThumbUp = e => {
if (this.state.dragging) {
let value = this.getValue();
this.setState({ dragging: false }, () =>
this.props.onSubmit(value);
});
}
}
<div onPointerDown={this.onThumbDown} ref={this.sliderRef}>
<div>+</div>
</div>
Edit: Okay, I think I have finally figured out the problem (but I don't know the solution yet): So, modals often (mine does as well) have the tinted black backdrop that you can click on to close the modal easily. I can either have that black backdrop as a direct parent or a parent's sibling of the model's contents.
If I have it as a direct parent, then I have to call stopPropagation / return false on literally every single possible child component, so that clicking them doesn't cause the click to propagate up and click the backdrop modal and cause it to close. So I don't want it as a parent because that's a huge hassle.
On the other hand, if I have it as a parent's sibling, then I cannot call stopPropagation on the document thumbUp event, because the modal backdrop mask is not a direct parent, so it will be registered as a click and close the modal despite the stopPropagation call, because it's not a direct parent.
Basically, both approaches don't work. I don't know how to reconcile this.
As #Sarvesh mentioned in the comments, you can add a third argument to your event listener but instead of passing a boolean, you need to use the capture option to fire events from top to bottom instead of bottom to top as you wanted like this:
document.addEventListener("pointerup", this.onThumbUp, { capture: true });
You can now add a stopPropagation() at the mouseUp event or at any other event depending on what you want.

JQuery detect click outside element and stop all other click events

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/

Mousewheel handler for child element and his parent

I made a jQuery plugin that replaces default scrollbar with my own and handles mousewheeling and dragging the bar events.
When I put the content with my scrollbar into another content with my scrollbar, and then if I use mousewheel on the child content, the parent content wheels as well.
It happens because I bound mousewheel event listener to both child and parent contents, and when my mouse over them both, it triggers both event handlers.
The problem is that I need to wheel only the child content without affecting the parent.
Do you have any tips how to resolve that? Dragging scrollbar event works ok.
You need to stop the propagation of the event. This will stop the event from bubbling up the DOM tree and triggering on parent elements.
http://jsbin.com/yelijelowa/1/edit?js,output
$('body').on('mousewheel', function (e) {
alert('Body scroll');
});
$('.child').on('mousewheel', function (e) {
e.stopPropagation();
alert('Child scroll only');
});
If you comment out the e.stopPropagation(); line you'll note that both alerts fire.
Documentation:
MDN
jQuery API

Draggable cancel triggering blur event

I have a draggable div which of course could be dragged around.
Inside, I have input text which has two events (focus and blur).
Also inside, I have another div which serve as draggable cancellation div (dragging that div prevent any drag to occur).
you could see it here:
http://jsfiddle.net/wUDzh/4/
Clicking the input box would trigger focus event and clicking outside main div (the red one) would trigger blur event. This is fine.
Clicking or dragging the main div would NOT trigger blur event. That's what I want.
The problem is, clicking the cancellation div (the blue one) cause blur event of the input box to occur. Which is make sense, but that's not what I want.
is there any way to prevent blur event to occur just like the behavior of the main div?
thx
To prevent the input field from blurring when you click the cancellation div, simply do:
$('#cancel').mousedown(function(e) {
e.preventDefault();
});
See fiddle
You could do
$("#text").blur(function(e)
{
// alert('a');
if(e.target.id === 'text'){
//this prevents other blur handlers from firing
e.stopImmediatePropagation();
//this prevent the default action and returns
return false;
}
$(this).val("I've got blur");
});
fiddle here http://jsfiddle.net/wUDzh/6/

retain links functionality inside a div that has an onclick event

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.

Categories

Resources