As said in the title, I am trying to customize the contextmenu event. The situation is this: I want to catch the event, preventing it from firing on some elements (I'm ok here, all good), then I want to call it targeting another element (this is not working). At first I just tried dispatching it by creating a custom event and using myTargetElement.dispatchEvent(), the custom element does fire, but context menu won't open.
The reason I need this is that I want to open the contenteditable context menu when the user clicks anywhere. I've tried something similar to the last example on this MDN page, and I logged the event type, it is firing. Here's some example code of what I'm doing.
HTML
<div id="prevent">This div will prevent default event behaviour.</div>
<div id="my-target" contenteditable>Fire here the event and open context menu</div>
For instance, I cannot put one div inside the other.
JS
function showMenu(){
const preventer = document.getElementById('prevent');
const myTarget = document.getElementById('my-target');
const myEvent = new Event('contextmenu', {
bubbles:false //I had to use this, as setting it true was logging an error on Firefox
});
myTarget.dispatchEvent(myEvent);
console.log(myEvent.type); //it does log the event name
}
The listener that prevents default is not important, as when I just run the showMenu() (even when removing every other bit of js) on console it still has not the intended effect. I'm also able to listen to the 'contextmenu' event when I add a listener and run showMenu().
I'm beginning to think that there is not a direct solution to this, but workarounds and ideas would be really appreciated.
Related
I have a problem in a page with an event not being fired under certain circumstances. It's not my code and I'm trying to debug minified and obscured JavaScript code and I'm going crazy.
When replying to a comment in our website, if the text of the reply is empty this event (an analytics event to register that the user clicked the reply button) is fired. If there's text, the event isn't fired.
The element is a <span> tag, but the event listener is actually attached to the <body> tag. My current theory is that some code that's being executed when there's actually text in the reply is removing that event listener and therefore it's not being fired.
So, is there a way to monitor the event listeners in <body> to know when (and where in the code) is this event listener being removed?
Update
We found the problem. The third-party library we use for the comments is removing the parent element of the button and that's making impossible for the event to propagate to body.
If you can run script code before the event handler is removed, you can replace removeEventListener on document.body with your own function:
const original = document.body.removeEventListener;
document.body.removeEventListener = function(...args) {
console.log("Removed");
return original.apply(this, args);
};
function handler() {
console.log("Clicked");
}
document.body.addEventListener("click", handler);
document.body.removeEventListener("click", handler);
(To cover your bases you might also convert onclick (or whatever is being used) to an accessor so you can capture it if code does document.body.onclick = null;)
There are other ways a handler can be added and removed, but hopefully whatever library you're using uses the standard.
Side note: To confirm your theory about the handler being removed, you can inspect the handlers on an element in Chrome's devtools: Right-click on the page and choose "Inspect element", navigate up to body, and look on the Event Listeners tab.
I think you can use also the google developers tools that you can open with a button on the top right corner on the window. In the source section you can see al the event listener
what is the best way to catch and handle a click event on "anything except specific DOM-node(s)" in a React app?
The handler of the click event is the easy part: this can be any method.
The registration of the event, and the trigger to invoke the handler, is the hard part.
There is no clean way to capture a "clicks outside ...." event.
There are however various (HTML/ CSS/ Javascript) tricks you could apply:
If it is a modal page/ popup, you could also render a full page background rectangle (e.g. slightly transparent grey), which is in front of the whole page, but behind the popup. Add a click-event-listener to this background to remove the modal + the grey background.
Another method is to use the focusout javascript event on your top-react component:
the top HTML component rendered by react should be able to get focus (needs to be an <a> or similar HTML, or - somewhat less clean - needs a tabindex=... to work)
give the element focus as soon as it is rendered (inside componentDidMount()`)
add a focusout event listener, which triggers the handler to do something with the click outside.
The focusout event is fired as soon as the component no longer has focus:
- if a child of the component gets focus (e.g. you click something inside the component) focusout is also fired: usually no problem for menu's, but undesired for popups with forms
- the focusout is also fired if the user presses TAB.
There's no React-specific way to do this; all React event handlers are tied to the component they're set on. The best way to accomplish this depends on the details of what you need to get done, but a fairly straightforward way to address this would be to add a delegated click handler to the body element, or the closest ancestor element that includes the area you want to capture clicks from. You'd attach this event handler either on the component's componentDidMount() or whenever it becomes relevant, for example, after toggling the component's state so that it shows a dropdown menu.
Attach the event handler however you normally would – element.addEventListener or jQuery's $().on or what-have-you - and evaluate the event target when it fires to determine whether you need to execute your custom logic.
Simple example, without jQuery:
componentDidMount() {
document.body.addEventListener('click', (e) => {
if (e.target !== [your dom node]) {
// do something
}
}
}
Attaching a single event handler on the body element shouldn't pose any performance issues, but best practice for most cases where you'd use something like this would be to remove the event handler when it's no longer needed.
Some code that looks like the following is firing the click event via the Enter key, but is not responding to the mouse click.
//a is an anchor element
a.addEventListener('click', function (e)
{
//Do Stuff...
});
This page demonstrates the problem. The relevant bit of code is at line 176. This is in the middle of development and currently only (sort of) works in Chrome.
Also, I just verified that it works if I use mousedown, so it's not just the case of an invisible element sitting in front of the anchor.
Any ideas?
Edit: Now that you've shown us the actual code you're using, the problem is related to the fact that the autoSuggest() function has it's own click handler and in that click handler, it is clearing the container which removes all <a> elements in the container so your link object gets destroyed (probably before your click event gets to process). So, you can get events that happen before the click (like mousedown), but after a click, the element is removed from the DOM.
If you tell us what you're trying to actually do when an auto-suggest item is clicked that is different than the default behavior of the autoSuggest() function and you point to any documentation for that function, then perhaps we could offer a better way to solve your issue.
The link may be firing and taking you off to a new page (or reloading the current page), thus preventing you from seeing the click code run. Usually when you process a click event on a link element, you need to prevent the default behavior:
//a is an anchor element
a.addEventListener('click', function (e) {
e.preventDefault();
//Do Stuff...
});
Another possibility is that you are trying to install the event handler too soon either before the DOM has been loaded or before this particular link has been created and thus no actual click event handler is attached to the DOM object. You can verify whether the event handler is even getting called by temporarily putting an alert("Click handler called"); in the event handler and see if that pops up or not.
I have a div on a page that shows some info about a particular category (Image, Name etc).
When I click on the edit image it puts the category into edit mode which allows me to update the name. As you can see from the image below it shows that "Soup" is currently in edit mode, the others are in normal view mode. This all works as expected with the cancel / save buttons doing everything right. (I tried adding an image but wouldn't let me, need more love)
However once in edit mode if I click anywhere else on the page (Outside of the div) the expected result would be that the soup category would go back to view mode. Upon an event firing of some sort, this should also allow me to ask if they wanted to save changes.
So what I then decided to do is create an blur event on the "soups" parent div. This works as expected if I click anywhere on the page, however if I click on the inner element of the div it also causes the parents blur event to be fired, thus causing the category to go back to view mode.
So, is there a way to prevent the parent div from firing the blur event if any one of its children receive focus?
<div tabindex="-1" onblur="alert('outer')">
<input type="text" value="Soup" />
</div>
I just wrote the code without a compiler so not sure if that even works but with that hopefully you get the idea.
I'm using Knockout.js to update the GUI on the fly but that shouldn't effect this answer I wouldn't have thought.
I faced the same issue. This what worked for me.
handleBlur(event) {
// if the blur was because of outside focus
// currentTarget is the parent element, relatedTarget is the clicked element
if (!event.currentTarget.contains(event.relatedTarget)) {
.....
}
}
Enjoy :)
I've had to tackle this problem before. I am not sure if it is the best solution, but it is what I ended up using.
Since the click event fires after the blur, there is no (cross-browser, reliable) way to tell what element is gaining focus.
Mousedown, however, fires before blur. This means that you can set some flag in the mousedown of your children elements, and interrogate that flag in the blur of your parent.
Working example: http://jsfiddle.net/L5Cts/
Note that you will also have to handle keydown (and check for tab/shift-tab) if you want to also catch blurs caused by the keyboard.
I don't think there is any guarantee mousedown will happen before the focus events in all browsers, so a better way to handle this might be to use evt.relatedTarget. For the focusin event, the eventTarget property is a reference to the element that is currently losing focus. You can check if that element is a descendant of the parent, and if its not, you know focus is entering the parent from the outside. For the focusout event, relatedTarget is a reference to the element that is currently receiving focus. Use the same logic to determine if focus is fully leaving the parent:
const parent = document.getElementById('parent');
parent.addEventListener('focusin', e => {
const enteringParent = !parent.contains(e.relatedTarget);
if (enteringParent) {
// do things in response to focus on any child of the parent or the parent itself
}
});
parent.addEventListener('focusout', e => {
const leavingParent = !parent.contains(e.relatedTarget);
if (leavingParent) {
// do things in response to fully leaving the parent element and all of its children
}
});
I have designed a website with a menu that is initially invisible. When the user clicks on a button, the menu becomes visible. There are two ways for the user to hide the now visible menu:
Click the button that caused the menu to become visible
Click anywhere on the web page that isn't the menu
The way I have coded the second option is to tie an onclick event to the window element, and have it compare where the user clicked to the menu's position to determine if the menu should be hidden. This works great in Firefox and Safari, but it fails in Mobile Safari.
I noticed that the window onclick event only fires when I click on another element with an onclick event already assigned. If I click on an element with no event(s) assigned, the window's onclick event never fires. If I click on the button which displays the menu, it fires along with the event tied to the button.
Is it possible to assign events to the window element in Mobile Safari?
I'v been encountering this same problem. Here is what worked for me. (Note: I am working within a Modernizr and jQuery context)
First, I add a custom Modernizr class using Modernizr's addTest Plugin API to test for iOS, which will add the class appleios or no-appleios accordingly.
Because in my research the body seems to fire events on it's own agenda, I am taking a little precaution by wrapping all the document's content with an element in an iOS context. Then I add an event handler to this element.
$(".appleios body").wrapInner('<div id="appleios-helper" />');
$("#appleios-helper").bind("mouseup", function(){return;});
What was suggested earlier in this thread is using void(0). I did some quick testing, and found that void(0) as the event just wasn't causing touches on the body to be recognized. When I plugged in my own "empty" function in the form of function(){return;} things started working.
This all hinges on the fact that no events are fired in Mobile Safari unless the element explicitly has events to fire (Safari Web Content Guide.) By inserting this empty event on the wrapper, things will bubble up to the body.
If you're doing strait JavaScript with none of these libraries, the same effect could be achieved in the HTML markup
<html>
...
<body>
<div id="appleios-helper" onmouseup="function(){return;}">
...
</div>
</body>
</html>
This worked for me to hide tooltips when touching anywhere on the document's body. Your mileage may vary.
Simply adding the dummy onclick handler to the html body works for me:
<body onclick="void(0)">
Note that I am using usual live event handlers as shown below:
function liveHandler( event ) {
var target = event.target; ...}
window.addEventListener(evtype, liveHandler, true);
// evtype such as 'mousedown' or 'click'
// we use the capturing mode here (third parameter true)
This is an old question, but I struggled with the same thing today.
I found that using touchstart event works.
I solved it like this:
var isTouchDevice = 'ontouchstart' in document.documentElement;
if (isTouchDevice) {
// Do touch related stuff
$(document).on('touchstart', function (event) {
// Do stuff
});
} else {
// Do non-touch related stuff
$(document).on('click', function () {
// Do stuff
});
}
You could just add onclick="void(0);" to some <div> that covers the whole page so that no matter what, you are always clicking on an element that has an onclick event. Not a great solution, though.
I'd prefer not having the onclick event be tied to the window. Why don't you create a container <div> that has that event on it. Then handle it just like you currently are.
You can also:
$('body').css('cursor', 'pointer');
No idea what those "engineers" at Apple are doing. LOL.
This has problems though. You wouldn't want to do this on every touch device. Only touch devices that don't also have a pointing device (Laptops with Touch Screens, for example).
Source: http://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
The conclusion of the article is this:
So I don’t understand why all this is the case, but it most certainly is the case. If you’re having bubbling problems, just add an empty-function event handler anywhere between the body and the element, and you’re set to go. But it shouldn’t be necessary.