From 0c1362861dedc396688f46d145aa8249b4c2b8ea Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Thu, 3 Apr 2014 13:52:48 -0500 Subject: [PATCH] refactor ionic.tap and start writing tests --- js/ext/angular/src/directive/ionicTouch.js | 8 +- js/ext/angular/test/service/ionicTap.unit.js | 222 +++++++++ js/utils/tap.js | 455 ++++++++++--------- 3 files changed, 463 insertions(+), 222 deletions(-) create mode 100644 js/ext/angular/test/service/ionicTap.unit.js diff --git a/js/ext/angular/src/directive/ionicTouch.js b/js/ext/angular/src/directive/ionicTouch.js index a7699799c5..bff629e603 100644 --- a/js/ext/angular/src/directive/ionicTouch.js +++ b/js/ext/angular/src/directive/ionicTouch.js @@ -18,9 +18,9 @@ angular.module('ionic.ui.touch', []) * @private */ .factory('$ionicNgClick', ['$parse', function($parse) { - function onTap(e) { + function onRelease(e) { // wire this up to Ionic's tap/click simulation - ionic.tapElement(e.target, e); + ionic.tap.simulateClick(e.target, e); } return function(scope, element, clickExpr) { var clickHandler = $parse(clickExpr); @@ -31,14 +31,14 @@ angular.module('ionic.ui.touch', []) }); }); - ionic.on("release", onTap, element[0]); + ionic.on("release", onRelease, element[0]); // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click // something else nearby. element.onclick = function(event) { }; scope.$on('$destroy', function () { - ionic.off("release", onTap, element[0]); + ionic.off("release", onRelease, element[0]); }); }; }]) diff --git a/js/ext/angular/test/service/ionicTap.unit.js b/js/ext/angular/test/service/ionicTap.unit.js new file mode 100644 index 0000000000..1d93fdb411 --- /dev/null +++ b/js/ext/angular/test/service/ionicTap.unit.js @@ -0,0 +1,222 @@ +describe('Ionic Tap', function() { + + beforeEach(function() { + window.console.debug = function(){}; + ionic.tap.reset(); + }); + + it('Should not focus on an input if it has scrolled', function() { + var targetEle = { + dispatchEvent: function() {}, + focus: function() { this.isFocused = true; } + }; + + ionic.tap.setStart({clientX: 100, clientY: 100}); + + targetEle.tagName = 'INPUT'; + var e = { + clientX: 100, clientY: 200, + preventDefault: function() {} + }; + ionic.tap.simulateClick(targetEle, e); + expect(targetEle.isFocused).toBeUndefined(); + }); + + it('Should focus on an input if it hasnt scrolled', function() { + var targetEle = { + dispatchEvent: function() {}, + focus: function() { this.isFocused = true; } + }; + + ionic.tap.setStart({clientX: 100, clientY: 100}); + + targetEle.tagName = 'INPUT'; + var e = { + clientX: 100, clientY: 100, + preventDefault: function() {} + }; + ionic.tap.simulateClick(targetEle, e); + + expect(targetEle.isFocused).toEqual(true); + }); + + it('Should preventDefault on an input', function() { + var targetEle = { + dispatchEvent: function() {}, + focus: function() {} + }; + + ionic.tap.setStart({ clientX: 100, clientY: 100 }); + var e = { + clientX: 100, clientY: 100, + preventDefault: function() { this.preventedDefault = true } + }; + + targetEle.tagName = 'INPUT'; + ionic.tap.simulateClick(targetEle, e); + expect(e.preventedDefault).toEqual(true); + e.preventedDefault = false; + + targetEle.tagName = 'TEXTAREA'; + ionic.tap.simulateClick(targetEle, e); + expect(e.preventedDefault).toEqual(true); + e.preventedDefault = false; + + targetEle.tagName = 'DIV'; + ionic.tap.simulateClick(targetEle, e); + expect(e.preventedDefault).toEqual(false); + e.preventedDefault = false; + }); + + it('Should setStart and hasScrolled true if >= touch tolerance', function() { + ionic.tap.setStart({ clientX: 100, clientY: 100 }); + + var s = ionic.tap.hasScrolled({ clientX: 105, clientY: 100 }); + expect(s).toEqual(true); + + s = ionic.tap.hasScrolled({ clientX: 95, clientY: 100 }); + expect(s).toEqual(true); + + s = ionic.tap.hasScrolled({ clientX: 100, clientY: 103 }); + expect(s).toEqual(true); + + s = ionic.tap.hasScrolled({ clientX: 100, clientY: 97 }); + expect(s).toEqual(true); + + s = ionic.tap.hasScrolled({ clientX: 100, clientY: 200 }); + expect(s).toEqual(true); + }); + + it('Should setStart and hasScrolled false if less than touch tolerance', function() { + ionic.tap.setStart({ clientX: 100, clientY: 100 }); + + var s = ionic.tap.hasScrolled({ clientX: 100, clientY: 100 }); + expect(s).toEqual(false); + + s = ionic.tap.hasScrolled({ clientX: 104, clientY: 100 }); + expect(s).toEqual(false); + + s = ionic.tap.hasScrolled({ clientX: 96, clientY: 100 }); + expect(s).toEqual(false); + + s = ionic.tap.hasScrolled({ clientX: 100, clientY: 102 }); + expect(s).toEqual(false); + + s = ionic.tap.hasScrolled({ clientX: 100, clientY: 98 }); + expect(s).toEqual(false); + }); + + it('Should not be hasScrolled if 0 coordinates', function() { + var s = ionic.tap.hasScrolled({ clientX: 0, clientY: 0 }); + expect(s).toEqual(false); + }); + + it('Should dispatch a mouse event', function() { + var targetEle = { + dispatchEvent: function(clickEvent) { + this.clickEvent = clickEvent; + } + }; + var e = { clientX: 99, clientY: 88 }; + ionic.tap.simulateClick(targetEle, e); + + expect(targetEle.clickEvent.clientX).toEqual(99); + expect(targetEle.clickEvent.clientY).toEqual(88); + }); + + it('Should get coordinates from client originalEvent changedTouches', function() { + var e = { + clientX: 99, + clientY: 99, + originalEvent: { + changedTouches: [{ clientX: 77, clientY: 77 }] + } + }; + var c = ionic.tap.getCoordinates(e); + expect(c).toEqual({x:77, y: 77}); + }); + + it('Should get coordinates from client mouse originalEvent', function() { + var e = { + clientX: 99, + clientY: 99, + originalEvent: { + clientX: 88, + clientY: 88, + } + }; + var c = ionic.tap.getCoordinates(e); + expect(c).toEqual({x:88, y: 88}); + }); + + it('Should get coordinates from page mouse event', function() { + var e = { pageX: 77, pageY: 77 }; + var c = ionic.tap.getCoordinates(e); + expect(c).toEqual({x:77, y: 77}); + }); + + it('Should get coordinates from client mouse event', function() { + var e = { clientX: 77, clientY: 77 }; + var c = ionic.tap.getCoordinates(e); + expect(c).toEqual({x:77, y: 77}); + }); + + it('Should get coordinates from changedTouches touches', function() { + var e = { + touches: [{ clientX: 99, clientY: 99 }], + changedTouches: [{ clientX: 88, clientY: 88 }] + }; + var c = ionic.tap.getCoordinates(e); + expect(c).toEqual({x:88, y: 88}); + }); + + it('Should get coordinates from page touches', function() { + var e = { + touches: [{ pageX: 99, pageY: 99 }] + }; + var c = ionic.tap.getCoordinates(e); + expect(c).toEqual({x:99, y: 99}); + }); + + it('Should get coordinates from client touches', function() { + var e = { + touches: [{ clientX: 99, clientY: 99 }] + }; + var c = ionic.tap.getCoordinates(e); + expect(c).toEqual({x:99, y: 99}); + }); + + it('Should get 0 coordinates', function() { + var e = {}; + var c = ionic.tap.getCoordinates(e); + expect(c).toEqual({x:0, y: 0}); + }); + + it('Should not fire a tap for disabled elements', function() { + // Disabled elements should not be tapped + var targetEle = document.createElement('input'); + targetEle.disabled = true; + var event = {}; + ionic.tap.simulateClick(targetEle, event); + expect(event.tapIgnored).toEqual(true); + }); + + it('Should not fire a tap for input[file] elements', function() { + // Reported that on Android input[file] does not open using the tap + var targetEle = document.createElement('input'); + targetEle.type = 'file'; + var event = {}; + ionic.tap.simulateClick(targetEle, event); + expect(event.tapIgnored).toEqual(true); + }); + + it('Should not fire a tap for input[range] elements', function() { + // Range and tap do not agree, probably because it doesn't have a delay to begin with + var targetEle = document.createElement('input'); + targetEle.type = 'range'; + var event = {}; + ionic.tap.simulateClick(targetEle, event); + expect(event.tapIgnored).toEqual(true); + }); + +}); diff --git a/js/utils/tap.js b/js/utils/tap.js index 305614c1ec..02d3e8f8c1 100644 --- a/js/utils/tap.js +++ b/js/utils/tap.js @@ -1,190 +1,232 @@ (function(window, document, ionic) { 'use strict'; - // polyfill use to simulate native "tap" - ionic.tapElement = function(target, e) { - // simulate a normal click by running the element's click method then focus on it - - var ele = target.control || target; - - if(ele.disabled || ele.type === 'file' || ele.type === 'range') return; - - console.debug('tapElement', ele.tagName, ele.className); - - var c = getCoordinates(e); - - // using initMouseEvent instead of MouseEvent for our Android friends - var clickEvent = document.createEvent("MouseEvents"); - clickEvent.initMouseEvent('click', true, true, window, - 1, 0, 0, c.x, c.y, - false, false, false, false, 0, null); - - ele.dispatchEvent(clickEvent); - - if(ele.tagName === 'INPUT' || ele.tagName === 'TEXTAREA') { - if(!isScrolledSinceStart(e)) { - ele.focus(); - } - e.preventDefault(); - } else { - blurActive(); - } - - // remember the coordinates of this tap so if it happens again we can ignore it - // but only if the coordinates are not already being actively disabled - if( !isRecentTap(e) ) { - recordCoordinates(e); - } - - if(target.control) { - console.debug('tapElement, target.control, stop'); - return stopEvent(e); - } - }; - - function tapPolyfill(orgEvent) { - // if the source event wasn't from a touch event then don't use this polyfill - if(!orgEvent.gesture || !orgEvent.gesture.srcEvent) return; - - var e = orgEvent.gesture.srcEvent; // evaluate the actual source event, not the created event by gestures.js - var ele = e.target; - - if( isRecentTap(e) || e.type === 'touchcancel' ) { - // if a tap in the same area just happened, - // or it was a touchcanel event, don't continue - console.debug('tapPolyfill', 'isRecentTap', ele.tagName, 'type:', e.type); - return stopEvent(e); - } - - for(var x=0; x<5; x++) { - // climb up the DOM looking to see if the tapped element is, or has a parent, of one of these - // only climb up a max of 5 parents, anything more probably isn't beneficial - if(!ele) break; - - if( ele.tagName === "INPUT" || - ele.tagName === "A" || - ele.tagName === "BUTTON" || - ele.tagName === "LABEL" || - ele.tagName === "TEXTAREA" ) { - - return ionic.tapElement(ele, e); - } - ele = ele.parentElement; - } - - // they didn't tap one of the above elements - // if the currently active element is an input, and they tapped outside - // of the current input, then unset its focus (blur) so the keyboard goes away - blurActive(); - } - - function preventGhostClick(e) { - - console.debug((function(){ - // Great for debugging, and thankfully this gets removed from the build, OMG it's ugly - - if(e.target.control) { - // this is a label that has an associated input - // the native layer will send the actual event, so stop this one - console.debug('preventGhostClick', 'label'); - - } else if(isRecentTap(e)) { - // a tap has already happened at these coordinates recently, ignore this event - console.debug('preventGhostClick', 'isRecentTap', e.target.tagName); - - } else if(isScrolledSinceStart(e)) { - // this click's coordinates are different than its touchstart/mousedown, must have been scrolling - console.debug('preventGhostClick', 'isScrolledSinceStart, startCoordinates, x:' + startCoordinates.x + ' y:' + startCoordinates.y); - } - - var c = getCoordinates(e); - return 'click at x:' + c.x + ', y:' + c.y; - })()); - - - if(e.target.control || isRecentTap(e) || isScrolledSinceStart(e)) { - return stopEvent(e); - } - - // 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 isRecentTap(event) { - // loop through the tap coordinates and see if the same area has been tapped recently - 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 - 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 existingCoordinates; - } - } - } - - function isScrolledSinceStart(event) { - // check if this click's coordinates are different than its touchstart/mousedown - var c = getCoordinates(event); - - // Quick check for 0,0 which could be simulated mouse click for form submission - if(c.x === 0 && c.y === 0) { - return false; - } - - return (c.x > startCoordinates.x + TOUCH_TOLERANCE_X || - c.x < startCoordinates.x - TOUCH_TOLERANCE_X || - c.y > startCoordinates.y + TOUCH_TOLERANCE_Y || - c.y < startCoordinates.y - TOUCH_TOLERANCE_Y); - } - - function recordCoordinates(event) { - var c = getCoordinates(event); - if(c.x && c.y) { - var tapId = Date.now(); - - // only record tap coordinates if we have valid ones - tapCoordinates[tapId] = { x: c.x, y: c.y, id: tapId }; - - setTimeout(function() { - // delete the tap coordinates after X milliseconds, basically allowing - // it so a tap can happen again in the same area in the future - delete tapCoordinates[tapId]; - }, CLICK_PREVENT_DURATION); - } - } - - function getCoordinates(event) { - // This method can get coordinates for both a mouse click - // or a touch depending on the given event - var gesture = (event.gesture ? event.gesture : event); - - if(gesture) { - var touches = gesture.touches && gesture.touches.length ? gesture.touches : [gesture]; - var e = (gesture.changedTouches && gesture.changedTouches[0]) || - (gesture.originalEvent && gesture.originalEvent.changedTouches && - gesture.originalEvent.changedTouches[0]) || - touches[0].originalEvent || touches[0]; - - if(e) return { x: e.clientX || e.pageX, y: e.clientY || e.pageY }; - } - return { x:0, y:0 }; - } - + var CLICK_PREVENT_DURATION = 1500; // max milliseconds ghostclicks in the same area should be prevented + var REMOVE_PREVENT_DELAY = 380; // delay after a touchend/mouseup before removing the ghostclick prevent + var REMOVE_PREVENT_DELAY_GRADE_C = 800; // same as REMOVE_PREVENT_DELAY, but for grade c devices + var HIT_RADIUS = 15; // surrounding area of a click that if a ghostclick happens it would get ignored + var TOUCH_TOLERANCE_X = 4; // how much the X coordinates can be off between start/end, but still a click + var TOUCH_TOLERANCE_Y = 2; // how much the Y coordinates can be off between start/end, but still a click + var tapCoordinates = {}; // used to remember coordinates to ignore if they happen again quickly + var startCoordinates = {}; // used to remember where the coordinates of the start of a touch var clickPreventTimerId; - function removeClickPrevent(e) { - clearTimeout(clickPreventTimerId); - clickPreventTimerId = setTimeout(function(){ - var tap = isRecentTap(e); - if(tap) delete tapCoordinates[tap.id]; - }, REMOVE_PREVENT_DELAY); - } + + + ionic.tap = { + + tapInspect: function(orgEvent) { + // if the event doesn't have a gesture then don't continue + if(!orgEvent.gesture || !orgEvent.gesture.srcEvent) return; + + var e = orgEvent.gesture.srcEvent; // evaluate the actual source event, not the created event by gestures.js + var ele = e.target; // get the target element that was actually tapped + + if( ionic.tap.isRecentTap(e) || e.type === 'touchcancel' ) { + // if a tap in the same area just happened, + // or it was a touchcanel event, don't continue + console.debug('tapInspect', 'isRecentTap', ele.tagName, 'type:', e.type); + return stopEvent(e); + } + + for(var x=0; x<5; x++) { + // climb up the DOM looking to see if the tapped element is, or has a parent, of one of these + // only climb up a max of 5 parents, anything more probably isn't beneficial + if(!ele) break; + + if( ele.tagName === "INPUT" || + ele.tagName === "A" || + ele.tagName === "BUTTON" || + ele.tagName === "LABEL" || + ele.tagName === "TEXTAREA" ) { + + return ionic.tap.simulateClick(ele, e); + } + ele = ele.parentElement; + } + + // they didn't tap one of the above elements + // if the currently active element is an input, and they tapped outside + // of the current input, then unset its focus (blur) so the keyboard goes away + ionic.tap.blurActive(); + }, + + simulateClick: function(target, e) { + // simulate a normal click by running the element's click method then focus on it + + var ele = target.control || target; + + if(ele.disabled || ele.type === 'file' || ele.type === 'range') { + e.tapIgnored = true; + return; + } + + console.debug('simulateClick', ele.tagName, ele.className); + + var c = ionic.tap.getCoordinates(e); + + // using initMouseEvent instead of MouseEvent for our Android friends + var clickEvent = document.createEvent("MouseEvents"); + clickEvent.initMouseEvent('click', true, true, window, + 1, 0, 0, c.x, c.y, + false, false, false, false, 0, null); + + ele.dispatchEvent(clickEvent); + + if(ele.tagName === 'INPUT' || ele.tagName === 'TEXTAREA') { + if(!ionic.tap.hasScrolled(e)) { + ele.focus(); + } + e.preventDefault(); + } else { + ionic.tap.blurActive(); + } + + // remember the coordinates of this tap so if it happens again we can ignore it + // but only if the coordinates are not already being actively disabled + if( !ionic.tap.isRecentTap(e) ) { + ionic.tap.recordCoordinates(e); + } + + if(target.control) { + console.debug('simulateClick, target.control, stop'); + return stopEvent(e); + } + + }, + + preventGhostClick: function(e) { + + console.debug((function(){ + // Great for debugging, and thankfully this gets removed from the build, OMG it's ugly + + if(e.target.control) { + // this is a label that has an associated input + // the native layer will send the actual event, so stop this one + console.debug('preventGhostClick', 'label'); + + } else if(ionic.tap.isRecentTap(e)) { + // a tap has already happened at these coordinates recently, ignore this event + console.debug('preventGhostClick', 'isRecentTap', e.target.tagName); + + } else if(ionic.tap.hasScrolled(e)) { + // this click's coordinates are different than its touchstart/mousedown, must have been scrolling + console.debug('preventGhostClick', 'hasScrolled, startCoordinates, x:' + startCoordinates.x + ' y:' + startCoordinates.y); + } + + var c = ionic.tap.getCoordinates(e); + return 'click at x:' + c.x + ', y:' + c.y; + })()); + + + if(e.target.control || ionic.tap.isRecentTap(e) || ionic.tap.hasScrolled(e)) { + return stopEvent(e); + } + + // remember the coordinates of this click so if a tap or click in the + // same area quickly happened again we can ignore it + ionic.tap.recordCoordinates(e); + }, + + getCoordinates: function(event) { + // This method can get coordinates for both a mouse click + // or a touch depending on the given event + var gesture = (event.gesture ? event.gesture : event); + + if(gesture) { + var touches = gesture.touches && gesture.touches.length ? gesture.touches : [gesture]; + var e = (gesture.changedTouches && gesture.changedTouches[0]) || + (gesture.originalEvent && gesture.originalEvent.changedTouches && + gesture.originalEvent.changedTouches[0]) || + touches[0].originalEvent || touches[0]; + + if(e) return { x: e.clientX || e.pageX || 0, y: e.clientY || e.pageY || 0 }; + } + return { x:0, y:0 }; + }, + + hasScrolled: function(event) { + // check if this click's coordinates are different than its touchstart/mousedown + var c = ionic.tap.getCoordinates(event); + + // Quick check for 0,0 which could be simulated mouse click for form submission + if(c.x === 0 && c.y === 0) { + return false; + } + + return (c.x > startCoordinates.x + TOUCH_TOLERANCE_X || + c.x < startCoordinates.x - TOUCH_TOLERANCE_X || + c.y > startCoordinates.y + TOUCH_TOLERANCE_Y || + c.y < startCoordinates.y - TOUCH_TOLERANCE_Y); + }, + + recordCoordinates: function(event) { + // get the coordinates of this event and remember them for later + var c = ionic.tap.getCoordinates(event); + if(c.x && c.y) { + var tapId = Date.now(); + + // only record tap coordinates if we have valid ones + tapCoordinates[tapId] = { x: c.x, y: c.y, id: tapId }; + + setTimeout(function() { + // delete the tap coordinates after X milliseconds, basically allowing + // it so a tap can happen again in the same area in the future + // this is only a fallback, most tap coordinates will be removed + // from the removeClickPrevent event fired by touchend/mouseup + delete tapCoordinates[tapId]; + }, CLICK_PREVENT_DURATION); + } + }, + + removeClickPrevent: function(e) { + // fired by touchend/mouseup + // after X milliseconds, remove tap coordinates + clearTimeout(clickPreventTimerId); + clickPreventTimerId = setTimeout(function(){ + var tap = ionic.tap.isRecentTap(e); + if(tap) delete tapCoordinates[tap.id]; + }, REMOVE_PREVENT_DELAY); + }, + + isRecentTap: function(event) { + // loop through the tap coordinates and see if the same area has been tapped recently + var tapId, existingCoordinates, currentCoordinates; + + for(tapId in tapCoordinates) { + existingCoordinates = tapCoordinates[tapId]; + if(!currentCoordinates) currentCoordinates = ionic.tap.getCoordinates(event); // lazy load it when needed + + 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 existingCoordinates; + } + } + }, + + blurActive: function() { + var ele = document.activeElement; + if(ele && (ele.tagName === "INPUT" || + ele.tagName === "TEXTAREA")) { + // using a timeout to prevent funky scrolling while a keyboard hides + setTimeout(function(){ + ele.blur(); + }, 400); + } + }, + + setStart: function(e) { + startCoordinates = ionic.tap.getCoordinates(e); + }, + + reset: function() { + tapCoordinates = {}; + startCoordinates = {}; + } + + }; function stopEvent(e){ e.stopPropagation(); @@ -192,48 +234,25 @@ return false; } - function blurActive() { - var ele = document.activeElement; - if(ele && (ele.tagName === "INPUT" || - ele.tagName === "TEXTAREA")) { - // using a timeout to prevent funky scrolling while a keyboard hides - setTimeout(function(){ - ele.blur(); - }, 400); - } - } - - function recordStartCoordinates(e) { - startCoordinates = getCoordinates(e); - } - - var tapCoordinates = {}; // used to remember coordinates to ignore if they happen again quickly - var startCoordinates = {}; // used to remember where the coordinates of the start of a touch - var CLICK_PREVENT_DURATION = 1500; // max milliseconds ghostclicks in the same area should be prevented - var REMOVE_PREVENT_DELAY = 380; // delay after a touchend/mouseup before removing the ghostclick prevent - var HIT_RADIUS = 15; // surrounding area of a click that if a ghostclick happens it would get ignored - var TOUCH_TOLERANCE_X = 4; // how much the X coordinates can be off between start/end, but still a click - var TOUCH_TOLERANCE_Y = 2; // how much the Y coordinates can be off between start/end, but still a click - ionic.Platform.ready(function(){ if(ionic.Platform.grade === 'c') { - // low performing phones should have a longer ghostclick prevent - REMOVE_PREVENT_DELAY = 800; + // low performing devices should have a longer ghostclick prevent + REMOVE_PREVENT_DELAY = REMOVE_PREVENT_DELAY_GRADE_C; } }); - // set global click handler and check if the event should stop or not - document.addEventListener('click', preventGhostClick, true); + // set click handler and check if the event should be stopped or not + document.addEventListener('click', ionic.tap.preventGhostClick, true); - // global release event listener polyfill for HTML elements that were tapped or held - ionic.on("release", tapPolyfill, document); + // set release event listener for HTML elements that were tapped or held + ionic.on("release", ionic.tap.tapInspect, document); - // listeners used to remove ghostclick prevention - document.addEventListener('touchend', removeClickPrevent, false); - document.addEventListener('mouseup', removeClickPrevent, false); - - // in the case the user touched the screen, then scrolled, it shouldn't fire the click - document.addEventListener('touchstart', recordStartCoordinates, false); + // listeners used to clear out active taps which are used to prevention ghostclicks + document.addEventListener('touchend', ionic.tap.removeClickPrevent, false); + document.addEventListener('mouseup', ionic.tap.removeClickPrevent, false); + // remember where the user first started touching the screen + // so that if they scrolled, it shouldn't fire the click + document.addEventListener('touchstart', ionic.tap.setStart, false); })(this, document, ionic);