'use strict';
/**
* Global header component.
* @namespace header
* @memberof UCASDesignFramework
* @param {object} global - UCASDesignFramework object.
* @param {object} _u - UCASUtilities object.
*/
(function (global, _u) {
var initialised = false // @todo DF-1231
var header
var userMenuToggle
var userMenuToggleRef
var userMenu
var userMenuSubMenuToggles
var userMenuMouseenterTimeout
var headerSearchToggle
var headerSearchToggleRef
var headerSearch
var headerSearchInput
var headerSearchSmall = false
var meganavToggle
var meganav
var meganavTopLevelListItems
var skipTarget
/**
* Initialise plugin.
* @function init
* @memberof! UCASDesignFramework.header
* @public
* @return {Boolean} - @todo DF-1122 what should this return?
*/
function init () {
if (initialised) { return false }
header = document.getElementById('section--upper-header')
// Initilise user menu.
userMenuToggle = document.getElementById('user-menu-toggle')
userMenu = document.getElementById('user-menu')
if (!userMenuToggle || !userMenu) {
_u.log.error('Failed to find user menu')
} else {
userMenuToggleRef = _u.toggle.create(userMenuToggle.id, showUserMenu, hideUserMenu, true)
// Exclude the mouseenter event listener from mobile as Android reacts to it,
// which in this case makes the use tap twice to open the user menu.
// IE doesn't understand the media query, so just include that anyway with document.documentMode.
if (window.matchMedia('(pointer:fine)').matches || document.documentMode) {
userMenu.parentElement.addEventListener('mouseenter', userMenuMouseenterHandler, false)
userMenu.parentElement.addEventListener('mouseleave', userMenuMouseleaveHandler, false)
}
userMenuToggle.setAttribute('data-initialised', 'true')
}
// Initialise meganav.
meganav = document.querySelector('.meganav__menu')
meganavTopLevelListItems = meganav ? meganav.querySelectorAll('.global-link') : false
meganavToggle = document.getElementById('meganav-toggle')
if (!meganavToggle || !meganav || !meganavTopLevelListItems) {
_u.log.error('Failed to find meganav')
} else {
_u.toggle.create('meganav-toggle', function () { _u.toggle.hideOthers('meganav-toggle') }, null, true)
meganav.addEventListener('mouseenter', meganavMouseenterHandler)
_u.forEach(meganavTopLevelListItems, function (i, el) {
el.setAttribute('data-global-link-state', 'closed')
if (!('ontouchstart' in document.documentElement)) {
el.addEventListener('mouseenter', meganavLinkMouseenterHandler)
el.addEventListener('mouseleave', meganavLinkMouseleaveHandler)
}
// Handle keyboard events.
var link = el.querySelector('.global-link__link')
if (link) {
link.addEventListener('focus', closeMeganav)
}
var linkPanelToggle = el.querySelector('.link-panel__toggle')
if (linkPanelToggle) {
linkPanelToggle.style.display = ''
linkPanelToggle.addEventListener('click', meganavLinkClickHandler)
}
var meganavPanelLinks = el.querySelectorAll('.link-panel a')
var lastLink = meganavPanelLinks[meganavPanelLinks.length - 1]
if (lastLink) {
lastLink.addEventListener('blur', meganavLinkBlurHandler)
}
})
meganav.addEventListener('mouseleave', meganavMouseleaveHandler)
}
// Initialise header search.
headerSearchToggle = document.getElementById('header-search-toggle')
if (!headerSearchToggle) {
_u.log.error('Failed to find header search')
} else {
headerSearchToggleRef = _u.toggle.create('header-search-toggle', showHeaderSearch, hideHeaderSearch, false)
headerSearch = document.querySelector('.search-widget__inner')
headerSearchInput = headerSearch.querySelector('input[type="search"]')
renderHeaderSearch()
// Uses event listener to handle resize in global.size so we can have suitable throttling and window measurement, etc.
window.addEventListener('dfResize', renderHeaderSearch)
// Allow the search to be closed by clicking on the body.
document.body.addEventListener('click', headerSearchToggleRef.hide, false)
headerSearch.addEventListener('click', function (e) { e.stopPropagation() }, false)
}
// Check skip navigation target.
skipTarget = document.getElementById('main-content')
if (!skipTarget) {
createSkipTarget()
}
initialised = true
}
/**
* Render header search.
* Moves elements around in the DOM so they're in the right place on mobile or desktop.
*/
function renderHeaderSearch () {
if (!headerSearchSmall && (global.size.cssBreakpoint === 'xsmall' || global.size.cssBreakpoint === 'small')) {
// Should be the small size but isn't, so recitfy that.
renderHeaderSearchSmall()
} else if (headerSearchSmall && global.size.cssBreakpoint !== 'xsmall' && global.size.cssBreakpoint !== 'small') {
// Shouldn't be the small size, so recitfy that.
renderHeaderSearchLarge()
}
function renderHeaderSearchSmall () {
meganav.insertBefore(headerSearch, document.querySelector('.meganav__menu').firstChild)
headerSearchSmall = true
headerSearch.addEventListener('click', function (e) { e.stopPropagation() })
headerSearch.classList.remove('search-widget__inner')
headerSearch.classList.add('search-widget--small')
}
function renderHeaderSearchLarge () {
document.querySelector('.search-widget__wrapper').insertBefore(headerSearch, this.firstChild)
headerSearch.classList.remove('search-widget--small')
headerSearch.classList.add('search-widget__inner')
headerSearchSmall = false
}
}
/**
* Usermenu mouseenter handler.
* @param {Event} e - DOM event
*/
function userMenuMouseenterHandler (e) {
// Don't open the user menu on hover when the search is open as it's really annoying.
if (header.getAttribute('data-meganav-state') !== 'open') {
userMenuToggleRef.show()
userMenuToggle.setAttribute('data-toggle-trigger', 'hover')
userMenuMouseenterTimeout = window.setTimeout(function () {
userMenuToggle.removeAttribute('data-toggle-trigger')
}, 1000)
}
}
/**
* Usermenu mouseleave handler.
* @param {Event} e - DOM event
*/
function userMenuMouseleaveHandler (e) {
userMenuToggle.removeAttribute('data-toggle-trigger')
window.clearTimeout(userMenuMouseenterTimeout)
userMenuToggleRef.hide()
}
/**
* Meganav mouseenter handler.
* @param {Event} e - DOM event
*/
function meganavMouseenterHandler (e) {
_u.toggle.hide('user-menu-toggle')
this.meganavEnterTimeout = setTimeout(function () { meganav.setAttribute('data-meganav-mouseover', 'true') }, 150)
meganav.setAttribute('data-meganav-mouseover-immediate', 'true')
window.clearTimeout(this.meganavLeaveTimeout)
}
/**
* Meganav mouseleave handler.
* @param {Event} e - DOM event
*/
function meganavMouseleaveHandler (e) {
this.meganavLeaveTimeout = setTimeout(function () { meganav.setAttribute('data-meganav-mouseover', 'false') }, 150)
meganav.setAttribute('data-meganav-mouseover-immediate', 'false')
window.clearTimeout(this.meganavEnterTimeout)
}
/**
* Meganav link mouseenter handler.
* @param {Event} e - DOM event
*/
function meganavLinkMouseenterHandler (e) {
this.meganavLinkEnterTimeout = setTimeout(function () { e.target.setAttribute('data-global-link-state', 'open') }, 150)
window.clearTimeout(this.meganavLinkLeaveTimeout)
}
/**
* Meganav link mouseleave handler.
* @param {Event} e - DOM event
*/
function meganavLinkMouseleaveHandler (e) {
this.meganavLinkLeaveTimeout = setTimeout(function () { e.target.setAttribute('data-global-link-state', 'closed') }, 200)
window.clearTimeout(this.meganavLinkEnterTimeout)
}
/**
* Meganav link focus handler.
* @param {Event} e - DOM event
*/
function meganavLinkClickHandler (e) {
if (e.target.parentElement.getAttribute('data-global-link-state') === 'closed') {
meganavMouseenterHandler(e)
e.target.parentElement.setAttribute('data-global-link-state', 'open')
e.target.innerText = 'Close panel'
e.target.setAttribute('data-global-link-toggle-visible', 'true')
} else {
closeMeganav(e)
e.target.innerText = 'Open panel'
e.target.setAttribute('data-global-link-toggle-visible', 'false')
}
}
/**
* Meganav link blur handler.
* @param {Event} e - DOM event
*/
function meganavLinkBlurHandler (e) {
meganavMouseleaveHandler(e)
var globalLink = e.target.closest('.global-link')
if (globalLink) {
closeMeganavTopLevelListItem(globalLink)
}
}
/**
* Close all meganav panels.
* @param {Event} e - DOM event
*/
function closeMeganav (e) {
meganavMouseleaveHandler(e)
_u.forEach(meganavTopLevelListItems, function (i, el) {
closeMeganavTopLevelListItem(el)
})
}
/**
* Close a meganavTopLevelListItem.
* @param {Node} meganavTopLevelListItem - A meganavTopLevelListItem node
*/
function closeMeganavTopLevelListItem (meganavTopLevelListItem) {
meganavTopLevelListItem.setAttribute('data-global-link-state', 'closed')
var linkPanelToggle = meganavTopLevelListItem.querySelector('.link-panel__toggle')
if (linkPanelToggle) {
closeLinkPanelToggle(linkPanelToggle)
}
}
/**
* Close a linkPanelToggle.
* @param {Node} linkPanelToggle - A linkPanelToggle node
*/
function closeLinkPanelToggle (linkPanelToggle) {
linkPanelToggle.innerText = 'Open panel'
linkPanelToggle.setAttribute('data-global-link-toggle-visible', 'false')
}
/**
* Header search toggle show callback.
*/
function showHeaderSearch () {
_u.toggle.hideOthers('header-search-toggle')
header.setAttribute('data-meganav-state', 'open')
// Timeout needed for focus to work.
window.setTimeout(function () {
headerSearchInput.focus()
}, 500)
}
/**
* Header search toggle hide callback.
*/
function hideHeaderSearch () {
header.setAttribute('data-meganav-state', 'closed')
}
/**
* User menu show toggle callback.
*/
function showUserMenu () {
header.setAttribute('data-user-menu-state', 'open')
_u.toggle.hideOthers('user-menu-toggle')
// Add click handlers for submenus.
if (userMenu.getAttribute('data-initialised') !== 'true') { initUserMenuSubMenus() }
}
/**
* User menu sub menus init.
*/
function initUserMenuSubMenus () {
userMenuSubMenuToggles = document.querySelectorAll('.user-menu > .user-menu__menu-item > .user-menu__menu-link:not(:only-child)')
_u.forEach(userMenuSubMenuToggles, function initUserMenuSubToggles (i, el) {
el.addEventListener('click', userMenuSubMenuToggleHandler, false)
})
userMenu.setAttribute('data-initialised', 'true')
}
/**
* User menu hide toggle callback.
* @returns {Boolean|undefined} Return false to cancel the toggle action.
*/
function hideUserMenu () {
if (userMenuToggle.getAttribute('data-toggle-trigger') === 'hover') {
userMenuToggle.removeAttribute('data-toggle-trigger')
return false
} else {
header.setAttribute('data-user-menu-state', 'closed')
if (userMenuSubMenuToggles) {
_u.forEach(userMenuSubMenuToggles, function (i, el) { hideUserMenuSubMenu(el) })
}
}
}
/**
* User sub menu toggle handler.
* @param {Event} e - DOM event
*/
function userMenuSubMenuToggleHandler (e) {
e.preventDefault()
e.stopPropagation()
e.target.getAttribute('data-toggle-state') === 'open' ? hideUserMenuSubMenu(e.target) : showUserMenuSubMenu(e.target)
}
/**
* User sub menu show.
* @param {Node} subMenu - the submenu node
*/
function showUserMenuSubMenu (subMenu) {
_u.forEach(userMenu.querySelectorAll('[data-toggle-state="open"]'), function (i, el) { hideUserMenuSubMenu(el) })
subMenu.setAttribute('data-toggle-state', 'open')
}
/**
* User sub menu hide.
* @param {Node} subMenu - the submenu node
*/
function hideUserMenuSubMenu (subMenu) {
subMenu.setAttribute('data-toggle-state', 'closed')
}
/**
* Create skip target.
*/
function createSkipTarget () {
// Check we have the main content.
// Allow for either legacy or v5 id.
var main = document.getElementById('section--content') || document.getElementById('content')
if (main) {
// Create the anchor and its contaiing div.
var anchor = document.createElement('a')
anchor.id = 'main-content'
anchor.setAttribute('tabindex', '-1')
var div = document.createElement('div')
div.classList.add('skip-target')
div.appendChild(anchor)
// Add to the DOM.
main.insertBefore(div, main.firstChild)
}
}
// Expose public methods and properties.
global.header = {
init: init,
initOnLoad: true
}
})(UCASDesignFramework, UCASUtilities)