Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Fix issue with Jquery for ngMobile #3198

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 40 additions & 23 deletions src/ngMobile/directive/ngClick.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,27 +108,39 @@ ngMobile.directive('ngClick', ['$parse', '$timeout', '$rootElement',
return false; // No allowable region; bust it.
}

// get the touch location for the first touch object in touch event
function getSingleTouchLocation (event) {
// retrieve original event if it is wrapped by jquery
// the original event will have an array of touches
event = event.originalEvent || event;

// get the first touch object in the event
var touches = event.touches && event.touches.length ? event.touches : [event];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be

var touches = (event.changedTouches && event.changedTouches.length) ? event.changedTouches :
 ((event.touches && event.touches.length) ? event.touches : [event]);

For this function to work.

var e = touches[0].originalEvent || touches[0];

return { x: e.clientX, y: e.clientY }
}

// Global click handler that prevents the click if it's in a bustable zone and preventGhostClick
// was called recently.
function onClick(event) {
if (Date.now() - lastPreventedTime > PREVENT_DURATION) {
return; // Too old.
}

var touches = event.touches && event.touches.length ? event.touches : [event];
var x = touches[0].clientX;
var y = touches[0].clientY;
var p = getSingleTouchLocation(event);

// Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label
// and on the input element). Depending on the exact browser, this second click we don't want
// to bust has either (0,0) or negative coordinates.
if (x < 1 && y < 1) {
if (p.x < 1 && p.y < 1) {
return; // offscreen
}

// Look for an allowable region containing this click.
// If we find one, that means it was created by touchstart and not removed by
// preventGhostClick, so we don't bust it.
if (checkAllowableRegions(touchCoordinates, x, y)) {
if (checkAllowableRegions(touchCoordinates, p.x, p.y)) {
return;
}

Expand All @@ -141,15 +153,14 @@ ngMobile.directive('ngClick', ['$parse', '$timeout', '$rootElement',
// Global touchstart handler that creates an allowable region for a click event.
// This allowable region can be removed by preventGhostClick if we want to bust it.
function onTouchStart(event) {
var touches = event.touches && event.touches.length ? event.touches : [event];
var x = touches[0].clientX;
var y = touches[0].clientY;
touchCoordinates.push(x, y);

var p = getSingleTouchLocation(event);
touchCoordinates.push(p.x, p.y);

$timeout(function() {
// Remove the allowable region.
for (var i = 0; i < touchCoordinates.length; i += 2) {
if (touchCoordinates[i] == x && touchCoordinates[i+1] == y) {
if (touchCoordinates[i] == p.x && touchCoordinates[i+1] == p.y) {
touchCoordinates.splice(i, i + 2);
return;
}
Expand Down Expand Up @@ -187,6 +198,7 @@ ngMobile.directive('ngClick', ['$parse', '$timeout', '$rootElement',

element.on('touchstart', function(event) {
tapping = true;

tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement.
// Hack for Safari, which can target text nodes instead of containers.
if(tapElement.nodeType == 3) {
Expand All @@ -197,14 +209,23 @@ ngMobile.directive('ngClick', ['$parse', '$timeout', '$rootElement',

startTime = Date.now();

var touches = event.touches && event.touches.length ? event.touches : [event];
var e = touches[0].originalEvent || touches[0];
touchStartX = e.clientX;
touchStartY = e.clientY;
var p = getSingleTouchLocation(event);
touchStartX = p.x;
touchStartY = p.y;
});

element.on('touchmove', function(event) {
resetState();
event = event.originalEvent || event;

// calculate the distance to touch start location
var p = getSingleTouchLocation(event);
var dist = Math.sqrt( Math.pow(p.x - touchStartX, 2) + Math.pow(p.y - touchStartY, 2) );

// if current position is not far away from touch start
// we still consider it as a tap event
// if it is farther than the MOVE_TOLERANCE, then resetState to cancel tapping
if (dist >= MOVE_TOLERANCE)
resetState();
});

element.on('touchcancel', function(event) {
Expand All @@ -214,16 +235,12 @@ ngMobile.directive('ngClick', ['$parse', '$timeout', '$rootElement',
element.on('touchend', function(event) {
var diff = Date.now() - startTime;

var touches = (event.changedTouches && event.changedTouches.length) ? event.changedTouches :
((event.touches && event.touches.length) ? event.touches : [event]);
var e = touches[0].originalEvent || touches[0];
var x = e.clientX;
var y = e.clientY;
var dist = Math.sqrt( Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2) );
var p = getSingleTouchLocation(event);
var dist = Math.sqrt( Math.pow(p.x - touchStartX, 2) + Math.pow(p.y - touchStartY, 2) );

if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) {
if (tapping && dist < MOVE_TOLERANCE && diff < TAP_DURATION) {
// Call preventGhostClick so the clickbuster will catch the corresponding click.
preventGhostClick(x, y);
preventGhostClick(p.x, p.y);

// Blur the focused element (the button, probably) before firing the callback.
// This doesn't work perfectly on Android Chrome, but seems to work elsewhere.
Expand Down
59 changes: 56 additions & 3 deletions test/ngMobile/directive/ngClickSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,70 @@ describe('ngClick (mobile)', function() {
expect($rootScope.tapped).toBeUndefined();
}));

it('should click if the touchend is within mvoe tolerance', inject(function($rootScope, $compile, $rootElement) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: mvoe

element = $compile('<div ng-click="tapped = true"></div>')($rootScope);
$rootElement.append(element);
$rootScope.$digest();

it('should not click if a touchmove comes before touchend', inject(function($rootScope, $compile, $rootElement) {
expect($rootScope.tapped).toBeUndefined();

browserTrigger(element, 'touchstart', [], 10, 10);
browserTrigger(element, 'touchend', [], 15, 15);

expect($rootScope.tapped).toEqual(true);
}));

it('should click if a touchmove comes before touchend, and the touchmove is inside the radius', inject(function($rootScope, $compile, $rootElement) {
element = $compile('<div ng-click="tapped = true"></div>')($rootScope);
$rootElement.append(element);
$rootScope.$digest();

expect($rootScope.tapped).toBeUndefined();

browserTrigger(element, 'touchstart', [], 10, 10);
browserTrigger(element, 'touchmove');
browserTrigger(element, 'touchend', [], 400, 400);
browserTrigger(element, 'touchmove', [], 13, 13);
browserTrigger(element, 'touchend', [], 15, 15);

expect($rootScope.tapped).toEqual(true);
}));

it('should not click if there is a touchmove outside the radius, but touchend is inside the radius', inject(function($rootScope, $compile, $rootElement) {
element = $compile('<div ng-click="tapped = true"></div>')($rootScope);
$rootElement.append(element);
$rootScope.$digest();

expect($rootScope.tapped).toBeUndefined();

browserTrigger(element, 'touchstart', [], 10, 10);
browserTrigger(element, 'touchmove', [], 25, 25);
browserTrigger(element, 'touchend', [], 15, 15);

expect($rootScope.tapped).toBeUndefined();
}));

it('should not click if touchmoves are inside the radius, but touchend is outside the radius', inject(function($rootScope, $compile, $rootElement) { element = $compile('<div ng-click="tapped = true"></div>')($rootScope);
$rootElement.append(element);
$rootScope.$digest();

expect($rootScope.tapped).toBeUndefined();

browserTrigger(element, 'touchstart', [], 10, 10);
browserTrigger(element, 'touchmove', [], 20, 20);
browserTrigger(element, 'touchend', [], 25, 25);

expect($rootScope.tapped).toBeUndefined();
}));

it('should not click if touchmoves and touchend are all outside the radius', inject(function($rootScope, $compile, $rootElement) {
element = $compile('<div ng-click="tapped = true"></div>')($rootScope);
$rootElement.append(element);
$rootScope.$digest();

expect($rootScope.tapped).toBeUndefined();

browserTrigger(element, 'touchstart', [], 10, 10);
browserTrigger(element, 'touchmove', [], 25, 25);
browserTrigger(element, 'touchend', [], 30, 30);

expect($rootScope.tapped).toBeUndefined();
}));
Expand Down