fast-buttons.js - Ubuntu HTML5 API

# Ubuntu HTML5 API: fast-buttons.js

API Docs for: HTML-14.04-dev~bzr202

#### fast-buttons.js

    /*
     * Copyright (C) 2013 Adnane Belmadiaf <daker@ubuntu.com>
     * License granted by Canonical Limited
     *
     * This file is part of ubuntu-html5-ui-toolkit.
     *
     * This package is free software; you can redistribute it and/or modify
     * it under the terms of the GNU Lesser General Public License as
     * published by the Free Software Foundation; either version 3 of the
     * License, or
     * (at your option) any later version.

     * This package is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.

     * You should have received a copy of the GNU Lesser General Public
     * License along with this program. If not, see
     * <http://www.gnu.org/licenses/>.
     */

    (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 */

        function addListener(el, type, listener, useCapture) {
            if (el.addEventListener) {
                el.addEventListener(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);
                };
                el.attachEvent('on' + type, handler);

                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
            this.events = [];
            this.touchEvents = [];
            this.element = element;
            this.handler = handler;
            this.useCapture = 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 */
        this.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 = this.fastButton = null;
        };

        /* acts as an event dispatcher */
        this.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;
            }
        };

        /* 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));
            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) {
                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);
            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
            var result = this.handler.call(this.element, event);
            if (event.type == 'touchend')
                clickbuster.preventGhostClick(this.startX, this.startY);
            return result;
        };

        this.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);
                }
            }
        };

        if (isTouch) {
            // 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);