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