I am coding a JavaScript-application and have the problem, that on firefox about 5-10% of mouse clicks on a custom button are ignored.
The prototype for setting the click handler ist
function Button(parent, styleClassName) {
Element.call(this, parent, "div");
this.setStyleClass(styleClassName);
}
Button.prototype = Object.create(Element.prototype);
Button.prototype.constructor = Button;
Button.prototype.setOnClick = function(handler) {
this.domNode.handler = handler;
// sporadically skipped
this.domNode.onclick = function(event) {
console.log("ocl: " + event.currentTarget.id);
this.handler(event);
if(event.preventDefault) event.preventDefault();
event.stopPropagation();
}
this.domNode.ontouchstart = function(event) {
console.log("ots: " + event.currentTarget.id);
this.handler(event);
if(event.preventDefault) event.preventDefault();
event.stopPropagation();
}
}
An example for an affected html element (the event handler is assigned correctly to the outer div):
<div style="position: absolute; overflow: hidden; visibility: visible; left: 291px; top: 4px; width: 34px; height: 34px;" id="LandscapeScreen0_pMain_dlg_Element0_btnCloseDialog">
<svg style="position: absolute; overflow: hidden; visibility: visible; width: 32px; height: 32px; cursor: pointer;" id="LandscapeScreen0_pMain_dlg_Element0_btnCloseDialog_RefSVG0">
<use href="#svg08"/>
</svg>
</div>
Some additional information:
These handlers, once set, are never modified or removed
Not even a log message is written to the console
Using addEventListener() does not solve the problem
The problem only exists in Firefox (tested on v25.0)
most important: even the window does not retrieve the events!
regarding the last point: i added these lines for debugging:
window.onclick=function(e){console.log("onclick0");};
window.addEventListener("click",function(e){console.log("onclick1");}, true);
window.addEventListener("click",function(e){console.log("onclick2");}, false);
Whenever the button does not retrieve the mouse event, the window does not even receive one of these events either. So somehow, the events must have been dropped globally!?
So what I need is a hint what could be a possible cause for such a behaviour... it really seems to be non-deterministic when an event is skipped and when not.
Thanks for your help!
Related
I currently have a popped-up context menu. This popup gets closed when you click outside of it.
This is done with a div behind the popup that takes up the whole window, and listens for the mousedown event.
I need this mouse event to be sent to elements on the page behind the layer, so I call trigger() on those.
function onLayerMouseDown(e) {
layer.hide();
$("#menu").hide();
target = document.elementFromPoint(e.pageX - $(window).scrollLeft(), e.pageY - $(window).scrollTop());
if (target != null)
$(target).trigger(e);
This works well for the mousedown and mouseup events, but the click event which would come when the mouse presses and releases without leaving the element is never sent.
How can I make it so the click event gets sent too?
See this fiddle for an example with the layer. Click the text behind while the menu is open and see the console.
The problem is easy to understand, but the solution will depend on your requirements. Once the mousedown event is sent, the first thing that the script does is to hide the layer. This means that it will no longer detect mouse events. This means that no click event is ever emitted, neither for the layer (the click event would need a mouseup inside the same element) nor for the inside (the mousedown event received is a copy and comes from layer). It is important to notice that the click event is managed by the browser. You cannot trigger a click by just sending a mousedown and then a mouseup. That also explains why inside receives a mouseup event, as this event only happens after layer has disappeared.
Regarding how to solve it, you might:
Set the layermousedown function to handle the click event. With this design, it would mean that mousedown and mouseup are not captured.
Change the design. The idea would be to set the pointer-events of layer to none:
$("#inside").on("mousedown", (e) => {
console.log("inside mousedown (triggered manually from the layer)");
});
$("#inside").on("click", (e) => {
console.log("inside click (should happen right before mousup, if the mouse stayed inside the element");
});
$("#inside").on("mouseup", (e) => {
console.log("inside mouseup (triggered automatically when releasing mouse button)");
});
$("#button").click(() => {
createlayer();
return false;
});
$(document).on('click', layermousedown);
$("#menu").click(() =>{return false});
function layermousedown(e) {
$("#layer").hide();
$("#menu").hide();
$("#button").prop('disabled', false);
}
function createlayer() {
$("#button").prop('disabled', true);
$("#layer").show();
$("#menu").show();
console.log("added the invisible layer");
console.log("_______________________");
}
createlayer();
#layer {
height: 100vh;
width: 100vw;
display: block;
position: fixed;
z-index: 1;
top: 0;
left: 0;
opacity: 0;
filter: alpha(opacity=0);
background-color: #000;
pointer-events: none;
}
#menu {
display: block;
position: fixed;
z-index: 2;
top: 20px;
left: 50px;
background-color: #ff5;
width: 50px;
height: 100px;
border: 1px solid black;
pointer-events: auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="outer">
<span id="inside">click me please</span>
</div>
<div id="layer"></div>
<button id="button">
Add the layer again
</button>
<div id="menu">
<span>this is part of the menu</span>
</div>
The idea is to react to document events and intercept the click events in the elements that should not be responsive. That is why some callbacks return false.
While moving my application from mouse to touch events I noticed some strange behaviour. Basically, touchmove stops working after DOM changes. Mouse events work fine in the same situation. I tested it with chrome developer tools as well as firefox's. They seem to agree on results. Is it a bug or am I missing something?
I created very simple code example to demonstrate that the problem is not connected to any frameworks or libs I use. I also found seemingly related question which unfortunately contains no solution.
Touch demo:
window.addEventListener("touchmove", onTouchMove, {passive: false})
document.addEventListener('DOMContentLoaded', function(){
var elem = document.getElementById("nice");
console.log(elem)
elem.addEventListener("touchstart", onTouchStart)
})
function onTouchMove(event) {
console.log("touch move")
}
function onTouchStart(event) {
console.log("touch start")
var elem = document.getElementById("nice")
elem.remove()
}
<!DOCTYPE html>
<html>
<body style="width: 100%; height: 100%; background-color: yellow">
<div style="position: absolute; width: 100px; height: 100px; background-color: red; left: 100px; top: 100px" id="nice"></div>
</body>
</html>
Mouse demo:
window.addEventListener("mousemove", onMouseMove, {passive: false})
document.addEventListener('DOMContentLoaded', function(){
var elem = document.getElementById("nice");
console.log(elem)
elem.addEventListener("mousedown", onMouseDown)
})
function onMouseMove(event) {
console.log("mouse move")
}
function onMouseDown(event) {
console.log("mouse start")
var elem = document.getElementById("nice")
elem.remove()
}
<!DOCTYPE html>
<html>
<body style="width: 100%; height: 100%; background-color: yellow">
<div style="position: absolute; width: 100px; height: 100px; background-color: red; left: 100px; top: 100px" id="nice"></div>
</body>
</html>
One continuous drag gesture starting from red square should cause 1) 'start' message in the log, 2) disappiaring of that square, which is the DOM change in this case 3) sequence of 'move' messages in the log. It is so in mouse demo, but in touch demo there are no 'move' events after square disappears.
This is an intended behaviour if your element is deleted.
According to the docs, if you delete an element, the events will still be targeted at it, and hence won't necessarily bubble up to the window or document anymore.
So there are two solutions if you want to delete the element. You can modify the "remove" method so that it would only hide the element until the touch process ends, or you can attach events to the target itself.
Here is an example, you can see the window touchmove events do not appear, while the element touchmove events appear even after the element's removal.
window.addEventListener("touchmove", onTouchMoveWindow, {passive: false})
document.addEventListener('DOMContentLoaded', function(){
var elem = document.getElementById("nice");
console.log(elem)
elem.addEventListener("touchstart", onTouchStart)
elem.addEventListener("touchmove", onTouchMoveElement)
})
function onTouchMoveWindow(event) {
console.log("touch move window")
}
function onTouchMoveElement(event) {
console.log("touch move element")
}
function onTouchStart(event) {
console.log("touch start")
var elem = document.getElementById("nice")
elem.remove()
}
<!DOCTYPE html>
<html>
<body style="width: 100%; height: 100%; background-color: yellow">
<div style="position: absolute; width: 100px; height: 100px; background-color: red; left: 100px; top: 100px" id="nice"></div>
</body>
</html>
Related questions:
Touchmove event stops triggering after any element is removed from dom
Touch Move event don't fire after Touch Start target is removed
touchmove events stop after replacing innerHTML
I can't get the pointerup event to fire on a link (an A tag with a href attribtue set) for event.pointerType == 'mouse' if the mouse was moved between pointerdown and pointerup.
I have the following scenario:
var lastEvent = false;
var handler = function (e) {
if (lastEvent != e.type) {
e.target.innerHTML += e.type + ' with ' + e.pointerType + '<br/>';
e.target.scrollTo(0, e.target.scrollHeight);
}
lastEvent = e.type;
}
document.querySelector('a').addEventListener('pointerdown', handler);
document.querySelector('a').addEventListener('pointermove', handler);
document.querySelector('a').addEventListener('pointerup', handler);
div {
height: 100vh;
display: flex;
}
a {
height: 60vh;
width: 75vw;
margin: auto;
background-color: red;
display: inline-block;
user-select: none;
touch-action: none;
overflow-y: scroll;
}
<div>
</div>
If I press a mouse button, keeps it pressed for a while and then releases it, I get this sequence:
pointerdown with mouse
pointerup with mouse
However if I press my mouse button, keeps it pressed and moves the mouse pointer around, and then releases it, I get this sequence:
pointerdown with mouse
pointermove with mouse
Problem: pointerup never fires.
I guess it is the built-in click-or-drag features in the browser (I have tested in Chrome) which blocks the pointerup event from firing, because If do this on a span or removes the href from the anchor link it works with this sequence:
pointerdown with mouse
pointermove with mouse
pointerup with mouse
Also, it works flawlessly for touch-driven PointerEvents:
pointerdown with touch
pointermove with touch
pointerup with touch
I guess there is a clever css property to set on the element to disable this pointerup prevention. Something like a { pointer-actions: click; }, but I haven't been able to find what.
D'oh! I stated the answer in the question, basically. It was the drag action which prevented the pointerup event from firing. Simply adding the draggable="false" attribute on the link element fixed this issue.
Proof of concept:
var lastEvent = false;
var handler = function (e) {
if (lastEvent != e.type) {
e.target.innerHTML += e.type + ' with ' + e.pointerType + '<br/>';
e.target.scrollTo(0, e.target.scrollHeight);
}
lastEvent = e.type;
}
document.querySelector('a').addEventListener('pointerdown', handler);
document.querySelector('a').addEventListener('pointermove', handler);
document.querySelector('a').addEventListener('pointerup', handler);
div {
height: 100vh;
display: flex;
}
a {
height: 60vh;
width: 75vw;
margin: auto;
background-color: red;
display: inline-block;
user-select: none;
touch-action: none;
overflow-y: scroll;
}
<div>
</div>
The only downside is that with draggable set to false, all mousedown-mousemove-mouseup will trigger a click event. But this can be prevented by checking if the difference for clientX/clientY between pointerdown and pointerup event is greater than a certain amount of pixels, store that in a variable and add an click event handler which runs e.preventDefault(); e.stopPropagation(); in this case.
I am loading a JQmodal with an ajax call with some basic input elements like a, input, label and button. I need to add a custom class for the elements on focus after immediately opening the modal
Note: Please use tab key to focus each elements
HTML
<p>HTML Images is a link to a page on this website.</p>
<p>W3C is a link to a website on the World Wide Web.</p>
view
...
<div class="jqmWindow" id="dialog">
</div>
CSS:
.jqmWindow {
display: none;
position: fixed;
top: 17%;
left: 50%;
margin-left: -300px;
width: 600px;
background-color: #EEE;
color: #333;
border: 1px solid black;
padding: 12px;
}
.jqmOverlay { background-color: #000; }
/* Fixed posistioning emulation for IE6
Star selector used to hide definition from browsers other than IE6
For valid CSS, use a conditional include instead */
* html .jqmWindow {
position: absolute;
top: expression((document.documentElement.scrollTop || document.body.scrollTop) + Math.round(17 * (document.documentElement.offsetHeight || document.body.clientHeight) / 100) + 'px');
}
*.focused
{
outline-width: 2px ;
outline-color: #282828;
outline-style: dotted;
}
Java Script
$(document).ready(function() {
$('#dialog').jqm({ajax: 'https://raw.githubusercontent.com/jothikannan89/jqModal/ed840123588cf99ebe061e749e9774e64387ba7f/examples/ajax_tab.html'});
});
$("a,input,button,select,textarea,.jqmClose").on('focus',
function(event) {
event.preventDefault();
$(this).addClass('focused');
});
$("a,input,button,select,textarea,.jqmClose").on('blur',
function(event) {
event.preventDefault() ;
$(this).removeClass('focused');
});
What I am getting is weird, focus class is adding for the parent page element but doesn't add to the elements loaded through ajax to the Modal but default focus is working
Fiddle example: Fiddle
When you do:
$("a,input,button,select,textarea,.jqmClose").on('focus',
function(event) {
event.preventDefault();
$(this).addClass('focused');
});
your dynamic content is not loaded in the DOM yet (that's why you have the expected behavior on the main page, but not in the modal content). You must wait for the return of your ajax request to attach event handlers.
I don't know how JQM works, but it must give you a promise or a way to pass some callbacks.
EDIT:
From the JQM documentation, there is an onload callback:
onLoad (callback) Called right after ajax content is loaded.
// onLoad : assign Mike Alsup's most excellent ajaxForm plugin to the returned form element(s).
var myLoad = function(hash){ $('form',hash.w).ajaxForm(); };
$('#dialog').jqm({onLoad:myLoad});
Use it to attach your handlers in the onLoad function and it will do the trick.
I have a element (it is an icon) with onClick event. I want to show that element only if certain criteria is met and I'm able to read that criteria and handle the show/hide completely from CSS.
If the element is visible and I click on it, I want it to make two actions:
trigger JS event,
hide the element.
But CSS hides the element somehow quicker than JS can respond and the event is simply not triggered.
I did not studied how the the event system in JS works, but it seems to me, that firstly the CSS is resolved and then the JS event system receives info about click event on certain x/y position, where the element is no more, so the event is not triggered.
I tried several CSS options to hide the element including this CSS properties:
display: block / none;
visibility: visible / hidden;
z-index: 1 / -1;
width|height: auto / 0;
top|left|right|bottom: 0 / -9999px;
If I hide the element with opacity: 1 / 0, the event is triggered, because the element remains clickable in place, where it resides, but that is also problem, because I do not want the element to be clickable if not visible.
Is there any hack, how to hide element via pure CSS and at the same time trigger event on it?
I tried delay the CSS with transition: all 160ms;, but the CSS rules, that hides the element, are instant (you cannot transition display, visibility or z-index) so this had no use.
I challenge this problem in Google Chrome 53.0.2785.116 platform Win10 x64
EDIT : JSBin
Problem with click is when the item is hidden, the click action can not be completed. So you can switch to mousedown instead of click
document.getElementById("test_click").addEventListener("mousedown", function(){
console.log("Clicked");
});
#test_container {
position: relative;
display: inline-block;
}
#test_click {
position: absolute;
display: none;
left: 100%;
top: 50%;
}
#test_input:focus + #test_click {
display: inline;
}
#test_input:focus + #test_click:hover {
color: dodgerblue;
cursor: pointer;
}
<div id="test_container">
<input type="text" id="test_input" placeholder="focus me...">
<span id="test_click">CLICK</span>
</div>
<h3>Click on the input and "CLICK" appears, click on "CLICK" and "CLICK" hides and no JS event is triggered although it is attached. Then, remove "display: none;" from CSS and try click again, the JS event is triggered.</h3>
or add css to keep element visible when hovered
document.getElementById("test_click").addEventListener("click", function(){
console.log("Clicked");
});
#test_container {
position: relative;
display: inline-block;
}
#test_click {
position: absolute;
display: none;
left: 100%;
top: 50%;
}
#test_click:hover,
#test_input:focus + #test_click {
display: inline;
}
#test_input:focus + #test_click:hover {
color: dodgerblue;
cursor: pointer;
}
<div id="test_container">
<input type="text" id="test_input" placeholder="focus me...">
<span id="test_click">CLICK</span>
</div>
<h3>Click on the input and "CLICK" appears, click on "CLICK" and "CLICK" hides and no JS event is triggered although it is attached. Then, remove "display: none;" from CSS and try click again, the JS event is triggered.</h3>