toolbars.js - Ubuntu HTML5 API

# Ubuntu HTML5 API: toolbars.js

API Docs for: HTML-14.04-dev~bzr202

#### toolbars.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/>.
     */

    /**
     * A Toolbar is the JavaScript representation of an Ubuntu HTML5 app <em>footer</em>.

    ######Contained List provides buttons
    The Toolbar contains a List, where each list item is treated as a Button (see below). List items (Buttons) are pushed to the right. The default Back button always exists to the left and does not need to be declared.

    #####Default and custom footers
    See the Pagestack class documentation for information about the default application-wide Footer, customizing it, and adding Page-specific Footers.
     * @class Toolbar
     * @constructor
     * @namespace UbuntuUI
     * @example
          <footer data-role="footer" class="revealed" id="footerID">
            <nav>
              <ul>
                <li>
                  <a href="#" id="home">Home</a>
                </li>
              </ul>
            </nav>
          </footer>

          JavaScript access:
          var toolbar = UI.toolbar("toolbarID");
          UI.button('home').click(function () {
            UI.pagestack.push("main");
          });

     */

    var Toolbar = (function () {

        function ToolbarListener(id) {
            this._id = id;
            this._onChangedCallbacks = [];
            this._listen();
        };

        ToolbarListener.prototype = {
            onchanged: function (callback) {
                if (callback && typeof callback === 'function')
                    this._onChangedCallbacks.push(callback);
            },
            _listen: function () {
                var mutationObserverClass =
                    this._getNativeMutationObserverClass();
                if (!mutationObserverClass) {
                    console.error(
                        'Could not listen to toolbar changes: no mutation observer found');
                    return;
                }
                var toolbar = document.getElementById(this._id);
                if (toolbar) {
                    var observer = new mutationObserverClass(
                        this._onMutated.bind(this));
                    observer.observe(toolbar, {
                        attributes: true
                    });
                }
            },
            _onMutated: function (mutations, observer) {
                for (var i = 0; i < this._onChangedCallbacks.length; ++i) {
                    this._onChangedCallbacks[i](mutations);
                }
            },
            _getNativeMutationObserverClass: function () {
                return window.MutationObserver || window.WebKitMutationObserver;
            },
        };

        function Toolbar(id, touchInfoDelegate) {

            this.PHASE_START = "start";
            this.PHASE_MOVE = "move";
            this.PHASE_END = "end";
            this.PHASE_CANCEL = "cancel";

            this.phase = null;

            this.toolbar = document.getElementById(id);
            if ( ! this.toolbar)
                throw "Invalid toolbar id";

            this._touchDown = false;
            this._touchInfoDelegate = touchInfoDelegate;

            this.fingerData = [];
            this.fingerData.push({
                start: {
                    x: 0,
                    y: 0
                },
                end: {
                    x: 0,
                    y: 0
                },
                identifier: 0
            });

            var touchEvents = touchInfoDelegate.touchEvents;
            touchInfoDelegate.registerTouchEvent(
                touchEvents.touchStart, this.toolbar, this.__onTouchStart.bind(this));
            touchInfoDelegate.registerTouchEvent(
                touchEvents.touchEnd, this.toolbar, this.__onTouchEnd.bind(this));
            touchInfoDelegate.registerTouchEvent(
                touchEvents.touchMove, this.toolbar, this.__onTouchMove.bind(this));
            touchInfoDelegate.registerTouchEvent(
                touchEvents.touchLeave, this.toolbar, this.__onTouchLeave.bind(this));

            this._timer = null;

            var listener = new ToolbarListener(id);
            var self = this;
            listener.onchanged(function () {
                var toolbar = self.toolbar;

                function __isToolbarVisible() {
                    return Array.prototype.slice.call(toolbar.classList)
                        .indexOf('revealed') >= 0;
                }
                if (__isToolbarVisible()) {
                    self._timer = window.setTimeout(
                        function () {
                            self.hide();
                        },
                        5000);
                } else {
                    if (self._timer) {
                        window.clearTimeout(self._timer);
                        self._timer = null;
                    }
                }
            });
        };

        Toolbar.prototype = {
            /**-
             * Display a Toolbar
             * @method show
             */
            show: function () {
                this.toolbar.classList.add('revealed');
            },

            /**-
             * Hide a Toolbar
             * @method hide
             */
            hide: function () {
                this.toolbar.classList.remove('revealed');
            },

            /**
             * Toggle show/hide status of a Toolbar
             * @method toggle
             */
            toggle: function () {
                this.toolbar.classList.toggle('revealed');
            },

            /**
             * Returns the DOM element associated with the id this widget is bind to.
             * @method element
             * @example
                var mytoolbar = UI.toolbar("toolbarid").element();
             */
            element: function () {
                return this.toolbar;
            },

            /**
             * @private
             */
            __onTouchStart: function (evt) {
                this._touchDown = true;

                this.phase = this.PHASE_START;
                var identifier = evt.identifier !== undefined ? evt.identifier : 0;

                var touchEvent =
                    this._touchInfoDelegate.translateTouchEvent(evt);

                this.fingerData[0].identifier = identifier;
                this.fingerData[0].start.x =
                    this.fingerData[0].end.x = touchEvent.touches[0].pageX;
                this.fingerData[0].start.y =
                    this.fingerData[0].end.y = touchEvent.touches[0].pageY;
            },

            /**
             * @private
             */
            __onTouchMove: function (evt) {
                if ( ! this._touchDown)
                    return;

                if (this.phase === this.PHASE_END || this.phase === this.PHASE_CANCEL)
                    return;

                if (this.phase == this.PHASE_START) {
                    var touchEvent =
                        this._touchInfoDelegate.translateTouchEvent(evt);

                    var identifier = evt.identifier !== undefined ? evt.identifier : 0;
                    var f = this.__getFingerData(identifier);

                    f.end.x = touchEvent.touches[0].pageX;
                    f.end.y = touchEvent.touches[0].pageY;

    		// Validate that the movement has a big enough amplitude
    		// before considering it as a 'move'. The 0.4 is a value
    		// that was setup after some experimentation on touch.
    		var amplitude = this.__norm(f.start, f.end);
    		if ((amplitude / this.toolbar.offsetHeight) < 0.4)
    			return;

                    evt.preventDefault();

                    direction = this.__calculateDirection(f.start, f.end);
                    if (direction == "DOWN") {
                        this.hide();
                    }

                    if (direction == "UP") {
                        this.show();
                    }

                    phase = this.PHASE_MOVE;
                }
            },

            /**
             * @private
             */
            __onTouchEnd: function (e) {
                this._touchDown = false;
                phase = this.PHASE_END;
            },

            /**
             * @private
             */
            __onTouchLeave: function (e) {
                this._touchDown = false;
                phase = this.PHASE_CANCEL;
            },

            /**
             * @private
             */
            __norm: function (p1, p2) {
    	    var vx = p2.x - p1.x;
    	    var vy = p2.y - p1.y;
    	    return Math.sqrt(vx * vx + vy * vy);
    	},

            /**
             * @private
             */
            __calculateDirection: function (startPoint, endPoint) {
                var angle = this.__calculateAngle(startPoint, endPoint);

                if ((angle <= 45) && (angle >= 0)) {
                    return "LEFT";
                } else if ((angle <= 360) && (angle >= 315)) {
                    return "LEFT";
                } else if ((angle >= 135) && (angle <= 225)) {
                    return "RIGHT";
                } else if ((angle > 45) && (angle < 135)) {
                    return "DOWN";
                } else {
                    return "UP";
                }
            },

            /**
             * @private
             */
            __getFingerData: function (id) {
                for (var i = 0; i < this.fingerData.length; i++) {
                    if (this.fingerData[i].identifier == id) {
                        return this.fingerData[i];
                    }
                }
            },

            /**
             * @private
             */
            __calculateAngle: function (startPoint, endPoint) {
                var x = startPoint.x - endPoint.x;
                var y = endPoint.y - startPoint.y;
                var r = Math.atan2(y, x); //radians
                var angle = Math.round(r * 180 / Math.PI); //degrees

                //ensure value is positive
                if (angle < 0) {
                    angle = 360 - Math.abs(angle);
                }

                return angle;
            }
        };
        return Toolbar;
    })();