I'm trying to handle a button click. The "button" is a custom div that can also contain children. The click callback should fire when the user has both clicked and released inside the element. If the user clicks inside and then drags and releases outside, the handler should not fire. I need to get this working on both desktop and mobile, hence I'm using mousedown/mouseup
and touchstart/touchend
events.
I need to change the button class from "pressed" to "normal" even if the user releases outside, so I need to add a listener to capture the releasing event over the document:
var $myElement = $("#myelement"); //This is a div and can also contain children.
$myElement.on("mousedown touchstart", function(e){
$(this).addClass("pressed");
$(document).on("mouseup touchend", function listener(e){
$myElement.removeClass("pressed");
$(document).off("mouseup touchend", listener);
var $target = $(e.target);
if ($target.is($myElement) || $target.parents().is($myElement)){
//FIXME
alert("Inside!");
//do something here
return false;
} else {
//FIXME
alert("Outside!");
//let event bubble
}
});
return false;
});
It works fine with clicks, but it does not work as expected with touch events. I've tested this in Chrome for Android and when pressing over the element, then dragging out, the "Inside!" alert is shown.
The problem is in this condition:
if ($target.is($myElement) || $target.parents().is($myElement)){
I'm trying to check whether the touchend
event has occurred in the button or any of the the children. If the button has no children, when clicking on it and then releasing outside, the first part of the OR clause resolves to true. If the button has children and I click in the children, then release in any other part of the screen, the second part of the clause is true.
What is wrong with this code?
Thanks in Advance.
UPDATE
I've tested it also in BlackBerry 10 with the same results. Apparently the target for the touchend
event is the button (or the children), even when I've clicked outside. Is this how it is supposed to work?
UPDATE
And here's why. This is what the W3C docs say about the event:
The target of this event must be the same Element that received the touchstart event when this touch point was placed on the surface, even if the touch point has since moved outside the interactive area of the target element.
It makes no sense to me, but anyway this explain my problem. I was assuming the touchend
event would be called over the element where the finger was released. Would it be possible to detect the finger release outside using a different approach?
UPDATE
I've tried to get the coordinates of the touchevent
to get the element there using document.elementFromPoint
. The problem is this event does not contain the coordinates (the changedTouches
and touches
lists are empty). I've thorougly inspected the event in the debugger and there's no way to retrieve coords from it other than the original event's. Then I thought I could cache the last touchmove
event and get the coords from there, but again this event has no own coordinates! Why on Earth do these two events exist when they are useless? And I've tested this approach in iOS, Android and BB10 with identical results.
I've given up. I'll call the callback even if the user clicked outside. I can't believe it is 2013 guys and there's no (simple) way of doing this? I could use the drag&drop api but according to this it is unsupported on mobile (Gotta love mobile web development).
The touchend event occurs when the user removes the finger from an element. Note: The touchend event will only work on devices with a touch screen. Tip: Other events related to the touchend event are: touchstart - occurs when the user touches an element.
Because mobile browsers should also work with with web applications that were build for mouse devices, touch devices also fire classic mouse events like mousedown or click . When a user follows a link on a touch device, the following events will be fired in sequence: touchstart.
The touchmove event occurs when the user moves the finger across the screen. The touchmove event will be triggered once for each movement, and will continue to be triggered until the finger is released. Note: The touchmove event will only work on devices with a touch screen.
The touchend event fires when one or more touch points are removed from the touch surface.
Finally (almost) got it working in BB10 and Android. I've left a trail of problems in the question updates, so to sum up: the touchend
event target is the same as the touchstart
, and it comes without coordinates (the API designer for some reason decided to "hide" them in the originalEvent
property :) . So I'm retrieving the touch end coordinates from changedTouches[0].pageX
and changedTouches[0].pageY
and then obtaining the element where the finger is released using document.elementFromPoint
. (Before feeding this method with the event coordinates you must add the scroll offset to x and y). Then if the element at that point is the button (or is a child of it), I handle the click.
var $myElement = $("#myelement"); //This is a div and can also contain children.
var startEvent = touchDevice() ? "touchstart" : "mousedown";
var releaseEvent = touchDevice() ? "touchend" : "mouseup";
$myElement.on(startEvent, function(e){
$(this).addClass("pressed");
$(document).on(releaseEvent, function listener(e){
$myElement.removeClass("pressed");
$(document).off(releaseEvent, listener);
var $target = null;
if(e.type === "mouseup"){
$target = $(e.target);
} else {
var x = e.originalEvent.changedTouches[0].pageX - window.pageXOffset;
var y = e.originalEvent.changedTouches[0].pageY - window.pageYOffset;
var target = document.elementFromPoint(x, y);
if(target){
$target = $(target);
}
}
if ($target !== null && ($target.is($myElement) || $target.parents().is($myElement))){
//FIXME
alert("Inside!");
//callback here
return false;
} else {
//FIXME
alert("Outside!");
}
});
return false;
});
Now I've a new problem: in iOS somehow the click events are being duplicated. But this deserves a different question (if not existing already).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With