Source: scripts/utilities/_overflowMenu.js

'use strict';

/**
 * Overflow menu component.
 * @namespace overflowMenu
 * @memberof UCASUtilities
 * @param {object} _u - UCASUtilities object.
 */
(function (_u) {
  var overflowMenus = []

  /**
   * overflowMenu.
   * @private
   */
  var overflowMenu = {
    init: function (id, contractCallback, expandCallback) {
      this.id = id
      this.contractCallback = contractCallback
      this.expandCallback = expandCallback
      this.$element = document.getElementById(id)

      this.$navigationPriority = this.$element.querySelector('.overflow-menu--main')
      this.$navigationOverflow = this.$element.querySelector('.overflow-menu--overflow')
      this.$menuItems = Array.prototype.slice.call(this.$element.querySelectorAll('.overflow-menu--main > li'))

      if (!this.$navigationPriority || !this.$menuItems) {
        _u.log.info('Failed to find correct overflowMenu markup')
        return
      }

      if (!this.$navigationOverflow) {
        this.$navigationOverflow = createNavigationOverflow(this)
      }

      this.overflowWidths = []
      this.$overflowItems = []
      this.$navigationToggles = []

      this.contract = function contract () {
        if (typeof this.contractCallback === 'function') {
          this.contractCallback()
        }
      }
      this.expand = function expand () {
        if (typeof this.expandCallback === 'function') {
          this.expandCallback()
        }
      }
      this.handleEvent = function (event) {
        switch (event.type) {
          case 'dfResize':
            dfResizeHandler(event, this)
            break
        }
      }
      addEventListeners(this)
      renderOverflowMenu(this)
      // Sometimes renderOverflowMenu gets called before things have fully rendered,
      // resulting in mis-measurements.
      // Better solutions for solving this problem welcome :-)
      setTimeout(renderOverflowMenu, 50, this)
    },
    destroy: function () {
      removeEventListeners(this)
      // @todo DF-1133 Move menu items back where they were.
      // @todo DF-1133 Remove generated overflow menu container.
      delete overflowMenus[this.id]
    }
  }

  /**
   * @param {overflowMenu} overflowMenu - overflowMenu instance
   */
  function addEventListeners (overflowMenu) {
    var toggles = overflowMenu.$element.querySelectorAll('.overflow-menu--main .overflow-menu__main-toggle, .overflow-menu--overflow .overflow-menu__overflow-toggle')
    toggles.forEach(function (toggle) {
      if (toggle.id) {
        _u.toggle.create(toggle.id, showSubMenu, hideSubMenu, true)
        overflowMenu.$navigationToggles.push(toggle)
        toggle.setAttribute('data-toggle-active', '')
      }
    })

    window.addEventListener('dfResize', overflowMenu, true)
  }

  /**
   * @param {overflowMenu} overflowMenu - overflowMenu instance
   * @return {Node} - the navigationOverflow element
   */
  function createNavigationOverflow (overflowMenu) {
    var ul = document.createElement('ul')
    var li = document.createElement('li')
    var a = document.createElement('a')
    var sub = document.createElement('ul')

    ul.className = 'overflow-menu overflow-menu--overflow'
    li.className = 'overflow-menu__item'
    a.className = 'overflow-menu__overflow-toggle icon-inline--right icon--chevron-down-dark'
    a.setAttribute('aria-expanded', false)
    a.href = ('#')
    a.id = overflowMenu.id + 'NavigationOverflow'
    a.text = 'More'
    sub.className = 'overflow-menu__submenu'

    li.appendChild(a)
    li.appendChild(sub)
    ul.appendChild(li)
    overflowMenu.$navigationPriority.parentNode.appendChild(ul)
    return ul
  }

  /**
   * @param {overflowMenu} overflowMenu - overflowMenu instance
   */
  function removeEventListeners (overflowMenu) {
    // @todo DF-1133 Remove toggles.
    window.removeEventListener('dfResize', overflowMenu, true)
  }

  function showSubMenu () {
    _u.toggle.hideOthers(this.id)
    this.$element.setAttribute('aria-expanded', true)
  }

  function hideSubMenu () {
    this.$element.setAttribute('aria-expanded', false)
  }

  /**
   * @param {Event} event - DOM event
   * @param {Node} overflowMenu - the overflowMenu
   */
  function dfResizeHandler (event, overflowMenu) {
    renderOverflowMenu(overflowMenu)
  }

  /**
   * @param {Node} overflowMenu - the overflowMenu
   */
  function renderOverflowMenu (overflowMenu) {
    // The width of the container, with a bit chopped off.
    var availableWidth = overflowMenu.$element.clientWidth - 30
    // The width of the two possible menus: the main one and the overflow.
    var width = overflowMenu.$navigationPriority.scrollWidth + overflowMenu.$navigationOverflow.scrollWidth
    // The list element of the overflow menu.
    var listElement = overflowMenu.$element.querySelector('.overflow-menu--overflow ul')

    // If we're taking up too much space and still have menu items...
    if (width > availableWidth && overflowMenu.$menuItems.length) {
      var lastMenuItem = overflowMenu.$menuItems.pop()
      var firstChild = listElement.firstChild
      // Insert at the beginning of the overflow menu list.
      listElement.insertBefore(lastMenuItem, firstChild)
      // Disable any toggles.
      lastMenuItem.querySelectorAll('.overflow-menu__main-toggle').forEach(function (toggle) {
        toggle.removeAttribute('data-toggle-active')
        toggle.setAttribute('data-toggle-state', 'open')
        toggle.setAttribute('aria-expanded', true)
      })

      // Make a note of the breakpoint.
      overflowMenu.overflowWidths.push(width)
      // Make a note of the menu item.
      overflowMenu.$overflowItems.push(lastMenuItem)
      // Make sure the overflow menu is visible.
      overflowMenu.$navigationOverflow.style.display = 'block'
      // Set an attribute if we're all alone in the world.
      if (!overflowMenu.$menuItems.length) {
        overflowMenu.$navigationOverflow.setAttribute('data-overflow-menu-alone', '')
      }
      // Call the contract callback.
      overflowMenu.contract()
      // Check to see if we're still taking up too much space.
      if (width > availableWidth) {
        renderOverflowMenu(overflowMenu)
      }
    // Else check to see if we've got more space than the last breakpoint.
    } else if (availableWidth > overflowMenu.overflowWidths[overflowMenu.overflowWidths.length - 1]) {
      var lastOverflowMenuItem = overflowMenu.$overflowItems.pop()
      // Put the menu item back at the end of the main menu.
      overflowMenu.$element.querySelector('.overflow-menu--main').appendChild(lastOverflowMenuItem)
      // Delete the breakpoint.
      overflowMenu.overflowWidths.pop()
      // Make a note of the menu item.
      overflowMenu.$menuItems.push(lastOverflowMenuItem)
      // Hide the overflow menu if it's now empty.
      if (overflowMenu.overflowWidths.length === 0) {
        overflowMenu.$navigationOverflow.style.display = ''
      }
      // Enable any toggles.
      lastOverflowMenuItem.querySelectorAll('.overflow-menu__main-toggle').forEach(function (toggle) {
        toggle.setAttribute('data-toggle-active', '')
        toggle.setAttribute('data-toggle-state', 'closed')
        toggle.setAttribute('aria-expanded', false)
      })
      // The overflow menu won't be alone, now.
      overflowMenu.$navigationOverflow.removeAttribute('data-overflow-menu-alone')
      // Call the expand callback.
      overflowMenu.expand()
      // Check to see if there's any more room.
      if (width < availableWidth) {
        renderOverflowMenu(overflowMenu)
      }
    }
  }

  /**
   * Creates and registers a overflowMenu, which will contract or expand,
   * depending on how much space it has.
   * An overflowMenu can accept two callbacks: one for the contract event and one for the expand event.
   * @function create
   * @memberof! UCASUtilities.overflowMenu
   * @param {string} id - the unique ID of the overflowMenu element
   * @param {function} [contract] - a callback on contract
   * @param {function} [expand] - a callback on expand
   * @example
   * UCASUtilities.overflowMenu.create('my-id', function(){console.log('contract')}, function(){console.log('expand')})
   */
  function create (id, contract, expand) {
    if (document.getElementById(id)) {
      overflowMenus[id] = Object.create(overflowMenu)
      overflowMenus[id].init(id, contract, expand)
    } else {
      _u.log.info('No overflowMenu with id ' + id)
    }
  }

  /**
   * @function destroy
   * @memberof! UCASUtilities.overflowMenu
   * @param {string} id - the unique ID of the overflowMenu element
   * @example
   * UCASUtilities.overflowMenu.destroy('my-id')
   */
  function destroy (id) {
    if (overflowMenus[id]) {
      overflowMenus[id].destroy()
    } else {
      _u.log.info('No overflowMenu with id ' + id)
    }
  }

  _u.overflowMenu = {
    create: create,
    destroy: destroy
  }
}(UCASUtilities))