Source: components/header/_header.js

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