aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Constantin2014-02-17 16:10:36 -0800
committerCaitlin Potter2014-03-18 22:30:23 -0400
commitbc42950b514b60f319812eeb87aae2915e394237 (patch)
tree997f541e230fbbff096d3fbeebd63ae091db81d3
parent6680b7b97c0326a80bdccaf0a35031e4af641e0e (diff)
downloadangular.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.js16
-rw-r--r--test/ngTouch/directive/ngClickSpec.js106
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');
+ });
+ });
});