diff options
| author | Chris Constantin | 2014-02-17 16:10:36 -0800 |
|---|---|---|
| committer | Caitlin Potter | 2014-03-18 22:30:23 -0400 |
| commit | bc42950b514b60f319812eeb87aae2915e394237 (patch) | |
| tree | 997f541e230fbbff096d3fbeebd63ae091db81d3 | |
| parent | 6680b7b97c0326a80bdccaf0a35031e4af641e0e (diff) | |
| download | angular.js-bc42950b514b60f319812eeb87aae2915e394237.tar.bz2 | |
fix(ngTouch): update workaround for desktop Webkit quirk
Fix click busting of input click triggered by a label click quickly
following a touch event on a different element, in desktop
and mobile WebKit
To reproduce the issue fixed by this commit set up a page with
- an element with ng-click
- a radio button (with hg-model) and associated label
In a quick sequence tap on the element and then on the label.
The radio button will not be checked, unless PREVENT_DURATION has passed
Closes #6302
| -rw-r--r-- | src/ngTouch/directive/ngClick.js | 16 | ||||
| -rw-r--r-- | test/ngTouch/directive/ngClickSpec.js | 106 |
2 files changed, 98 insertions, 24 deletions
diff --git a/src/ngTouch/directive/ngClick.js b/src/ngTouch/directive/ngClick.js index 6cd0ff76..7e558def 100644 --- a/src/ngTouch/directive/ngClick.js +++ b/src/ngTouch/directive/ngClick.js @@ -53,6 +53,7 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement', var ACTIVE_CLASS_NAME = 'ng-click-active'; var lastPreventedTime; var touchCoordinates; + var lastLabelClickCoordinates; // TAP EVENTS AND GHOST CLICKS @@ -124,10 +125,23 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement', var y = touches[0].clientY; // 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. + // to bust has either (0,0), negative coordinates, or coordinates equal to triggering label + // click event if (x < 1 && y < 1) { return; // offscreen } + if (lastLabelClickCoordinates && + lastLabelClickCoordinates[0] === x && lastLabelClickCoordinates[1] === y) { + return; // input click triggered by label click + } + // reset label click coordinates on first subsequent click + if (lastLabelClickCoordinates) { + lastLabelClickCoordinates = null; + } + // remember label click coordinates to prevent click busting of trigger click event on input + if (event.target.tagName.toLowerCase() === 'label') { + lastLabelClickCoordinates = [x, y]; + } // Look for an allowable region containing this click. // If we find one, that means it was created by touchstart and not removed by diff --git a/test/ngTouch/directive/ngClickSpec.js b/test/ngTouch/directive/ngClickSpec.js index 43735709..921c6457 100644 --- a/test/ngTouch/directive/ngClickSpec.js +++ b/test/ngTouch/directive/ngClickSpec.js @@ -370,40 +370,100 @@ describe('ngClick (touch)', function() { })); - it('should not cancel clicks that come long after', inject(function($rootScope, $compile) { - element1 = $compile('<div ng-click="count = count + 1"></div>')($rootScope); + describe('when clicking on a label immediately following a touch event', function() { + var touch = function(element, x, y) { + time = 10; + browserTrigger(element, 'touchstart',{ + keys: [], + x: x, + y: y + }); - $rootScope.count = 0; + time = 50; + browserTrigger(element, 'touchend',{ + keys: [], + x: x, + y: y + }); + }; - $rootScope.$digest(); + var click = function(element, x, y) { + browserTrigger(element, 'click',{ + keys: [], + x: x, + y: y + }); + }; - expect($rootScope.count).toBe(0); + var $rootScope; + var container, otherElement, input, label; + beforeEach(inject(function(_$rootScope_, $compile, $rootElement) { + $rootScope = _$rootScope_; + var container = $compile('<div><div ng-click="count = count + 1"></div>' + + '<input id="input1" type="radio" ng-model="selection" value="radio1">' + + '<label for="input1">Input1</label></div>')($rootScope); + $rootElement.append(container); + otherElement = container.children()[0]; + input = container.children()[1]; + label = container.children()[2]; - time = 10; - browserTrigger(element1, 'touchstart',{ - keys: [], - x: 10, - y: 10 + $rootScope.selection = 'initial'; + + $rootScope.$digest(); + })); + + + afterEach(function() { + dealoc(label); + dealoc(input); + dealoc(otherElement); + dealoc(container); }); - time = 50; - browserTrigger(element1, 'touchend',{ - keys: [], - x: 10, - y: 10 + + it('should not cancel input clicks with (0,0) coordinates', function() { + touch(otherElement, 100, 100); + + time = 500; + click(label, 10, 10); + click(input, 0, 0); + + expect($rootScope.selection).toBe('radio1'); }); - expect($rootScope.count).toBe(1); - time = 2700; - browserTrigger(element1, 'click',{ - keys: [], - x: 10, - y: 10 + it('should not cancel input clicks with negative coordinates', function() { + touch(otherElement, 100, 100); + + time = 500; + click(label, 10, 10); + click(input, -1, -1); + + expect($rootScope.selection).toBe('radio1'); }); - expect($rootScope.count).toBe(2); - })); + + it('should not cancel input clicks with positive coordinates identical to label click', function() { + touch(otherElement, 100, 100); + + time = 500; + click(label, 10, 10); + click(input, 10, 10); + + expect($rootScope.selection).toBe('radio1'); + }); + + + it('should cancel input clicks with positive coordinates different than label click', function() { + touch(otherElement, 100, 100); + + time = 500; + click(label, 10, 10); + click(input, 11, 11); + + expect($rootScope.selection).toBe('initial'); + }); + }); }); |
