Source: components/menu.js

define('application/components/menu', [
    'antie/runtimecontext',
    'antie/widgets/component',
    'application/widgets/menu/sections/base',
    'application/widgets/menu/sections/top',
    'rofl/lib/utils',
    'antie/widgets/verticallist',
    'rofl/events/keyevent',
    'application/models/production/menu/menuitem',
    'application/models/production/menu',
    'rofl/lib/l10n',
    'rofl/widgets/label',
    'rofl/widgets/container',
    'rofl/analytics/web/google',
    'rofl/widgets/image'
], function (
    RuntimeContext,
    Component,
    BaseSection,
    TopSection,
    Utils,
    VerticalList,
    KeyEvent,
    MenuItem,
    Menu,
    L10N,
    Label,
    Container,
    GoogleAnalytics,
    Image
) {
    'use strict';

    var application = RuntimeContext.getCurrentApplication(),
        layout = application.getLayout().menu,
        l10n = L10N.getInstance(),
        Positioning = {
            LEFT: 'left',
            RIGHT: 'right'
        },
        Sections = {
            TOP: {
                id: 'top-menu',
                name: 'top'
            },
            MIDDLE: {
                id: 'middle-menu',
                name: 'middle'
            },
            BOTTOM: {
                id: 'bottom-menu',
                name: 'bottom'
            }
        },
        config = {
            position: 'left',
            totallyHidden: true,
            cssClass: 'side-menu',
            selectedIndex: {
                section: 'middle',
                index: 1
            }
        },
        EVENT = {
            openMenu: 'OpenMenu',
            closeMenu: 'ExitMenu',
            menuItemClicked: 'MenuItemClicked'
        },
        GALabels = {
            search: 'Search',
            channellist: 'NowonTV',
            guide: 'Guide',
            recordings: 'Recordings',
            settings: 'Settings',
            movies: 'Movies',
            series: 'Series',
            sports: 'Sports',
            exit: 'Exit'
        };

    return Component.extend({

        /**
         * Initialises the menu.
         */
        init: function init () {
            init.base.call(this, 'component-menu');
            this.addClass('side-menu');

            this._position = null;
            this._menuSections = null;

            this._onBeforeRenderBound = Utils.bind(this._onBeforeRender, this);
            this._onBeforeShowBound = Utils.bind(this._onBeforeShow, this);

            this._onFocusBound = Utils.bind(this._onFocus, this);
            this._onBlurBound = Utils.bind(this._onBlur, this);

            this._onKeyDownBound = Utils.bind(this._onKeyDown, this);

            this._onClickHandler = Utils.bind(this._onClickEvent, this);

            this.addEventListener('beforerender', this._onBeforeRenderBound);
            this.addEventListener('beforeshow', this._onBeforeShowBound);

            this.addEventListener('focus', this._onFocusBound);
            this.addEventListener('blur', this._onBlurBound);

            this.addEventListener('keydown', this._onKeyDownBound);

            this._menuSections = this.appendChildWidget(new VerticalList('section-menu'));
        },

        /**
         * OnBeforeRenderEvent.
         *
         * @param {Event} evnt - The beforerender event.
         * @param {Object} evnt.args - The component arguments.
         * @param {String|null} [evnt.args.position] - The menu position where the menu will be set.
         * @param {boolean} evnt.args.totallyHidden - It indicates if the menu must be totally hidden or not.
         * @param {string|null} [evnt.args.cssClass] - The component css class.
         * @param {Object|null} [evnt.args.indexSelected] - It contains the specific selected index by sections.
         * @param {Number|null} [evnt.args.indexSelected.section] - Section where the item must be selected.
         * @param {Number|null} [evnt.args.indexSelected.index] - Child widget index to be selected.
         * @param {Object|null} [evnt.args.top] - The arguments for the top section.
         * @param {Object|null} [evnt.args.top.logo] - The logo for the top section.
         * @param {Object|null} [evnt.args.top.title] - The title for the top section.
         * @param {Array|null} [evnt.args.top.items] - MenuItem array.
         * @param {Number|null} [evnt.args.top.visibleItems] - Visible items in top section.
         * @param {Number|null} [evnt.args.top.scrollOffset] - Scroll offset for the top section.
         * @param {Widget|null} [evnt.args.top.widget] - The top custom widget.
         * @param {Object|null} [evnt.args.top.menuItemFormatter] - The top menu item formatter.
         * @param {string|null} [evnt.args.top.widgetCssClass] - The top widget css class.
         * @param {Object|null} evnt.args.middle - The arguments for the middle section.
         * @param {Array|null} evnt.args.middle.items - MenuItem array.
         * @param {Number|null} [evnt.args.middle.visibleItems] - Visible items in top section.
         * @param {Number|null} [evnt.args.middle.scrollOffset] - Scroll offset for the middle section.
         * @param {Widget|null} [evnt.args.middle.widget] - The middle custom widget.
         * @param {Object|null} [evnt.args.middle.menuItemFormatter] - The middle menu item formatter.
         * @param {string|null} [evnt.args.middle.widgetCssClass] - The middle widget css class.
         * @param {Object|null} [evnt.args.middle.submenu] - The submenu conf.
         * @param {Object|null} [evnt.args.middle.submenu.submenuItemFormatter] - The submenu item formatter.
         * @param {Number|null} [evnt.args.middle.submenu.visibleItems] - Visible items in submenu section.
         * @param {Number|null} [evnt.args.middle.submenu.scrollOffset] - Scroll offset for the submenu section.
         * @param {Object|null} evnt.args.bottom - The arguments for the bottom section.
         * @param {Array|null} evnt.args.bottom.items - MenuItem array.
         * @param {Number|null} [evnt.args.bottom.visibleItems] - Visible items in bottom section.
         * @param {Number|null} [evnt.args.bottom.scrollOffset] - Scroll offset for the bottom section.
         * @param {Widget|null} [evnt.args.bottom.widget] - The bottom custom widget.
         * @param {object|null} [evnt.args.bottom.menuItemFormatter] - The bottom menu item formatter.
         * @param {string|null} [evnt.args.bottom.widgetCssClass] - The bottom widget css class.
         * @private
         */
        _onBeforeRender: function (evnt) {
            var args = evnt.args,
                cssClass = args.cssClass;

            config.middle = {
                items: this._getMenuItems('middle')
            };
            config.bottom = {
                items: this._getMenuItems('bottom')
            };

            this._position = args.position || Positioning.LEFT;
            this._totallyHidden = args.totallyHidden;

            this.addClass([this._position, cssClass]);

            this._menuSections.removeChildWidgets();

            if (!this._overlay) {
                this._createOverlay();
                this._createBranding();
            }
        },

        /**
         * OnBeforeShowEvent.
         *
         * @private
         */
        _onBeforeShow: function () {
            this.addEventListener('select', Utils.bind(this._onSelect, this));
            this._buildSections(config);
            this.setSelectedIndex(config.selectedIndex);

            this._hide();
            this.closeMenu();
        },

        /**
         * This handles setting the active state back to the last known active menu item when leaving the menu.
         *
         * @param {BlurEvent} evnt - The AppCore blur event.
         * @private
         */
        _onBlur: function (evnt) {
            if (evnt.target === this) {
                this._hide();
                evnt.stopPropagation();

                if (!this._callingComponent || this._callingComponent === 'player') {
                    this.closeMenu();
                } else {
                    this.showCollapsed(this._callingComponent);
                }
            }
        },

        /**
         * Shows logout dialog.
         *
         * @private
         */
        _showLogout: function () {
            this._onceOpened = false;

            application.route('error', {
                type: 'exit-popup',
                title: l10n.get('errors.exit_confirmation'),
                button: [
                    {
                        name: 'confirmbtn',
                        id: 'exit-confirm-button',
                        label: l10n.get('errors.confirm_close')
                    },
                    {
                        name: 'rejectbtn',
                        id: 'exit-reject-button',
                        label: l10n.get('errors.reject_close')
                    }
                ]
            });
        },

        /**
         * Handles keydown events.
         *
         * @param {antie.events.KeyEvent} event - KeyDownEvent.
         * @param {number} event.keyCode - The normalised keyCode of the key that caused this event
         *                                 to be raised (e.g. <code>KeyEvent.VK_ENTER</code>,
         *                                 <code>KeyEvent.VK_UP</code>).
         * @private
         */
        _onKeyDown: function (event) {
            var activeSection = this._menuSections.getActiveChildWidget(),
                main = application.getComponent('main'),
                player = application.getComponent('player'),
                section,
                activeChildWidgetId;

            switch (event.keyCode) {
                case KeyEvent.VK_BACK:
                    if (activeSection === Sections.MIDDLE.section) {
                        activeSection.removeSubmenus();
                    }
                    this._focusMainComponent(event);

                    activeChildWidgetId = main.getActiveChildWidget().id;

                    if (this._callingComponent === 'player') {
                        this.handleCloseFocus();

                        if (this._onceOpened) {
                            this._showLogout();
                        }

                        return;
                    }

                    if (main.getChildWidgetCount() === 0 || player.outputElement.style.display === 'block' || activeChildWidgetId === 'channel-list') {

                        if (this._onceOpened) {
                            this._showLogout();

                            if (!main.isVisible()) {
                                application.hideMenu();
                            }
                        } else {

                            if (activeChildWidgetId === 'search'
                                || activeChildWidgetId === 'guide'
                                || activeChildWidgetId === 'settings'
                                || activeChildWidgetId === 'recordings') {
                                application.showPlayer();
                                player.focus();
                                application.hideMenu();
                            }
                            this._onceOpened = true;
                        }
                    }
                    break;
                case KeyEvent.VK_LEFT:
                    if (activeSection.isSubmenuVisible() || activeSection.isSubmenu()) {
                        event.stopPropagation();
                        break;
                    }
                    if (this._position === Positioning.RIGHT) {
                        this._focusMainComponent(event);
                    }
                    break;
                case KeyEvent.VK_RIGHT:
                    if (activeSection.isSubmenuVisible() || activeSection.isSubmenu()) {
                        event.stopPropagation();
                        break;
                    }
                    if (this._position === Positioning.LEFT) {
                        this._focusMainComponent(event);
                    }

                    this.handleCloseFocus();
                    break;
                case KeyEvent.VK_UP:
                    section = this._menuSections.getActiveChildWidget().getActiveChildWidget();
                    section.setActiveChildIndex(section.getChildWidgetCount() - 1);
                    break;
                case KeyEvent.VK_DOWN:
                    section = this._menuSections.getActiveChildWidget().getActiveChildWidget();
                    section.setActiveChildIndex(0);
                    break;
            }
        },

        /**
         * Closes menu and focuses on the active component.
         */
        handleCloseFocus: function () {
            var main = application.getComponent('main'),
                player = application.getComponent('player'),
                showPlayer;

            if (this._callingComponent) {
                showPlayer = this._callingComponent === 'player' || (main && !main.getChildWidgetCount());

                if (showPlayer) {
                    application.showPlayer();
                    player.focus();
                    application.hideMenu(this._callingComponent);
                } else if (main) {
                    main.focus();
                }
            } else if (main.outputElement.style.display !== 'none' && main.getChildWidgets().length) {
                main.focus();
            } else {
                application.showPlayer();
                player.focus();
                application.hideMenu(this._callingComponent);
            }
        },

        /**
         * When this component gets focussed, make sure it's visible, because this component might
         * get hidden (for instance when entering the player).
         *
         * @param {FocusEvent} event - Focus event.
         * @private
         */
        _onFocus: function (event) {
            var menu = application.getComponent('menu');

            if (event.target === this) {
                if (Utils.isFunction(application.isMenuAllowed) && Utils.isFunction(application.isMenuAllowed) &&
                    application.updateMenu()) {
                    application.updateMenu({show: true});
                } else {
                    this._show();
                    this.focus();
                }
                event.stopPropagation();
            }

            if (menu.hasClass('invisible') || menu.hasClass('collapse')) {
                this.openMenu(this._callingComponent);
            }
        },

        /**
         * Builds the view menu sections.
         *
         * @param {Object} args - Arguments to build the component.
         * @private
         */
        _buildSections: function (args) {
            var topArgs = args.top,
                middleArgs = args.middle,
                bottomArgs = args.bottom,
                topSection,
                topMenuWidget,
                middleSection,
                middleMenuWidget,
                bottomSection,
                bottomMenuWidget;

            if (topArgs) {
                topMenuWidget = topArgs.widget || topArgs.items ? BaseSection : TopSection;
                topArgs.props = Sections.TOP;
                topSection = new topMenuWidget(topArgs);
                Sections.TOP.section = topSection;
                this._menuSections.appendChildWidget(topSection);
            }

            if (middleArgs && middleArgs.items.length) {
                middleMenuWidget = middleArgs.widget || BaseSection;
                middleArgs.props = Sections.MIDDLE;
                middleArgs.position = this._position;
                middleSection = new middleMenuWidget(middleArgs);
                Sections.MIDDLE.section = middleSection;
                this._menuSections.appendChildWidget(middleSection);
            }

            if (bottomArgs && bottomArgs.items.length) {
                bottomMenuWidget = bottomArgs.widget || BaseSection;
                bottomArgs.props = Sections.BOTTOM;
                bottomSection = new bottomMenuWidget(bottomArgs);
                Sections.BOTTOM.section = bottomSection;
                this._menuSections.appendChildWidget(bottomSection);
            }
        },

        /**
         * Hide the menu whether it's rendered and it must be totally hidden.
         *
         * @private
         */
        _hide: function () {
            if (this.isRendered() && this._totallyHidden) {
                this.setStyleTo('display', 'none');
            }
        },

        /**
         * Show the menu whether it's rendered and it was totally hidden.
         *
         * @private
         */
        _show: function () {
            if (this.isRendered() && this._totallyHidden) {
                this.setStyleTo('display', 'block');
            }
        },

        /**
         * Focus the main component and stop the event propagation.
         *
         * @param {antie.events.KeyEvent} event - KeyDownEvent.
         * @private
         */
        _focusMainComponent: function (event) {
            application.getComponent('main').focus();
            event.stopPropagation();
        },

        /**
         * Set selected index in a section.
         *
         * @param {Object} selectedIndexObject - Selected index info.
         * @param {string} selectedIndexObject.section - Selected section.
         * @param {number} selectedIndexObject.index - Selected index.
         */
        setSelectedIndex: function (selectedIndexObject) {
            var section,
                selectedIndex;

            if (selectedIndexObject && selectedIndexObject.section) {

                section = Sections[(selectedIndexObject.section).toUpperCase()].section;
                this._selectedIndex = selectedIndex = parseInt(selectedIndexObject.index, 10);

                if (selectedIndex >= 0) {
                    section.selectChildWidgetByIndex(selectedIndex);
                }
            }
        },

        /**
         * Get the menu items in models from the config.
         *
         * @param {string} section - As top|bottom.
         * @returns {Array} Menu items.
         * @private
         */
        _getMenuItems: function (section) {
            var menu = new Menu().getLocalData(),
                items = [],
                item;

            Utils.each(menu[section], function (menuItem) {
                item = new MenuItem(menuItem);
                items.push(item);
            });

            return items;
        },

        /**
         * Select event.
         *
         * @param {Object} e - The event data.
         * @private
         */
        _onSelect: function (e) {
            var route = e.target.getMenuItem().getAction().getRoute(),
                self = this;

            if (this._selected) {
                return;
            }

            this._selectedIndex = e.target.parentWidget.getActiveChildWidgetIndex();
            this._selected = true;

            setTimeout(function () {
                self._selected = false;
            }, 2000);

            this.reportMenuItem(route);

            if (route !== 'player') {
                this.showCollapsed(this._callingComponent);
            }
        },

        /**
         * Report selected menu item to Google Analytics.
         *
         * @param {string} name - The name of the selected item.
         */
        reportMenuItem: function (name) {
            var GA = GoogleAnalytics.getInstance();

            GA.onEvent('Menu', EVENT.menuItemClicked, {eventLabel: GALabels[name]});
        },

        /**
         * Show menu.
         *
         * @param {string} value - Parent component name passed from component.
         * @param {boolean} [shouldBlockMenu] - True if the menu should be blocked.
         * which called the function.
         */
        openMenu: function (value, shouldBlockMenu) {
            var menu = application.getComponent('menu'),
                GA = GoogleAnalytics.getInstance();

            menu.removeClass(['invisible', 'collapse']);
            this._callingComponent = value;

            // Keep the index in the selected item.
            this._resetToSelectedIndex();
            this._blockMenu(shouldBlockMenu);
            GA.onEvent('Menu', EVENT.openMenu);
        },

        /**
         * Hide menu.
         */
        closeMenu: function () {
            var menu = application.getComponent('menu'),
                GA = GoogleAnalytics.getInstance();

            if (!menu.hasClass('invisible') || !menu.hasClass('collapse')) {
                GA.onEvent('Menu', EVENT.closeMenu);
            }

            menu.addClass('invisible');
            menu.removeClass('collapse');
        },

        /**
         * Show lint menu.
         *
         * @param {string} callingComponent - The calling component that opened the collapsed menu.
         */
        showCollapsed: function (callingComponent) {
            var menu = application.getComponent('menu');

            this._callingComponent = callingComponent;

            this.setSelectedIndex(this._getMenuItemIndex(callingComponent));
            this.removeClass('force-open');
            menu.removeClass('invisible');
            menu.addClass('collapse');
        },

        /**
         * Returns the index object of the given callingComponent.
         *
         * @param {string} callingComponent - The menu component to be focused.
         * @return {{index: number, section: string}} - The index to set selection.
         */
        _getMenuItemIndex: function (callingComponent) {
            var i = 0,
                menuMiddleItems = config.middle.items;

            for (;i < menuMiddleItems.length; i++) {
                if (menuMiddleItems[i].getAction().getRoute() === callingComponent) {
                    this._selectedIndex = i;

                    return {
                        section: 'middle',
                        index: i
                    };
                }
            }
        },

        /**
         * Resets focus to the selected index menu item.
         */
        _resetToSelectedIndex: function () {
            this.setSelectedIndex({
                section: 'middle',
                index: this._selectedIndex
            });
        },

        /**
         * Retrieve the calling component that opened the menu.
         *
         * @returns {Object} - The calling component.
         */
        getCallingComponent: function () {
            return this._callingComponent;
        },

        /**
         * Creates the overlay.
         *
         * @private
         */
        _createOverlay: function () {
            var overlay = this._overlay = new Container();

            overlay.addClass(['display-hidden', 'blocked']);

            this.appendChildWidget(overlay);
        },

        /**
         * Creates branding.
         *
         * @private
         */
        _createBranding: function () {
            var branding = new Container(),
                brandingLogoBig = new Image('branding-logo-big', 'src/assets/images/kpn-tv.png'),
                brandingLogo = new Image('branding-logo', 'src/assets/images/kpn-tv-small.png');

            branding.addClass('menu-branding');
            brandingLogo.addClass('menu-branding-logo');
            brandingLogoBig.addClass('menu-branding-logo-big');

            brandingLogo.appendChildWidget(brandingLogoBig);
            this.appendChildWidget(branding);
            this.appendChildWidget(brandingLogo);
        },

        /**
         * Blocks or unblocks the menu.
         *
         * @param {boolean} [shouldBlockMenu] - True if the menu should be blocked.
         * @private
         */
        _blockMenu: function (shouldBlockMenu) {
            var overlay = this._overlay;

            if (shouldBlockMenu) {
                if (overlay.hasClass('display-hidden')) {
                    overlay.removeClass('display-hidden');
                }
                this.addClass('force-open');
            } else {
                if (!overlay.hasClass('display-hidden')) {
                    overlay.addClass('display-hidden');
                }
                this.removeClass('force-open');
            }
        },

        /**
         * Renders the component.
         *
         * @param {Object} device - The device.
         * @returns {Object} - The output element.
         */
        render: function render (device) {
            var outputElement = render.base.call(this, device);

            this._setOnClickEvent();

            return outputElement;
        },

        /**
         * Sets the click event on the button.
         *
         * @private
         */
        _setOnClickEvent: function () {

            if (this.isRendered()) {

                this._onClickHandler = Utils.bind(this._onClickEvent, this);
                this.parentWidget.outputElement.onclick = this._onClickHandler;
            }
        },

        /**
         * Click event.
         *
         * @param {Object} e - The click event.
         * @private
         */
        _onClickEvent: function (e) {

            if (e.clientX >= layout.width) {
                this.closeMenu();
                this._manageOnCloseClick(e);
            }
        },

        /**
         * Manages the click behaviour from menu to the background page.
         *
         * @param {Object} ev - The click event.
         **/
        _manageOnCloseClick: function (ev) {
            var main,
                player,
                activeChildWidgetComponent,
                callingComponent = this._callingComponent && application.getComponent(this._callingComponent);

            if (callingComponent && callingComponent.isVisible() && callingComponent.isVisible()) {
                callingComponent.focus();
            } else {
                main = application.getComponent('main');
                player = application.getComponent('player');

                if (main.getChildWidgetCount() && main.isVisible()) {
                    this._focusMainComponent(ev);
                    activeChildWidgetComponent = main.getActiveChildWidget();

                    if (activeChildWidgetComponent) {
                        activeChildWidgetComponent.focus();
                    }

                    application.showMenu(this._callingComponent, false, true);
                } else {
                    application.showPlayer();
                    player.focus();
                    application.hideMenu();
                }
            }
        }
    });
});