pagestacks.js - Ubuntu HTML5 API

# Ubuntu HTML5 API: pagestacks.js

API Docs for: HTML-14.04-dev~bzr202

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

    /**
     * The Pagestack manages all Pages in a stack data structure. Initially, the Pagestack contains no Pages. The <em>push()</em> method is normally executed on load to display the app starting page.

          UI.pagestack.push("pageID")

    The topmost Page on the Pagestack is always displayed.

    The Pagestack is declared as a direct child of the <em>content</em> div.

    #####Default application wide footer
    The Pagestack contains a default <em>footer</em> (represented in JavaScript as a Toolbar), even if you do not declare one in HTML. The <em>footer</em> has a single Back button.
    #####Customized application wide footer
    This application-wide <em>footer</em> can be customized (for example, you can add Buttons) by declaring a <em>footer</em> as a direct child of the <em>pagestack</em> div (see example).
    ######Page specific footers
    A <em>page</em> may declare a page-specific <em>footer</em> as a child element.

     * @class Pagestack
     * @namespace UbuntuUI
     * @constructor
     * @example

         <div data-role="mainview">

            <header data-role="header">
            </header>

            <div data-role="content">

              <div data-role="pagestack">

                <div data-role="page" id="main" data-title="Page 1">
                </div>

                <div data-role="page" id="page2" data-title="Page 2">
                  [...]
                  <footer data-role="footer" class="revealed" id="footerPage2">
                    [...]
                  </footer>
                </div>

                <footer data-role="footer" class="revealed" id="footerAppWide">
                  [...]
                </footer>

              </div>  <!-- end of Pagestack div -->

            </div>

          </div>

          JavaScript access:
          UI.pagestack.METHOD();

     */
    var Pagestack = (function () {

        function __safeCall(f, args, errorfunc) {
            if (typeof (f) !== 'function')
                return;
            try {
                f.apply(null, args);
            } catch (e) {
                if (errorfunc && typeof (errorfunc) === 'function') errorfunc(e)
            }
        };

        function Pagestack(pageStack) {
            this._pages = [];
            this._pageStack = pageStack;
            this._backBtn = document.querySelector('[data-role="back-btn"]');
        };

        Pagestack.prototype = {
            /**
             * Push a page to the top of this pagestack
             * @method push
             * @param {String} id - The id attribute of the page element to be pushed
             * @param {Object} properties - A list of properties passed down to the page that is to be activated
             */
            push: function (id, properties) {
                try {
                    __safeCall(this.__setAllPagesVisibility.bind(this), [false]);
                    (new Page(id)).activate(properties);
                    this._pages.push(id);

                    this.__dispatchPageChanged(this.currentPage());
                } catch (e) {}
            },

            /**
             * Checks for zero pages in this pagestack
             * @method isEmpty
             * @return {Boolean} - True when this pagestack has no pages, else false
             */
            isEmpty: function () {
                return this._pages.length === 0;
            },

            /**
             * Gets the id attribute of the page element on top of this pagestack
             * @method currentPage
             * @return {PageID|Null} - The topmost page's id attribute, else null when there are no pages on this pagestack
             */
            currentPage: function () {
                return this.isEmpty() ? null : this._pages[this._pages.length - 1];
            },

            /**
             * Gets the number of pages in this pagestack
             * @method depth
             * @return {Number} - The number of pages in this pagestack
             */
            depth: function () {
                return this._pages.length;
            },

            /**
             * Clears the whole page stack
             * @method clear
             */
            clear: function () {
                if (this.isEmpty())
                    return;
                __safeCall(Page.prototype.deactivate.bind(new Page(this.currentPage())), []);
                this._pages = [];
            },

            /**
             * Pops the current page off this pagestack, which causes the next page to become the top page and to display
             * @method pop
             */
            pop: function () {
                if (this.isEmpty())
                    return;
                __safeCall(Page.prototype.deactivate.bind(new Page(this.currentPage())), []);
                this._pages.pop();
                __safeCall(Page.prototype.activate.bind(new Page(this.currentPage())), []);

                this.__dispatchPageChanged(this.currentPage());
            },

            onPageChanged : function(callback){
                this._pageStack.addEventListener("pagechanged", callback);
            },

            /**
             * @private
             */
            __setAllPagesVisibility: function (visible) {
                var visibility = visible ? "block" : "none";

                var children = [].slice.call(this._pageStack.children);
                children.forEach(function(element) {
                    var pageHelper = new Page();
                    if (pageHelper.isPage(element)) {
                        element.style.display = visibility;
                    }
                });
            },

            /**
             * @private
             */
            __isPage: function (element) {
                return element.getAttribute('data-role') === 'page';
            },

            /**
             * @private
             */
            __dispatchPageChanged: function (page) {
                this._backBtn.disabled = (this.depth() ==  1);

                [].forEach.call( document.querySelectorAll('[data-role="actions-wrapper"]'), function(el) {
                    el.style.display = 'none';
                });

                var pageActions = document.getElementById("actions_" + page);
                if (pageActions !== null) {
                    pageActions.style.display = 'block';
                }

                var event = document.createEvent('Event');
                event.initEvent('pagechanged',true,true);
                event.page = page;
                this._pageStack.dispatchEvent(event);
            },
        };

        return Pagestack;
    })();