I'd like to have an event fire whenever a user does a mouse click or a touchscreen tap. I've tried using the "click" event, but on touchscreens it waits until the tap has finished and is more of a tap-and-release than a tap event. It does not feel correct.
Is there an event which does either tap (no release) or click listening?
P.S. This question (JavaScript custom tap event) is about jquery.
No, there is no single event which represents the default "selection" action of both mouse and touch screen. You can, though, use a mix of events.
The two most common events users expect to create a reaction are "click" and "touchstart", but you can't simply listen to both of those because the click event is generally triggered after the touchstart event (leading to a double trigger).
The following is some simple code for avoiding this issue:
function onTapOrClick(element, cb) {
let debounce; // temporarily disables on click events when touchstarts happen
element.addEventListener("touchstart", (event) => {
if (debounce) { clearTimeout(debounce); }
debounce = setTimeout(() => debounce = undefined, 1000); // debounce is 1000ms, could easily be longer
cb(event);
});
element.addEventListener("click", (event) => {
if (debounce) { return; }
cb(event);
});
}
Related
I am trying to simulate a mouse wheel event on an input type number on a react web application, using the Cypress Testing Framework. I am using trigger as per their docs (https://docs.cypress.io/api/commands/trigger.html) but it seems like there is very little information online as to how to actually create the synthetic event and what arguments it requires. This is what I have so far:
cy.get("#cost-amount-input")
.click()
.trigger("mouseover")
//.trigger("mousewheel", {deltaY: 99})
.trigger("wheel", { deltaY: -66.666666, wheelDelta: 120, wheelDeltaX: 0, wheelDeltaY: 120, bubbles: true})
.blur()
.then(() => {
/* test if the mouse wheel event chagned the value as expected */
});
Adding an event listener to the document for all wheel events shows that my event is firing, but it is not causing the value to change as happens when I do an actual mouse wheel event. Here are the two different events as captured by my event listener.
Cypress synthetic event (does not work)
Actual mouse wheel event (does work - changes the value):
Any info is appreciated.
Following the example here MDN - Creating and triggering events, I built a minimal example to reproduce the problem.
<body>
<input type="number" />
<script>
const event = new Event('wheel');
const elem = document.querySelector('input');
elem.addEventListener('wheel', function (e) {
elem.value = +elem.value + 1; // treat input value as a number
}, false);
elem.dispatchEvent(event); // dispatch an event to show it's working
</script>
</body>
This is the test (passing).
it('Cypress "trigger" fires synthetic event', () => {
cy.visit('../app/synthetic-event.html');
cy.get('input').invoke('val').should('eq', '1'); // input has '1' since event triggered on load
cy.get('input').trigger("wheel"); // trigger the synthetic event
cy.get('input').invoke('val').should('eq', '2'); // confirm value has changed
})
The only practical difference I can see is your "Actual mouse wheel event" log shows negative wheelDelta and positive deltaY, whereas your test is showing the opposite polarity.
Perhaps the input does not allow change in the opposite direction (i.e negative input)?
Please share the code for event constructor and event listener.
I have a mobile web app, which uses a lot of click event handlers on buttons, etc. All of this works fine if the user really "clicks" (i.e. "touchdown-touchup") the button. However if the user does a short swipe, then the click event does not fire. This causes a lot of complaints from my users that the app doesn't register clicks/taps and that other apps work correctly.
Of course, I can get coordinates of the touch in ontouchstart and ontouchend and compute the distance - but I need to also know whether that distance is under the maximum that the browser would treat as 'click'. I do not want to switch to using touchstart/touchend events instead of click.
I used to use fastclick.js library for handling clicks/taps in the past, but now use native 'click' events with touch-action: manipulation. Is there any way of specify/controlling the maximum movement of the finger on the button that still registers as a 'click'?
Update based on comments. The application is very large and there are hundreds if not thousands of event handler assignments throughout it (the app has been developed over the last 8 years). Changing all of these is not practical, therefore I'm looking for a solution that would allow me to either set the threshold once globally or solve the problem with a global-like touchstart/touchend handlers.
I thought this was an interesting problem so I took a shot at solving it for you. In a way it's somewhat similar to the problem of preventing a click event when a dblclick happens.
Using a distance threshold for a "short swipe" seems, to me at least, problematic in that the threshold distance might be system dependent. Instead of that I decided to trigger on if the "click" event actually happens. I used mousedown as a simulated touchstart and mouseup as a simulated touchend. mouseup always happens before click so it is similar to touchend in that respect.
Normally if you "click" (mousedown) on an element and then move your mouse pointer off the element, the click event does not happen. This is much like the situation you describe as being a "short swipe". After a certain distance the click event just doesn't happen. The code below will send a click event for the button even if you mousedown on it, move the pointer off it and then mouseup. I believe that this would solve the problem if you used it for touchstart and touchend instead
// The pre-exisiting click handler
function handleClick(ev) {
console.log('button clicked. do work.');
}
document.getElementById('theButton').addEventListener('click', handleClick);
// our global "touch" handlers
var touchHandler = {
curPending: null,
curElem: null,
handleTouch: function handleTouch(ev) {
switch (ev.type) {
case 'mousedown':
// capture the target that the click is being initiated on
touchHandler.curElem = ev.target;
// add an extra click handler so we know if the click event happens
ev.target.addEventListener('click', touchHandler.specialClick);
break;
case 'mouseup':
// start a pending click timer in case the click event doesn't happen
touchHandler.curPending = setTimeout(touchHandler.pendingClick, 1);
break;
}
},
specialClick: function(ev) {
// the click event happened
// clear our extra handler
touchHandler.curElem.removeEventListener('click', touchHandler.specialClick);
// make sure we don't send an extra click event
clearTimeout(touchHandler.curPending);
},
pendingClick: function() {
// we never heard the click event
// clear our extra handler
touchHandler.curElem.removeEventListener('click', touchHandler.specialClick);
// trigger a click event on the element that started it all
touchHandler.curElem.click();
}
};
// using "mousedown" as "touchstart" and "mouseup" as "touchend"
document.addEventListener('mouseup', touchHandler.handleTouch);
document.addEventListener('mousedown', touchHandler.handleTouch);
<p>I work when clicked normally but I also work when
mousedown, drag pointer off me, mouseup</p>
<button id="theButton">Click Me</button>
I'm using hammer.js and jquery.hammer.js in order to handle multiple different types of events (mostly tap events).
I made a wrapper function to be used for any/all tap event listener declarations.
This is the function.
var OnClick = function(button, CallbackFunction, TurnBackOnAfterStartCallback)
{
if(TurnBackOnAfterStartCallback != false)
{
TurnBackOnAfterStartCallback = true;
}
if(!button)
{
LogResult("Error: Attempted to create Hammer Click Event Listener without assigning a jQuery Object to listen too...");
return;
}
if(!CallbackFunction)
{
LogResult("Error: Attempted to create Hammer Click Event Listener without assigning a Callback Function...");
return;
}
$(button).hammer().on("tap", function(event)
{
var target = event.target;
// Disable the button so that we can't spam the event....
$(target).hammer().off("tap");
// We receive the event Object, incase we need it...
// Then we call our CallBackFunction...
if(CallbackFunction)
{
CallbackFunction(target);
}
// Renable the button for future use if need be.
if(TurnBackOnAfterStartCallback)
{
$(target).hammer().on("tap", CallbackFunction);
}
});
};
When I register an event using this function it works as expected. First it disables the event listener so you can't spam the event by clicking the button 100 times... Like so...
$(target).hammer().off("tap");
Then it preforms any callback functionality if there exists any...
if(CallbackFunction)
{
CallbackFunction(target);
}
Finally we re-enable the button for future use, unless we've specified that it will not be turned back on...
// Renable the button for future use if need be.
if(TurnBackOnAfterStartCallback)
{
$(target).hammer().on("tap", CallbackFunction);
}
This works perfectly during the first event launch... However, once I trigger the event again the Callback function is sent the event and not the event.target for some reason...
If I remove the .off and .on calls then it works as expected but can be spammed...
For a live example checkout this jsfiddle... It prints the result to the console... The first output is correct, everything after that isn't as expected.
https://jsfiddle.net/xupd7nL1/12/
Never mind, I had a dumb moment there...
The issue was that I was calling the event listener directly and not through my wrapper function, OnClick...
In other words change...
if(TurnBackOnAfterStartCallback)
{
$(target).hammer().on("tap", CallbackFunction);
}
to
if(TurnBackOnAfterStartCallback)
{
OnClick(target, CallbackFunction, TurnBackOnAfterStartCallback);
}
Is there any way to know, in a jQuery onmouseup handler, if the event is going to be followed by a click event for the same element?
I have an event handler for a menu hyperlink which unbinds itself when the user either clicks on an element or "drops" (as in drag-n-drop) on an element. I want to avoid prematurely unbinding the handler on mouseup if a click is coming next.
I realize I can track mousedown and mouseup events myself or otherwise hack up a solution (e.g. wait 50 msecs to see if a click comes soon), but I was hoping to avoid rolling my own implementation if there's something built-in for this purpose.
There is nothing built-in because it's really specific to your needs. Thus, there would kilometers of code and documentation to maintain if jQuery would handle any combination of clicks, long clicks, moves, etc.
It's also hard to give you a snippet that satisfies your needs, but a setTimeout is usually the first step to take, with something like that :
obj.mouseup = function (){
obj.click = action; // do action
setTimeout ( function() {
obj.click = functionOrigin // after 500 ms, disable the click interception
}, 500);
};
you can use $(selector).data('events') for that
$('div').mouseup(function(){
if($(this).data('events').click){
console.log('Has a click event handler')
}
});
I am doing iphone web development and I've noticed that: gesture events can also be touch events, which means a touch event handler also fires up when there is a gesture event.
Any way that I can add specific event handler to gesture (not touch)? Or how can I know if this is a pure touch event or a pure gesture event?
Edit: This was an answer for your original revision that asked if something is a "pure touch event". It won't help you with your changed question on getting pure gesture events.
Listen for gesture events and have a gesturing boolean that you check during touch events that is set to true by the event handler for the gesture events and set back to false by the event handler for the touch events if it is true.
I have not researched at all about these events, but here's a sample implementation:
var gesturing = false;
document.addEventListener(aTouchEventName, function () {
if (gesturing) {
return gesturing = false;
}
// your touch event handler code here
}, false);
document.addEventListener(aGestureEventName, function () {
gesturing = true;
}, false);
I am not sure the specifics on this but it looks like there are 3 events fired in a gesture.
-touchstart
-touchmove
-touchend
Ultimately your touch end will fire. What you can do is create a threshold amount between your star and end. And trigger a custom event if the (x,y) difference is past a certain amount.