From 7c07c3c6aea616f338966b9207ebee7982f8f499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82=C4=99biowski?= Date: Tue, 31 Mar 2015 15:59:19 +0200 Subject: [PATCH] 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 --- src/ngScenario/browserTrigger.js | 35 ++++++++++++++++++++++ src/ngTouch/directive/ngClick.js | 15 ++++++---- src/ngTouch/swipe.js | 8 ++--- test/ngTouch/directive/ngClickSpec.js | 43 +++++++++++++++++++++++++-- test/ngTouch/swipeSpec.js | 4 +-- 5 files changed, 91 insertions(+), 14 deletions(-) diff --git a/src/ngScenario/browserTrigger.js b/src/ngScenario/browserTrigger.js index 35aa54aad93e..bcbdb27acc29 100644 --- a/src/ngScenario/browserTrigger.js +++ b/src/ngScenario/browserTrigger.js @@ -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; + } }()); diff --git a/src/ngTouch/directive/ngClick.js b/src/ngTouch/directive/ngClick.js index 90274aba7322..4d48f8e86a28 100644 --- a/src/ngTouch/directive/ngClick.js +++ b/src/ngTouch/directive/ngClick.js @@ -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)); diff --git a/src/ngTouch/swipe.js b/src/ngTouch/swipe.js index 884e0800d83c..ff5416b2d51b 100644 --- a/src/ngTouch/swipe.js +++ b/src/ngTouch/swipe.js @@ -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, diff --git a/test/ngTouch/directive/ngClickSpec.js b/test/ngTouch/directive/ngClickSpec.js index 921c64578b2b..53b34db37dea 100644 --- a/test/ngTouch/directive/ngClickSpec.js +++ b/test/ngTouch/directive/ngClickSpec.js @@ -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('
')($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('
')($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('
')($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(); + }); }); }); diff --git a/test/ngTouch/swipeSpec.js b/test/ngTouch/swipeSpec.js index 416d9f5d632d..1135ba3c19d8 100644 --- a/test/ngTouch/swipeSpec.js +++ b/test/ngTouch/swipeSpec.js @@ -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; } }