How to set up touch and click events - javascript

I have a bunch of buttons with onclick handlers, which I want to behave differently when touched. From reading up on the topic, I have come up with this code:
var reds = document.getElementsByClassName('red');
for (var idx in reds) {
var element = reds[idx];
element.addEventListener('touchstart', function () {
element.addEventListener('touchend', function (event) {
alert('stuff');
event.preventDefault(); // we don't want the 'click' handler
});
element.addEventListener('touchmove', function (event) {
element.removeEventListener('touchend');
element.removeEventListener('touchmove');
});
});
}
The theory behind it being that when the user touches the button, I want to wait for him to also let go, if he instead moves(scrolls) the page, I want to ignore that action completely since it obviously was not meant as a 'click'. However, neither the touchend nor the touchmove events get fired, only the touchstart event. So is this setup unviable? What alternative could I use?

Related

JavaScript click event does not work on mobile touchscreen [duplicate]

I'm working on a mobile web site that has to work on a variety of devices. The ones giving me a headache at the moment are BlackBerry.
We need to support both keyboard clicks as well as touch events.
Ideally I'd just use:
$thing.click(function(){...})
but the issue we're running into is that some of these blackberry devices have a very annoying delay from the time of the touch to it triggering a click.
The remedy is to instead use touchstart:
$thing.bind('touchstart', function(event){...})
But how do I go about binding both events, but only firing one? I still need the click event for keyboard devices, but of course, don't want the click event firing if I'm using a touch device.
A bonus question: Is there anyway to do this and additionally accommodate browsers that don't even have a touchstart event? In researching this, it looks like BlackBerry OS5 doesn't support touchstart so will also need to rely on click events for that browser.
ADDENDUM:
Perhaps a more comprehensive question is:
With jQuery, is it possible/recommended to handle both touch interactions and mouse interactions with the same bindings?
Ideally, the answer is yes. If not, I do have some options:
We use WURFL to get device info so could create our own matrix of devices. Depending on the device, we'll use touchstart OR click.
Detect for touch support in the browser via JS (I need to do some more research on that, but it seems like that is doable).
However, that still leaves one issue: what about devices that support BOTH. Some of the phones we support (namely the Nokias and BlackBerries) have both touch screens and keyboards. So that kind of takes me full circle back to the original question...is there a way to allow for both at once somehow?
Update: Check out the jQuery Pointer Events Polyfill project which allows you to bind to "pointer" events instead of choosing between mouse & touch.
Bind to both, but make a flag so the function only fires once per 100ms or so.
var flag = false;
$thing.bind('touchstart click', function(){
if (!flag) {
flag = true;
setTimeout(function(){ flag = false; }, 100);
// do something
}
return false
});
This is the fix that I "create" and it take out the GhostClick and implements the FastClick. Try on your own and let us know if it worked for you.
$(document).on('touchstart click', '.myBtn', function(event){
if(event.handled === false) return
event.stopPropagation();
event.preventDefault();
event.handled = true;
// Do your magic here
});
You could try something like this:
var clickEventType=((document.ontouchstart!==null)?'click':'touchstart');
$("#mylink").bind(clickEventType, myClickHandler);
Usually this works as well:
$('#buttonId').on('touchstart click', function(e){
e.stopPropagation(); e.preventDefault();
//your code here
});
Just adding return false; at the end of the on("click touchstart") event function can solve this problem.
$(this).on("click touchstart", function() {
// Do things
return false;
});
From the jQuery documentation on .on()
Returning false from an event handler will automatically call event.stopPropagation() and event.preventDefault(). A false value can also be passed for the handler as a shorthand for function(){ return false; }.
I had to do something similar. Here is a simplified version of what worked for me. If a touch event is detected, remove the click binding.
$thing.on('touchstart click', function(event){
if (event.type == "touchstart")
$(this).off('click');
//your code here
});
In my case the click event was bound to an <a> element so I had to remove the click binding and rebind a click event which prevented the default action for the <a> element.
$thing.on('touchstart click', function(event){
if (event.type == "touchstart")
$(this).off('click').on('click', function(e){ e.preventDefault(); });
//your code here
});
I succeeded by the following way.
Easy Peasy...
$(this).on('touchstart click', function(e){
e.preventDefault();
//do your stuff here
});
I believe the best practice is now to use:
$('#object').on('touchend mouseup', function () { });
touchend
The touchend event is fired when a touch point is removed from the touch surface.
The touchend event will not trigger any mouse events.
mouseup
The mouseup event is sent to an element when the mouse pointer is over the element, and the mouse button is released. Any HTML element can receive this event.
The mouseup event will not trigger any touch events.
EXAMPLE
$('#click').on('mouseup', function () { alert('Event detected'); });
$('#touch').on('touchend', function () { alert('Event detected'); });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1 id="click">Click me</h1>
<h1 id="touch">Touch me</h1>
EDIT (2017)
As of 2017, browsers starting with Chrome are making steps towards making the click event .on("click") more compatible for both mouse and touch by eliminating the delay generated by tap events on click requests.
This leads to the conclusion that reverting back to using just the click event would be the simplest solution moving forward.
I have not yet done any cross browser testing to see if this is practical.
check fast buttons and chost clicks from google https://developers.google.com/mobile/articles/fast_buttons
Well... All of these are super complicated.
If you have modernizr, it's a no-brainer.
ev = Modernizr.touch ? 'touchstart' : 'click';
$('#menu').on(ev, '[href="#open-menu"]', function(){
//winning
});
Generally you don't want to mix the default touch and non-touch (click) api. Once you move into the world of touch it easier to deal only with the touch related functions. Below is some pseudo code that would do what you want it to.
If you connect in the touchmove event and track the locations you can add more items in the doTouchLogic function to detect gestures and whatnot.
var touchStartTime;
var touchStartLocation;
var touchEndTime;
var touchEndLocation;
$thing.bind('touchstart'), function() {
var d = new Date();
touchStartTime = d.getTime();
touchStartLocation = mouse.location(x,y);
});
$thing.bind('touchend'), function() {
var d = new Date();
touchEndTime= d.getTime();
touchEndLocation= mouse.location(x,y);
doTouchLogic();
});
function doTouchLogic() {
var distance = touchEndLocation - touchStartLocation;
var duration = touchEndTime - touchStartTime;
if (duration <= 100ms && distance <= 10px) {
// Person tapped their finger (do click/tap stuff here)
}
if (duration > 100ms && distance <= 10px) {
// Person pressed their finger (not a quick tap)
}
if (duration <= 100ms && distance > 10px) {
// Person flicked their finger
}
if (duration > 100ms && distance > 10px) {
// Person dragged their finger
}
}
Another implementation for better maintenance. However, this technique will also do event.stopPropagation (). The click is not caught on any other element that clicked for 100ms.
var clickObject = {
flag: false,
isAlreadyClicked: function () {
var wasClicked = clickObject.flag;
clickObject.flag = true;
setTimeout(function () { clickObject.flag = false; }, 100);
return wasClicked;
}
};
$("#myButton").bind("click touchstart", function (event) {
if (!clickObject.isAlreadyClicked()) {
...
}
}
Just for documentation purposes, here's what I've done for the fastest/most responsive click on desktop/tap on mobile solution that I could think of:
I replaced jQuery's on function with a modified one that, whenever the browser supports touch events, replaced all my click events with touchstart.
$.fn.extend({ _on: (function(){ return $.fn.on; })() });
$.fn.extend({
on: (function(){
var isTouchSupported = 'ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch;
return function( types, selector, data, fn, one ) {
if (typeof types == 'string' && isTouchSupported && !(types.match(/touch/gi))) types = types.replace(/click/gi, 'touchstart');
return this._on( types, selector, data, fn);
};
}()),
});
Usage than would be the exact same as before, like:
$('#my-button').on('click', function(){ /* ... */ });
But it would use touchstart when available, click when not. No delays of any kind needed :D
I just came up with the idea to memorize if ontouchstart was ever triggered. In this case we are on a device which supports it and want to ignore the onclick event. Since ontouchstart should always be triggered before onclick, I'm using this:
<script> touchAvailable = false; </script>
<button ontouchstart="touchAvailable=true; myFunction();" onclick="if(!touchAvailable) myFunction();">Button</button>
You could try like this:
var clickEvent = (('ontouchstart' in document.documentElement)?'touchstart':'click');
$("#mylink").on(clickEvent, myClickHandler);
In my case this worked perfectly:
jQuery(document).on('mouseup keydown touchend', function (event) {
var eventType = event.type;
if (eventType == 'touchend') {
jQuery(this).off('mouseup');
}
});
The main problem was when instead mouseup I tried with click, on touch devices triggered click and touchend at the same time, if i use the click off, some functionality didn't worked at all on mobile devices. The problem with click is that is a global event that fire the rest of the event including touchend.
This worked for me, mobile listens to both, so prevent the one, which is the touch event. desktop only listen to mouse.
$btnUp.bind('touchstart mousedown',function(e){
e.preventDefault();
if (e.type === 'touchstart') {
return;
}
var val = _step( _options.arrowStep );
_evt('Button', [val, true]);
});
This hasn't been mentioned here, but you may want to check out this link: https://joshtronic.com/2015/04/19/handling-click-and-touch-events-on-the-same-element/
To recap for posterity, instead of trying to assign to both handlers and then sort out the result, you can simply check if the device is a touchscreen or not and only assign to the relevant event. Observe:
var clickEvent = (function() {
if ('ontouchstart' in document.documentElement === true)
return 'touchstart';
else
return 'click';
})();
// and assign thusly:
el.addEventListener( clickEvent, function( e ){
// things and stuff
});
I am using this to bind my events so that I can test on touchscreens that handle both touchstart and click events which would fire twice, and on my development PC which only hears the click
One problem the author of that link mentions though, is touchscreen laptops designed to handle both events:
I learned about a third device I was not considering, the touchscreen laptop. It’s a hybrid device that supports both touch and click events. Binding one event means only that event be supported. Does that mean someone with a touchscreen and mouse would have to explicitly touch because that’s the only event I am handling?
Binding touchstart and click seemed ideal to handle these hybrid devices. To keep the event from firing twice, I added e.stopPropagation() and e.preventDefault() to the callback functions. e.stopPropagation() stops events from “bubbling up” to their parents but also keeps a second event from firing. I included e.preventDefault() as a “just in case” but seems like it could be omitted.
Being for me the best answer the one given by Mottie, I'm just trying to do his code more reusable, so this is my contribution:
bindBtn ("#loginbutton",loginAction);
function bindBtn(element,action){
var flag = false;
$(element).bind('touchstart click', function(e) {
e.preventDefault();
if (!flag) {
flag = true;
setTimeout(function() {
flag = false;
}, 100);
// do something
action();
}
return false;
});
I am also working on an Android/iPad web app, and it seems that if only using "touchmove" is enough to "move components" ( no need touchstart ).
By disabling touchstart, you can use .click(); from jQuery. It's actually working because it hasn't be overloaded by touchstart.
Finally, you can binb .live("touchstart", function(e) { e.stopPropagation(); }); to ask the touchstart event to stop propagating, living room to click() to get triggered.
It worked for me.
There are many things to consider when trying to solve this issue. Most solutions either break scrolling or don't handle ghost click events properly.
For a full solution see https://developers.google.com/mobile/articles/fast_buttons
NB: You cannot handle ghost click events on a per-element basis. A delayed click is fired by screen location, so if your touch events modify the page in some way, the click event will be sent to the new version of the page.
It may be effective to assign to the events 'touchstart mousedown' or 'touchend mouseup' to avoid undesired side-effects of using click.
Taking advantage of the fact that a click will always follow a touch event, here is what I did to get rid of the "ghost click" without having to use timeouts or global flags.
$('#buttonId').on('touchstart click', function(event){
if ($(this).data("already")) {
$(this).data("already", false);
return false;
} else if (event.type == "touchstart") {
$(this).data("already", true);
}
//your code here
});
Basically whenever an ontouchstart event fires on the element, a flag a set and then subsequently removed (and ignored), when the click comes.
Why not use the jQuery Event API?
http://learn.jquery.com/events/event-extensions/
I've used this simple event with success. It's clean, namespaceable and flexible enough to improve upon.
var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
var eventType = isMobile ? "touchstart" : "click";
jQuery.event.special.touchclick = {
bindType: eventType,
delegateType: eventType
};
If you are using jQuery the following worked pretty well for me:
var callback; // Initialize this to the function which needs to be called
$(target).on("click touchstart", selector, (function (func){
var timer = 0;
return function(e){
if ($.now() - timer < 500) return false;
timer = $.now();
func(e);
}
})(callback));
Other solutions are also good but I was binding multiple events in a loop and needed the self calling function to create an appropriate closure. Also, I did not want to disable the binding since I wanted it to be invoke-able on next click/touchstart.
Might help someone in similar situation!
For simple features, just recognize touch or click I use the following code:
var element = $("#element");
element.click(function(e)
{
if(e.target.ontouchstart !== undefined)
{
console.log( "touch" );
return;
}
console.log( "no touch" );
});
This will return "touch" if the touchstart event is defined and "no touch" if not. Like I said this is a simple approach for click/tap events just that.
I am trying this and so far it works (but I am only on Android/Phonegap so caveat emptor)
function filterEvent( ob, ev ) {
if (ev.type == "touchstart") {
ob.off('click').on('click', function(e){ e.preventDefault(); });
}
}
$('#keypad').on('touchstart click', '.number, .dot', function(event) {
filterEvent( $('#keypad'), event );
console.log( event.type ); // debugging only
... finish handling touch events...
}
I don't like the fact that I am re-binding handlers on every touch, but all things considered touches don't happen very often (in computer time!)
I have a TON of handlers like the one for '#keypad' so having a simple function that lets me deal with the problem without too much code is why I went this way.
Try to use Virtual Mouse (vmouse) Bindings from jQuery Mobile.
It's virtual event especially for your case:
$thing.on('vclick', function(event){ ... });
http://api.jquerymobile.com/vclick/
Browser support list: http://jquerymobile.com/browser-support/1.4/
EDIT: My former answer (based on answers in this thread) was not the way to go for me. I wanted a sub-menu to expand on mouse enter or touch click and to collapse on mouse leave or another touch click. Since mouse events normally are being fired after touch events, it was kind of tricky to write event listeners that support both touchscreen and mouse input at the same time.
jQuery plugin: Touch Or Mouse
I ended up writing a jQuery plugin called "Touch Or Mouse" (897 bytes minified) that can detect whether an event was invoked by a touchscreen or mouse (without testing for touch support!). This enables the support of both touchscreen and mouse at the same time and completely separate their events.
This way the OP can use touchstart or touchend for quickly responding to touch clicks and click for clicks invoked only by a mouse.
Demonstration
First one has to make ie. the body element track touch events:
$(document.body).touchOrMouse('init');
Mouse events our bound to elements in the default way and by calling $body.touchOrMouse('get', e) we can find out whether the event was invoked by a touchscreen or mouse.
$('.link').click(function(e) {
var touchOrMouse = $(document.body).touchOrMouse('get', e);
if (touchOrMouse === 'touch') {
// Handle touch click.
}
else if (touchOrMouse === 'mouse') {
// Handle mouse click.
}
}
See the plugin at work at http://jsfiddle.net/lmeurs/uo4069nh.
Explanation
This plugin needs to be called on ie. the body element to track touchstart and touchend events, this way the touchend event does not have to be fired on the trigger element (ie. a link or button). Between these two touch events this plugin considers any mouse event to be invoked by touch.
Mouse events are fired only after touchend, when a mouse event is being fired within the ghostEventDelay (option, 1000ms by default) after touchend, this plugin considers the mouse event to be invoked by touch.
When clicking on an element using a touchscreen, the element gains the :active state. The mouseleave event is only fired after the element loses this state by ie. clicking on another element. Since this could be seconds (or minutes!) after the mouseenter event has been fired, this plugin keeps track of an element's last mouseenter event: if the last mouseenter event was invoked by touch, the following mouseleave event is also considered to be invoked by touch.
Here's a simple way to do it:
// A very simple fast click implementation
$thing.on('click touchstart', function(e) {
if (!$(document).data('trigger')) $(document).data('trigger', e.type);
if (e.type===$(document).data('trigger')) {
// Do your stuff here
}
});
You basically save the first event type that is triggered to the 'trigger' property in jQuery's data object that is attached to the root document, and only execute when the event type is equal to the value in 'trigger'. On touch devices, the event chain would likely be 'touchstart' followed by 'click'; however, the 'click' handler won't be executed because "click" doesn't match the initial event type saved in 'trigger' ("touchstart").
The assumption, and I do believe it's a safe one, is that your smartphone won't spontaneously change from a touch device to a mouse device or else the tap won't ever register because the 'trigger' event type is only saved once per page load and "click" would never match "touchstart".
Here's a codepen you can play around with (try tapping on the button on a touch device -- there should be no click delay): http://codepen.io/thdoan/pen/xVVrOZ
I also implemented this as a simple jQuery plugin that also supports jQuery's descendants filtering by passing a selector string:
// A very simple fast click plugin
// Syntax: .fastClick([selector,] handler)
$.fn.fastClick = function(arg1, arg2) {
var selector, handler;
switch (typeof arg1) {
case 'function':
selector = null;
handler = arg1;
break;
case 'string':
selector = arg1;
if (typeof arg2==='function') handler = arg2;
else return;
break;
default:
return;
}
this.on('click touchstart', selector, function(e) {
if (!$(document).data('trigger')) $(document).data('trigger', e.type);
if (e.type===$(document).data('trigger')) handler.apply(this, arguments);
});
};
Codepen: http://codepen.io/thdoan/pen/GZrBdo/

Is there in JQuery or JS event only for mouse click but not for touch [duplicate]

I'm working on a mobile web site that has to work on a variety of devices. The ones giving me a headache at the moment are BlackBerry.
We need to support both keyboard clicks as well as touch events.
Ideally I'd just use:
$thing.click(function(){...})
but the issue we're running into is that some of these blackberry devices have a very annoying delay from the time of the touch to it triggering a click.
The remedy is to instead use touchstart:
$thing.bind('touchstart', function(event){...})
But how do I go about binding both events, but only firing one? I still need the click event for keyboard devices, but of course, don't want the click event firing if I'm using a touch device.
A bonus question: Is there anyway to do this and additionally accommodate browsers that don't even have a touchstart event? In researching this, it looks like BlackBerry OS5 doesn't support touchstart so will also need to rely on click events for that browser.
ADDENDUM:
Perhaps a more comprehensive question is:
With jQuery, is it possible/recommended to handle both touch interactions and mouse interactions with the same bindings?
Ideally, the answer is yes. If not, I do have some options:
We use WURFL to get device info so could create our own matrix of devices. Depending on the device, we'll use touchstart OR click.
Detect for touch support in the browser via JS (I need to do some more research on that, but it seems like that is doable).
However, that still leaves one issue: what about devices that support BOTH. Some of the phones we support (namely the Nokias and BlackBerries) have both touch screens and keyboards. So that kind of takes me full circle back to the original question...is there a way to allow for both at once somehow?
Update: Check out the jQuery Pointer Events Polyfill project which allows you to bind to "pointer" events instead of choosing between mouse & touch.
Bind to both, but make a flag so the function only fires once per 100ms or so.
var flag = false;
$thing.bind('touchstart click', function(){
if (!flag) {
flag = true;
setTimeout(function(){ flag = false; }, 100);
// do something
}
return false
});
This is the fix that I "create" and it take out the GhostClick and implements the FastClick. Try on your own and let us know if it worked for you.
$(document).on('touchstart click', '.myBtn', function(event){
if(event.handled === false) return
event.stopPropagation();
event.preventDefault();
event.handled = true;
// Do your magic here
});
You could try something like this:
var clickEventType=((document.ontouchstart!==null)?'click':'touchstart');
$("#mylink").bind(clickEventType, myClickHandler);
Usually this works as well:
$('#buttonId').on('touchstart click', function(e){
e.stopPropagation(); e.preventDefault();
//your code here
});
Just adding return false; at the end of the on("click touchstart") event function can solve this problem.
$(this).on("click touchstart", function() {
// Do things
return false;
});
From the jQuery documentation on .on()
Returning false from an event handler will automatically call event.stopPropagation() and event.preventDefault(). A false value can also be passed for the handler as a shorthand for function(){ return false; }.
I had to do something similar. Here is a simplified version of what worked for me. If a touch event is detected, remove the click binding.
$thing.on('touchstart click', function(event){
if (event.type == "touchstart")
$(this).off('click');
//your code here
});
In my case the click event was bound to an <a> element so I had to remove the click binding and rebind a click event which prevented the default action for the <a> element.
$thing.on('touchstart click', function(event){
if (event.type == "touchstart")
$(this).off('click').on('click', function(e){ e.preventDefault(); });
//your code here
});
I succeeded by the following way.
Easy Peasy...
$(this).on('touchstart click', function(e){
e.preventDefault();
//do your stuff here
});
I believe the best practice is now to use:
$('#object').on('touchend mouseup', function () { });
touchend
The touchend event is fired when a touch point is removed from the touch surface.
The touchend event will not trigger any mouse events.
mouseup
The mouseup event is sent to an element when the mouse pointer is over the element, and the mouse button is released. Any HTML element can receive this event.
The mouseup event will not trigger any touch events.
EXAMPLE
$('#click').on('mouseup', function () { alert('Event detected'); });
$('#touch').on('touchend', function () { alert('Event detected'); });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1 id="click">Click me</h1>
<h1 id="touch">Touch me</h1>
EDIT (2017)
As of 2017, browsers starting with Chrome are making steps towards making the click event .on("click") more compatible for both mouse and touch by eliminating the delay generated by tap events on click requests.
This leads to the conclusion that reverting back to using just the click event would be the simplest solution moving forward.
I have not yet done any cross browser testing to see if this is practical.
check fast buttons and chost clicks from google https://developers.google.com/mobile/articles/fast_buttons
Well... All of these are super complicated.
If you have modernizr, it's a no-brainer.
ev = Modernizr.touch ? 'touchstart' : 'click';
$('#menu').on(ev, '[href="#open-menu"]', function(){
//winning
});
Generally you don't want to mix the default touch and non-touch (click) api. Once you move into the world of touch it easier to deal only with the touch related functions. Below is some pseudo code that would do what you want it to.
If you connect in the touchmove event and track the locations you can add more items in the doTouchLogic function to detect gestures and whatnot.
var touchStartTime;
var touchStartLocation;
var touchEndTime;
var touchEndLocation;
$thing.bind('touchstart'), function() {
var d = new Date();
touchStartTime = d.getTime();
touchStartLocation = mouse.location(x,y);
});
$thing.bind('touchend'), function() {
var d = new Date();
touchEndTime= d.getTime();
touchEndLocation= mouse.location(x,y);
doTouchLogic();
});
function doTouchLogic() {
var distance = touchEndLocation - touchStartLocation;
var duration = touchEndTime - touchStartTime;
if (duration <= 100ms && distance <= 10px) {
// Person tapped their finger (do click/tap stuff here)
}
if (duration > 100ms && distance <= 10px) {
// Person pressed their finger (not a quick tap)
}
if (duration <= 100ms && distance > 10px) {
// Person flicked their finger
}
if (duration > 100ms && distance > 10px) {
// Person dragged their finger
}
}
Another implementation for better maintenance. However, this technique will also do event.stopPropagation (). The click is not caught on any other element that clicked for 100ms.
var clickObject = {
flag: false,
isAlreadyClicked: function () {
var wasClicked = clickObject.flag;
clickObject.flag = true;
setTimeout(function () { clickObject.flag = false; }, 100);
return wasClicked;
}
};
$("#myButton").bind("click touchstart", function (event) {
if (!clickObject.isAlreadyClicked()) {
...
}
}
Just for documentation purposes, here's what I've done for the fastest/most responsive click on desktop/tap on mobile solution that I could think of:
I replaced jQuery's on function with a modified one that, whenever the browser supports touch events, replaced all my click events with touchstart.
$.fn.extend({ _on: (function(){ return $.fn.on; })() });
$.fn.extend({
on: (function(){
var isTouchSupported = 'ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch;
return function( types, selector, data, fn, one ) {
if (typeof types == 'string' && isTouchSupported && !(types.match(/touch/gi))) types = types.replace(/click/gi, 'touchstart');
return this._on( types, selector, data, fn);
};
}()),
});
Usage than would be the exact same as before, like:
$('#my-button').on('click', function(){ /* ... */ });
But it would use touchstart when available, click when not. No delays of any kind needed :D
I just came up with the idea to memorize if ontouchstart was ever triggered. In this case we are on a device which supports it and want to ignore the onclick event. Since ontouchstart should always be triggered before onclick, I'm using this:
<script> touchAvailable = false; </script>
<button ontouchstart="touchAvailable=true; myFunction();" onclick="if(!touchAvailable) myFunction();">Button</button>
You could try like this:
var clickEvent = (('ontouchstart' in document.documentElement)?'touchstart':'click');
$("#mylink").on(clickEvent, myClickHandler);
In my case this worked perfectly:
jQuery(document).on('mouseup keydown touchend', function (event) {
var eventType = event.type;
if (eventType == 'touchend') {
jQuery(this).off('mouseup');
}
});
The main problem was when instead mouseup I tried with click, on touch devices triggered click and touchend at the same time, if i use the click off, some functionality didn't worked at all on mobile devices. The problem with click is that is a global event that fire the rest of the event including touchend.
This worked for me, mobile listens to both, so prevent the one, which is the touch event. desktop only listen to mouse.
$btnUp.bind('touchstart mousedown',function(e){
e.preventDefault();
if (e.type === 'touchstart') {
return;
}
var val = _step( _options.arrowStep );
_evt('Button', [val, true]);
});
This hasn't been mentioned here, but you may want to check out this link: https://joshtronic.com/2015/04/19/handling-click-and-touch-events-on-the-same-element/
To recap for posterity, instead of trying to assign to both handlers and then sort out the result, you can simply check if the device is a touchscreen or not and only assign to the relevant event. Observe:
var clickEvent = (function() {
if ('ontouchstart' in document.documentElement === true)
return 'touchstart';
else
return 'click';
})();
// and assign thusly:
el.addEventListener( clickEvent, function( e ){
// things and stuff
});
I am using this to bind my events so that I can test on touchscreens that handle both touchstart and click events which would fire twice, and on my development PC which only hears the click
One problem the author of that link mentions though, is touchscreen laptops designed to handle both events:
I learned about a third device I was not considering, the touchscreen laptop. It’s a hybrid device that supports both touch and click events. Binding one event means only that event be supported. Does that mean someone with a touchscreen and mouse would have to explicitly touch because that’s the only event I am handling?
Binding touchstart and click seemed ideal to handle these hybrid devices. To keep the event from firing twice, I added e.stopPropagation() and e.preventDefault() to the callback functions. e.stopPropagation() stops events from “bubbling up” to their parents but also keeps a second event from firing. I included e.preventDefault() as a “just in case” but seems like it could be omitted.
Being for me the best answer the one given by Mottie, I'm just trying to do his code more reusable, so this is my contribution:
bindBtn ("#loginbutton",loginAction);
function bindBtn(element,action){
var flag = false;
$(element).bind('touchstart click', function(e) {
e.preventDefault();
if (!flag) {
flag = true;
setTimeout(function() {
flag = false;
}, 100);
// do something
action();
}
return false;
});
I am also working on an Android/iPad web app, and it seems that if only using "touchmove" is enough to "move components" ( no need touchstart ).
By disabling touchstart, you can use .click(); from jQuery. It's actually working because it hasn't be overloaded by touchstart.
Finally, you can binb .live("touchstart", function(e) { e.stopPropagation(); }); to ask the touchstart event to stop propagating, living room to click() to get triggered.
It worked for me.
There are many things to consider when trying to solve this issue. Most solutions either break scrolling or don't handle ghost click events properly.
For a full solution see https://developers.google.com/mobile/articles/fast_buttons
NB: You cannot handle ghost click events on a per-element basis. A delayed click is fired by screen location, so if your touch events modify the page in some way, the click event will be sent to the new version of the page.
It may be effective to assign to the events 'touchstart mousedown' or 'touchend mouseup' to avoid undesired side-effects of using click.
Taking advantage of the fact that a click will always follow a touch event, here is what I did to get rid of the "ghost click" without having to use timeouts or global flags.
$('#buttonId').on('touchstart click', function(event){
if ($(this).data("already")) {
$(this).data("already", false);
return false;
} else if (event.type == "touchstart") {
$(this).data("already", true);
}
//your code here
});
Basically whenever an ontouchstart event fires on the element, a flag a set and then subsequently removed (and ignored), when the click comes.
Why not use the jQuery Event API?
http://learn.jquery.com/events/event-extensions/
I've used this simple event with success. It's clean, namespaceable and flexible enough to improve upon.
var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
var eventType = isMobile ? "touchstart" : "click";
jQuery.event.special.touchclick = {
bindType: eventType,
delegateType: eventType
};
If you are using jQuery the following worked pretty well for me:
var callback; // Initialize this to the function which needs to be called
$(target).on("click touchstart", selector, (function (func){
var timer = 0;
return function(e){
if ($.now() - timer < 500) return false;
timer = $.now();
func(e);
}
})(callback));
Other solutions are also good but I was binding multiple events in a loop and needed the self calling function to create an appropriate closure. Also, I did not want to disable the binding since I wanted it to be invoke-able on next click/touchstart.
Might help someone in similar situation!
For simple features, just recognize touch or click I use the following code:
var element = $("#element");
element.click(function(e)
{
if(e.target.ontouchstart !== undefined)
{
console.log( "touch" );
return;
}
console.log( "no touch" );
});
This will return "touch" if the touchstart event is defined and "no touch" if not. Like I said this is a simple approach for click/tap events just that.
I am trying this and so far it works (but I am only on Android/Phonegap so caveat emptor)
function filterEvent( ob, ev ) {
if (ev.type == "touchstart") {
ob.off('click').on('click', function(e){ e.preventDefault(); });
}
}
$('#keypad').on('touchstart click', '.number, .dot', function(event) {
filterEvent( $('#keypad'), event );
console.log( event.type ); // debugging only
... finish handling touch events...
}
I don't like the fact that I am re-binding handlers on every touch, but all things considered touches don't happen very often (in computer time!)
I have a TON of handlers like the one for '#keypad' so having a simple function that lets me deal with the problem without too much code is why I went this way.
Try to use Virtual Mouse (vmouse) Bindings from jQuery Mobile.
It's virtual event especially for your case:
$thing.on('vclick', function(event){ ... });
http://api.jquerymobile.com/vclick/
Browser support list: http://jquerymobile.com/browser-support/1.4/
EDIT: My former answer (based on answers in this thread) was not the way to go for me. I wanted a sub-menu to expand on mouse enter or touch click and to collapse on mouse leave or another touch click. Since mouse events normally are being fired after touch events, it was kind of tricky to write event listeners that support both touchscreen and mouse input at the same time.
jQuery plugin: Touch Or Mouse
I ended up writing a jQuery plugin called "Touch Or Mouse" (897 bytes minified) that can detect whether an event was invoked by a touchscreen or mouse (without testing for touch support!). This enables the support of both touchscreen and mouse at the same time and completely separate their events.
This way the OP can use touchstart or touchend for quickly responding to touch clicks and click for clicks invoked only by a mouse.
Demonstration
First one has to make ie. the body element track touch events:
$(document.body).touchOrMouse('init');
Mouse events our bound to elements in the default way and by calling $body.touchOrMouse('get', e) we can find out whether the event was invoked by a touchscreen or mouse.
$('.link').click(function(e) {
var touchOrMouse = $(document.body).touchOrMouse('get', e);
if (touchOrMouse === 'touch') {
// Handle touch click.
}
else if (touchOrMouse === 'mouse') {
// Handle mouse click.
}
}
See the plugin at work at http://jsfiddle.net/lmeurs/uo4069nh.
Explanation
This plugin needs to be called on ie. the body element to track touchstart and touchend events, this way the touchend event does not have to be fired on the trigger element (ie. a link or button). Between these two touch events this plugin considers any mouse event to be invoked by touch.
Mouse events are fired only after touchend, when a mouse event is being fired within the ghostEventDelay (option, 1000ms by default) after touchend, this plugin considers the mouse event to be invoked by touch.
When clicking on an element using a touchscreen, the element gains the :active state. The mouseleave event is only fired after the element loses this state by ie. clicking on another element. Since this could be seconds (or minutes!) after the mouseenter event has been fired, this plugin keeps track of an element's last mouseenter event: if the last mouseenter event was invoked by touch, the following mouseleave event is also considered to be invoked by touch.
Here's a simple way to do it:
// A very simple fast click implementation
$thing.on('click touchstart', function(e) {
if (!$(document).data('trigger')) $(document).data('trigger', e.type);
if (e.type===$(document).data('trigger')) {
// Do your stuff here
}
});
You basically save the first event type that is triggered to the 'trigger' property in jQuery's data object that is attached to the root document, and only execute when the event type is equal to the value in 'trigger'. On touch devices, the event chain would likely be 'touchstart' followed by 'click'; however, the 'click' handler won't be executed because "click" doesn't match the initial event type saved in 'trigger' ("touchstart").
The assumption, and I do believe it's a safe one, is that your smartphone won't spontaneously change from a touch device to a mouse device or else the tap won't ever register because the 'trigger' event type is only saved once per page load and "click" would never match "touchstart".
Here's a codepen you can play around with (try tapping on the button on a touch device -- there should be no click delay): http://codepen.io/thdoan/pen/xVVrOZ
I also implemented this as a simple jQuery plugin that also supports jQuery's descendants filtering by passing a selector string:
// A very simple fast click plugin
// Syntax: .fastClick([selector,] handler)
$.fn.fastClick = function(arg1, arg2) {
var selector, handler;
switch (typeof arg1) {
case 'function':
selector = null;
handler = arg1;
break;
case 'string':
selector = arg1;
if (typeof arg2==='function') handler = arg2;
else return;
break;
default:
return;
}
this.on('click touchstart', selector, function(e) {
if (!$(document).data('trigger')) $(document).data('trigger', e.type);
if (e.type===$(document).data('trigger')) handler.apply(this, arguments);
});
};
Codepen: http://codepen.io/thdoan/pen/GZrBdo/

setTimeout and event propagation

I want to create a one-time event triggered by clicking anywhere. This event is created by clicking a button. I do not want the event to trigger upon clicking the button, only any subsequent clicks anywhere (including the button).
So say I've got some html like the following:
<body>
<div id="someparent">
<div id="btn"></div>
</div>
</body>
And the following javascript (jquery):
$('#btn').click( function() {
$(document).one('click', function() {
console.log('triggered');
});
});
$('#someparent').click(function() {
// this must always be triggered
});
I want to avoid stopping event propagation, but in the above example, the event is bound to document, the event then bubbles up, and the event is triggered.
One way to fix this seems to be to wrap the event creation in a timeout:
$('#btn').click( function() {
setTimeout(function() {
$(document).one('click', function() {
console.log('triggered');
});
}, 1);
});
$('#someparent').click(function() {
// this must always be triggered
});
Now, this works fine, but I'm wondering whether this is safe. Does some notion of order of execution guarantee that this will always work, or is it just working by chance? I know there are other solutions (another nested .one() event for instance), but I'm specifically looking for an answer to how setTimeout and event propagation interoperates.
The following fiddle shows two divs. The first one has the wrong behaviour (the document event is triggered immediately). Clicking the second div, and then anywhere on document (white area) illustrates the wanted behaviour:
https://jsfiddle.net/Lwocuuf8/7/
Event bubbling means that, after an event triggers on the deepest possible element, it then triggers on parents in nesting order.
From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#Adding_messages
Adding messages
In web browsers, messages are added any time an event occurs and there
is an event listener attached to it. If there is no listener, the
event is lost. So a click on an element with a click event handler
will add a message--likewise with any other event.
Calling setTimeout will add a message to the queue after the time
passed as second argument. If there is no other message in the queue,
the message is processed right away; however, if there are messages,
the setTimeout message will have to wait for other messages to be
processed. For that reason the second argument indicates a minimum
time and not a guaranteed time.
So, the behaviour you have, is not by chance, you are guaranteed that the messages in the queue will be processed before processing the message you add after a timeout.
Here is a workaround by using a flag instead of a second event :
var documentWaitingClick = false;
$(function() {
$(document).on('click', function(e) {
if (documentWaitingClick) {
//simulate the "one"
documentWaitingClick = false;
console.log('document click');
} else if (e.target.tagName === 'BUTTON') {
documentWaitingClick = true;
console.log('button click')
}
});
});
My understanding of what you want: after clicking an "activation" button, the next click anywhere on the page (including on the "activation" button) should trigger a special event. This is easily handled with a flag:
var activationFlag = false;
$('#btn').click(function(event){
event.preventDefault();
if(!activationFlag) {
event.stopPropagation();
console.log('Global event activated');
}
activationFlag = true;
});
$('#someparent').click(function(event){
if(activationFlag) {
console.log('Time for a special event');
} else {
event.preventDefault();
event.stopPropagation();
}
});

How to run a function only once when it's triggered by both focus and click events

I have an input element with 2 events attached: focus and click. They both fire off the same helper function.
When I tab to the input, the focus event fires and my helper is run once. No problems there.
When the element already has focus, and I click on it again, the click event fires and my helper runs once. No problems there either.
But when the element does not have focus, and I click on it, BOTH events fire, and my helper is run TWICE. How can I keep this helper only running once?
I saw a couple similar questions on here, but didn't really follow their answers. I also discovered the .live jQuery handler, which seems like it could work if I had it watch a status class. But seems like there should be a simpler way. The .one handler would work, except I need this to work more than once.
Thanks for any help!
The best answer here would be to come up with a design that isn't trying to trigger the same action on two different events that can both occur on the same user action, but since you haven't really explained the overall problem you're coding, we can't really help you with that approach.
One approach is to keep a single event from triggering the same thing twice is to "debounce" the function call and only call the function from a given element if it hasn't been called very recently (e.g. probably from the same user event). You can do this by recording the time of the last firing for this element and only call the function if the time has been longer than some value.
Here's one way you could do that:
function debounceMyFunction() {
var now = new Date().getTime();
var prevTime = $(this).data("prevActionTime");
$(this).data("prevActionTime", now);
// only call my function if we haven't just called it (within the last second)
if (!prevTime || now - prevTime > 1000) {
callMyFunction();
}
}
$(elem).focus(debounceMyFunction).click(debounceMyFunction);
This worked for me:
http://jsfiddle.net/cjmemay/zN8Ns/1/
$('.button').on('mousedown', function(){
$(this).data("mouseDown", true);
});
$('.button').on('mouseup', function(){
$(this).removeData("mouseDown");
});
$('.button').on('focus', function(){
if (!$(this).data("mouseDown"))
$(this).trigger('click.click');
});
$(".button").on('click.click',evHandler);
Which I stole directly from this:
https://stackoverflow.com/a/9440580/264498
You could use a timeout which get's cleared and set. This would introduce a slight delay but ensures only the last event is triggered.
$(function() {
$('#field').on('click focus', function() {
debounce(function() {
// Your code goes here.
console.log('event');
});
});
});
var debounceTimeout;
function debounce(callback) {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(callback, 500);
}
Here's the fiddle http://jsfiddle.net/APEdu/
UPDATE
To address a comment elsewhere about use of a global, you could make the doubleBounceTimeout a collection of timeouts with a key passed in the event handler. Or you could pass the same timeout to any methods handling the same event. This way you could use the same method to handle this for any number of inputs.
Live demo (click).
I'm just simply setting a flag to gate off the click when the element is clicked the first time (focus given). Then, if the element gets focus from tabbing, the flag is also removed so that the first click will work.
var $foo = $('#foo');
var flag = 0;
$foo.click(function() {
if (flag) {
flag = 0;
return false;
}
console.log('clicked');
});
$foo.focus(function() {
flag = 1;
console.log('focused');
});
$(document).keyup(function(e) {
if (e.which === 9) {
var $focused = $('input:focus');
if ($focused.is($foo)) {
flag = 0;
}
}
});
It seems to me that you don't actually need the click handler. It sounds like this event is attached to an element which when clicked gains focus and fires the focus handler. So clicking it is always going to fire your focus handler, so you only need the focus handler.
If this is not the case then unfortunately no, there is no easy way to achieve what you are asking. Adding/removing a class on focus and only firing the click when the class isn't present is about the only way I can think of.
I have it - 2 options
1 - bind the click handler to the element in the focus callback
2 - bind the focus and the click handler to a different class, and use the focus callback to add the click class and use blur to remove the click class
Thanks for the great discussion everybody. Seems like the debouncing solution from #jfriend00, and the mousedown solution from Chris Meyers, are both decent ways to handle it.
I thought some more, and also came up with this solution:
// add focus event
$myInput.focus(function() {
myHelper();
// while focus is active, add click event
setTimeout(function() {
$myInput.click(function() {
myHelper();
});
}, 500); // slight delay seems to be required
});
// when we lose focus, unbind click event
$myInput.blur(function() {
$myInput.off('click');
});
But seems like those others are slightly more elegant. I especially like Chris' because it doesn't involve dealing with the timing.
Thanks again!!
Improving on #Christopher Meyers solution.
Some intro: Before the click event fires, 2 events are preceding it, mousedown & mouseup, if the mousedown is fired, we know that probably the mouseup will fire.
Therefore we probably wouldn't like that the focus event handler would execute its action. One scenario in which the mouseup wouldn't fire is if the user starts clicking the button then drags the cursor away, for that we use the blur event.
let mousedown = false;
const onMousedown = () => {
mousedown = true;
};
const onMouseup = () => {
mousedown = false;
// perform action
};
const onFocus = () => {
if (mousedown) return;
// perform action
};
const onBlur = () => {
mousedown = false;
// perform action if wanted
};
The following events would be attached:
const events = [
{ type: 'focus', handler: onFocus },
{ type: 'blur', handler: onBlur },
{ type: 'mousedown', handler: onMousedown },
{ type: 'mouseup', handler: onMouseup }
];

How to temporarily disable all event handlers attached to a control

Is there a way to do.
$("#controlId").suspendEvents();
$("#controlId").resumeEvents();
I'm aware of preventDefault and stopPropagation. I want to do from outside the event.
Please consider the following in your answer.
I cannot modify these bound events.
I do not know the bound events (Although it will be possible it will take me long time to do it). so it is not possible to .off() and then add them back one by one.
I was able to put together answers from 2 other questions.
1.Bind an event handler to front of the queue
2.Attach handler to all events in a control
The idea is to bind an event handler with e.stopImmediatePropagation to front of the queue for all events. It seems crude i would be glad if this can be improved.
The solution...
$.fn.preBind = function (type, data, fn) {
this.each(function () {
var $this = $(this);
$this.bind(type, data, fn);
$.each(type.split(/ +/), function () {
var currentBindings = $this.data('events')[this];
if ($.isArray(currentBindings)) {
currentBindings.unshift(currentBindings.pop());
}
});
});
return this;
};
$.fn.suspendEvents = function () {
this.preBind("click keydown keyup keypress mouseover mouseenter mouseout mouseleave mousedown mouseup mousemove change blur focus focusin focusout scroll resize load unload beforeunload", null, blockEvents);
}
$.fn.resumeEvents = function () {
var _this = this;
$.each("click keydown keyup keypress mouseover mouseenter mouseout mouseleave mousedown mouseup mousemove change blur focus focusin focusout scroll resize load unload beforeunload".split(/ +/), function () {
_this.unbind(this, blockEvents);
});
}
function blockEvents(e) {
e.stopImmediatePropagation();
}
Now i could use
$("#controlId").suspendEvents();
$("#controlId").resumeEvents();
EDIT: Modified resumeEvents() to overcome IE issue.
everything bubbles up, so catch any event in body and prevent them.
alternative
var myCtlrs = $("all i want").attr("disabled", disabled");
then
myCtlrs.removeAttr("disabled");

Categories

Resources