From ace01be3cf8f1d1a754e6ca46ff0f8dd9df80bb4 Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Sun, 14 Sep 2014 17:11:05 +0800 Subject: [PATCH] add fastclick v0.3.x --- ajax/libs/fastclick/0.3.2/fastclick.js | 366 ++++++++++++++++++++ ajax/libs/fastclick/0.3.2/fastclick.min.js | 1 + ajax/libs/fastclick/0.3.3/fastclick.js | 371 ++++++++++++++++++++ ajax/libs/fastclick/0.3.3/fastclick.min.js | 1 + ajax/libs/fastclick/0.3.5/fastclick.js | 376 +++++++++++++++++++++ ajax/libs/fastclick/0.3.5/fastclick.min.js | 1 + 6 files changed, 1116 insertions(+) create mode 100644 ajax/libs/fastclick/0.3.2/fastclick.js create mode 100644 ajax/libs/fastclick/0.3.2/fastclick.min.js create mode 100644 ajax/libs/fastclick/0.3.3/fastclick.js create mode 100644 ajax/libs/fastclick/0.3.3/fastclick.min.js create mode 100644 ajax/libs/fastclick/0.3.5/fastclick.js create mode 100644 ajax/libs/fastclick/0.3.5/fastclick.min.js diff --git a/ajax/libs/fastclick/0.3.2/fastclick.js b/ajax/libs/fastclick/0.3.2/fastclick.js new file mode 100644 index 000000000..3b40a8386 --- /dev/null +++ b/ajax/libs/fastclick/0.3.2/fastclick.js @@ -0,0 +1,366 @@ +/** + * @preserve FastClick: polyfill to remove click delays on browsers with touch UIs. + * + * @version 0.3.2 + * @copyright The Financial Times Limited [All Rights Reserved] + * @license MIT License (see LICENSE.txt) + */ + +/*jslint browser:true*/ +/*global Node, define*/ + + +/** + * Instantiate fast-clicking listeners on the specificed layer. + * + * @constructor + * @param {Element} layer The layer to listen on + */ +function FastClick(layer) { + 'use strict'; + var oldOnClick, that = this; + + + /** + * Whether a click is currently being tracked. + * + * @type boolean + */ + this.trackingClick = false, + + + /** + * The element being tracked for a click. + * + * @type Element + */ + this.targetElement = null; + + + /** + * The FastClick layer. + * + * @type Element + */ + this.layer = layer; + + if (!layer || !layer.nodeType) { + throw new TypeError('Layer must be a document node'); + } + + // Bind handlers to this instance + this.onClick = function() { FastClick.prototype.onClick.apply(that, arguments); }; + this.onTouchStart = function() { FastClick.prototype.onTouchStart.apply(that, arguments); }; + this.onTouchMove = function() { FastClick.prototype.onTouchMove.apply(that, arguments); }; + this.onTouchEnd = function() { FastClick.prototype.onTouchEnd.apply(that, arguments); }; + this.onTouchCancel = function() { FastClick.prototype.onTouchCancel.apply(that, arguments); }; + + // Devices that don't support touch don't need FastClick + if (typeof window.ontouchstart === 'undefined') { + return; + } + + // Set up event handlers as required + layer.addEventListener('click', this.onClick, true); + layer.addEventListener('touchstart', this.onTouchStart, true); + layer.addEventListener('touchmove', this.onTouchMove, true); + layer.addEventListener('touchend', this.onTouchEnd, true); + layer.addEventListener('touchcancel', this.onTouchCancel, true); + + // If a handler is already declared in the element's onclick attribute, it will be fired before + // FastClick's onClick handler. Fix this by pulling out the user-defined handler function and + // adding it as listener. + if (typeof layer.onclick === 'function') { + + // Android browser on at least 3.2 requires a new reference to the function in layer.onclick + // - the old one won't work if passed to addEventListener directly. + oldOnClick = layer.onclick; + layer.addEventListener('click', function(event) { + oldOnClick(event); + }, false); + layer.onclick = null; + } +} + + +/** + * Android requires an exception for labels. + * + * @type boolean + */ +FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0; + + +/** + * Determine whether a given element requires a native click. + * + * @param {Element} target DOM element + * @returns {boolean} Returns true if the element needs a native click + */ +FastClick.prototype.needsClick = function(target) { + 'use strict'; + switch (target.nodeName.toLowerCase()) { + case 'label': + case 'video': + return true; + default: + return (/\bneedsclick\b/).test(target.className); + } +}; + + +/** + * Determine whether a given element requires a call to focus to simulate click into element. + * + * @param {Element} target target DOM element. + * @returns {boolean} Returns true if the element requires a call to focus to simulate native click. + */ +FastClick.prototype.needsFocus = function(target) { + 'use strict'; + switch(target.nodeName.toLowerCase()) { + case 'textarea': + case 'select': + return true; + case 'input': + switch (target.type) { + case 'button': + case 'checkbox': + case 'file': + case 'image': + case 'radio': + case 'submit': + return false; + default: + return true; + } + break; + default: + return (/\bneedsfocus\b/).test(target.className); + } +}; + + +/** + * Send a click event to the element if it needs it. + * + * @returns {boolean} Whether the click was sent or not + */ +FastClick.prototype.maybeSendClick = function(targetElement, event) { + 'use strict'; + var clickEvent, touch; + + // Prevent the actual click from going though - unless the target node is marked as requiring + // real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted + // to open the options list and so the original event is required. + if (this.needsClick(targetElement)) { + return false; + } + + touch = event.changedTouches[0]; + + // Synthesise a click event, with an extra attribute so it can be tracked + clickEvent = document.createEvent('MouseEvents'); + clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null); + clickEvent.forwardedTouchEvent = true; + targetElement.dispatchEvent(clickEvent); + + return true; +}; + + +/** + * On touch start, record the position and scroll offset. + * + * @param {Event} event + * @returns {boolean} + */ +FastClick.prototype.onTouchStart = function(event) { + 'use strict'; + var touch = event.targetTouches[0]; + + this.trackingClick = true; + this.targetElement = event.target; + + this.touchStartX = touch.pageX; + this.touchStartY = touch.pageY; + + return true; +}; + + +/** + * Based on a touchmove event object, check whether the touch has moved past a boundary since it started. + * + * @param {Event} event + * @returns {boolean} + */ +FastClick.prototype.touchHasMoved = function(event) { + 'use strict'; + var touch = event.targetTouches[0]; + + if (Math.abs(touch.pageX - this.touchStartX) > 10 + || Math.abs(touch.pageY - this.touchStartY) > 10) { + return true; + } + + return false; +}; + + +/** + * Update the last position. + * + * @param {Event} event + * @returns {boolean} + */ +FastClick.prototype.onTouchMove = function(event) { + 'use strict'; + if (!this.trackingClick) { + return true; + } + + // If the touch has moved, cancel the click tracking + if (this.targetElement !== event.target || this.touchHasMoved(event)) { + this.trackingClick = false; + this.targetElement = null; + } + + return true; +}; + + +/** + * On touch end, determine whether to send a click event at once. + * + * @param {Event} event + * @returns {boolean} + */ +FastClick.prototype.onTouchEnd = function(event) { + 'use strict'; + var forElement, targetElement = this.targetElement; + + if (!this.trackingClick) { + return true; + } + + this.trackingClick = false; + + if (targetElement.nodeName.toLowerCase() === 'label' && targetElement.htmlFor) { + forElement = document.getElementById(targetElement.htmlFor); + if (forElement) { + targetElement.focus(); + if (this.android) { + return false; + } + + if (this.maybeSendClick(forElement, event)) { + event.preventDefault(); + } + + return false; + } + } else if (this.needsFocus(targetElement)) { + targetElement.focus(); + if (targetElement.tagName.toLowerCase() !== 'select') { + event.preventDefault(); + } + return false; + } + + if (!this.maybeSendClick(targetElement, event)) { + return false; + } + + event.preventDefault(); + return false; +}; + + +/** + * On touch cancel, stop tracking the click. + * + * @returns {void} + */ +FastClick.prototype.onTouchCancel = function() { + 'use strict'; + this.trackingClick = false; + this.targetElement = null; +}; + + +/** + * On actual clicks, determine whether this is a touch-generated click, a click action occurring + * naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or + * an actual click which should be permitted. + * + * @param {Event} event + * @returns {boolean} + */ +FastClick.prototype.onClick = function(event) { + 'use strict'; + var oldTargetElement; + + if (event.forwardedTouchEvent) { + return true; + } + + // If a target element was never set (because a touch event was never fired) allow the click + if (!this.targetElement) { + return true; + } + + oldTargetElement = this.targetElement; + this.targetElement = null; + + // Programmatically generated events targeting a specific element should be permitted + if (!event.cancelable) { + return true; + } + + // Derive and check the target element to see whether the click needs to be permitted; + // unless explicitly enabled, prevent non-touch click events from triggering actions, + // to prevent ghost/doubleclicks. + if (!this.needsClick(oldTargetElement)) { + + // Prevent any user-added listeners declared on FastClick element from being fired. + if (event.stopImmediatePropagation) { + event.stopImmediatePropagation(); + } + + // Cancel the event + event.stopPropagation(); + event.preventDefault(); + + return false; + } + + // If clicks are permitted, return true for the action to go through. + return true; +}; + + +/** + * Remove all FastClick's event listeners. + * + * @returns {void} + */ +FastClick.prototype.destroy = function() { + 'use strict'; + var layer = this.layer; + + layer.removeEventListener('click', this.onClick, true); + layer.removeEventListener('touchstart', this.onTouchStart, true); + layer.removeEventListener('touchmove', this.onTouchMove, true); + layer.removeEventListener('touchend', this.onTouchEnd, true); + layer.removeEventListener('touchcancel', this.onTouchCancel, true); +}; + + +if (typeof define === 'function' && define.amd) { + + // AMD. Register as an anonymous module. + define(function() { + 'use strict'; + return FastClick; + }); +} diff --git a/ajax/libs/fastclick/0.3.2/fastclick.min.js b/ajax/libs/fastclick/0.3.2/fastclick.min.js new file mode 100644 index 000000000..4118df49f --- /dev/null +++ b/ajax/libs/fastclick/0.3.2/fastclick.min.js @@ -0,0 +1 @@ +function FastClick(t){"use strict";var e,n=this;if(this.trackingClick=!1,this.targetElement=null,this.layer=t,!t||!t.nodeType)throw new TypeError("Layer must be a document node");this.onClick=function(){FastClick.prototype.onClick.apply(n,arguments)},this.onTouchStart=function(){FastClick.prototype.onTouchStart.apply(n,arguments)},this.onTouchMove=function(){FastClick.prototype.onTouchMove.apply(n,arguments)},this.onTouchEnd=function(){FastClick.prototype.onTouchEnd.apply(n,arguments)},this.onTouchCancel=function(){FastClick.prototype.onTouchCancel.apply(n,arguments)},"undefined"!=typeof window.ontouchstart&&(t.addEventListener("click",this.onClick,!0),t.addEventListener("touchstart",this.onTouchStart,!0),t.addEventListener("touchmove",this.onTouchMove,!0),t.addEventListener("touchend",this.onTouchEnd,!0),t.addEventListener("touchcancel",this.onTouchCancel,!0),"function"==typeof t.onclick&&(e=t.onclick,t.addEventListener("click",function(t){e(t)},!1),t.onclick=null))}FastClick.prototype.deviceIsAndroid=navigator.userAgent.indexOf("Android")>0,FastClick.prototype.needsClick=function(t){"use strict";switch(t.nodeName.toLowerCase()){case"label":case"video":return!0;default:return/\bneedsclick\b/.test(t.className)}},FastClick.prototype.needsFocus=function(t){"use strict";switch(t.nodeName.toLowerCase()){case"textarea":case"select":return!0;case"input":switch(t.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1;default:return!0}break;default:return/\bneedsfocus\b/.test(t.className)}},FastClick.prototype.maybeSendClick=function(t,e){"use strict";var n,o;return this.needsClick(t)?!1:(o=e.changedTouches[0],n=document.createEvent("MouseEvents"),n.initMouseEvent("click",!0,!0,window,1,o.screenX,o.screenY,o.clientX,o.clientY,!1,!1,!1,!1,0,null),n.forwardedTouchEvent=!0,t.dispatchEvent(n),!0)},FastClick.prototype.onTouchStart=function(t){"use strict";var e=t.targetTouches[0];return this.trackingClick=!0,this.targetElement=t.target,this.touchStartX=e.pageX,this.touchStartY=e.pageY,!0},FastClick.prototype.touchHasMoved=function(t){"use strict";var e=t.targetTouches[0];return Math.abs(e.pageX-this.touchStartX)>10||Math.abs(e.pageY-this.touchStartY)>10?!0:!1},FastClick.prototype.onTouchMove=function(t){"use strict";return this.trackingClick?((this.targetElement!==t.target||this.touchHasMoved(t))&&(this.trackingClick=!1,this.targetElement=null),!0):!0},FastClick.prototype.onTouchEnd=function(t){"use strict";var e,n=this.targetElement;if(!this.trackingClick)return!0;if(this.trackingClick=!1,"label"===n.nodeName.toLowerCase()&&n.htmlFor){if(e=document.getElementById(n.htmlFor))return n.focus(),this.android?!1:(this.maybeSendClick(e,t)&&t.preventDefault(),!1)}else if(this.needsFocus(n))return n.focus(),"select"!==n.tagName.toLowerCase()&&t.preventDefault(),!1;return this.maybeSendClick(n,t)?(t.preventDefault(),!1):!1},FastClick.prototype.onTouchCancel=function(){"use strict";this.trackingClick=!1,this.targetElement=null},FastClick.prototype.onClick=function(t){"use strict";var e;return t.forwardedTouchEvent?!0:this.targetElement?(e=this.targetElement,this.targetElement=null,t.cancelable?this.needsClick(e)?!0:(t.stopImmediatePropagation&&t.stopImmediatePropagation(),t.stopPropagation(),t.preventDefault(),!1):!0):!0},FastClick.prototype.destroy=function(){"use strict";var t=this.layer;t.removeEventListener("click",this.onClick,!0),t.removeEventListener("touchstart",this.onTouchStart,!0),t.removeEventListener("touchmove",this.onTouchMove,!0),t.removeEventListener("touchend",this.onTouchEnd,!0),t.removeEventListener("touchcancel",this.onTouchCancel,!0)},"function"==typeof define&&define.amd&&define(function(){"use strict";return FastClick}); \ No newline at end of file diff --git a/ajax/libs/fastclick/0.3.3/fastclick.js b/ajax/libs/fastclick/0.3.3/fastclick.js new file mode 100644 index 000000000..6c4c1eae5 --- /dev/null +++ b/ajax/libs/fastclick/0.3.3/fastclick.js @@ -0,0 +1,371 @@ +/** + * @preserve FastClick: polyfill to remove click delays on browsers with touch UIs. + * + * @version 0.3.3 + * @copyright The Financial Times Limited [All Rights Reserved] + * @license MIT License (see LICENSE.txt) + */ + +/*jslint browser:true*/ +/*global Node, define*/ + + +/** + * Instantiate fast-clicking listeners on the specificed layer. + * + * @constructor + * @param {Element} layer The layer to listen on + */ +function FastClick(layer) { + 'use strict'; + var oldOnClick, that = this; + + + /** + * Whether a click is currently being tracked. + * + * @type boolean + */ + this.trackingClick = false, + + + /** + * The element being tracked for a click. + * + * @type Element + */ + this.targetElement = null; + + + /** + * The FastClick layer. + * + * @type Element + */ + this.layer = layer; + + if (!layer || !layer.nodeType) { + throw new TypeError('Layer must be a document node'); + } + + // Bind handlers to this instance + this.onClick = function() { FastClick.prototype.onClick.apply(that, arguments); }; + this.onTouchStart = function() { FastClick.prototype.onTouchStart.apply(that, arguments); }; + this.onTouchMove = function() { FastClick.prototype.onTouchMove.apply(that, arguments); }; + this.onTouchEnd = function() { FastClick.prototype.onTouchEnd.apply(that, arguments); }; + this.onTouchCancel = function() { FastClick.prototype.onTouchCancel.apply(that, arguments); }; + + // Devices that don't support touch don't need FastClick + if (typeof window.ontouchstart === 'undefined') { + return; + } + + // Set up event handlers as required + layer.addEventListener('click', this.onClick, true); + layer.addEventListener('touchstart', this.onTouchStart, true); + layer.addEventListener('touchmove', this.onTouchMove, true); + layer.addEventListener('touchend', this.onTouchEnd, true); + layer.addEventListener('touchcancel', this.onTouchCancel, true); + + // If a handler is already declared in the element's onclick attribute, it will be fired before + // FastClick's onClick handler. Fix this by pulling out the user-defined handler function and + // adding it as listener. + if (typeof layer.onclick === 'function') { + + // Android browser on at least 3.2 requires a new reference to the function in layer.onclick + // - the old one won't work if passed to addEventListener directly. + oldOnClick = layer.onclick; + layer.addEventListener('click', function(event) { + oldOnClick(event); + }, false); + layer.onclick = null; + } +} + + +/** + * Android requires an exception for labels. + * + * @type boolean + */ +FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0; + + +/** + * Determine whether a given element requires a native click. + * + * @param {Element} target DOM element + * @returns {boolean} Returns true if the element needs a native click + */ +FastClick.prototype.needsClick = function(target) { + 'use strict'; + switch (target.nodeName.toLowerCase()) { + case 'label': + case 'video': + return true; + default: + return (/\bneedsclick\b/).test(target.className); + } +}; + + +/** + * Determine whether a given element requires a call to focus to simulate click into element. + * + * @param {Element} target target DOM element. + * @returns {boolean} Returns true if the element requires a call to focus to simulate native click. + */ +FastClick.prototype.needsFocus = function(target) { + 'use strict'; + switch(target.nodeName.toLowerCase()) { + case 'textarea': + case 'select': + return true; + case 'input': + switch (target.type) { + case 'button': + case 'checkbox': + case 'file': + case 'image': + case 'radio': + case 'submit': + return false; + default: + return true; + } + break; + default: + return (/\bneedsfocus\b/).test(target.className); + } +}; + + +/** + * Send a click event to the element if it needs it. + * + * @returns {boolean} Whether the click was sent or not + */ +FastClick.prototype.maybeSendClick = function(targetElement, event) { + 'use strict'; + var clickEvent, touch; + + // Prevent the actual click from going though - unless the target node is marked as requiring + // real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted + // to open the options list and so the original event is required. + if (this.needsClick(targetElement)) { + return false; + } + + touch = event.changedTouches[0]; + + // Synthesise a click event, with an extra attribute so it can be tracked + clickEvent = document.createEvent('MouseEvents'); + clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null); + clickEvent.forwardedTouchEvent = true; + targetElement.dispatchEvent(clickEvent); + + return true; +}; + + +/** + * On touch start, record the position and scroll offset. + * + * @param {Event} event + * @returns {boolean} + */ +FastClick.prototype.onTouchStart = function(event) { + 'use strict'; + var touch = event.targetTouches[0]; + + this.trackingClick = true; + this.targetElement = event.target; + + this.touchStartX = touch.pageX; + this.touchStartY = touch.pageY; + + return true; +}; + + +/** + * Based on a touchmove event object, check whether the touch has moved past a boundary since it started. + * + * @param {Event} event + * @returns {boolean} + */ +FastClick.prototype.touchHasMoved = function(event) { + 'use strict'; + var touch = event.targetTouches[0]; + + if (Math.abs(touch.pageX - this.touchStartX) > 10 + || Math.abs(touch.pageY - this.touchStartY) > 10) { + return true; + } + + return false; +}; + + +/** + * Update the last position. + * + * @param {Event} event + * @returns {boolean} + */ +FastClick.prototype.onTouchMove = function(event) { + 'use strict'; + if (!this.trackingClick) { + return true; + } + + // If the touch has moved, cancel the click tracking + if (this.targetElement !== event.target || this.touchHasMoved(event)) { + this.trackingClick = false; + this.targetElement = null; + } + + return true; +}; + + +/** + * On touch end, determine whether to send a click event at once. + * + * @param {Event} event + * @returns {boolean} + */ +FastClick.prototype.onTouchEnd = function(event) { + 'use strict'; + var forElement, targetElement = this.targetElement; + + if (!this.trackingClick) { + return true; + } + + this.trackingClick = false; + + if (targetElement.nodeName.toLowerCase() === 'label' && targetElement.htmlFor) { + forElement = document.getElementById(targetElement.htmlFor); + if (forElement) { + targetElement.focus(); + if (this.android) { + return false; + } + + if (this.maybeSendClick(forElement, event)) { + event.preventDefault(); + } + + return false; + } + } else if (this.needsFocus(targetElement)) { + targetElement.focus(); + if (targetElement.tagName.toLowerCase() !== 'select') { + event.preventDefault(); + } + return false; + } + + if (!this.maybeSendClick(targetElement, event)) { + return false; + } + + event.preventDefault(); + return false; +}; + + +/** + * On touch cancel, stop tracking the click. + * + * @returns {void} + */ +FastClick.prototype.onTouchCancel = function() { + 'use strict'; + this.trackingClick = false; + this.targetElement = null; +}; + + +/** + * On actual clicks, determine whether this is a touch-generated click, a click action occurring + * naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or + * an actual click which should be permitted. + * + * @param {Event} event + * @returns {boolean} + */ +FastClick.prototype.onClick = function(event) { + 'use strict'; + var oldTargetElement; + + if (event.forwardedTouchEvent) { + return true; + } + + // If a target element was never set (because a touch event was never fired) allow the click + if (!this.targetElement) { + return true; + } + + oldTargetElement = this.targetElement; + this.targetElement = null; + + // Programmatically generated events targeting a specific element should be permitted + if (!event.cancelable) { + return true; + } + + // Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of 'fake' click event will be triggered with the submit-type input element as the target. + if (event.target.type === 'submit' && event.detail === 0) { + return true; + } + + // Derive and check the target element to see whether the click needs to be permitted; + // unless explicitly enabled, prevent non-touch click events from triggering actions, + // to prevent ghost/doubleclicks. + if (!this.needsClick(oldTargetElement)) { + + // Prevent any user-added listeners declared on FastClick element from being fired. + if (event.stopImmediatePropagation) { + event.stopImmediatePropagation(); + } + + // Cancel the event + event.stopPropagation(); + event.preventDefault(); + + return false; + } + + // If clicks are permitted, return true for the action to go through. + return true; +}; + + +/** + * Remove all FastClick's event listeners. + * + * @returns {void} + */ +FastClick.prototype.destroy = function() { + 'use strict'; + var layer = this.layer; + + layer.removeEventListener('click', this.onClick, true); + layer.removeEventListener('touchstart', this.onTouchStart, true); + layer.removeEventListener('touchmove', this.onTouchMove, true); + layer.removeEventListener('touchend', this.onTouchEnd, true); + layer.removeEventListener('touchcancel', this.onTouchCancel, true); +}; + + +if (typeof define === 'function' && define.amd) { + + // AMD. Register as an anonymous module. + define(function() { + 'use strict'; + return FastClick; + }); +} diff --git a/ajax/libs/fastclick/0.3.3/fastclick.min.js b/ajax/libs/fastclick/0.3.3/fastclick.min.js new file mode 100644 index 000000000..6feaffc38 --- /dev/null +++ b/ajax/libs/fastclick/0.3.3/fastclick.min.js @@ -0,0 +1 @@ +function FastClick(t){"use strict";var e,n=this;if(this.trackingClick=!1,this.targetElement=null,this.layer=t,!t||!t.nodeType)throw new TypeError("Layer must be a document node");this.onClick=function(){FastClick.prototype.onClick.apply(n,arguments)},this.onTouchStart=function(){FastClick.prototype.onTouchStart.apply(n,arguments)},this.onTouchMove=function(){FastClick.prototype.onTouchMove.apply(n,arguments)},this.onTouchEnd=function(){FastClick.prototype.onTouchEnd.apply(n,arguments)},this.onTouchCancel=function(){FastClick.prototype.onTouchCancel.apply(n,arguments)},"undefined"!=typeof window.ontouchstart&&(t.addEventListener("click",this.onClick,!0),t.addEventListener("touchstart",this.onTouchStart,!0),t.addEventListener("touchmove",this.onTouchMove,!0),t.addEventListener("touchend",this.onTouchEnd,!0),t.addEventListener("touchcancel",this.onTouchCancel,!0),"function"==typeof t.onclick&&(e=t.onclick,t.addEventListener("click",function(t){e(t)},!1),t.onclick=null))}FastClick.prototype.deviceIsAndroid=navigator.userAgent.indexOf("Android")>0,FastClick.prototype.needsClick=function(t){"use strict";switch(t.nodeName.toLowerCase()){case"label":case"video":return!0;default:return/\bneedsclick\b/.test(t.className)}},FastClick.prototype.needsFocus=function(t){"use strict";switch(t.nodeName.toLowerCase()){case"textarea":case"select":return!0;case"input":switch(t.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1;default:return!0}break;default:return/\bneedsfocus\b/.test(t.className)}},FastClick.prototype.maybeSendClick=function(t,e){"use strict";var n,o;return this.needsClick(t)?!1:(o=e.changedTouches[0],n=document.createEvent("MouseEvents"),n.initMouseEvent("click",!0,!0,window,1,o.screenX,o.screenY,o.clientX,o.clientY,!1,!1,!1,!1,0,null),n.forwardedTouchEvent=!0,t.dispatchEvent(n),!0)},FastClick.prototype.onTouchStart=function(t){"use strict";var e=t.targetTouches[0];return this.trackingClick=!0,this.targetElement=t.target,this.touchStartX=e.pageX,this.touchStartY=e.pageY,!0},FastClick.prototype.touchHasMoved=function(t){"use strict";var e=t.targetTouches[0];return Math.abs(e.pageX-this.touchStartX)>10||Math.abs(e.pageY-this.touchStartY)>10?!0:!1},FastClick.prototype.onTouchMove=function(t){"use strict";return this.trackingClick?((this.targetElement!==t.target||this.touchHasMoved(t))&&(this.trackingClick=!1,this.targetElement=null),!0):!0},FastClick.prototype.onTouchEnd=function(t){"use strict";var e,n=this.targetElement;if(!this.trackingClick)return!0;if(this.trackingClick=!1,"label"===n.nodeName.toLowerCase()&&n.htmlFor){if(e=document.getElementById(n.htmlFor))return n.focus(),this.android?!1:(this.maybeSendClick(e,t)&&t.preventDefault(),!1)}else if(this.needsFocus(n))return n.focus(),"select"!==n.tagName.toLowerCase()&&t.preventDefault(),!1;return this.maybeSendClick(n,t)?(t.preventDefault(),!1):!1},FastClick.prototype.onTouchCancel=function(){"use strict";this.trackingClick=!1,this.targetElement=null},FastClick.prototype.onClick=function(t){"use strict";var e;return t.forwardedTouchEvent?!0:this.targetElement?(e=this.targetElement,this.targetElement=null,t.cancelable?"submit"===t.target.type&&0===t.detail?!0:this.needsClick(e)?!0:(t.stopImmediatePropagation&&t.stopImmediatePropagation(),t.stopPropagation(),t.preventDefault(),!1):!0):!0},FastClick.prototype.destroy=function(){"use strict";var t=this.layer;t.removeEventListener("click",this.onClick,!0),t.removeEventListener("touchstart",this.onTouchStart,!0),t.removeEventListener("touchmove",this.onTouchMove,!0),t.removeEventListener("touchend",this.onTouchEnd,!0),t.removeEventListener("touchcancel",this.onTouchCancel,!0)},"function"==typeof define&&define.amd&&define(function(){"use strict";return FastClick}); \ No newline at end of file diff --git a/ajax/libs/fastclick/0.3.5/fastclick.js b/ajax/libs/fastclick/0.3.5/fastclick.js new file mode 100644 index 000000000..a23e1a90d --- /dev/null +++ b/ajax/libs/fastclick/0.3.5/fastclick.js @@ -0,0 +1,376 @@ +/** + * @preserve FastClick: polyfill to remove click delays on browsers with touch UIs. + * + * @version 0.3.5 + * @copyright The Financial Times Limited [All Rights Reserved] + * @license MIT License (see LICENSE.txt) + */ + +/*jslint browser:true*/ +/*global define*/ + + +/** + * Instantiate fast-clicking listeners on the specificed layer. + * + * @constructor + * @param {Element} layer The layer to listen on + */ +function FastClick(layer) { + 'use strict'; + var oldOnClick, that = this; + + + /** + * Whether a click is currently being tracked. + * + * @type boolean + */ + this.trackingClick = false, + + + /** + * The element being tracked for a click. + * + * @type Element + */ + this.targetElement = null; + + + /** + * The FastClick layer. + * + * @type Element + */ + this.layer = layer; + + if (!layer || !layer.nodeType) { + throw new TypeError('Layer must be a document node'); + } + + // Bind handlers to this instance + this.onClick = function() { FastClick.prototype.onClick.apply(that, arguments); }; + this.onTouchStart = function() { FastClick.prototype.onTouchStart.apply(that, arguments); }; + this.onTouchMove = function() { FastClick.prototype.onTouchMove.apply(that, arguments); }; + this.onTouchEnd = function() { FastClick.prototype.onTouchEnd.apply(that, arguments); }; + this.onTouchCancel = function() { FastClick.prototype.onTouchCancel.apply(that, arguments); }; + + // Devices that don't support touch don't need FastClick + if (typeof window.ontouchstart === 'undefined') { + return; + } + + // Set up event handlers as required + layer.addEventListener('click', this.onClick, true); + layer.addEventListener('touchstart', this.onTouchStart, true); + layer.addEventListener('touchmove', this.onTouchMove, true); + layer.addEventListener('touchend', this.onTouchEnd, true); + layer.addEventListener('touchcancel', this.onTouchCancel, true); + + // If a handler is already declared in the element's onclick attribute, it will be fired before + // FastClick's onClick handler. Fix this by pulling out the user-defined handler function and + // adding it as listener. + if (typeof layer.onclick === 'function') { + + // Android browser on at least 3.2 requires a new reference to the function in layer.onclick + // - the old one won't work if passed to addEventListener directly. + oldOnClick = layer.onclick; + layer.addEventListener('click', function(event) { + oldOnClick(event); + }, false); + layer.onclick = null; + } +} + + +/** + * Android requires an exception for labels. + * + * @type boolean + */ +FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0; + + +/** + * Determine whether a given element requires a native click. + * + * @param {Element} target DOM element + * @returns {boolean} Returns true if the element needs a native click + */ +FastClick.prototype.needsClick = function(target) { + 'use strict'; + switch (target.nodeName.toLowerCase()) { + case 'label': + case 'video': + return true; + default: + return (/\bneedsclick\b/).test(target.className); + } +}; + + +/** + * Determine whether a given element requires a call to focus to simulate click into element. + * + * @param {Element} target target DOM element. + * @returns {boolean} Returns true if the element requires a call to focus to simulate native click. + */ +FastClick.prototype.needsFocus = function(target) { + 'use strict'; + switch(target.nodeName.toLowerCase()) { + case 'textarea': + case 'select': + return true; + case 'input': + switch (target.type) { + case 'button': + case 'checkbox': + case 'file': + case 'image': + case 'radio': + case 'submit': + return false; + default: + return true; + } + break; + default: + return (/\bneedsfocus\b/).test(target.className); + } +}; + + +/** + * Send a click event to the element if it needs it. + * + * @returns {boolean} Whether the click was sent or not + */ +FastClick.prototype.maybeSendClick = function(targetElement, event) { + 'use strict'; + var clickEvent, touch; + + // Prevent the actual click from going though - unless the target node is marked as requiring + // real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted + // to open the options list and so the original event is required. + if (this.needsClick(targetElement)) { + return false; + } + + // On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24) + if (document.activeElement && document.activeElement !== targetElement) { + document.activeElement.blur(); + } + + touch = event.changedTouches[0]; + + // Synthesise a click event, with an extra attribute so it can be tracked + clickEvent = document.createEvent('MouseEvents'); + clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null); + clickEvent.forwardedTouchEvent = true; + targetElement.dispatchEvent(clickEvent); + + return true; +}; + + +/** + * On touch start, record the position and scroll offset. + * + * @param {Event} event + * @returns {boolean} + */ +FastClick.prototype.onTouchStart = function(event) { + 'use strict'; + var touch = event.targetTouches[0]; + + this.trackingClick = true; + this.targetElement = event.target; + + this.touchStartX = touch.pageX; + this.touchStartY = touch.pageY; + + return true; +}; + + +/** + * Based on a touchmove event object, check whether the touch has moved past a boundary since it started. + * + * @param {Event} event + * @returns {boolean} + */ +FastClick.prototype.touchHasMoved = function(event) { + 'use strict'; + var touch = event.targetTouches[0]; + + if (Math.abs(touch.pageX - this.touchStartX) > 10 + || Math.abs(touch.pageY - this.touchStartY) > 10) { + return true; + } + + return false; +}; + + +/** + * Update the last position. + * + * @param {Event} event + * @returns {boolean} + */ +FastClick.prototype.onTouchMove = function(event) { + 'use strict'; + if (!this.trackingClick) { + return true; + } + + // If the touch has moved, cancel the click tracking + if (this.targetElement !== event.target || this.touchHasMoved(event)) { + this.trackingClick = false; + this.targetElement = null; + } + + return true; +}; + + +/** + * On touch end, determine whether to send a click event at once. + * + * @param {Event} event + * @returns {boolean} + */ +FastClick.prototype.onTouchEnd = function(event) { + 'use strict'; + var forElement, targetElement = this.targetElement; + + if (!this.trackingClick) { + return true; + } + + this.trackingClick = false; + + if (targetElement.nodeName.toLowerCase() === 'label' && targetElement.htmlFor) { + forElement = document.getElementById(targetElement.htmlFor); + if (forElement) { + targetElement.focus(); + if (this.deviceIsAndroid) { + return false; + } + + if (this.maybeSendClick(forElement, event)) { + event.preventDefault(); + } + + return false; + } + } else if (this.needsFocus(targetElement)) { + targetElement.focus(); + if (targetElement.tagName.toLowerCase() !== 'select') { + event.preventDefault(); + } + return false; + } + + if (!this.maybeSendClick(targetElement, event)) { + return false; + } + + event.preventDefault(); + return false; +}; + + +/** + * On touch cancel, stop tracking the click. + * + * @returns {void} + */ +FastClick.prototype.onTouchCancel = function() { + 'use strict'; + this.trackingClick = false; + this.targetElement = null; +}; + + +/** + * On actual clicks, determine whether this is a touch-generated click, a click action occurring + * naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or + * an actual click which should be permitted. + * + * @param {Event} event + * @returns {boolean} + */ +FastClick.prototype.onClick = function(event) { + 'use strict'; + var oldTargetElement; + + if (event.forwardedTouchEvent) { + return true; + } + + // If a target element was never set (because a touch event was never fired) allow the click + if (!this.targetElement) { + return true; + } + + oldTargetElement = this.targetElement; + this.targetElement = null; + + // Programmatically generated events targeting a specific element should be permitted + if (!event.cancelable) { + return true; + } + + // Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of 'fake' click event will be triggered with the submit-type input element as the target. + if (event.target.type === 'submit' && event.detail === 0) { + return true; + } + + // Derive and check the target element to see whether the click needs to be permitted; + // unless explicitly enabled, prevent non-touch click events from triggering actions, + // to prevent ghost/doubleclicks. + if (!this.needsClick(oldTargetElement)) { + + // Prevent any user-added listeners declared on FastClick element from being fired. + if (event.stopImmediatePropagation) { + event.stopImmediatePropagation(); + } + + // Cancel the event + event.stopPropagation(); + event.preventDefault(); + + return false; + } + + // If clicks are permitted, return true for the action to go through. + return true; +}; + + +/** + * Remove all FastClick's event listeners. + * + * @returns {void} + */ +FastClick.prototype.destroy = function() { + 'use strict'; + var layer = this.layer; + + layer.removeEventListener('click', this.onClick, true); + layer.removeEventListener('touchstart', this.onTouchStart, true); + layer.removeEventListener('touchmove', this.onTouchMove, true); + layer.removeEventListener('touchend', this.onTouchEnd, true); + layer.removeEventListener('touchcancel', this.onTouchCancel, true); +}; + + +if (typeof define === 'function' && define.amd) { + + // AMD. Register as an anonymous module. + define(function() { + 'use strict'; + return FastClick; + }); +} diff --git a/ajax/libs/fastclick/0.3.5/fastclick.min.js b/ajax/libs/fastclick/0.3.5/fastclick.min.js new file mode 100644 index 000000000..e6e818784 --- /dev/null +++ b/ajax/libs/fastclick/0.3.5/fastclick.min.js @@ -0,0 +1 @@ +function FastClick(t){"use strict";var e,n=this;if(this.trackingClick=!1,this.targetElement=null,this.layer=t,!t||!t.nodeType)throw new TypeError("Layer must be a document node");this.onClick=function(){FastClick.prototype.onClick.apply(n,arguments)},this.onTouchStart=function(){FastClick.prototype.onTouchStart.apply(n,arguments)},this.onTouchMove=function(){FastClick.prototype.onTouchMove.apply(n,arguments)},this.onTouchEnd=function(){FastClick.prototype.onTouchEnd.apply(n,arguments)},this.onTouchCancel=function(){FastClick.prototype.onTouchCancel.apply(n,arguments)},"undefined"!=typeof window.ontouchstart&&(t.addEventListener("click",this.onClick,!0),t.addEventListener("touchstart",this.onTouchStart,!0),t.addEventListener("touchmove",this.onTouchMove,!0),t.addEventListener("touchend",this.onTouchEnd,!0),t.addEventListener("touchcancel",this.onTouchCancel,!0),"function"==typeof t.onclick&&(e=t.onclick,t.addEventListener("click",function(t){e(t)},!1),t.onclick=null))}FastClick.prototype.deviceIsAndroid=navigator.userAgent.indexOf("Android")>0,FastClick.prototype.needsClick=function(t){"use strict";switch(t.nodeName.toLowerCase()){case"label":case"video":return!0;default:return/\bneedsclick\b/.test(t.className)}},FastClick.prototype.needsFocus=function(t){"use strict";switch(t.nodeName.toLowerCase()){case"textarea":case"select":return!0;case"input":switch(t.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1;default:return!0}break;default:return/\bneedsfocus\b/.test(t.className)}},FastClick.prototype.maybeSendClick=function(t,e){"use strict";var n,o;return this.needsClick(t)?!1:(document.activeElement&&document.activeElement!==t&&document.activeElement.blur(),o=e.changedTouches[0],n=document.createEvent("MouseEvents"),n.initMouseEvent("click",!0,!0,window,1,o.screenX,o.screenY,o.clientX,o.clientY,!1,!1,!1,!1,0,null),n.forwardedTouchEvent=!0,t.dispatchEvent(n),!0)},FastClick.prototype.onTouchStart=function(t){"use strict";var e=t.targetTouches[0];return this.trackingClick=!0,this.targetElement=t.target,this.touchStartX=e.pageX,this.touchStartY=e.pageY,!0},FastClick.prototype.touchHasMoved=function(t){"use strict";var e=t.targetTouches[0];return Math.abs(e.pageX-this.touchStartX)>10||Math.abs(e.pageY-this.touchStartY)>10?!0:!1},FastClick.prototype.onTouchMove=function(t){"use strict";return this.trackingClick?((this.targetElement!==t.target||this.touchHasMoved(t))&&(this.trackingClick=!1,this.targetElement=null),!0):!0},FastClick.prototype.onTouchEnd=function(t){"use strict";var e,n=this.targetElement;if(!this.trackingClick)return!0;if(this.trackingClick=!1,"label"===n.nodeName.toLowerCase()&&n.htmlFor){if(e=document.getElementById(n.htmlFor))return n.focus(),this.deviceIsAndroid?!1:(this.maybeSendClick(e,t)&&t.preventDefault(),!1)}else if(this.needsFocus(n))return n.focus(),"select"!==n.tagName.toLowerCase()&&t.preventDefault(),!1;return this.maybeSendClick(n,t)?(t.preventDefault(),!1):!1},FastClick.prototype.onTouchCancel=function(){"use strict";this.trackingClick=!1,this.targetElement=null},FastClick.prototype.onClick=function(t){"use strict";var e;return t.forwardedTouchEvent?!0:this.targetElement?(e=this.targetElement,this.targetElement=null,t.cancelable?"submit"===t.target.type&&0===t.detail?!0:this.needsClick(e)?!0:(t.stopImmediatePropagation&&t.stopImmediatePropagation(),t.stopPropagation(),t.preventDefault(),!1):!0):!0},FastClick.prototype.destroy=function(){"use strict";var t=this.layer;t.removeEventListener("click",this.onClick,!0),t.removeEventListener("touchstart",this.onTouchStart,!0),t.removeEventListener("touchmove",this.onTouchMove,!0),t.removeEventListener("touchend",this.onTouchEnd,!0),t.removeEventListener("touchcancel",this.onTouchCancel,!0)},"function"==typeof define&&define.amd&&define(function(){"use strict";return FastClick}); \ No newline at end of file