From 62fefc3c2d25dfee35ae559aafd30a04c42e7daa Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Tue, 18 Feb 2014 09:03:09 -0600 Subject: [PATCH] click/tap refactor --- js/ext/angular/test/clickTests.html | 6 +- js/utils/poly.js | 113 ++++++++++++---------------- 2 files changed, 51 insertions(+), 68 deletions(-) diff --git a/js/ext/angular/test/clickTests.html b/js/ext/angular/test/clickTests.html index 5a10274f2e..f5137df082 100644 --- a/js/ext/angular/test/clickTests.html +++ b/js/ext/angular/test/clickTests.html @@ -231,9 +231,9 @@ oldDot.parentElement.removeChild(oldDot) }, 3000); }); - document.addEventListener('mousedown', function(){ - console.debug('mousedown'); - }); + // document.addEventListener('mousedown', function(){ + // console.debug('mousedown'); + // }); document.addEventListener('mouseup', function(){ console.debug('mouseup'); }); diff --git a/js/utils/poly.js b/js/utils/poly.js index a38a9bd9b7..3b9c9dafe0 100644 --- a/js/utils/poly.js +++ b/js/utils/poly.js @@ -46,9 +46,6 @@ if( !isRecentTap(e) ) { recordCoordinates(e); } - - // set the last tap time so if a click event quickly happens it knows to ignore it - ele.lastTap = Date.now(); }; function tapPolyfill(orgEvent) { @@ -64,13 +61,6 @@ return stopEvent(e); } - if(ele.lastClick && ele.lastClick + CLICK_PREVENT_DURATION > Date.now()) { - // if a click recently happend on this element, don't continue - // (yes on some devices it's possible for a click to happen before a touchend) - console.debug('tapPolyfill', 'recent lastClick', ele.tagName); - return stopEvent(e); - } - while(ele) { // climb up the DOM looking to see if the tapped element is, or has a parent, of one of these if( ele.tagName === "INPUT" || @@ -97,25 +87,11 @@ } function preventGhostClick(e) { - if(e.target.tagName === "LABEL" && e.target.control) { + if(e.target.control) { // this is a label that has an associated input - - if(e.target.control.labelLastTap && e.target.control.labelLastTap + CLICK_PREVENT_DURATION > Date.now()) { - // Android will fire a click for the label, and a click for the input which it is associated to - // this stops the second ghost click from the label from continuing - console.debug('preventGhostClick', 'labelLastTap'); - return stopEvent(e); - } - - // remember the last time this label was clicked to it can prevent a second label ghostclick - e.target.control.labelLastTap = Date.now(); - - // The input's click event will propagate so don't bother letting this label's click - // propagate cuz it causes double clicks. However, do NOT e.preventDefault(), because - // the native layer still needs to click the input which the label controls - console.debug('preventGhostClick', 'label stopPropagation'); - e.stopPropagation(); - return; + // the native layer will send the actual event, so stop this one + console.debug('preventGhostClick', 'label'); + return stopEvent(e); } if( isRecentTap(e) ) { @@ -124,53 +100,25 @@ return stopEvent(e); } - if(e.target.lastTap && e.target.lastTap + CLICK_PREVENT_DURATION > Date.now()) { - // this element has already had the tap poly fill run on it recently, ignore this event - console.debug('preventGhostClick', 'e.target.lastTap', e.target.tagName); - return stopEvent(e); - } - - // remember the last time this element was clicked - e.target.lastClick = Date.now(); - // remember the coordinates of this click so if a tap or click in the // same area quickly happened again we can ignore it recordCoordinates(e); } - function stopEvent(e){ - e.stopPropagation(); - e.preventDefault(); - return false; - } - - function blurActive() { - var ele = document.activeElement; - if(ele && (ele.tagName === "INPUT" || - ele.tagName === "TEXTAREA" || - ele.tagName === "SELECT")) { - // using a timeout to prevent funky scrolling while a keyboard hides - setTimeout(function(){ - ele.blur(); - }, 400); - } - } - function isRecentTap(event) { // loop through the tap coordinates and see if the same area has been tapped recently - var tapId, existingCoordinates, currentCoordinates, - hitRadius = 15; + var tapId, existingCoordinates, currentCoordinates; for(tapId in tapCoordinates) { existingCoordinates = tapCoordinates[tapId]; if(!currentCoordinates) currentCoordinates = getCoordinates(event); // lazy load it when needed - if(currentCoordinates.x > existingCoordinates.x - hitRadius && - currentCoordinates.x < existingCoordinates.x + hitRadius && - currentCoordinates.y > existingCoordinates.y - hitRadius && - currentCoordinates.y < existingCoordinates.y + hitRadius) { + if(currentCoordinates.x > existingCoordinates.x - HIT_RADIUS && + currentCoordinates.x < existingCoordinates.x + HIT_RADIUS && + currentCoordinates.y > existingCoordinates.y - HIT_RADIUS && + currentCoordinates.y < existingCoordinates.y + HIT_RADIUS) { // the current tap coordinates are in the same area as a recent tap - return true; + return existingCoordinates; } } } @@ -178,10 +126,10 @@ function recordCoordinates(event) { var c = getCoordinates(event); if(c.x && c.y) { - var tapId = 't' + Date.now(); + var tapId = Date.now(); // only record tap coordinates if we have valid ones - tapCoordinates[tapId] = { x: c.x, y:c.y }; + tapCoordinates[tapId] = { x: c.x, y: c.y, id: tapId }; setTimeout(function() { // delete the tap coordinates after X milliseconds, basically allowing @@ -208,8 +156,39 @@ return { x:0, y:0 }; } + function removeClickPrevent(e) { + setTimeout(function(){ + if(e.target && e.target.control && e.target.control.labelLastTap) { + e.target.control.labelLastTap = null; + } + var c = isRecentTap(e); + if(c) delete tapCoordinates[c.id]; + }, REMOVE_PREVENT_DELAY); + return stopEvent(e); + } + + function stopEvent(e){ + e.stopPropagation(); + e.preventDefault(); + return false; + } + + function blurActive() { + var ele = document.activeElement; + if(ele && (ele.tagName === "INPUT" || + ele.tagName === "TEXTAREA" || + ele.tagName === "SELECT")) { + // using a timeout to prevent funky scrolling while a keyboard hides + setTimeout(function(){ + ele.blur(); + }, 400); + } + } + var tapCoordinates = {}; // used to remember coordinates to ignore if they happen again quickly - var CLICK_PREVENT_DURATION = 450; // amount of milliseconds to check for ghostclicks + var CLICK_PREVENT_DURATION = 1500; // max milliseconds ghostclicks in the same area should be prevented + var REMOVE_PREVENT_DELAY = 325; // delay after a touchend/mouseup before removing the ghostclick prevent + var HIT_RADIUS = 15; // set global click handler and check if the event should stop or not document.addEventListener('click', preventGhostClick, true); @@ -217,4 +196,8 @@ // global tap event listener polyfill for HTML elements that were "tapped" by the user ionic.on("tap", tapPolyfill, document); + // listeners used to remove ghostclick prevention + document.addEventListener('touchend', removeClickPrevent, false); + document.addEventListener('mouseup', removeClickPrevent, false); + })(this, document, ionic);