Event capturing as in Event Capturing/Bubbling, not event-handlers.
I have a web-application where the user can create visualization elements within a div.
In one example the user created a div that’s clickable and has a hover-event, but is a sibling to, and displayed on top of, elements that have context-menu events on them.
The user still wants these context-menu events to work, as they open a context-menu that enables value-specific behaviour.
This causes trouble since I can’t seem to find a way to imitate the way the browser fires off events.
What I want to do is handle right-click events in the ‘editor’ and if the cursor position overlaps another of the children of the ‘editor’, recursively re-dispatch the event on all elements directly below the cursor.
Creating events and dispatching them manually only causes them to bubble up, not propagate down.
I also cannot disable pointer-events on the user’s div, as their events would stop working.
Even if I did in the instant the button is clicked, I still need to be able to re-run the browser’s event programmatically so that the elements below are also affected by it.
My application only knows of the editor itself and the elements outside; everything within is dynamically assembled by the user.
This means I cannot find the text-element by id and bubble the events upwards.
The following snippet shows an example of the current state.
Because of the elements in the sibling div being nested, simply dispatching an event on the sibling itself doesn’t work.
let editor = document.getElementById("editor");
editor.oncontextmenu = e => {
// Skip opening the browser context-menu.
e.preventDefault();
for (let i = 0; i < editor.children.length; i++) {
let currentChild = editor.children[i];
if (currentChild == e.target) {
// Avoid executing the event on the element that received it.
continue;
}
let currentRect = currentChild.getBoundingClientRect();
if (currentRect.left < e.offsetX &&
currentRect.top < e.offsetY &&
currentRect.right > e.offsetX &&
currentRect.bottom > e.offsetY) {
// Problem here is that the new event is only sent to the sibling, not further down to the texts.
// Since the text boxes have context menu event, not the sibling, this won't do anything.
// I would preferrably like to just re-execute the right-click while the browser calculates the destination.
// The current way requires every layer to have this custom logic in 'oncontextmenu',
// which is not possible in my scenario.
let ev = new MouseEvent('contextmenu', {
screenX: e.screenX,
screenY: e.screenY,
clientX: e.clientX,
clientY: e.clientY,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
altKey: e.altKey,
metaKey: e.metaKey,
button: e.button,
});
currentChild.dispatchEvent(ev);
}
}
}
let header = document.getElementById("header");
document.onmousemove = e => {
header.innerHTML = 'Header text<br/>x: ' + e.pageX + '<br/>y: ' + e.pageY;
}
#header {
padding: 25px;
border: 1px solid black;
}
#editor {
position: relative;
}
.sibling {
position: absolute;
background-color: red;
border: 5px solid blue;
opacity: 0.3;
width: 250px;
height: 250px;
top: 50px;
left: 75px;
}
.me {
position: absolute;
opacity: 0.3;
width: 300px;
height: 300px;
top: 25px;
left: 50px;
border: 5px solid black;
}
.me:hover {
border-color: red;
background-color: green;
}
.nephew {
height: calc(100% - 30px);
width: calc(100% - 30px);
margin: 9px;
padding: 5px;
border: 1px solid white;
}
.cousin {
color: yellow;
align-content: center;
text-align: center;
margin: auto 0;
height: 50%;
width: 100%;
border: 1px solid blue;
}
<header id="header">Header text<br/> x: ?<br/> y: ?</header>
<div id="editor">
<div class="sibling">
<div class="nephew">
<div class="cousin">
<div class="text" oncontextmenu="alert('context-menu on 'Value 1'')">Value 1</div>
</div>
<div class="cousin">
<div class="text" oncontextmenu="alert('context-menu on 'Value 2'')">Value 2</div>
</div>
</div>
</div>
<div class="me" onclick="alert('clicked 'me'')" oncontextmenu="alert('context-menu on 'me'')"></div>
</div>
Same snippet in JSFiddle:
https://jsfiddle.net/7gnqsm51/9/
The project does not currently use JQuery, so I would prefer solutions without it.