'use strict';
/**
* Floating help.
* @namespace floatingHelp
* @memberof UCASDesignFramework
* @param {object} global - UCASDesignFramework object.
* @param {object} _u - UCASUtilities object.
*/
(function (global, _u) {
var floatingHelpContainer
var floatingHelp
var floatingHelpTrigger
var floatingHelpBackButton
var floatingHelpBottom
var floatingHelpMaster
var floatingHelpDetails
var footer
var clientWidth
var currentScreenSize
var observer = null
/**
* Initialise the floating help.
* @function init
* @memberof! UCASDesignFramework.floatingHelp
* @public
*/
function init () {
// Only one floating help component is expected to be on the page,
// so these selectors only take account of the first one they encounter.
floatingHelpContainer = document.querySelector('.floating-help-container')
floatingHelp = document.querySelector('.floating-help-container .floating-help')
floatingHelpTrigger = document.querySelector('.floating-help-container .floating-help-trigger')
floatingHelpMaster = document.querySelector('.floating-help-container .floating-help__master')
footer = document.getElementById('section--footer')
if (floatingHelp === null || floatingHelpContainer === null || floatingHelpMaster === null) {
return
}
// Set the details to their initial state.
floatingHelpDetails = floatingHelp.querySelectorAll('.floating-help__detail')
floatingHelpDetails.forEach(function (detail) {
detail.hidden = true
detail.setAttribute('tabIndex', '0')
detail.setAttribute('aria-hidden', 'true')
})
// Make sure we can give the master focus.
floatingHelpMaster.setAttribute('tabIndex', '0')
// Give the main section an ID so we can refer to it using aria-controls.
floatingHelp.id = 'floatingHelp'
clientWidth = document.body.clientWidth
// IE won't get this Observer, which adds a scroll listener, but that's OK,
// it's a nicety and not essential.
if ('IntersectionObserver' in window && observer === null) {
observer = new IntersectionObserver(intersectionHandler)
}
prepareButtons()
preparefloatingHelp(calculateScreenSize())
addAnchorListeners()
window.addEventListener('dfResize', resizeEventHandler, false)
}
/**
* Clear out all the local variables and remove the event listeners.
* @function destroy
* @memberof! UCASDesignFramework.floatingHelp
* @public
*/
function destroy () {
closeAndReset()
if (floatingHelpTrigger) {
floatingHelpTrigger.parentElement.removeChild(floatingHelpTrigger)
}
if (floatingHelpBackButton) {
floatingHelpBackButton.parentElement.removeChild(floatingHelpBackButton)
}
if (observer) {
observer.disconnect()
}
floatingHelpContainer = null
floatingHelp = null
floatingHelpTrigger = null
floatingHelpBackButton = null
floatingHelpBottom = null
footer = null
clientWidth = null
currentScreenSize = null
observer = null
window.removeEventListener('dfResize', resizeEventHandler, false)
window.removeEventListener('dfScroll', scrollEventHandler, false)
}
/**
* Make adjustments according to screen size.
*/
function resizeEventHandler () {
// Check width as triggering the keyboard on Android changes page size.
// We're only interested if the width changes.
if (clientWidth !== document.body.clientWidth) {
clientWidth = document.body.clientWidth
closeFloatingHelp()
preparefloatingHelp(calculateScreenSize())
}
}
/**
* Recalculate the position on scroll.
*/
function scrollEventHandler () {
avoidFooter()
}
/**
* Position above the footer if necessary.
*/
function avoidFooter () {
var distanceFromBottom = parseFloat(window.innerHeight + window.scrollY - footer.offsetTop)
if (distanceFromBottom > 0) {
floatingHelpTrigger.style.bottom = distanceFromBottom + 'px'
floatingHelp.style.bottom = (floatingHelpBottom + distanceFromBottom) + 'px'
} else {
floatingHelpTrigger.style.bottom = null
floatingHelp.style.bottom = null
}
}
/**
* Decide how to display the floating help.
* @return {string} - mobile|desktop
*/
function calculateScreenSize () {
return global.size.cssBreakpoint === 'xsmall' ? 'mobile' : 'desktop'
}
/**
* Prepare the toggle button.
* @function createButton
* @memberof! UCASDesignFramework.floatingHelp
*/
function prepareButtons () {
if (floatingHelpTrigger == null || floatingHelpBackButton == null) {
createButtons()
}
addButtonListeners()
}
/**
* Create the toggle and back buttons if they don't already exist.
* @function createButtons
* @memberof! UCASDesignFramework.floatingHelp
*/
function createButtons () {
// Add the trigger button.
if (!floatingHelpTrigger) {
// Create the trigger button.
floatingHelpTrigger = document.createElement('button')
floatingHelpTrigger.setAttribute('class', 'button floating-help-trigger')
floatingHelpTrigger.setAttribute('aria-controls', 'floatingHelp')
floatingHelpTrigger.setAttribute('aria-expanded', 'false')
floatingHelpTrigger.innerText = floatingHelp.getAttribute('data-floating-help-trigger-label') || 'Help'
floatingHelpContainer.insertBefore(floatingHelpTrigger, floatingHelp)
}
// Add the back button.
if (!floatingHelpBackButton) {
// Create the trigger button.
floatingHelpBackButton = document.createElement('button')
floatingHelpBackButton.setAttribute('class', 'button button--back floating-help__back')
floatingHelpBackButton.innerText = floatingHelp.getAttribute('data-floating-help-back-label') || 'Back'
floatingHelpDetails[0].insertBefore(floatingHelpBackButton, floatingHelpDetails[0].firstChild)
}
}
/**
* Add the button listeners.
* @function addButtonListeners
* @memberof! UCASDesignFramework.floatingHelp
*/
function addButtonListeners () {
floatingHelpTrigger.addEventListener('click', toggleFloatingHelp, false)
floatingHelpBackButton.addEventListener('click', floatingHelpBackClickHandler, false)
}
/**
* Toggle the help.
* @function toggleFloatingHelp
* @memberof! UCASDesignFramework.floatingHelp
*/
function toggleFloatingHelp () {
floatingHelp.getAttribute('aria-hidden') === 'false' ? closeFloatingHelp() : openFloatingHelp()
}
/**
* Close the help.
* @function closeFloatingHelp
* @memberof! UCASDesignFramework.floatingHelp
*/
function closeFloatingHelp () {
if (floatingHelp) {
floatingHelp.setAttribute('aria-hidden', 'true')
floatingHelpTrigger.setAttribute('aria-expanded', 'false')
window.setTimeout(function () {
floatingHelp.hidden = true
global.size.unlockPosition() // Not just doing this on mobile as we may have switched size while the help was open.
}, 300)
}
}
/**
* Close the help with no delay.
* @function closeImmediately
* @memberof! UCASDesignFramework.floatingHelp
*/
function closeAndReset () {
if (floatingHelp) {
floatingHelp.setAttribute('aria-hidden', 'true')
floatingHelpTrigger.setAttribute('aria-expanded', 'false')
floatingHelp.hidden = true
global.size.unlockPosition() // Not just doing this on mobile as we may have switched size while the help was open.
floatingHelpBackClickHandler()
}
}
/**
* Open the help.
* @function openFloatingHelp
* @memberof! UCASDesignFramework.floatingHelp
*/
function openFloatingHelp () {
if (floatingHelp) {
floatingHelp.hidden = false
floatingHelpMaster.focus()
window.setTimeout(function () {
floatingHelp.setAttribute('aria-hidden', 'false')
floatingHelpTrigger.setAttribute('aria-expanded', 'true')
if (calculateScreenSize() === 'mobile') {
global.size.lockPosition()
}
}, 10)
}
}
/**
* Render the floating help.
* @param {string} screenSize - mobile|desktop
*/
function preparefloatingHelp (screenSize) {
if (screenSize === 'desktop') {
avoidFooter()
}
if (screenSize === currentScreenSize) {
// Don't do anything if nothing has changed.
return
}
currentScreenSize = screenSize
floatingHelp.hidden = true
if (screenSize === 'mobile') {
// We don't have to worry about the position of the help text.
if (footer && observer) {
observer.unobserve(footer)
}
window.removeEventListener('dfScroll', scrollEventHandler, false)
floatingHelp.style.bottom = floatingHelpTrigger.style.bottom = ''
} else if (screenSize === 'desktop') {
// Use the IntersectionObserver to see whether we need to add the scroll event listener.
var options = {
root: null,
threshold: '0'
}
if (observer) {
observer.observe(footer, options)
}
}
}
/**
* Add the anchor listeners.
* @function addAnchorListeners
* @memberof! UCASDesignFramework.floatingHelp
*/
function addAnchorListeners () {
var floatingHelpAnchors = floatingHelp.querySelectorAll('.floating-help__anchor')
floatingHelpAnchors.forEach(function (anchor) {
anchor.addEventListener('click', floatingHelpAnchorClickHandler, false)
})
}
/**
* Handle the intersectionObserver.
* This is used to add a scroll event listener if required.
* @param {Array.<IntersectionObserverEntry>} entries - the list of entries
* @param {IntersectionObserver} observer - the observer
*/
function intersectionHandler (entries, observer) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
// Calculate the bottom of the help text.
// Only do this the once here, as otherwise we may risk adding to existing values if we do it in the scroll handler.
floatingHelpBottom = parseFloat(window.getComputedStyle(floatingHelp).bottom)
// Add the scroll handler.
scrollEventHandler()
window.addEventListener('dfScroll', scrollEventHandler, false)
} else {
// Remove the scroll handler.
scrollEventHandler()
floatingHelpBottom = null
window.removeEventListener('dfScroll', scrollEventHandler, false)
}
})
}
/**
* Handle the back button.
* @function floatingHelpBackClickHandler
* @memberof! UCASDesignFramework.floatingHelp
*/
function floatingHelpBackClickHandler () {
// When we click back, we're hiding the detail panel and the back button
// both visually and also from the tab index.
var detail = floatingHelp.querySelector('[aria-hidden="false"]')
if (detail) {
floatingHelpMaster.hidden = false
detail.setAttribute('aria-hidden', 'true')
// The parent has an attribute set so it can change its styles.
floatingHelp.setAttribute('data-floating-help-detail-state', 'hidden')
// We delay hiding the detail so that the animation can play out.
window.setTimeout(function () {
detail.hidden = true
floatingHelpMaster.focus()
}, 300)
}
}
/**
* Handle the anchors.
* @function floatingHelpAnchorClickHandler
* @memberof! UCASDesignFramework.floatingHelp
* @param {Event} e - the click event.
*/
function floatingHelpAnchorClickHandler (e) {
e.preventDefault()
var detail = document.getElementById(this.hash.substring(1))
if (detail) {
detail.hidden = false
detail.insertBefore(floatingHelpBackButton, detail.firstChild)
// The parent has an attribute set so it can change its styles.
floatingHelp.setAttribute('data-floating-help-detail-state', 'visible')
// Slight delay needed to give the animation a chance to start.
window.setTimeout(function () {
detail.setAttribute('aria-hidden', 'false')
}, 10)
// We delay hiding the master so that the animation can play out.
window.setTimeout(function () {
floatingHelpMaster.hidden = true
detail.focus()
}, 300)
}
}
/**
* Assign public methods.
*/
global.floatingHelp = {
addSubscriptions: true,
init: init,
destroy: destroy,
open: openFloatingHelp,
close: closeFloatingHelp,
closeAndReset: closeAndReset,
refresh: init
}
})(UCASDesignFramework, UCASUtilities)