fix(ngTouch): register touches properly when jQuery is used

If jQuery was used with Angular the touch logic was looking for touches
under the original event object. However, jQuery wraps all events, keeping
the original one under the originalEvent property and copies/normalizes some
of event properties. Not all properties are copied, e.g. touches which caused
them to not be recognized properly.

Thanks to @mcmar & @pomerantsev for original patch ideas.

Fixes #4001
Closes #8584
Closes #10797
Closes #11488
This commit is contained in:
Michał Gołębiowski
2015-03-31 15:59:19 +02:00
committed by Peter Bacon Darwin
parent 2cdb2016b9
commit 06a9f0a95f
5 changed files with 91 additions and 14 deletions
+35
View File
@@ -77,6 +77,8 @@
evnt.initAnimationEvent(eventType, null, null, null, eventData.elapsedTime || 0);
}
}
} else if (/touch/.test(eventType) && supportsTouchEvents()) {
evnt = createTouchEvent(element, eventType, x, y);
} else {
evnt = document.createEvent('MouseEvents');
x = x || 0;
@@ -112,4 +114,37 @@
return finalProcessDefault;
};
function supportsTouchEvents() {
if ('_cached' in supportsTouchEvents) {
return supportsTouchEvents._cached;
}
if (!document.createTouch || !document.createTouchList) {
supportsTouchEvents._cached = false;
return false;
}
try {
document.createEvent('TouchEvent');
} catch (e) {
supportsTouchEvents._cached = false;
return false;
}
supportsTouchEvents._cached = true;
return true;
}
function createTouchEvent(element, eventType, x, y) {
var evnt = document.createEvent('TouchEvent');
x = x || 0;
y = y || 0;
var touch = document.createTouch(window, element, Date.now(), x, y, x, y);
var touches = document.createTouchList(touch);
var targetTouches = document.createTouchList(touch);
var changedTouches = document.createTouchList(touch);
evnt.initTouchEvent(eventType, true, true, window, null, 0, 0, 0, 0, false, false, false, false,
touches, targetTouches, changedTouches, 1, 0);
return evnt;
}
}());
+10 -5
View File
@@ -221,8 +221,10 @@ ngTouch.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];
// Use jQuery originalEvent
var originalEvent = event.originalEvent || event;
var touches = originalEvent.touches && originalEvent.touches.length ? originalEvent.touches : [originalEvent];
var e = touches[0];
touchStartX = e.clientX;
touchStartY = e.clientY;
});
@@ -238,9 +240,12 @@ ngTouch.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];
// Use jQuery originalEvent
var originalEvent = event.originalEvent || event;
var touches = (originalEvent.changedTouches && originalEvent.changedTouches.length) ?
originalEvent.changedTouches :
((originalEvent.touches && originalEvent.touches.length) ? originalEvent.touches : [originalEvent]);
var e = touches[0];
var x = e.clientX;
var y = e.clientY;
var dist = Math.sqrt(Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2));
+3 -5
View File
@@ -40,11 +40,9 @@ ngTouch.factory('$swipe', [function() {
};
function getCoordinates(event) {
var touches = event.touches && event.touches.length ? event.touches : [event];
var e = (event.changedTouches && event.changedTouches[0]) ||
(event.originalEvent && event.originalEvent.changedTouches &&
event.originalEvent.changedTouches[0]) ||
touches[0].originalEvent || touches[0];
var originalEvent = event.originalEvent || event;
var touches = originalEvent.touches && originalEvent.touches.length ? originalEvent.touches : [originalEvent];
var e = (originalEvent.changedTouches && originalEvent.changedTouches[0]) || touches[0];
return {
x: e.clientX,
+41 -2
View File
@@ -5,8 +5,8 @@ describe('ngClick (touch)', function() {
// TODO(braden): Once we have other touch-friendly browsers on CI, allow them here.
// Currently Firefox and IE refuse to fire touch events.
var chrome = /chrome/.test(navigator.userAgent.toLowerCase());
if (!chrome) {
// Enable iPhone for manual testing.
if (!/chrome|iphone/i.test(navigator.userAgent)) {
return;
}
@@ -48,6 +48,34 @@ describe('ngClick (touch)', function() {
expect($rootScope.event).toBeDefined();
}));
if (window.jQuery) {
it('should not unwrap a jQuery-wrapped event object on click', inject(function($rootScope, $compile) {
element = $compile('<div ng-click="event = $event"></div>')($rootScope);
$rootScope.$digest();
browserTrigger(element, 'click', {
keys: [],
x: 10,
y: 10
});
expect($rootScope.event.originalEvent).toBeDefined();
expect($rootScope.event.originalEvent.clientX).toBe(10);
expect($rootScope.event.originalEvent.clientY).toBe(10);
}));
it('should not unwrap a jQuery-wrapped event object on touchstart/touchend',
inject(function($rootScope, $compile, $rootElement) {
element = $compile('<div ng-click="event = $event"></div>')($rootScope);
$rootElement.append(element);
$rootScope.$digest();
browserTrigger(element, 'touchstart');
browserTrigger(element, 'touchend');
expect($rootScope.event.originalEvent).toBeDefined();
}));
}
it('should not click if the touch is held too long', inject(function($rootScope, $compile, $rootElement) {
element = $compile('<div ng-click="count = count + 1"></div>')($rootScope);
@@ -463,6 +491,17 @@ describe('ngClick (touch)', function() {
expect($rootScope.selection).toBe('initial');
});
it('should blur the other element on click', function() {
var blurSpy = spyOn(otherElement, 'blur');
touch(otherElement, 10, 10);
time = 500;
click(label, 10, 10);
expect(blurSpy).toHaveBeenCalled();
});
});
});
+2 -2
View File
@@ -67,8 +67,8 @@ describe('$swipe', function() {
if (restrictBrowsers) {
// TODO(braden): Once we have other touch-friendly browsers on CI, allow them here.
// Currently Firefox and IE refuse to fire touch events.
var chrome = /chrome/.test(navigator.userAgent.toLowerCase());
if (!chrome) {
// Enable iPhone for manual testing.
if (!/chrome|iphone/i.test(navigator.userAgent)) {
return;
}
}