Source: widgets/menu/sections/base.js

define('application/widgets/menu/sections/base', [
    'antie/widgets/horizontallist',
    'antie/runtimecontext',
    'antie/widgets/verticallist',
    'rofl/lib/utils',
    'application/formatters/menuitem',
    'rofl/decorators/pointer',
    'application/widgets/menu/sections/submenu/base'
], function (
    HorizontalList,
    RuntimeContext,
    VerticalList,
    Utils,
    defaultMenuItemFormatter,
    PointerDecorator,
    SubMenu
) {
    'use strict';

    var application = RuntimeContext.getCurrentApplication(),
        DefaultConfig = {
            SCROLL_OFFSET: {
                top: 1,
                middle: 3,
                bottom: 1
            },
            VISIBLE_ITEMS: {
                top: 1,
                middle: 8,
                bottom: 1
            },
            WIDGET_CLASS: 'menu-list',
            SCROLL_DURATION: 250
        },
        SUBMENU_VISIBLE_CLASS = 'submenu-visible';


    return HorizontalList.extend({

        _visibleItems: null,
        _totalDataItems: null,
        _scrollOffset: null,

        /**
         * Initializes the base menu widget.
         *
         * @param {Object} opts - The widget opts.
         */
        init: function init (opts) {
            var props = opts.props,
                id = props.id,
                name = props.name,
                items = opts.items,
                menuItemFormatter = opts.menuItemFormatter || defaultMenuItemFormatter,
                widgetClass = opts.widgetCssClass || DefaultConfig.WIDGET_CLASS;

            this._visibleItems = opts.visibleItems || DefaultConfig.VISIBLE_ITEMS[name];
            this._scrollOffset = opts.scrollOffset || DefaultConfig.SCROLL_OFFSET[name];
            this._scrollAnimationDuration = DefaultConfig.SCROLL_DURATION;
            this._subMenuConfig = opts.submenu || {};

            this._position = opts.position;

            init.base.call(this);
            this.addClass('section-list');

            // Set a formatter to keep those decisions out of the controller.
            this._menu = new VerticalList(id, new menuItemFormatter(), items);
            this._submenu = null;
            this._subSubmenu = null;
            this._menu.addClass([widgetClass, name]);

            this._onSelectBound = Utils.bind(this._onSelect, this);
            this._onSelectedIndexChangeBound = Utils.bind(this._onSelectedIndexChange, this);
            this._onKeyDownBound = Utils.bind(this._onKeyDown, this);
            this._onFocusBound = Utils.bind(this._onFocus, this);

            this.addEventListener('select', this._onSelectBound);
            this.addEventListener('selecteditemchange', this._onSelectedIndexChangeBound);
            this._menu.decorate([PointerDecorator]);

            this.addEventListener('focus', this._onFocusBound);
            this.appendChildWidget(this._menu);
        },

        /**
         * Returns if menu can be focused.
         *
         * @returns {boolean} - Is focusable.
         */
         isFocusable: function () {
             return Utils.isFunction(application.isMenuAllowed) && application.isMenuAllowed() || true;
         },

        /**
         * Focus to the vertical list.
         *
         * @param {Object} evt - The event data.
         * @private
         */
        _onFocus: function (evt) {
            var target = evt.target;

            if (target === this._menu || target === this._submenu
                || target === this._subSubmenu) {
                this._totalDataItems = target.getChildWidgetCount();
            }
            if (!this.isSubmenuVisible()) {
                this._menu.focus();
            }
        },

        /**
         * Pointer handling needs to be inited after widget is rendered.
         *
         * @param {Device} device - The current device.
         * @returns {Element} - The rendered element.
         */
        render: function render (device) {
            var outputElement = render.base.call(this, device);

            this._menu.initPointerHandlers();

            return outputElement;
        },

        /**
         * Add selected class and activate the child widget in the specific index.
         *
         * @param {number} selectedIndex - Index to select and activate.
         */
        selectChildWidgetByIndex: function (selectedIndex) {
            var activeChildWidget;

            this._menu.setActiveChildIndex(selectedIndex);
            activeChildWidget = this._menu.getActiveChildWidget();

            if (activeChildWidget) {
                this._removeSelectedClass();
                this._menu.getActiveChildWidget().addClass('selected');
                this.setActiveChildWidget(this._menu);
            }
        },

        /**
         * This handles the menu item action when selected.
         *
         * @param {SelectEvent} evt - The AppCore select event.
         * @private
         */
        _onSelect: function (evt) {
            var self = this,
                target = evt.target,
                menuItem = target.getMenuItem(),
                action = menuItem.getAction();

            // Preventing focus after mouse select.
            if (evt.fromClick) {
                evt.target.preventMouseFocus = true;

                setTimeout(function () {
                    evt.target.preventMouseFocus = false;
                }, 2000);
            }

            if (this._selected) {
                return;
            }

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

            if (menuItem.hasSubmenu() && !this.isSubmenu()) {
                this.removeSubmenus();

                // Create sub-menu level-1
                menuItem.getChildren().then(function (data) {
                    var opts = self._subMenuConfig;

                    opts.id = menuItem.getLabel().toLowerCase() + 'SubMenu';
                    opts.items = data;
                    opts.cssClass = 'submenu-list';
                    self._removeSelectedClass();
                    target.addClass('selected');
                    self._createSubmenu(opts);
                });
            } else if (menuItem.hasSubmenu()) {
                if (this._subSubmenu) {
                    this.removeChildWidget(this._subSubmenu);
                }

                // Create sub-menu level-2
                menuItem.getChildren().then(function (data) {
                    var opts = self._subMenuConfig;

                    opts.id = menuItem.getLabel().toLowerCase() + 'SubSubMenu';
                    opts.items = data;
                    opts.cssClass = 'sub-submenu-list';
                    self._removeSelectedClass();
                    target.addClass('selected');
                    self._createSubSubmenu(opts);
                });
            }

            if (action) {
                if (this._submenu) {
                    this.removeSubmenus();
                }
                action.prepare()
                    .then(function () {
                        var args = action.getArguments();

                        self._removeSelectedClass();
                        target.addClass('selected');

                        if (Utils.isFunction(action.getRoute) && action.getRoute()) {
                            application.route(action.getRoute(), args);
                        } else {

                            // TODO: Call to the callback action
                        }

                        evt.stopPropagation();

                    });
            }
        },

        /**
         * Creates the submenu.
         *
         * @param {Object} opts - The options to build the submenu.
         * @param {string} opts.id - The id of the submenu.
         * @param {string} opts.items - The items to fill the submenu.
         * @private
         */
        _createSubmenu: function (opts) {
            this._menu.addClass(SUBMENU_VISIBLE_CLASS);
            this._submenu = this.appendChildWidget(new SubMenu(opts));
            this.setActiveChildWidget(this._submenu);
            this._submenu.setActiveChildIndex(0);
        },

        /**
         * Creates the sub-submenu.
         *
         * @param {Object} opts - The options to build the submenu.
         * @param {string} opts.id - The id of the submenu.
         * @param {string} opts.items - The items to fill the submenu.
         * @private
         */
        _createSubSubmenu: function (opts) {
            this._subSubmenu = this.appendChildWidget(new SubMenu(opts));
            this.setActiveChildWidget(this._subSubmenu);
            this._subSubmenu.setActiveChildIndex(0);
        },

        /**
         * Removes the submenus.
         */
        removeSubmenus: function () {
            this._menu.removeClass(SUBMENU_VISIBLE_CLASS);
            if (this._submenu) {
                this.removeChildWidget(this._submenu);
                this._submenu = null;
            }
            if (this._subSubmenu) {
                this.removeChildWidget(this._subSubmenu);
                this._subSubmenu = null;
            }
        },

        /**
         * Gets executed when the selected index of the section changes.
         *
         * @param {SelectedItemChangeEvent} evnt - The event data.
         * @private
         */
        _onSelectedIndexChange: function (evnt) {
            var selectedIndex = evnt.index,
                item = evnt.item,
                value = 0;

            if (this._totalDataItems > this._visibleItems) {

                if (selectedIndex >= (this._visibleItems - this._scrollOffset) &&
                    selectedIndex <= (this._totalDataItems - (this._scrollOffset - 1))) {
                    value = (this._visibleItems - this._scrollOffset) - selectedIndex;

                    if (item.outputElement) {
                        this._scroll(value * item.outputElement.offsetHeight);
                    }
                }
            }

        },

        /**
         * Returns the widgets that wrap the menu for the purpose of setting classes on them.
         *
         * @returns {Array} - Array of widgets.
         * @private
         */
        _getMenuContainers: function () {
            return [this, this.getComponent(), this.getComponent().parentWidget];
        },

        /**
         * Handles the mouse over event.
         */
        _onMouseEnterHandler: function () {
            Utils.each(this._getMenuContainers(), function (container) {
                container.addClass('hover');
            });
        },

        /**
         * Handles the mouse leave event.
         */
        _onMouseLeaveHandler: function () {
            Utils.each(this._getMenuContainers(), function (container) {
                container.removeClass('hover');
            });
        },

        /**
         * Handles pointer off event.
         */
        _onPointerOffHandler: function () {
            Utils.each(this._getMenuContainers(), function (container) {
                container.removeClass('hover');
            });
        },

        /**
         * Remove selected class.
         *
         * @private
         */
        _removeSelectedClass: function () {
            var sections = this.parentWidget.getChildWidgets();

            Utils.each(sections, function (section) {
                if (section.getChildWidgets()) {
                    Utils.each(section.getChildWidgets(), function (widgets) {
                        if (widgets.getChildWidgets()) {
                            Utils.each(widgets.getChildWidgets(), function (widget) {
                                widget.removeClass('selected');
                            });
                        }
                    });
                }
            });
        },

        /**
         * Scrolls the menu section to the given value.
         *
         * @param {number} value - The value to scroll to.
         * @private
         */
        _scroll: function (value) {
            var self = this,
                animation = this._scrollAnimation,
                outputElement = this.getActiveChildWidget().outputElement,
                device = application.getDevice();

            if (outputElement) {

                if (animation) {
                    device.stopAnimation(animation);
                }

                this._scrollAnimation = device.moveElementTo({
                    el: outputElement,
                    duration: self._scrollAnimationDuration,
                    to: {
                        top: value
                    },
                    onComplete: function () {
                        self._scrollAnimation = null;
                    }
                });
            }
        },

        /**
         * Handles the menu's disposal.
         */
        dispose: function dispose () {
            this._menu.destroyPointerHandlers();

            this.removeSubmenus();
            dispose.base.call(this);
        },

        /**
         * Checks if submenu is visible or not.
         *
         * @returns {boolean} - Is submenu visible.
         */
        isSubmenuVisible: function () {
            return !!this._submenu;
        },

        /**
         * Checks if the section is a submenu.
         *
         * @returns {boolean} - Is submenu.
         */
        isSubmenu: function () {
            return this.getActiveChildWidget().hasClass('submenu-list');
        }
    });
});