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