/* global noUiSlider */
'use strict';
/**
* Range slider component.
* @namespace rangeSlider
* @memberof UCASDesignFramework
* @param {object} global - UCASDesignFramework object.
* @param {object} _u - UCASUtilities object.
* @param {object} noUiSlider - noUiSlider.
*/
(function (global, _u, noUiSlider) {
var sliders = []
/**
* Initialise plugin.
* @function init
* @memberof! UCASDesignFramework.rangeSlider
* @public
* @param {Node} [context=document] - DOM element containing the range slider
*/
function init (context) {
context = context || document
var rangeSliders = context.querySelectorAll('.form-item--range-slider:not([data-initialised="true"])')
_u.forEach(rangeSliders, function (i, el) {
create(el)
})
}
/**
* Create slider.
* @param {Node} el - a form item to be turned into a range slider
*/
function create (el) {
// Check it's not already initialsed.
if (el.getAttribute('data-initialised') === 'true') {
_u.log.warn('noUiSlider already initialised for ' + el.id)
return
}
// Get attributes from element.
el.options = JSON.parse(el.getAttribute('data-range-slider-options'))
if (el.options === null || typeof el.options !== 'object') {
_u.log.error('data-range-slider-options is not a JSON object for ' + el.id)
return
}
// Create slider.
var slider = document.createElement('div')
slider.classList.add('range-slider')
// Add to DOM.
el.appendChild(slider)
// Check we have inputs for this form item and set the options accordingly.
// This is to respect the initial value set in the HTML when a form is reloaded.
var inputs = el.querySelector('.range-slider-inputs')
if (inputs && inputs.childElementCount === 2) {
var start = inputs.children[0].value
var end = ''
// If string includes the + as added to the value when "addPlus": true, then we need to remove it again
// during slider creation otherwise it'll be NaN.
if (inputs.children[1].value.includes('+')) {
end = inputs.children[1].value.substring(0, inputs.length - 1)
} else {
end = inputs.children[1].value
}
if (start && end) {
el.options.start = [start, end]
}
}
// Initialise noUiSlider.
try {
noUiSlider.create(slider, el.options)
} catch (e) {
_u.log.error('Failed to initialise noUiSlider for ' + el.id + ': ' + e)
// Clean up the div we added.
el.removeChild(slider)
// Give up and go home.
return
}
el.setAttribute('data-initialised', 'true')
sliders[el.id] = slider
// Add a reference to the slider to the main form-item container so we can refer to it later.
// The destroy() function removes this as otherwise we'd be adding a memory leak.
el.slider = slider
// Check we have inputs and, if we do, change the tabindex of the handles so we can't tab to them
// and make them hidden to screen readers.
// See @todo DF-968.
if (inputs) {
_u.forEach(slider.querySelectorAll('.noUi-handle'), function (i, el) {
el.setAttribute('tabindex', '-1')
el.setAttribute('aria-hidden', 'true')
})
}
// Add event handlers.
addEventHandlers(el)
}
/**
* Create slider from ID.
* @function create
* @memberof! UCASDesignFramework.rangeSlider
* @param {string} id - the id of the form item to be turned into a range slider
*/
function createFromId (id) {
var el = document.getElementById(id)
el ? create(el) : _u.log.error('No element with id ' + id)
}
/**
* Add event handlers to both the noUiSlider and the original input elements.
* @param {Node} el - the original form item
*/
function addEventHandlers (el) {
var firstInput = el.querySelector('input[class$="-first"]')
var secondInput = el.querySelector('input[class$="-second"]')
// Only add the event handlers if the inputs exist.
if (firstInput && secondInput) {
el.slider.noUiSlider.on('update', function (values, handle) {
var value = Math.round(values[handle])
if (handle) {
if (el.options.addPlus === true && value === el.options.range.max) {
secondInput.value = el.options.range.max + '+'
} else {
secondInput.value = value
}
} else {
firstInput.value = value
}
})
// Make a note of which input is which so the update handler knows
// how to update noUiSlider.
firstInput.sliderInput = 0
secondInput.sliderInput = 1
// Add the event listener to the main form-item container so it can handle everything.
el.addEventListener('change', inputUpdateHandler)
}
}
/**
* Input update handler.
* @param {Event} ev - the DOM event
*/
function inputUpdateHandler (ev) {
var input = ev.target
// Basic validation checks.
// noUiSlider handles the rest.
if (this.options && this.options.range) {
var min = this.options.range.min
var max = this.options.range.max
// For the first slider, if the value is not an integer, then set it to the minimum.
if (input.sliderInput === 0) {
input.value = !isNaN(Math.round(input.value)) && input.value >= min ? input.value : min
}
// For the second slider, if the value is not an integer, then set it to the maximum.
if (input.sliderInput === 1) {
input.value = !isNaN(Math.round(input.value)) & input.value >= min && input.value <= max ? input.value : max
}
}
// this refers to the main form-item container that the event listener was attached to.
this.slider.noUiSlider.set(input.sliderInput ? [null, input.value] : [input.value, null])
}
/**
* Destroy slider.
* @param {Node} el - the range slider node
*/
function destroy (el) {
el.slider.noUiSlider.destroy()
el.removeAttribute('data-initialised')
el.removeEventListener('change', inputUpdateHandler)
el.removeChild(el.slider)
delete el.slider
delete el.options
delete sliders[el.id]
}
/**
* Destroy slider from ID.
* @function destroy
* @memberof! UCASDesignFramework.rangeSlider
* @public
* @param {string} id - the id of the form item
*/
function destroyFromId (id) {
var el = document.getElementById(id)
el ? destroy(el) : _u.log.error('No element with id ' + id)
}
// Expose public methods and properties.
global.rangeSlider = {
init: init,
initOnLoad: true,
create: createFromId,
destroy: destroyFromId,
/**
* @function sliders
* @memberof! UCASDesignFramework.rangeSlider
* @return {string[]} - list of slider ids
*/
get sliders () {
return Object.keys(sliders)
}
}
})(UCASDesignFramework, UCASUtilities, noUiSlider)