Adding a search bar (Input) to the Mega Menu – Menu Closes or Input Not Editable
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
07-24-2025 12:23 AM
Hi everyone,
I'm trying to add a custom button (input field) to the Mega Menu in the Employee Center portal.
Here’s what I did:
I cloned the widget: Employee Center Navigation
I successfully added my input field/button to the HTML.
The input is visible in the Mega Menu, but when I try to click on it (or anywhere on the menu), the menu immediately closes.
- I tried to use "ng-click="$event.stopPropagation();" and than when i click on it, the menu is not closing but i cant type anything in the input
I found the relevant function that causes this "c.closeMegaMenu();"
Any ideas on how to make the input work?
If someone has done this before or has a code sample, I’d really appreciate it.
html:
<div class="employee-center-navigation" role="navigation">
<div class="main-menu-width-calc">
<div ng-repeat="item in data.menuItems track by item.sys_id" on-finish-repeat="ngRepeatFinished" class="navigation-menu-item-calc">
<fa ng-if="::item.glyph" name="{{::item.glyph}}"></fa>
{{item.name ? item.name : item.label}}
<span class="icon" ng-class="(selectedMenuItem && item.sys_id === selectedMenuItem.sys_id) ? 'fa fa-caret-up' : 'fa fa-caret-down'" ng-if="hasChildItems(item)"></span>
</div>
</div>
<div ng-if="!showMobileMenuOnDesktop && !isMobileView">
<!-- topic-based navigation menu -->
<div ng-if="!(data.leftAlignMenuItems.length || data.rightAlignMenuItems.length)" class="main-menu" ng-click="handleClickOnMainMenu($event)" role="menubar">
<div ng-repeat="item in data.menuItems track by item.sys_id" class="navigation-menu-item" aria-label="{{item.name ? item.name : item.label}}" ng-class="{'selected-item': (selectedMenuItem && item.sys_id === selectedMenuItem.sys_id), 'menu-link': !c.doesMenuItemHaveChildren(item)}" ng-click="handleMainMenuItemClick(item, $event)" ng-attr-aria-expanded="{{ c.doesMenuItemHaveChildren(item) ? ((selectedMenuItem && item.sys_id === selectedMenuItem.sys_id) || false) : undefined}}" aria-owns="{{ (selectedMenuItem && selectedMenuItem.sys_id === selectedMenuItem.sys_id) ? 'menu_'+selectedMenuItem.sys_id : undefined }}" tabindex="0" role="menuitem" aria-haspopup="{{c.doesMenuItemHaveChildren(item)}}">
<fa ng-if="::item.glyph" name="{{::item.glyph}}" class="inline-icon"></fa>
{{item.name ? item.name : item.label}}
<span class="icon" ng-class="(selectedMenuItem && item.sys_id === selectedMenuItem.sys_id) ? 'fa fa-caret-up' : 'fa fa-caret-down'" ng-if="getChildNavItems(item).length"></span>
</div>
</div>
<div ng-if="data.leftAlignMenuItems.length || data.rightAlignMenuItems.length" class="main-menu main-menu-navigation" ng-click="handleClickOnMainMenu($event)" role="menubar">
<div class="left-align-menu">
<div ng-repeat="item in data.leftAlignMenuItems track by item.portal_nav_item"
class="navigation-menu-item" aria-label="{{item.name ? item.name : item.label}}"
ng-class="{'selected-item': ((selectedMainMenu && item.portal_nav_item === selectedMainMenu.portal_nav_item)
|| (selectedMenuItem && item.portal_nav_item == selectedMenuItem.portal_nav_item)), 'menu-link': !c.doesMenuItemHaveChildren(item), 'expanded-menu': item.submenu_type == 'expanded'}"
ng-click="handleMainMenuItemClick(item, $event)"
ng-attr-aria-expanded="{{ c.doesMenuItemHaveChildren(item) ? ((selectedMainMenu && item.portal_nav_item === selectedMainMenu.portal_nav_item) || (selectedMenuItem && item.portal_nav_item == selectedMenuItem.portal_nav_item) || false) : undefined}}"
aria-owns="{{ ((selectedMainMenu && item.portal_nav_item === selectedMainMenu.portal_nav_item) || (selectedMenuItem && item.portal_nav_item == selectedMenuItem.portal_nav_item)) ? 'menu_'+selectedMenuItem.sys_id : undefined }}"
tabindex="0" role="menuitem" aria-haspopup="{{c.doesMenuItemHaveChildren(item)}}">
<fa ng-if="::item.glyph" name="{{::item.glyph}}" class="inline-icon"></fa>
{{item.name ? item.name : item.label}}
<span class="icon"
ng-class="((selectedMainMenu && item.portal_nav_item === selectedMainMenu.portal_nav_item)
|| (selectedMenuItem && item.portal_nav_item == selectedMenuItem.portal_nav_item)) ? 'fa fa-caret-up' : 'fa fa-caret-down'" ng-if="hasChildItems(item)"></span>
</div>
</div>
<div class="right-align-menu">
<div ng-repeat="item in data.rightAlignMenuItems track by item.portal_nav_item"
class="navigation-menu-item" aria-label="{{item.name ? item.name : item.label}}"
ng-class="{'selected-item': ((selectedMainMenu && item.portal_nav_item === selectedMainMenu.portal_nav_item)
|| (selectedMenuItem && item.portal_nav_item == selectedMenuItem.portal_nav_item)), 'menu-link': !c.doesMenuItemHaveChildren(item), 'expanded-menu': item.submenu_type == 'expanded'}"
ng-click="handleMainMenuItemClick(item, $event)"
ng-attr-aria-expanded="{{ c.doesMenuItemHaveChildren(item) ? ((selectedMainMenu && item.portal_nav_item === selectedMainMenu.portal_nav_item) || (selectedMenuItem && item.portal_nav_item == selectedMenuItem.portal_nav_item) || false) : undefined}}"
aria-owns="{{ ((selectedMainMenu && item.portal_nav_item === selectedMainMenu.portal_nav_item) || (selectedMenuItem && item.portal_nav_item == selectedMenuItem.portal_nav_item)) ? 'menu_'+selectedMenuItem.sys_id : undefined }}" tabindex="0" role="menuitem" aria-haspopup="{{c.doesMenuItemHaveChildren(item)}}">
<fa ng-if="::item.glyph" name="{{::item.glyph}}" class="inline-icon"></fa>
{{item.name ? item.name : item.label}}
<span class="icon" ng-class="((selectedMainMenu && item.portal_nav_item === selectedMainMenu.portal_nav_item) || (selectedMenuItem && item.portal_nav_item == selectedMenuItem.portal_nav_item)) ? 'fa fa-caret-up' : 'fa fa-caret-down'" ng-if="hasChildItems(item)"></span>
</div>
</div>
</div>
<div class="dropdown-container visibility" ng-if="showMenu || selectedMenuItem" ng-style="megaMenuPosition">
<div class="menu-container" ng-class="{'expanded' : selectedMainMenu}">
<div class="tab-container" ng-if="showMenu && !showMenuOnLeft">
<div class="tab-list-container">
<div class="tab-list" ng-repeat="item in c.data.menuItemToChildrenMap[selectedMainMenu.portal_nav_item] | orderBy: 'order'" ng-click="handleMainMenuItemClick(item, $event, true)" tabindex="0" role="menuitem" ng-class="{'selected-item': (selectedMenuItem && item.sys_id === selectedMenuItem.sys_id), 'menu-link': !c.doesMenuItemHaveChildren(item)}">
<span class="tab-name">{{item.type == 'topic' ? item.name : item.label}}</span>
<span ng-if="c.hasSubMenuData(item)" class="tab-icon fa fa-angle-right fa-lg"></span>
</div>
</div>
<div class="tab-browse browse-button-container" ng-if="showMenu && getBrowseButtontext(selectedMainMenu, true)">
<a class="browse-button btn btn-primary" aria-label="{{tabbedBrowseButtontext}}" ng-href="{{returnRedirectionPath(selectedMainMenu)}}" tabindex="0" ng-click="browseAll(selectedMainMenu)">
<span>{{tabbedBrowseButtontext}}</span>
</a>
</div>
</div>
<!-- Simple dropdown menu for one level of sub-topics -->
<div ng-if="selectedMenuItem && !showMegaMenu && !getQuickLinksForMenuItem(selectedMenuItem).length && selectedMenuItem.render_as !== 'modal_window'" id="menu_{{selectedMenuItem.sys_id}}" class="simple-menu" ng-class="{ 'ie': c.isIE }">
<ul class="simple-list" role="menu">
<li class="dropdown-menu-item" ng-repeat="subMenuItem in getLevelOneMenuItems(selectedMenuItem) track by subMenuItem.sys_id" aria-label="{{subMenuItem.name ? subMenuItem.name : subMenuItem.label}}" ng-click="handleMegaMenuItemClick($event, subMenuItem)" tabindex="0" role="menuitem">
<span>
<fa ng-if="::subMenuItem.glyph" name="{{::subMenuItem.glyph}}" class="inline-icon"></fa>{{subMenuItem.name ? subMenuItem.name : subMenuItem.label}}
</span>
</li>
<li class="dropdown-menu-item" aria-label="${Browse all}" ng-click="handleMegaMenuItemClick($event, selectedMenuItem)" ng-if="selectedMenuItem.taxonomy && !c.data.activePortalNavRecord" tabindex="0" role="menuitem">
<span>${Browse all}</span>
<span class="icon fa fa-chevron-right"></span>
</li>
<li class="dropdown-menu-item" ng-click="handleMegaMenuItemClick($event, selectedMenuItem)" ng-if="c.data.activePortalNavRecord && getBrowseButtontext(selectedMenuItem)" tabindex="0" aria-label="{{browseButtontext}}" role="menuitem">
<span>{{browseButtontext}}</span>
<span class="icon fa fa-chevron-right"></span>
</li>
</ul>
</div>
<!-- Mega menu for 2 levels of topic hierarchy -->
<div ng-if="showMegaMenu" class="mega-menu visibility" id="menu_{{selectedMenuItem.sys_id}}">
<div class="mega-menu__items">
<div class="sub-topics-container" ng-if="getLevelOneMenuItems(selectedMenuItem).length">
<div ng-repeat="item in levelOneMenuItems track by item.sys_id" ng-if="getLevelTwoMenuItems(item).length" ng-init="groups = getLevelTwoTopicsAsGroupsOfTen(levelTwoMenuItems)" class="level-one-item">
<div>
<a ng-init="item.ngHref = getMegaMenuItemHref(item)" ng-href="{{item.ngHref}}" id="level-one-topic-name-{{$index}}" class="level-one-topic-name" ng-click="handleMegaMenuItemClick($event, item)" ng-attr-title="{{ c.isIE ? item.name : undefined }}" aria-label="{{item.name}}" ng-class="{'level-one-first-item': $index==0}" aria-owns="sub-topic-groups-{{$index}}">
<span class="level-one-topic-ellipsis">
{{item.name ? item.name : item.label}}
</span>
</a>
</div>
<!-- Grouping 10 sub topics each into one -->
<div class="sub-topic-groups" role="list" aria-labelledby="level-one-topic-name-{{$index}}" id="sub-topic-groups-{{$index}}">
<ul class="simple-list sub-topics-list" role="none" ng-repeat="group in groups track by $index">
<li ng-repeat="subTopic in group track by subTopic.sys_id">
<a ng-init="subTopic.ngHref = getMegaMenuItemHref(subTopic)" ng-href="{{subTopic.ngHref}}" class="level-two-topic-name" ng-attr-title="{{ c.isIE ? subTopic.name : undefined }}" ng-click="handleMegaMenuItemClick($event, subTopic)" aria-label="{{subTopic.name}}">
<span>{{subTopic.name ? subTopic.name : subTopic.label}}</span>
</a>
</li>
</ul>
</div>
</div>
<!-- All sub topics with no children sit inside a separate grid item -->
<div class="level-one-item" ng-if="getLevelOneTopicsWithNoChildren(selectedMenuItem).length">
<ul class="simple-list orphan-list" role="none">
<li ng-repeat="subTopic in levelOneTopicsWithNoChildren track by subTopic.sys_id">
<a ng-init="subTopic.ngHref = getMegaMenuItemHref(subTopic)" ng-href="{{subTopic.ngHref}}" class="level-one-topic-name" ng-attr-title="{{ c.isIE ? subTopic.name : undefined }}" ng-click="handleMegaMenuItemClick($event, subTopic)" aria-label="{{subTopic.name}}">
<span class="level-one-topic-ellipsis">
{{subTopic.name ? subTopic.name : subTopic.label}}
</span>
</a>
</li>
</ul>
</div>
</div>
<div class="quick-links-container" ng-if="getQuickLinksForMenuItem(selectedMenuItem).length">
<span class="quick-links-container__title" id="quick-links-container-title">${Quick Links}</span>
<ul class="simple-list" aria-labelledby="quick-links-container-title">
<li ng-repeat="link in QuickLinksForMenuItem">
<a ng-href="{{link.url}}" aria-label="{{link.title}}" ng-click="handleQuickLinkClick($event, link)" target="{{link.target}}">
<span class="quick-link-title" ng-attr-title="{{ c.isIE ? link.title : undefined }}">{{link.title}}</span>
<span class="icon fa fa-external-link fa-align-bottom" title="${External link}" tabindex="-1" ng-if="(link.target == '_blank')"></span>
</a>
</li>
</ul>
</div>
</div>
<div class="browse-button-container" ng-if="!c.data.activePortalNavRecord || (c.data.activePortalNavRecord && getBrowseButtontext(selectedMenuItem))"
>
<a class="browse-button btn btn-primary" ng-if="!c.data.activePortalNavRecord" ng-click="browseAll(selectedMenuItem)" ng-href="{{returnRedirectionPath(selectedMenuItem)}}" aria-label="${Browse all}" tabindex="0">
<span>${Browse all}</span>
</a>
<a id="inner-button" class="browse-button btn btn-primary" ng-if="c.data.activePortalNavRecord && getBrowseButtontext(selectedMenuItem)" ng-click="browseAll(selectedMenuItem)" ng-href="{{returnRedirectionPath(selectedMenuItem)}}" aria-label="{{browseButtontext}}" tabindex="0">
<span>{{browseButtontext}}</span>
</a>
<input
id="mega-menu-search"
type="text"
ng-model="megaMenuSearchText"
placeholder="Search..."
style="height:32px; padding:0 12px; border-radius:4px; border:1px solid #ccc; font-size:16px;"
ng-click="$event.stopPropagation();"
ng-mousedown="$event.stopPropagation();"
autocomplete="off"
/>
</div>
</div>
<!-- This div is used when megamenu opens on the left -->
<div class="tab-container" ng-if="showMenu && showMenuOnLeft">
<div class="tab-list-container">
<div class="tab-list" ng-repeat="item in c.data.menuItemToChildrenMap[selectedMainMenu.portal_nav_item] | orderBy: 'order'" ng-click="handleMainMenuItemClick(item, $event, true)" tabindex="0" role="menuitem" ng-class="{'selected-item': (selectedMenuItem && item.sys_id === selectedMenuItem.sys_id), 'menu-link': !c.doesMenuItemHaveChildren(item)}">
<span class="tab-name">{{item.type == 'topic' ? item.name : item.label}}</span>
<span ng-if="c.hasSubMenuData(item)" class="tab-icon fa fa-angle-right fa-lg"></span>
</div>
</div>
<div class="tab-browse browse-button-container" ng-if="showMenu && getBrowseButtontext(selectedMainMenu, true)">
<a class="browse-button btn btn-primary" aria-label="{{tabbedBrowseButtontext}}" ng-href="{{returnRedirectionPath(selectedMainMenu)}}" ng-click="browseAll(selectedMainMenu)" tabindex="0">
<span>{{tabbedBrowseButtontext}}</span>
</a>
</div>
</div>
</div>
</div>
</div>
<div ng-if="showMobileMenuOnDesktop || isMobileView" class="navigation--xs">
<!-- topic-based mobile navigation menu -->
<ul class="mobile-main-menu" role="menu" ng-if="!showMobileMegaMenu">
<li ng-repeat="item in data.menuItems track by item.sys_id" class="mobile-navigation-menu-item mega-menu-item {{'mobile-menu-item-' + item.sys_id}}" ng-click="handleMobileMainMenuItemClick(item)" aria-label="{{item.name ? item.name : item.label}}" ng-class="{'menu-link': !c.doesMenuItemHaveChildren(item)}" ng-attr-aria-expanded="{{ c.doesMenuItemHaveChildren(item) ? ((selectedMenuItem && item.sys_id === selectedMenuItem.sys_id) || false) : undefined}}" role="menuitem" tabindex="0" aria-haspopup={{c.doesMenuItemHaveChildren(item)}}>
<span>
<fa ng-if="::item.glyph" name="{{::item.glyph}}" class="inline-icon"></fa>{{item.name ? item.name : item.label}}
</span>
<span class="icon fa fa-chevron-right" tabindex="-1" ng-if="hasChildItems(item)"></span>
</li>
</ul>
<ul class="mobile-mega-menu" ng-if="showMobileMegaMenu" role="menu">
<li class="mobile-mega-menu__item back-container mega-menu-item" ng-click="goBackOnMobileView()" aria-label="${Back}" role="menuitem" tabindex="0">
<span tabindex="-1" class="icon fa fa-chevron-left back-button"></span> ${Back}
</li>
<li class="selected-topic-name" aria-hidden="true">
{{selectedMobileMenuItem.name ? selectedMobileMenuItem.name : selectedMobileMenuItem.label}}
</li>
<li ng-repeat="item in levelOneMenuItems track by item.sys_id" class="mobile-mega-menu__item mega-menu-item {{'mobile-menu-item-' + item.sys_id}}" ng-class="{'mobile-navigation-menu-item': levelOneMenuItems.length}" ng-attr-aria-expanded="{{ levelOneMenuItems.length ? ((selectedMenuItem && item.sys_id === selectedMenuItem.sys_id) || false) : undefined}}" ng-click="handleMobileMegaMenuItemClick(item)" aria-label="{{item.name ? item.name : item.label}}" role="menuitem" tabindex="0">
<span>
<fa ng-if="::item.glyph" name="{{::item.glyph}}" class="inline-icon"></fa> {{item.name ? item.name : item.label}}
</span>
<span tabindex="-1" class="icon fa fa-chevron-right" ng-if="hasChildItems(item) || (item.render_as == 'modal_window' && item.widget)"></span>
</li>
<li class="browse-all mega-menu-item" ng-if="selectedMobileMenuItem && selectedMobileMenuItem.taxonomy" ng-click="redirectToBrowseAll(selectedMobileMenuItem)" aria-label="${Browse all}" role="menuitem" tabindex="0">
<a tabindex="-1" ng-href="{{returnRedirectionPath(selectedMobileMenuItem)}}" aria-label="${Browse all}" aria-hidden="true">
<span>${Browse all}<span class="icon fa fa-chevron-right"></span></span>
</a>
</li>
<!-- Quick Links section -->
<li>
<ul class="mobile-mega-menu__quick-links-container" ng-if="isLevelOneMobileMegaMenu && getQuickLinksForMenuItem(selectedMobileMenuItem).length">
<li class="mobile-mega-menu__quick-links-title" aria-hidden="true">${Quick Links}</li>
<li class="mobile-mega-menu__quick-link mega-menu-item" role="menuitem" tabindex="0" ng-repeat="link in QuickLinksForMenuItem">
<a ng-href="{{link.url}}" aria-label="{{link.title}} - ${Quick Links}" tabindex="-1" ng-click="handleQuickLinkClick($event, link)" target="{{link.target}}">
<span class="quick-link-title" ng-attr-title="{{ c.isIE ? link.title : undefined }}">{{link.title}}</span>
<span class="icon fa fa-external-link fa-align-bottom" title="${External link}" tabindex="-1" ng-if="(link.target == '_blank')"></span>
</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
client script:
api.controller = function($scope, $rootScope, $location, $timeout, $window, spModal, spUtil) {
var c = this;
c.SUB_NAVBAR_HEIGHT = 51;
c.SIMPLE_MENU_WIDTH = 268;
c.MOBILE_DEVICE_SCREEN_WIDTH = 767;
c.MEGA_MENU_ITEMS_PADDING = 24;
c.GRID_ITEM_PADDING_RIGHT = 24;
c.GRID_ROW_GAP = 20;
c.MEGA_MENU_MAX_WIDTH = 1430;
c.MAX_BUTTON_WIDTH = 288;
c.DELTA_OFFSET = 24;
c.IE_PREFIX = 'Trident/';
c.isIE = (window.navigator.userAgent.indexOf(c.IE_PREFIX) !== -1);
c.DEFAULT_FONT_SIZE = 16;
c.textZoomFactor = 1;
c.tabWidth = 208;
c.minMegaMenuWidth = 400;
// Events fired from/to $rootScope for responsive mobile view.
c.SHOW_MOBILE_MEGA_MENU = 'showMobileMegaMenu';
c.SHOW_MOBILE_MENU_ON_DESKTOP = 'showMobileMenuOnDesktop';
c.TOGGLE_MOBILE_MENU_VISIBILITY = 'toggleMobileMenuVisibility';
// Below 2 scope variables (showMegaMenu & selectedMenuItem) are used together to show simple/mega menu.
$scope.showMegaMenu = false;
$scope.selectedMenuItem = undefined;
$scope.megaMenuPosition = undefined;
$scope.megaMenuLeftOffset = undefined;
$scope.levelOneTopicsWithNoChildren = [];
$scope.selectedMenuItemRightCoordinate = 0;
$scope.showMenuOnLeft = false;
$scope.showMenu = false;
// This value is computed after the grid layout is complete based on type (single-column, 2-column, orphan-list) of items present in the grid.
c.gridItemWidth = 0;
// Below 2 boolean scope variables are set to true in mutually exclusive manner based on how grid item width is computed after layout
c.usedOrphansColumnForGridItemWidth = false;
c.allGridItemsHaveTwoColumns = false;
/* We set this to false when we encounter 2-column grid item while resizing each item & use it to prevent executing
the logic to fill vertical gaps formed due to presence of 2-column grid items */
c.allGridItemsHaveSingleColumn = true;
/* Below 2 scope variables (showMobileMenuOnDesktop, showMobileMegaMenu) have counterparts in EC Header widget also.
So whenever we set these variables, make sure to emit an event on rootScope to update them in the former widget as well. */
$scope.showMobileMenuOnDesktop = false;
/* showMobileMegaMenu, isLevelOneMobileMegaMenu are used together. If showMobileMegaMenu is true and
isLevelOneMobileMegaMenu is false it means we are currently viewing level 2 subtopics in mobile mega menu */
$scope.showMobileMegaMenu = false;
$scope.isLevelOneMobileMegaMenu = false;
$scope.selectedMobileMenuItem = undefined;
$scope.levelOneSelectedMobileMenuItem = undefined;
$scope.lastSelectedElementId = undefined;
$scope.isWindowResized = false;
$scope.isMobileView = $rootScope.isHeaderInMobileView || c.data.isMobile || ($window.innerWidth <= c.MOBILE_DEVICE_SCREEN_WIDTH);
/**
* Used for capturing positions of columns in the mega menu:
* masonry layout.
* navigationMap : an object with key as left offset in pixel & value as array
*/
$scope.navigationMap = {};
$scope.keyboardNavRowIndex = 0;
$scope.keyboardNavColIndex = 0;
/**
* This function provides keyboard navigation within mega menu
*
* @Param {Object} event - keyboard event object
* @see {@link c.initialiseKeyboardNav}
* @ToDo change event.which to event.key after end of support for IE11.
*/
$scope.handleMegaMenuKeyboardEvent = function(event) {
event.preventDefault();
var navIndex = Object.keys($scope.navigationMap);
switch (event.which) {
case 32: //fall through, click on space or enter key
case 13:
$(event.target).click();
break;
case 38:
if (!$(event.target).hasClass("browse-button")) {
//if not on 1st row, move up a row
if ($scope.keyboardNavRowIndex != 0) {
$scope.keyboardNavRowIndex--;
} else if ($scope.keyboardNavRowIndex === 0) { //if on top most link, set it to last link of the column
$scope.keyboardNavRowIndex = $scope.navigationMap[navIndex[$scope.keyboardNavColIndex]].length - 1;
}
}
break;
case 40:
if (!$(event.target).hasClass("browse-button")) {
// if not on last row, move down one row
if ($scope.keyboardNavRowIndex + 1 !== $scope.navigationMap[navIndex[$scope.keyboardNavColIndex]].length)
$scope.keyboardNavRowIndex++;
// if on last row, go to 1st row
else if ($scope.keyboardNavRowIndex === $scope.navigationMap[navIndex[$scope.keyboardNavColIndex]].length - 1) {
$scope.keyboardNavRowIndex = 0;
}
}
break;
case 37:
$scope.keyboardNavRowIndex = 0;
// if 1st column => move to Browse button => last column now
if ($scope.keyboardNavColIndex === 0) {
$scope.keyboardNavColIndex = navIndex.length - 1;
}
// if not 1st column, decrease the column to the left one
else {
$scope.keyboardNavColIndex--;
}
break;
case 39:
$scope.keyboardNavRowIndex = 0;
// if not last column, move to next column
if ($scope.keyboardNavColIndex + 1 < navIndex.length) {
$scope.keyboardNavColIndex++;
}
// if last column (i.e. on browse button) => move to 1st column
else {
$scope.keyboardNavColIndex = 0;
}
break;
// shift keydown & keyup events are already being handled in link script.
case 9:
//if shift + tab
if (c.shiftKeyPressed) {
if ($scope.keyboardNavRowIndex != 0) {
$scope.keyboardNavRowIndex--;
}
//if it is 0th row
else {
// and column is also 0th, then move to parent selected navigation menu item
if ($scope.keyboardNavColIndex === 0) {
if ($scope.selectedMainMenu) {
$(".tab-list.selected-item").focus();
$scope.$apply(function() {
c.closeTabMenu();
});
} else {
$(".navigation-menu-item.selected-item").focus();
$scope.$apply(function() {
c.closeMegaMenu();
});
}
return;
} else { //if 0th row but not 0th column, then decrement column & go to its last row
$scope.keyboardNavColIndex--;
$scope.keyboardNavRowIndex = $scope.navigationMap[navIndex[$scope.keyboardNavColIndex]].length - 1;
}
}
}
// only tab
else {
// if not on the last row of current column, then increment the row
if ($scope.navigationMap[navIndex[$scope.keyboardNavColIndex]].length != $scope.keyboardNavRowIndex + 1) {
$scope.keyboardNavRowIndex++;
}
// if on the last row
else {
// but not the last column, then increment column & go to row 0 of next column.
if ($scope.keyboardNavColIndex !== navIndex.length - 1) {
$scope.keyboardNavRowIndex = 0;
$scope.keyboardNavColIndex++;
} //if on last row of last column, then move to 1st column
else if ($scope.keyboardNavColIndex === navIndex.length - 1) {
if($scope.selectedMainMenu) {
$('.tab-list.selected-item').focus();
c.closeTabMenu();
} else {
$('div.selected-item').focus();
c.closeMegaMenu();
}
$scope.keyboardNavColIndex = 0;
}
}
}
break;
default:
return;
}
$scope.navigationMap[navIndex[$scope.keyboardNavColIndex]][$scope.keyboardNavRowIndex].focus();
};
/**
* Helper function for c.initialiseKeyboardNav
* Searches existing column offsets in range of currentOffset ± DELTA_OFFSET
* and returns index of the nearest column offset, else -1.
* @Param {Array} columnOffsets - array of left offsets of columns
* @Param {string} currentOffset - current element's left offset
* @see {@link c.initialiseKeyboardNav}
*/
var columnSearch = function(columnOffsets, currentOffset) {
var start = 0, end = columnOffsets.length - 1;
while (start <= end) {
var mid = Math.floor((start + end) / 2);
if ((Number(columnOffsets[mid]) - c.DELTA_OFFSET <= currentOffset) && (currentOffset <= Number(columnOffsets[mid]) + c.DELTA_OFFSET))
return mid;
else if (Number(columnOffsets[mid]) + c.DELTA_OFFSET < currentOffset)
start = mid + 1;
else
end = mid - 1;
}
return -1;
};
/**
* This function identifies columns from the masonry layout and stores in navigationMap object
*/
c.initialiseKeyboardNav = function() {
$scope.navigationMap = {};
var allItems = $('.mega-menu').find('a').not('.browse-button');
for (var index = 0; index < allItems.length; index++) {
var element = allItems.get(index);
if ($scope.navigationMap.hasOwnProperty(element.offsetLeft))
$scope.navigationMap[element.offsetLeft].push(element);
else {
var _navKeys = Object.keys($scope.navigationMap);
var _currentOffset = element.offsetLeft;
var _closeColumn = columnSearch(_navKeys, _currentOffset);
if (_closeColumn !== -1) {
$scope.navigationMap[String(_navKeys[_closeColumn])].push(element);
} else {
$scope.navigationMap[_currentOffset] = [element];
}
}
}
var browseButton = $('.mega-menu').find('.browse-button');
if(browseButton.length > 0)
$scope.navigationMap["browse-button"] = [browseButton.get(0)];
};
$rootScope.$on(c.SHOW_MOBILE_MEGA_MENU, function(event, showMobileMegaMenu) {
$scope.showMobileMegaMenu = showMobileMegaMenu;
});
// 'ngRepeatFinished' is emitted from onFinishRepeat Angular Provider Directive
$scope.$on('ngRepeatFinished', function() {
/* On page load, when sub navbar is populated with taxonomy root topics & portal main menu items this method determines
whether to use desktop/mobile view for portal navigation by checking if sub navbar contents exceed viewport width. */
c.evaulateNavigationType();
});
$scope.$watch(
function() {
return $(".navigation-menu-item-calc").css("line-height");
},
function(newVal, oldVal) {
if (newVal !== oldVal) {
c.evaulateNavigationType();
if (!$scope.showMobileMenuOnDesktop && $scope.showMegaMenu) {
c.adjustTabPanelHeight(true);
c.preMasonryWorkForMegaMenuRefreshWithNewItems();
c.initializeMasonry();
}
}
});
$scope.$watch('showMegaMenu', function(newValue, oldValue) {
if (!oldValue && newValue) {
c.initializeMasonry();
}
});
$scope.$watch('selectedMenuItem', function(newSelectedMenuItem, oldSelectedMenuItem) {
/* When selected main menu item is changed to show mega menu for another root topic,
execute masonry logic again for refreshing mega menu UI with new menu items */
if ($scope.showMegaMenu && newSelectedMenuItem && oldSelectedMenuItem) {
if ((!c.data.activePortalNavRecord && newSelectedMenuItem.sys_id !== oldSelectedMenuItem.sys_id)
|| (c.data.activePortalNavRecord && newSelectedMenuItem.portal_nav_item !== oldSelectedMenuItem.portal_nav_item)) {
c.adjustTabPanelHeight(true);
c.preMasonryWorkForMegaMenuRefreshWithNewItems();
c.initializeMasonry();
}
}
});
// Check if mobile view on desktop is needed every 0.25 secs (check link function) browser window is resized.
$scope.$watch("isWindowResized", function(newValue, oldValue) {
if (newValue) {
c.evaulateNavigationType();
$scope.isWindowResized = false;
if ($scope.showMegaMenu) {
c.adjustTabPanelHeight(true);
c.preMasonryWorkForResponsiveViews();
c.initializeMasonry();
}
}
});
// Cleaning up previous grid data for refreshing Mega menu UI when switching from one root topic Mega menu to another root topic Mega menu
c.preMasonryWorkForMegaMenuRefreshWithNewItems = function() {
// Resetting Grid variables in controller scope to their initial values.
c.gridItemWidth = 0;
c.usedOrphansColumnForGridItemWidth = false;
c.allGridItemsHaveTwoColumns = false;
c.allGridItemsHaveSingleColumn = true;
// Revert the styles applied to grid items for previous selected main menu item
var gridItems = $(".level-one-item");
gridItems.each(function(currentIndex, gridItemDOMElement) {
$(gridItemDOMElement).css({
"grid-row-end": "auto",
"grid-column-end": "auto",
"margin-top": "0px",
"height": "fit-content"
});
});
$(".quick-links-container").css("min-height", c.isIE ? "0px" : "min-content");
$(".quick-links-container").css("height", "");
};
c.preMasonryWorkForResponsiveViews = function() {
/* Revert the styles applied to grid items to fill vertical gaps in the previous view.
We need not revert grid-row-end, grid-column-end values here as actual menu items are not
changing here like they were changing while switching from one Mega menu view to other. */
var gridItems = $(".level-one-item");
gridItems.each(function(currentIndex, gridItemDOMElement) {
$(gridItemDOMElement).css({
"margin-top": "0px",
"height": "fit-content"
});
});
$(".quick-links-container").css("min-height", "min-content");
};
$rootScope.$on('isHeaderInMobileView', function($evt, isHeaderInMobileView) {
//re-evalutate navigation type if header mobile type is changed
c.evaulateNavigationType();
});
// Enables mobile view on desktop if main menu items are overflowing viewport width
c.evaulateNavigationType = function() {
var mainMenuDOMElements = $(".main-menu-width-calc");
// Getting last element if there are multiple as 'ngRepeatFinished' is emitted before rendering last element.
var mainMenuDOMElement = mainMenuDOMElements[mainMenuDOMElements.length - 1];
if (mainMenuDOMElement && (mainMenuDOMElement.offsetWidth < mainMenuDOMElement.scrollWidth)) {
$scope.showMobileMenuOnDesktop = !$scope.isMobileView;
$rootScope.$broadcast(c.SHOW_MOBILE_MENU_ON_DESKTOP, !$scope.isMobileView);
if ($scope.showMobileMenuOnDesktop) {
c.closeMegaMenu();
}
} else {
$scope.showMobileMenuOnDesktop = $scope.isMobileView || $rootScope.isHeaderInMobileView;
$rootScope.$broadcast(c.SHOW_MOBILE_MENU_ON_DESKTOP, $scope.isMobileView || $rootScope.isHeaderInMobileView);
}
if ($scope.showMobileMenuOnDesktop) {
if ($('.toggle-dropdown').get(0)) {
$('.toggle-dropdown').get(0).tabIndex = -1;
}
if ($('.impersonate-and-logout .header-menu-item a').get(1)) {
$('.impersonate-and-logout .header-menu-item a').get(1).tabIndex = 0;
}
if ($('.impersonate-and-logout .header-menu-item a').get(2)) {
$('.impersonate-and-logout .header-menu-item a').get(2).tabIndex = 0;
}
}
};
c.initializeMasonry = function() {
// Do not use grid layout for IE. Using `flex` with display: -ms-flexbox; in CSS
if (c.isIE) {
$timeout(function() {
$('.mega-menu').addClass('ie');
$('.level-one-item').each(function(index, domElement) {
$(domElement).addClass('ie');
if (c.getSubTopicGroupCountInGridItem(domElement) === 2) {
$(domElement).addClass('ie-two-column');
}
});
$('.simple-list li').addClass('ie');
$('.level-one-topic-name').parent().addClass('ie');
$('.quick-link-title').addClass('ie');
$('.sub-topics-container').addClass('ie');
$('.sub-topics-list').addClass('ie');
}).then(function() {
c.adjustQuickLinksPanelHeight();
c.initialiseKeyboardNav();
topicTooltip();
});
return;
}
c.textZoomFactor = parseInt($('body').css('font-size')) / c.DEFAULT_FONT_SIZE;
// Setting scroll position to top before applying masonry logic for proper layout of grid items
if ($(".mega-menu__items").scrollTop() !== 0) {
$(".mega-menu__items").scrollTop(0);
}
/* Masonry logic is applied in 3 stages.
1. Allow grid items to span grid rows & columns based on their heights & no.of subtopic groups respectively.
2. Set mega menu width based on its contents.
3. Fill any gaps formed in the grid due to the 2-column grid items */
c.expandMegaMenuToFullWidth().then(function() {
$timeout(function() {
c.resizeAllGridItems();
c.adjustMegaMenuWidth();
c.fillVerticalGapsInTheGrid().then(function() {
c.adjustQuickLinksPanelHeight();
c.initialiseKeyboardNav();
c.updateMegaMenuPadding();
$timeout(function() {
c.toggleSubMenuVisibility();
c.toggleMegaMenuVisibility();
c.adjustTabPanelHeight();
});
});
});
});
};
c.adjustTabPanelHeight = function(reset) {
if(reset) {
$('.tab-container').css('height', 'fit-content');
return;
}
var dropDownHeight = $('.dropdown-container')[0].getBoundingClientRect().height;
var tabContainer = $('.tab-container');
if(tabContainer.length > 0 && tabContainer[0].getBoundingClientRect().height < dropDownHeight)
tabContainer.css('height', dropDownHeight);
}
// Returns a Promise ensuring that all grid items are laid out in Mega menu occupying 100% viewport width.
c.expandMegaMenuToFullWidth = function() {
return new Promise(function(resolve, reject) {
// Allow mega menu to consume viewport width for CSS Grid to layout items before applying masonry logic.
$('.dropdown-container').css('width', '100%');
$(".mega-menu").css("width", "100%");
resolve();
});
};
c.updateMegaMenuPadding = function() {
if (c.data.activePortalNavRecord) {
var subTopicsElement = c.data.menuItemToChildrenMap[$scope.selectedMenuItem.portal_nav_item];
var quickLinksElement = c.data.menuItemToQuickLinks[$scope.selectedMenuItem.portal_nav_item];
var megaMenuElement = $('.mega-menu__items');
megaMenuElement.removeClass("quick_link_only_menu");
if (subTopicsElement && subTopicsElement.length) {
return;
} else {
if (quickLinksElement && quickLinksElement.length) {
megaMenuElement.addClass("quick_link_only_menu");
}
}
}
return;
};
c.resizeAllGridItems = function() {
var grid = $(".sub-topics-container").get(0);
if (grid) {
var rowHeight = parseInt($window.getComputedStyle(grid).getPropertyValue('grid-auto-rows'));
var gridItems = $(".level-one-item");
c.gridItemWidth = c.getGridItemWidth(gridItems);
gridItems.each(function(currentIndex, gridItemDOMElement) {
c.resizeGridItem(gridItemDOMElement, rowHeight);
});
}
};
/* Grid item width is computed based on 3 scenarios.
1. There is a single column grid item. In this case, we use this grid items width to normalize other items widths.
2. All grid items have 2 columns. In this case, we take half of the 1st grid item content width and use it to normalize other items widths.
3. Grid has only 2-column and orphan-list items. In this case, we use the orphan list grid item as base to set other grid items widths. */
c.getGridItemWidth = function(gridItems) {
var gridItemCount = gridItems.length;
for (var index = 0; index < gridItemCount; index++) {
var gridItemDOMElement = gridItems[index];
if (c.getSubTopicGroupCountInGridItem(gridItemDOMElement) === 1) {
return gridItemDOMElement.getBoundingClientRect().width;
}
if (index === gridItemCount - 1) {
var orphanListGridItems = $(gridItemDOMElement).children(".orphan-list");
if (orphanListGridItems.length) {
c.usedOrphansColumnForGridItemWidth = true;
return orphanListGridItems[0].getBoundingClientRect().width;
}
c.allGridItemsHaveTwoColumns = true;
return $(gridItemDOMElement).find(".sub-topic-groups")[0].getBoundingClientRect().width / 2;
}
}
};
// This method sets the rowSpan & columnSpan values for grid item based on its height & no.of subtopic groups under it respectively.
c.resizeGridItem = function(gridItemDOMElement, rowHeight) {
var subTopicGroupsDOMElement = $(gridItemDOMElement).children(".sub-topic-groups").get(0);
var levelTwoItemColumnDOMElements = $(subTopicGroupsDOMElement).children("ul").get();
if (levelTwoItemColumnDOMElements.length > 1) {
c.allGridItemsHaveSingleColumn = false;
gridItemDOMElement.style.gridColumnEnd = "span " + levelTwoItemColumnDOMElements.length;
$(levelTwoItemColumnDOMElements).each(function(currentIndex, levelTwoItemColumnDOMElement) {
$(levelTwoItemColumnDOMElement).css("width", c.gridItemWidth - c.GRID_ITEM_PADDING_RIGHT + "px");
if (currentIndex != levelTwoItemColumnDOMElements.length - 1 || c.allGridItemsHaveTwoColumns) {
$(levelTwoItemColumnDOMElement).css({
"flex-grow": "1",
"padding-right": c.GRID_ITEM_PADDING_RIGHT + "px"
});
}
});
}
var correctedScrollHeight = gridItemDOMElement.scrollHeight / c.textZoomFactor;
var rowSpan = Math.ceil((correctedScrollHeight + c.GRID_ROW_GAP) / (rowHeight + c.GRID_ROW_GAP));
gridItemDOMElement.style.gridRowEnd = "span " + rowSpan;
};
c.adjustMegaMenuWidth = function() {
var gridItems = $(".level-one-item");
var headerWidth = c.getHeaderWidth();
var browseButton = $('#inner-button');
var browseButtonWidth = browseButton.length > 0 ? (2 * c.MEGA_MENU_ITEMS_PADDING) + Math.ceil(browseButton.get(0).getBoundingClientRect().width) : 0;
browseButtonWidth = browseButtonWidth > c.MAX_BUTTON_WIDTH ? c.MAX_BUTTON_WIDTH : browseButtonWidth;
var tabContainerWidth = $scope.showMenu ? Math.ceil($('.tab-container')[0].getBoundingClientRect().width) : 0;
if (gridItems.length) {
$('.sub-topics-container').css('width', '100%');
var gridElementTopValue = gridItems[0].getBoundingClientRect().top;
var quickLinkWidth = $(".quick-links-container").innerWidth();
// Computing no.of columns based on initial grid items layout.
var gridColumnCount = c.getGridColumnCount(gridItems, gridElementTopValue);
// Get computed grid (sub-topics-container) width.
var subTopicsContainerWidth = c.getGridContainerWidth(gridItems, gridColumnCount);
$('.sub-topics-container').css('width', c.MEGA_MENU_ITEMS_PADDING + subTopicsContainerWidth);
subTopicContainerWidth = c.MEGA_MENU_ITEMS_PADDING + subTopicsContainerWidth + quickLinkWidth;
var megaMenuWidth = browseButtonWidth > subTopicContainerWidth ? browseButtonWidth : subTopicContainerWidth;
if (c.data.activePortalNavRecord && $scope.selectedMainMenu)
megaMenuWidth += tabContainerWidth;
//if it is expanded menu type
if (c.data.activePortalNavRecord && $scope.selectedMainMenu && !$scope.showMenuOnLeft) {
var leftOffset = $('.tab-container')[0].getBoundingClientRect().left;
if (leftOffset + megaMenuWidth > headerWidth)
$('.dropdown-container').css('width', headerWidth - leftOffset);
else
$('.dropdown-container').css('width', megaMenuWidth);
} else {
// Checking if there is room for fitting another topic on the right side and adjusting width of mega menu.
var canFitAnotherTopicOnTheRight = c.canFitAnotherTopicOnTheRight(megaMenuWidth, gridItems, gridColumnCount);
megaMenuWidth = megaMenuWidth > c.MEGA_MENU_MAX_WIDTH ? c.MEGA_MENU_MAX_WIDTH : megaMenuWidth;
megaMenuWidth = canFitAnotherTopicOnTheRight && (headerWidth < c.MEGA_MENU_MAX_WIDTH) ? headerWidth : megaMenuWidth;
// If mega menu doesnt fit on the right side, fix the right coordinate and expand toward left
if (!$scope.selectedMainMenu && $scope.megaMenuLeftOffset + megaMenuWidth > headerWidth) {
$('.dropdown-container').css({
'right': 0,
'left': ''
});
}
$('.dropdown-container').css('width', megaMenuWidth == headerWidth ? '100%' : megaMenuWidth);
if (!canFitAnotherTopicOnTheRight && c.hasSecondRowStarted(gridItems, gridElementTopValue)) {
$(".dropdown-container").css("width", megaMenuWidth + c.MEGA_MENU_ITEMS_PADDING * c.textZoomFactor > c.MEGA_MENU_MAX_WIDTH ? c.MEGA_MENU_MAX_WIDTH : megaMenuWidth + c.MEGA_MENU_ITEMS_PADDING * c.textZoomFactor);
}
}
} else if ($scope.getQuickLinksForMenuItem($scope.selectedMenuItem)) {
var quickLinkContainerWidth = $('.quick-links-container').outerWidth() + parseFloat($('.mega-menu__items').css('padding-left')) + parseFloat($('.mega-menu__items').css('padding-right'));
$('.dropdown-container').css('width', tabContainerWidth + (quickLinkContainerWidth > browseButtonWidth ? quickLinkContainerWidth : browseButtonWidth));
} else if ($scope.getBrowseButtontext($scope.selectedMenuItem)) {
$('.dropdown-container').css('width', $('#inner-button').outerWidth(true) + tabContainerWidth);
}
};
c.adjustQuickLinksPanelHeight = function() {
var quicklinksContainer = $(".quick-links-container");
if (quicklinksContainer.length) {
var containerBottom = quicklinksContainer[0].getBoundingClientRect().bottom;
var quickLinksLastBottom = $(".quick-links-container > .simple-list > li:last-child")[0].getBoundingClientRect().bottom;
if (containerBottom <= quickLinksLastBottom) {
quicklinksContainer.css("height", "min-content");
}
var gridElements = $('.sub-topics-container');
if (gridElements.length) {
var gridDOMElement = gridElements.get(0);
var quickLinksPanelHeight = parseInt(gridDOMElement.offsetHeight);
var usedGridScrollHeight = false;
if (gridDOMElement.offsetHeight < gridDOMElement.scrollHeight) {
usedGridScrollHeight = true;
quickLinksPanelHeight = parseInt(gridDOMElement.scrollHeight);
}
$(".quick-links-container").css("min-height", quickLinksPanelHeight + (usedGridScrollHeight ? c.MEGA_MENU_ITEMS_PADDING : 0) + "px");
}
}
};
// Returns no.of columns based on initial (not final) grid items layout. Final column count may change after filling vertical gaps.
c.getGridColumnCount = function(gridItems, gridElementTopValue) {
var gridColumnCount = 0;
for (var index = 0; index < gridItems.length; index++) {
var gridItemDOMElement = gridItems[index];
if (gridItemDOMElement.getBoundingClientRect().top !== gridElementTopValue) {
break;
}
gridColumnCount += (c.getSubTopicGroupCountInGridItem(gridItemDOMElement) + $(gridItemDOMElement).children(".orphan-list").length);
}
return gridColumnCount;
};
// Grid (sub-topics-container) width is computed based on how c.gridItemWidth value is computed (Check c.getGridItemWidth method documentation).
c.getGridContainerWidth = function(gridItems, gridColumnCount) {
var subTopicsContainerWidth = c.allGridItemsHaveTwoColumns ? (gridItems[0].getBoundingClientRect().width * gridColumnCount / 2) : (gridColumnCount * c.gridItemWidth);
if (c.usedOrphansColumnForGridItemWidth) {
/* Orphan links column is used for computing grid item width only when all the grid items have 2-columns (i. e. level-2 sub topic count in each grid item > 10).
So we are halving the grid item width. But in case of 1-level hierarchy also mega menu is displayed and orphan links column is used for grid item width computation, so we need not half grid item width. */
subTopicsContainerWidth = Math.ceil(gridItems[0].getBoundingClientRect().width * gridColumnCount / (c.allGridItemsHaveTwoColumns ? 2 : 1));
}
return subTopicsContainerWidth;
};
// Checks if there is room for fitting another topic on the right side.
c.canFitAnotherTopicOnTheRight = function(megaMenuWidth, gridItems, gridColumnCount) {
var canFitAnotherTopicOnRight = (megaMenuWidth + c.gridItemWidth > $window.innerWidth) ||
(megaMenuWidth + 2 * c.gridItemWidth > $window.innerWidth && gridColumnCount < gridItems.length && c.getSubTopicGroupCountInGridItem(gridItems[gridColumnCount]) === 1);
if (c.allGridItemsHaveTwoColumns) {
canFitAnotherTopicOnRight = (megaMenuWidth + gridItems[0].getBoundingClientRect().width > $window.innerWidth);
}
return canFitAnotherTopicOnRight;
};
c.hasSecondRowStarted = function(gridItems, gridElementTopValue) {
for (var index = 0; index < gridItems.length; index++) {
var gridItemDOMElement = gridItems[index];
if (gridItemDOMElement.getBoundingClientRect().top !== gridElementTopValue) {
return true;
}
}
return false;
};
c.fillVerticalGapsInTheGrid = function() {
return new Promise(function(resolve, reject) {
// Vertical gaps show up due to presence of 2-column grid item in rows other than 1st row.
if (c.allGridItemsHaveSingleColumn) {
resolve();
}
var gridTop = $(".sub-topics-container").get(0).getBoundingClientRect().top;
var gridItems = $(".level-one-item");
for (var currentIndex = 0; currentIndex < gridItems.length; currentIndex++) {
var currentGridItemDOMElement = gridItems[currentIndex];
var currentGridItemTop = currentGridItemDOMElement.getBoundingClientRect().top,
currentGridItemRight = currentGridItemDOMElement.getBoundingClientRect().right,
currentGridItemLeft = currentGridItemDOMElement.getBoundingClientRect().left;
// Gaps show up only for items not in 1st row & are single-column items
if (currentGridItemTop !== gridTop && (c.getSubTopicGroupCountInGridItem(currentGridItemDOMElement) === 1 || $(currentGridItemDOMElement).children(".orphan-list").length)) {
var maxBottomValue = -1;
var currentItemLiesInTwoColumnItemRow = false,
currentItemRowSpanValue;
for (var index = 0; index < gridItems.length; index++) {
var gridItemElement = gridItems[index];
var subTopicGroupCountInGridItem = c.getSubTopicGroupCountInGridItem(gridItemElement);
// Need not continue if we reached current item while filling gaps, continue to next grid item (outer loop).
if (c.areSameGridItems(currentGridItemDOMElement, gridItemElement)) {
break;
}
// Checking if current item is lying in same row as a 2-column item. We will use this later below to adjust current items height.
if (subTopicGroupCountInGridItem === 2 && gridItemElement.getBoundingClientRect().top === currentGridItemTop) {
currentItemLiesInTwoColumnItemRow = true;
currentItemRowSpanValue = parseInt(currentGridItemDOMElement.style.gridRowEnd.substr(5));
}
// Collecting the grid item that sits right above current item to adjust the vertical gap formed between them.
if (gridItemElement.getBoundingClientRect().left === currentGridItemLeft ||
(gridItemElement.getBoundingClientRect().right === currentGridItemRight && subTopicGroupCountInGridItem === 2) ||
(gridItemElement.getBoundingClientRect().left < currentGridItemLeft && gridItemElement.getBoundingClientRect().right > currentGridItemLeft)) {
maxBottomValue = Math.max(maxBottomValue, gridItemElement.getBoundingClientRect().bottom);
}
}
/* If there is no item above current item (maxBottomValue = -1), it means it should sit in the 1st row. And if there is a maxBottomValue,
and the gap between the maxBottomValueGridItem & current item > grid row gap (20px), then add margin top to current element to fill this gap. */
if (maxBottomValue === -1 || (currentGridItemTop - maxBottomValue > c.GRID_ROW_GAP))
c.fillVerticalGapForCurrentGridItem(gridItems, currentIndex, maxBottomValue, currentItemLiesInTwoColumnItemRow, currentItemRowSpanValue);
}
}
resolve();
});
};
c.areSameGridItems = function(gridItem1, gridItem2) {
var gridItemTop = gridItem2.getBoundingClientRect().top;
var gridItemRight = gridItem2.getBoundingClientRect().right;
var gridItemBottom = gridItem2.getBoundingClientRect().bottom;
var gridItemLeft = gridItem2.getBoundingClientRect().left;
return gridItem1.getBoundingClientRect().top === gridItemTop &&
gridItem1.getBoundingClientRect().right === gridItemRight &&
gridItem1.getBoundingClientRect().bottom === gridItemBottom &&
gridItem1.getBoundingClientRect().left === gridItemLeft;
};
c.fillVerticalGapForCurrentGridItem = function(gridItems, currentIndex, maxBottomValue, currentItemLiesInTwoColumnItemRow, currentItemRowSpanValue) {
var gridTop = $(".sub-topics-container").get(0).getBoundingClientRect().top;
var currentGridItemDOMElement = gridItems[currentIndex];
var currentGridItemTop = currentGridItemDOMElement.getBoundingClientRect().top;
var marginTop = (maxBottomValue === -1) ? (currentGridItemTop - gridTop) : (currentGridItemTop - maxBottomValue - c.GRID_ROW_GAP);
$(currentGridItemDOMElement).css({
"margin-top": -1 * marginTop + "px"
});
/* If current item is lying in same row as a 2-column item, we need to adjust its height
to prevent it from occupying extra space below after applying margin-top on it. */
if (currentItemLiesInTwoColumnItemRow || currentIndex === gridItems.length - 1) {
var rowSpanValue = (currentItemRowSpanValue - marginTop / c.GRID_ROW_GAP);
currentGridItemDOMElement.style.gridRowEnd = "span " + (rowSpanValue > 0 ? rowSpanValue : 1);
if (rowSpanValue < 0) {
$(currentGridItemDOMElement).css({
"height": "fit-content"
});
}
}
};
$scope.handleMobileMainMenuItemClick = function(menuItem) {
if (menuItem.render_as == 'modal_window' && menuItem.widget) {
spModal.open({
title: menuItem.name ? menuItem.name : menuItem.label,
size: 'lg',
widget: menuItem.widget,
widgetInput: menuItem.widget_parameters,
buttons: [{
label: '${Close}',
primary: true
}]
});
return;
}
if (c.doesMenuItemHaveChildren(menuItem)) {
if (!$scope.showMobileMegaMenu)
$scope.showMobileMegaMenu = true;
$scope.selectedMobileMenuItem = menuItem;
c.isTabbedMobileMenu = (c.data.activePortalNavRecord && menuItem.submenu_type == 'expanded');
$scope.isLevelOneMobileMegaMenu = !c.isTabbedMobileMenu;
c.levelDrilled = 1;
$scope.isLevelOneMobileMegaMenu = true;
$scope.getLevelOneMenuItems(menuItem);
$scope.getBrowseButtontext(menuItem);
$rootScope.$broadcast(c.SHOW_MOBILE_MEGA_MENU, true);
return;
}
c.redirectToMenuItemPage(menuItem);
$rootScope.$broadcast(c.TOGGLE_MOBILE_MENU_VISIBILITY);
};
// 2 cases here, user either clicks level-one item or level-two item
$scope.handleMobileMegaMenuItemClick = function(menuItem) {
/* Case-1: If level-one item has children, we are showing level-two children under it.
Otheriwse simply redirect user to level-one topic page. */
if (c.doesMenuItemHaveChildren(menuItem)) {
++c.levelDrilled;
if(c.levelDrilled == 2)
$scope.levelOneSelectedMobileMenuItem = $scope.selectedMobileMenuItem;
if(c.levelDrilled == 3)
$scope.levelTwoSelectedMobileMenuItem = $scope.selectedMobileMenuItem;
$scope.selectedMobileMenuItem = menuItem;
/*if it is tabbed view, quick links should be activated only on 1st level*/
$scope.isLevelOneMobileMegaMenu = (c.isTabbedMobileMenu && c.levelDrilled == 2);
$scope.getLevelOneMenuItems(menuItem);
$scope.getBrowseButtontext(menuItem);
return;
}
// Case-2: level-two topics have no children in mega menu. So simply redirect user to clicked topic page.
c.redirectToMenuItemPage(menuItem);
$rootScope.$broadcast(c.TOGGLE_MOBILE_MENU_VISIBILITY);
};
/* If back button is clicked on level-1 of mega menu, go back to main menu.
Otherwise, change selected topic to parent of level-2 topics to refresh the view */
$scope.goBackOnMobileView = function() {
if (c.levelDrilled == 1) {
$scope.lastSelectedElementId = $scope.selectedMobileMenuItem.sys_id;
$scope.showMobileMegaMenu = false;
$rootScope.$broadcast(c.SHOW_MOBILE_MEGA_MENU, false);
return;
}
--c.levelDrilled;
$(".back-container").blur();
if(c.levelDrilled == 2) {
$scope.selectedMobileMenuItem = $scope.levelTwoSelectedMobileMenuItem;
$scope.levelTwoSelectedMobileMenuItem = undefined;
} else if(c.levelDrilled == 1) {
$scope.selectedMobileMenuItem = $scope.levelOneSelectedMobileMenuItem;
$scope.levelOneSelectedMobileMenuItem = undefined;
}
$scope.isLevelOneMobileMegaMenu = (!c.isTabbedMobileMenu || c.levelDrilled == 2);
$scope.getLevelOneMenuItems($scope.selectedMobileMenuItem);
};
// Handle click on Main menu anywhere other than a navigation menu item.
$scope.handleClickOnMainMenu = function($event) {
if ($event.target.className.indexOf("navigation-menu-item") < 0) {
c.closeMegaMenu();
}
};
/* There are 4 cases to handle when a main menu navigation item is clicked
1. If simple/mega menu is already open, then close it.
2. If there is no menu attached to this menu item, then redirect user to corresponding portal page.
3. Whether to show simple/mega menu based on hierarchy level of root topic i.e If there is only 1 level, show simple menu. otherwise show mega menu.
4. Show mega menu when there are 2 levels of topic hierarchy.
*/
$scope.handleMainMenuItemClick = function(menuItem, $event, isTab) {
$event.stopPropagation();
var headerWidth = c.getHeaderWidth();
$scope.keyboardNavRowIndex = 0;
$scope.keyboardNavColIndex = 0;
// Case 1
if ((!c.data.activePortalNavRecord && $scope.selectedMenuItem && $scope.selectedMenuItem.sys_id === menuItem.sys_id)
|| (c.data.activePortalNavRecord && $scope.selectedMenuItem && $scope.selectedMenuItem.portal_nav_item === menuItem.portal_nav_item)) {
if (isTab)
c.closeTabMenu();
else
c.closeMegaMenu();
return;
}
if ($scope.selectedMainMenu && $scope.selectedMainMenu.portal_nav_item === menuItem.portal_nav_item) {
c.closeMegaMenu();
return;
}
// Case 2
if (menuItem.render_as == 'modal_window' && menuItem.widget) {
c.closeMegaMenu();
$scope.showMegaMenu = false;
spModal.open({
title: menuItem.name ? menuItem.name : menuItem.label,
size: 'lg',
widget: menuItem.widget,
widgetInput: menuItem.widget_parameters,
buttons: [{
label: '${Close}',
primary: true
}]
});
return;
}
// Case 3
if ((!isTab && !c.doesMenuItemHaveChildren(menuItem)) || (isTab && !c.hasSubMenuData(menuItem))) {
c.closeMegaMenu();
c.redirectToMenuItemPage(menuItem);
return;
}
if (menuItem.submenu_type == 'expanded') {
if ($scope.selectedMenuItem || $scope.selectedMainMenu)
c.closeMegaMenu();
$timeout(function () {
$scope.selectedMainMenu = menuItem;
$scope.showMenu = true;
$timeout(function () {
var tabContainerEle = $('.tab-container')[0];
var tabWidth = tabContainerEle ? tabContainerEle.getBoundingClientRect().width : 0;
var leftOffset = $event.currentTarget.offsetLeft;
if (leftOffset + c.minMegaMenuWidth + tabWidth > headerWidth)
$scope.showMenuOnLeft = true;
if (!$scope.showMenuOnLeft)
$scope.megaMenuPosition = {
'left': leftOffset
};
else {
if (leftOffset + tabWidth > headerWidth)
$scope.megaMenuPosition = {
'right': headerWidth - leftOffset - $event.currentTarget.offsetWidth
};
else
$scope.megaMenuPosition = {
'right': headerWidth - leftOffset - tabWidth
};
}
c.resizeDropDownContainer();
c.toggleMegaMenuVisibility();
});
});
return;
}
if (!isTab)
c.closeMegaMenu();
else {
c.adjustTabPanelHeight(true);
c.toggleSubMenuVisibility(true);
}
$scope.selectedMenuItem = menuItem;
// Case 4
if (!isTab && c.isSingleLevelTopicHierarchy(menuItem)) {
var computedLeftCoordinate = $event.currentTarget.offsetLeft;
computedLeftCoordinate = (computedLeftCoordinate + c.SIMPLE_MENU_WIDTH <= headerWidth) ? computedLeftCoordinate : (headerWidth - c.SIMPLE_MENU_WIDTH);
$scope.megaMenuLeftOffset = computedLeftCoordinate;
$scope.megaMenuPosition = {
'left': computedLeftCoordinate > 0 ? computedLeftCoordinate : 0 + 'px'
};
var quickLinks = c.data.activePortalNavRecord ? c.data.menuItemToQuickLinks[menuItem.portal_nav_item] : c.data.menuItemToQuickLinks[menuItem.sys_id];
$scope.showMegaMenu = (quickLinks && quickLinks.length > 0) || menuItem.browse_button == 'true';
if(!$scope.showMegaMenu) {
$timeout(function() {
c.toggleMegaMenuVisibility();
});
}
return;
}
// Case 5
$scope.showMegaMenu = true;
if (!isTab) {
var computedLeftCoordinate = $event.currentTarget.offsetLeft;
computedLeftCoordinate = (computedLeftCoordinate + c.SIMPLE_MENU_WIDTH <= headerWidth) ? computedLeftCoordinate : (headerWidth - c.SIMPLE_MENU_WIDTH);
$scope.megaMenuLeftOffset = computedLeftCoordinate;
$scope.megaMenuPosition = {
'left': computedLeftCoordinate > 0 ? computedLeftCoordinate : 0 + 'px'
};
$scope.selectedMenuItemRightCoordinate = $event.currentTarget.getBoundingClientRect().right;
}
};
$scope.handleMegaMenuItemClick = function($event, megaMenuItem) {
$($event.target).parent().tooltip('hide');
$timeout(function() {
c.closeMegaMenu();
});
if ($event.ctrlKey || $event.metaKey)
return;
c.redirectToMenuItemPage(megaMenuItem);
};
$scope.handleQuickLinkClick = function($event, quickLink) {
$timeout(function() {
c.closeMegaMenu();
});
};
$scope.browseAll = function(selectedTopic) {
$timeout(function() {
c.closeMegaMenu();
});
c.redirectToMenuItemPage(selectedTopic);
if ($scope.showMobileMenuOnDesktop) {
$rootScope.$broadcast(c.TOGGLE_MOBILE_MENU_VISIBILITY);
}
};
// Returns all sub topics (under selected root topic)
$scope.getLevelOneMenuItems = function(selectedMenuItem) {
$scope.levelOneMenuItems = c.data.activePortalNavRecord ? c.data.menuItemToChildrenMap[selectedMenuItem.portal_nav_item] : c.data.menuItemToChildrenMap[selectedMenuItem.sys_id];
return $scope.levelOneMenuItems;
};
$scope.getLevelTwoMenuItems = function(selectedMenuItem) {
$scope.levelTwoMenuItems = c.data.activePortalNavRecord ? c.data.menuItemToChildrenMap[selectedMenuItem.portal_nav_item] : c.data.menuItemToChildrenMap[selectedMenuItem.sys_id];
return $scope.levelTwoMenuItems;
};
$scope.getQuickLinksForMenuItem = function(selectedMenuItem) {
$scope.QuickLinksForMenuItem = c.data.activePortalNavRecord ? c.data.menuItemToQuickLinks[selectedMenuItem.portal_nav_item] : c.data.menuItemToQuickLinks[selectedMenuItem.sys_id];
return $scope.QuickLinksForMenuItem;
};
// Returns all child navigation items for selectedMenuItem
$scope.getChildNavItems = function(selectedMenuItem) {
var childNavItems = c.data.activePortalNavRecord ? c.data.menuItemToChildrenMap[selectedMenuItem.portal_nav_item] : c.data.menuItemToChildrenMap[selectedMenuItem.sys_id];
return childNavItems;
};
// Returns all child items (quick links & browse button also) for selectedMenuItem
$scope.hasChildItems = function(selectedMenuItem) {
if (selectedMenuItem.render_as == 'modal_window') {
return false;
}
var childNavItems = c.data.activePortalNavRecord ? c.data.menuItemToChildrenMap[selectedMenuItem.portal_nav_item] : c.data.menuItemToChildrenMap[selectedMenuItem.sys_id];
var quickLinks = c.data.activePortalNavRecord ? c.data.menuItemToQuickLinks[selectedMenuItem.portal_nav_item] : c.data.menuItemToQuickLinks[selectedMenuItem.sys_id];
return (childNavItems && childNavItems.length > 0) || (quickLinks && quickLinks.length > 0);
};
$scope.getBrowseButtontext = function(selectedMenuItem, tabMenu) {
if (!selectedMenuItem || (selectedMenuItem && selectedMenuItem.browse_button !== 'true')) {
return false;
} else {
var buttonText = selectedMenuItem.browse_button_text=='' ?'${Browse all}': selectedMenuItem.browse_button_text;
if(tabMenu) {
$scope.tabbedBrowseButtontext = buttonText;
return true;
}
$scope.browseButtontext = buttonText;
return true;
}
};
// Returns all sub topics (under selected root topic) with no children
$scope.getLevelOneTopicsWithNoChildren = function(selectedRootMenuItem) {
var levelOneSubTopics = c.data.activePortalNavRecord ? c.data.menuItemToChildrenMap[selectedRootMenuItem.portal_nav_item] : c.data.menuItemToChildrenMap[selectedRootMenuItem.sys_id];
$scope.levelOneTopicsWithNoChildren = levelOneSubTopics.filter(function(levelOneSubTopic) {
return !c.doesMenuItemHaveChildren(levelOneSubTopic);
});
return $scope.levelOneTopicsWithNoChildren;
};
$scope.getLevelTwoTopicsAsGroupsOfTen = function(levelTwoSubTopics) {
var COLUMN_SPLIT_BREAKPOINT_TOPIC_COUNT = 10,
MAX_TOPIC_COUNT_UNDER_SUBTOPIC_GROUP = 20;
var groupsOfLevelTwoTopics = [],
subTopics = angular.copy(levelTwoSubTopics);
subTopics = subTopics.splice(0, MAX_TOPIC_COUNT_UNDER_SUBTOPIC_GROUP);
if (subTopics.length <= COLUMN_SPLIT_BREAKPOINT_TOPIC_COUNT) {
while (subTopics.length) {
groupsOfLevelTwoTopics.push(subTopics.splice(0, COLUMN_SPLIT_BREAKPOINT_TOPIC_COUNT));
}
} else {
var columnCount = Math.ceil(subTopics.length / COLUMN_SPLIT_BREAKPOINT_TOPIC_COUNT);
var topicCountPerColumn = Math.ceil(subTopics.length / columnCount);
var lastColumnTopicCount = subTopics.length % topicCountPerColumn;
if (lastColumnTopicCount !== 0) {
while (topicCountPerColumn + lastColumnTopicCount <= COLUMN_SPLIT_BREAKPOINT_TOPIC_COUNT) {
topicCountPerColumn++;
lastColumnTopicCount = subTopics.length % topicCountPerColumn;
}
}
while (subTopics.length) {
groupsOfLevelTwoTopics.push(subTopics.splice(0, topicCountPerColumn));
}
}
return groupsOfLevelTwoTopics;
};
c.isSingleLevelTopicHierarchy = function(rootTopic) {
var parentNavItemId = c.data.activePortalNavRecord ? rootTopic.portal_nav_item : rootTopic.sys_id;
if (!c.data.menuItemToChildrenMap[parentNavItemId])
return false;
return c.data.menuItemToChildrenMap[parentNavItemId].every(function(subTopic) {
return !c.doesMenuItemHaveChildren(subTopic);
});
};
c.doesMenuItemHaveChildren = function(menuItem) {
return (c.data.activePortalNavRecord ? $scope.hasChildItems(menuItem) : (menuItem.sys_id in c.data.menuItemToChildrenMap));
};
c.getSubTopicGroupCountInGridItem = function(gridItemDOMElement) {
return $(gridItemDOMElement).find(".sub-topic-groups > .simple-list").length;
};
/* Whenever we are not showing mega menu, make sure to clear selectedMenuItem as well.
Otherwise, simple dropdown will appear even when there are more than 1 level of sub topics. */
c.closeMegaMenu = function() {
c.toggleSubMenuVisibility(true);
c.toggleMegaMenuVisibility(true);
$scope.showMenu = false;
$scope.showMegaMenu = false;
$scope.selectedMenuItem = undefined;
$scope.selectedMainMenu = undefined;
$scope.showMenuOnLeft = false;
$scope.megaMenuPosition = undefined;
$timeout(function() {
c.resizeDropDownContainer();
});
};
c.toggleMegaMenuVisibility = function(hide) {
var dropDownEle = $('.dropdown-container');
return hide ? dropDownEle.addClass('visibility') : dropDownEle.removeClass('visibility');
}
c.toggleSubMenuVisibility = function(hide) {
var subMenuEle = $('.mega-menu');
return hide ? subMenuEle.addClass('visibility') : subMenuEle.removeClass('visibility');
}
c.closeTabMenu = function() {
c.toggleSubMenuVisibility(true);
c.adjustTabPanelHeight(true);
$scope.showMegaMenu = false;
$scope.selectedMenuItem = undefined;
$timeout(function() {
c.resizeDropDownContainer();
});
}
c.getHeaderWidth = function() {
return $('.employee-center-navigation')[0].getBoundingClientRect().width;
}
c.hasSubMenuData = function(item) {
if (c.doesMenuItemHaveChildren(item) || $scope.getQuickLinksForMenuItem(item))
return true;
return false;
}
c.resizeDropDownContainer = function() {
var megaMenuEle = $('.mega-menu')[0];
var tabContainerEle = $('.tab-container')[0];
if (tabContainerEle && megaMenuEle && $('.mega-menu').hasClass('visibility')) {
megaMenuEle = null;
}
$('.dropdown-container').css('width', (megaMenuEle ? megaMenuEle.getBoundingClientRect().width : null) + (tabContainerEle ? tabContainerEle.getBoundingClientRect().width : null));
}
c.redirectToMenuItemPage = function(menuItem) {
var searchString = c.getRedirectSearchParams(menuItem);
if (menuItem.type === "url") {
var url_target = menuItem.url_target || "_self";
if (menuItem.href == '') {
spUtil.addErrorMessage('${There is no destination set for this menu item}');
} else {
$window.open(menuItem.href, url_target);
}
} else {
$location.path('/' + c.data.portalSuffix).search(searchString);
}
};
$scope.returnRedirectionPath = function(menuItem) {
var searchString = c.getRedirectSearchParams(menuItem);
var path = '/' + c.data.portalSuffix + '?' + searchString;
return path;
}
$scope.redirectToBrowseAll = function(menuItem) {
var href = $scope.returnRedirectionPath(menuItem);
$window.open(href, "_self");
}
c.getRedirectSearchParams = function(menuItem) {
// Checking if menu item belongs to a taxonomy. If so, then redirect to topic page else, redirect to href.
var pageToRedirect = menuItem.template && menuItem.template.length > 0 ? menuItem.template : 'emp_taxonomy_topic';
var searchString = menuItem.taxonomy ? "id=" + pageToRedirect + "&topic_id=" + menuItem.sys_id : menuItem.href.substring(1);
return searchString;
};
$scope.getMegaMenuItemHref = function(menuItem) {
var searchString = c.getRedirectSearchParams(menuItem);
if (menuItem.type === "url") {
return menuItem.href;
} else {
return '/' + c.data.portalSuffix + '?' + searchString;
}
};
/**
* Checking ellipsis
*/
function checkEllipsis(topic) {
return topic.scrollWidth > $(topic).parent().width();
}
/**
* Applying tooltip to ellipsis elements
*/
function topicTooltip() {
var topics = $([
'.simple-list .dropdown-menu-item span:not(.icon)',
'.mega-menu-item span:not(.icon)',
'.browse-button span',
'.quick-link-title',
'.mobile-mega-menu__quick-link a',
'.quick-links-container a',
'.level-one-topic-name',
'.level-two-topic-name',
'.tab-list'
].join());
topics.each(function(_index, topic) {
if (checkEllipsis(topic)) {
$(topic).tooltip('enable');
}
});
}
};