diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25588d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/node_modules/ + +/bower_components/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6f26aac --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: node_js +node_js: + - "0.11" +before_script: + - npm install -g grunt-cli + - npm install -g bower + - bower install diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..cd0c434 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,28 @@ +module.exports = function(grunt) { + grunt.initConfig({ + jshint: { + with_overrides: { + files: { + src: [ + 'google.fastbutton.js', + 'jquery.google.fastbutton.js', + 'xui.google.fastbutton.js' + ] + } + } + }, + + qunit: { + all: ['tests/*.html'] + } + }); + + grunt.loadNpmTasks('grunt-contrib-qunit'); + + grunt.loadNpmTasks('grunt-contrib-jshint'); + + grunt.registerTask('travis', [ + 'jshint', + 'qunit' + ]); +}; diff --git a/README.md b/README.md index 54dcc4e..35eeed5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Google FastButton +[![Build Status](https://travis-ci.org/alexsomeoddpilot/google-fastbutton.svg?branch=master)](https://travis-ci.org/alexsomeoddpilot/google-fastbutton) + An implementation of [Google's FastButton javascript](http://code.google.com/mobile/articles/fast_buttons.html), to avoid the 300ms touch delay on Android and iOS devices. Code forked from: http://stackoverflow.com/questions/6300136/trying-to-implement-googles-fast-button - Doesn't break in IE6-8, Chrome 17, Firefox 11, or Windows Phone 7.5 diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..ce35503 --- /dev/null +++ b/bower.json @@ -0,0 +1,20 @@ +{ + "name": "fast-button", + "version": "0.0.0", + "homepage": "https://github.com/alexsomeoddpilot/google-fastbutton", + "authors": [ + "Alex Robertson " + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "devDependencies": { + "qunit": "~1.14.0", + "blanket": "~1.1.5" + } +} diff --git a/google.fastbutton.js b/google.fastbutton.js index 6cfc1bf..7191bd5 100644 --- a/google.fastbutton.js +++ b/google.fastbutton.js @@ -1,124 +1,197 @@ -(function() { - /** - * From: http://code.this.com/mobile/articles/fast_buttons.html - * Also see: http://stackoverflow.com/questions/6300136/trying-to-implement-googles-fast-button - */ - - /** For IE8 and earlier compatibility: https://developer.mozilla.org/en/DOM/element.addEventListener */ +/* jshint +quotmark: single + */ + +(function (root, factory) { + if (typeof exports === 'object') { + // CommonJS + module.exports = factory(); + } else if (typeof define === 'function' && define.amd) { + // AMD + define(function() { + return (root.FastButton = factory()); + }); + } else { + // Global variable + root.FastButton = factory(); + } +}(this, function() { + 'use strict'; + + // From: + // http://code.this.com/mobile/articles/fast_buttons.html + // + // Also see: + // http://stackoverflow.com/questions/6300136/trying-to-implement-googles-fast-button + + // For IE8 and earlier compatibility: + // https://developer.mozilla.org/en/DOM/element.addEventListener function addListener(el, type, listener, useCapture) { - if (el.addEventListener) { + if (el.addEventListener) { el.addEventListener(type, listener, useCapture); - return { - destroy: function() { el.removeEventListener(type, listener, useCapture); } + return { + destroy: function() { + el.removeEventListener(type, listener, useCapture); + } }; - } else { - // see: http://stackoverflow.com/questions/5198845/javascript-this-losing-context-in-ie - var handler = function(e) { listener.handleEvent(window.event, listener); } + } else { + // see: + // http://stackoverflow.com/questions/5198845/javascript-this-losing-context-in-ie + var handler = function() { + listener.handleEvent(window.event, listener); + }; + el.attachEvent('on' + type, handler); - - return { + + return { destroy: function() { el.detachEvent('on' + type, handler); } }; - } + } } - - var isTouch = "ontouchstart" in window; - /* Construct the FastButton with a reference to the element and click handler. */ - this.FastButton = function(element, handler, useCapture) { - // collect functions to call to cleanup events + var isTouch = 'ontouchstart' in window; + + // Construct the FastButton with a reference to the element and click handler. + function FastButton(element, handler, useCapture) { + // collect functions to call to cleanup events this.events = []; this.touchEvents = []; this.element = element; this.handler = handler; this.useCapture = useCapture; - if (isTouch) - this.events.push(addListener(element, 'touchstart', this, this.useCapture)); + if (isTouch) { + this.events.push( + addListener(element, 'touchstart', this, this.useCapture) + ); + } this.events.push(addListener(element, 'click', this, this.useCapture)); + } + + // Remove event handling when no longer needed for this button + FastButton.prototype.destroy = function() { + for (var i = this.events.length - 1; i >= 0; i -= 1) { + this.events[i].destroy(); + } + this.events = + this.touchEvents = + this.element = + this.handler = null; }; - - /* Remove event handling when no longer needed for this button */ - this.FastButton.prototype.destroy = function() { - for (i = this.events.length - 1; i >= 0; i -= 1) - this.events[i].destroy(); - this.events = this.touchEvents = this.element = this.handler = this.fastButton = null; - }; - - /* acts as an event dispatcher */ - this.FastButton.prototype.handleEvent = function(event) { + + // acts as an event dispatcher + FastButton.prototype.handleEvent = function(event) { switch (event.type) { - case 'touchstart': this.onTouchStart(event); break; - case 'touchmove': this.onTouchMove(event); break; - case 'touchend': this.onClick(event); break; - case 'click': this.onClick(event); break; + case 'touchstart': + this.onTouchStart(event); + break; + case 'touchmove': + this.onTouchMove(event); + break; + case 'touchend': + this.onClick(event); + break; + case 'click': + this.onClick(event); + break; } }; - - /* Save a reference to the touchstart coordinate and start listening to touchmove and - touchend events. Calling stopPropagation guarantees that other behaviors don’t get a - chance to handle the same click event. This is executed at the beginning of touch. */ - this.FastButton.prototype.onTouchStart = function(event) { - event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true); - this.touchEvents.push(addListener(this.element, 'touchend', this, this.useCapture)); - this.touchEvents.push(addListener(document.body, 'touchmove', this, this.useCapture)); + + // Save a reference to the touchstart coordinate and start listening + // to touchmove and touchend events. Calling stopPropagation guarantees + // that other behaviors don’t get a chance to handle the same click event. + // This is executed at the beginning of touch. + FastButton.prototype.onTouchStart = function(event) { + if (event.stopPropagation) { + event.stopPropagation(); + } else { + event.cancelBubble = true; + } + this.touchEvents.push( + addListener(this.element, 'touchend', this, this.useCapture) + ); + this.touchEvents.push( + addListener(document.body, 'touchmove', this, this.useCapture) + ); this.startX = event.touches[0].clientX; this.startY = event.touches[0].clientY; }; - - /* When /if touchmove event is invoked, check if the user has dragged past the threshold of 10px. */ - this.FastButton.prototype.onTouchMove = function(event) { - if (Math.abs(event.touches[0].clientX - this.startX) > 10 || Math.abs(event.touches[0].clientY - this.startY) > 10) { + + // When/if touchmove event is invoked, check if the user has dragged past + // the threshold of 10px. + FastButton.prototype.onTouchMove = function(event) { + if (Math.abs(event.touches[0].clientX - this.startX) > 10 || + Math.abs(event.touches[0].clientY - this.startY) > 10) { this.reset(); //if he did, then cancel the touch event } }; - - /* Invoke the actual click handler and prevent ghost clicks if this was a touchend event. */ - this.FastButton.prototype.onClick = function(event) { - event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true); + + // Invoke the actual click handler and prevent ghost clicks if + // this was a touchend event. + FastButton.prototype.onClick = function(event) { + if (event.stopPropagation) { + event.stopPropagation(); + } else { + event.cancelBubble = true; + } this.reset(); - // Use .call to call the method so that we have the correct "this": https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/call + // Use .call to call the method so that we have the correct "this": + // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/call var result = this.handler.call(this.element, event); - if (event.type == 'touchend') - clickbuster.preventGhostClick(this.startX, this.startY); + if (event.type === 'touchend') { + clickbuster.preventGhostClick(this.startX, this.startY); + } return result; }; - - this.FastButton.prototype.reset = function() { - for (i = this.touchEvents.length - 1; i >= 0; i -= 1) - this.touchEvents[i].destroy(); + + FastButton.prototype.reset = function() { + for (var i = this.touchEvents.length - 1; i >= 0; i -= 1) { + this.touchEvents[i].destroy(); + } this.touchEvents = []; }; - - this.clickbuster = function() {} - - /* Call preventGhostClick to bust all click events that happen within 25px of - the provided x, y coordinates in the next 2.5s. */ - this.clickbuster.preventGhostClick = function(x, y) { - clickbuster.coordinates.push(x, y); - window.setTimeout(clickbuster.pop, 2500); - }; - - this.clickbuster.pop = function() { - clickbuster.coordinates.splice(0, 2); - }; - - /* If we catch a click event inside the given radius and time threshold then we call - stopPropagation and preventDefault. Calling preventDefault will stop links - from being activated. */ - this.clickbuster.onClick = function(event) { - for (var i = 0; i < clickbuster.coordinates.length; i += 2) { - var x = clickbuster.coordinates[i]; - var y = clickbuster.coordinates[i + 1]; - if (Math.abs(event.clientX - x) < 25 && Math.abs(event.clientY - y) < 25) { - event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true); - event.preventDefault ? event.preventDefault() : (event.returnValue=false); + + var clickbuster = { + // Call preventGhostClick to bust all click events that happen within + // 25px of the provided x, y coordinates in the next 2.5s. + preventGhostClick: function (x, y) { + clickbuster.coordinates.push(x, y); + window.setTimeout(clickbuster.pop, 2500); + }, + + pop: function () { + clickbuster.coordinates.splice(0, 2); + }, + + // If we catch a click event inside the given radius and time threshold + // then we call stopPropagation and preventDefault. Calling preventDefault + // will stop links from being activated. + onClick: function (event) { + for (var i = 0; i < clickbuster.coordinates.length; i += 2) { + var x = clickbuster.coordinates[i]; + var y = clickbuster.coordinates[i + 1]; + if (Math.abs(event.clientX - x) < 25 && + Math.abs(event.clientY - y) < 25) { + if (event.stopPropagation) { + event.stopPropagation(); + } else { + event.cancelBubble = true; + } + if (event.preventDefault) { + event.preventDefault(); + } else { + event.returnValue = false; + } + } } } }; - + if (isTouch) { - // Don't need to use our custom addListener function since we only bust clicks on touch devices + // Don't need to use our custom addListener function + // since we only bust clicks on touch devices document.addEventListener('click', clickbuster.onClick, true); clickbuster.coordinates = []; } -})(this); + + return FastButton; +})); diff --git a/jquery.google.fastbutton.js b/jquery.google.fastbutton.js index e89bb52..9835e23 100644 --- a/jquery.google.fastbutton.js +++ b/jquery.google.fastbutton.js @@ -1,30 +1,44 @@ -(function($) { +(function (factory) { + if (typeof exports === 'object') { + // CommonJS + module.exports = factory(require('jquery')); + } else if (typeof define === 'function' && define.amd) { + // AMD + define(['jquery'], factory); + } else { + factory(jQuery); + } +}(function($) { + 'use strict'; + $.event.special.fastClick = { - setup: function () { - $(this).data('fastClick', new FastButton(this, $.event.special.fastClick.handler)); - }, - teardown: function () { - $(this).data('fastClick').destroy(); - $(this).removeData('fastClick'); - }, - handler: function (e) { - // convert native event to jquery event - e = $.event.fix(e); - e.type = 'fastClick'; - - /* - event.handle is deprecated and removed as of version 1.9 - use event.dispatch instead, - $.event.handle.apply(this, arguments); - */ - $.event.dispatch.apply(this, arguments); - } - }; + setup: function () { + $(this).data( + 'fastClick', + new window.FastButton(this, $.event.special.fastClick.handler) + ); + }, + teardown: function () { + $(this).data('fastClick').destroy(); + $(this).removeData('fastClick'); + }, + handler: function (e) { + // convert native event to jquery event + e = $.event.fix(e); + e.type = 'fastClick'; - $.fn.fastClick = function(fn) { - return $(this).each(function() { - return fn ? $(this).bind("fastClick", fn) : $(this).trigger("fastClick"); - }); - }; -}(jQuery)); + /* + event.handle is deprecated and removed as of version 1.9 + use event.dispatch instead, + $.event.handle.apply(this, arguments); + */ + $.event.dispatch.apply(this, arguments); + } + }; + $.fn.fastClick = function(fn) { + return $(this).each(function() { + return fn ? $(this).bind('fastClick', fn) : $(this).trigger('fastClick'); + }); + }; +})); diff --git a/package.json b/package.json new file mode 100644 index 0000000..970fa46 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "fast-button", + "version": "0.0.0", + "description": "An implementation of [Google's FastButton javascript](http://code.google.com/mobile/articles/fast_buttons.html), to avoid the 300ms touch delay on Android and iOS devices. Code forked from: http://stackoverflow.com/questions/6300136/trying-to-implement-googles-fast-button", + "main": "Gruntfile.js", + "scripts": { + "test": "grunt travis" + }, + "repository": { + "type": "git", + "url": "https://alex%40someoddpilot.com@github.com/alexsomeoddpilot/google-fastbutton.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/alexsomeoddpilot/google-fastbutton/issues" + }, + "homepage": "https://github.com/alexsomeoddpilot/google-fastbutton", + "devDependencies": { + "bower": "^1.3.3", + "grunt": "^0.4.5", + "grunt-contrib-jshint": "^0.11.0", + "grunt-contrib-qunit": "^0.5.2", + "jshint": "^2.5.1" + } +} diff --git a/tests/index.html b/tests/index.html new file mode 100644 index 0000000..264beb6 --- /dev/null +++ b/tests/index.html @@ -0,0 +1,19 @@ + + + + + Tests + + + + + Button + +
+ + + + + + + diff --git a/tests/tests.js b/tests/tests.js new file mode 100644 index 0000000..eefc496 --- /dev/null +++ b/tests/tests.js @@ -0,0 +1,32 @@ +(function (test, equal) { + "use strict"; + + test("element", function() { + var button = document.getElementById("button"); + + var handler = function handler() { + window.globalVar = true; + }; + + var testButton = new window.FastButton(button, handler); + + // equal(testButton.events.length, 1); + equal(testButton.element, button); + equal(testButton.handler, handler); + + var event = document.createEvent("HTMLEvents"); + event.initEvent("click", true, false); + button.dispatchEvent(event); + + equal(window.globalVar, true); + + testButton.destroy(); + + equal(testButton.element, null); + equal(testButton.events, null); + equal(testButton.handler, null); + equal(testButton.touchEvents, null); + }); + +}(window.test, window.equal)); + diff --git a/xui.google.fastbutton.js b/xui.google.fastbutton.js index 2dda484..92d99cf 100644 --- a/xui.google.fastbutton.js +++ b/xui.google.fastbutton.js @@ -1,16 +1,28 @@ /** Integrate google.fastbutton.js with XUI */ -(function(x$) { +(function (factory) { + if (typeof exports === 'object') { + // CommonJS + module.exports = factory(require('xui')); + } else if (typeof define === 'function' && define.amd) { + // AMD + define(['xui'], factory); + } else { + factory(xui); + } +}(function(x$) { + "use strict"; + x$.fn.fastClick = function(handler, useCapture) { return this.each(function(element, index, xui) { - element.fastButton = new FastButton(xui[index], handler, useCapture); + element.fastButton = new window.FastButton(xui[index], handler, useCapture); }); - } - - x$.fn.unFastClick = function() { - return this.each(function(element, index, xui) { + }; + + x$.fn.unFastClick = function() { + return this.each(function (element) { element.fastButton.destroy(); element.fastButton = null; }); - } -}(xui)); + }; +}));