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');
}
});
});