'use strict';
/**
* Range progress component.
* @namespace rangeAction
* @memberof UCASDesignFramework
* @param {object} global - UCASDesignFramework object.
* @param {object} _u - UCASUtilities object.
*/
(function (global, _u) {
var rangeInputs = {}
/**
* Initialises any form items it finds that are of type "range" to:
* - Allow the background before and after the thumb to be different colours.
* Only needed for webkit browsers as Firefox and IE have their own
* prefixed pseudo elements to handle this.
* - Update any associated inputs with the current range value.
* @function init
* @memberof! UCASDesignFramework.rangeAction
* @param {Node} [context=document] - context to limit the selector
* @param {String} [selector=input[type="range"]] - a valid CSS selector for the range input
* @returns {Boolean} - true for success
* @example
* UCASDesignFramework.rangeAction.init()
*/
function init (context, selector) {
context = context || document
selector = selector || 'input[type="range"]'
var formItems = context.querySelectorAll(selector)
if (formItems.length === 0) {
_u.log.info('Failed to find any range inputs to style')
return false
} else {
var progressRequired, associatedRequired
formItems.forEach(function (formItem) {
if (!formItem.id) {
_u.log.info('Form item must have an ID')
return
}
if (rangeInputs[formItem.id]) {
_u.log.info('Already initialised range styles on input with ID ' + formItem.id)
return
}
// Will return true if the ::-webkit-slider-runnable-track pseudo element is supported or false if not.
// If false, browser based progress styles are supported so the JavaScript method is not needed.
progressRequired = !!window.getComputedStyle(context.querySelector(selector, '::-webkit-slider-runnable-track')).getPropertyValue('background')
// Will return true if the range has an associated input, false if not.
associatedRequired = !!formItem.getAttribute('data-associated-input')
// If neither action is required skip doing anything for this input.
if (!progressRequired && !associatedRequired) {
return
}
createRangeActions(formItem, context, progressRequired, associatedRequired)
})
}
return true
}
/**
* Destroy all styled range inputs.
* @function destroyAll
* @memberof! UCASDesignFramework.rangeAction
* @param {Node} [context=document] - context to limit the selector
* @public
*/
function destroyAll (context) {
context = context || document
for (var rangeInputId in rangeInputs) {
var formItem = context.getElementById(rangeInputId)
if (formItem === null) {
delete rangeInputs[rangeInputId]
} else {
rangeInputs[rangeInputId].destroy(formItem)
}
}
}
/**
* RangeProgress.
* @class
* @memberof! UCASDesignFramework.rangeAction
*/
var RangeProgress = {
init: function (formItem, progressRequired, associatedRequired) {
if (progressRequired) {
addRangeProgress(formItem)
}
if (associatedRequired) {
addRangeAssociation(formItem)
}
},
destroy: function (formItem) {
removeRangeProgress(formItem)
removeRangeAssociation(formItem)
delete rangeInputs[formItem.id]
},
progressHandler: function (event) {
rangeProgressHandler(event.target)
},
updateAssociatedHandler: function (event) {
rangeUpdateAssociatedHandler(event.target)
},
associatedUpdatedHandler: function (event) {
rangeAssociatedUpdatedHandler(event.target, event.type)
},
associatedInput: null
}
/**
* @function addRangeProgress
* @memberof! UCASDesignFramework.rangeAction
* @param {Node} formItem - the form element
* @private
*/
function addRangeProgress (formItem) {
// Add the progress input event to the range input. The handler function had to be setup this way
// as we need to pass the formitem and using bind makes the function appear different (causing
// event listener not to be removed with removeEventListener).
formItem.addEventListener('input', rangeInputs[formItem.id].progressHandler, false)
// Add the range_progress class to disable default range styles.
formItem.classList.add('range_progress')
// Set the initial state of the range input.
rangeProgressHandler(formItem)
}
/**
* @function removeRangeProgress
* @memberof! UCASDesignFramework.rangeAction
* @param {Node} formItem - the form element
* @private
*/
function removeRangeProgress (formItem) {
// Remove the progress input event from the range input.
formItem.removeEventListener('input', rangeInputs[formItem.id].progressHandler, false)
// Remove the background styles and range_progress class to re-enable the default range styles.
formItem.style.removeProperty('--range-progress')
formItem.classList.remove('range_progress')
}
/**
* @function addRangeAssociation
* @memberof! UCASDesignFramework.rangeAction
* @param {Node} formItem - the form element
* @private
*/
function addRangeAssociation (formItem) {
var associatedInput = document.querySelector(formItem.getAttribute('data-associated-input'))
if (associatedInput) {
associatedInput.setAttribute('data-associated-range-id', formItem.id)
rangeInputs[formItem.id].associatedInput = associatedInput
formItem.addEventListener('change', rangeInputs[formItem.id].updateAssociatedHandler, false)
formItem.addEventListener('input', rangeInputs[formItem.id].updateAssociatedHandler, false)
formItem.addEventListener('mouseup', rangeInputs[formItem.id].updateAssociatedHandler, false)
associatedInput.addEventListener('keyup', rangeInputs[formItem.id].associatedUpdatedHandler, false)
associatedInput.addEventListener('blur', rangeInputs[formItem.id].associatedUpdatedHandler, false)
}
}
/**
* @function removeRangeAssociation
* @memberof! UCASDesignFramework.rangeAction
* @param {Node} formItem - the form element
* @private
*/
function removeRangeAssociation (formItem) {
var associatedInput = rangeInputs[formItem.id].associatedInput
if (associatedInput) {
associatedInput.removeAttribute('data-associated-range-id')
formItem.removeEventListener('change', rangeInputs[formItem.id].updateAssociatedHandler, false)
formItem.removeEventListener('input', rangeInputs[formItem.id].updateAssociatedHandler, false)
formItem.removeEventListener('mouseup', rangeInputs[formItem.id].updateAssociatedHandler, false)
associatedInput.removeEventListener('keyup', rangeInputs[formItem.id].associatedUpdatedHandler, false)
associatedInput.removeEventListener('blur', rangeInputs[formItem.id].associatedUpdatedHandler, false)
}
}
/**
* Handler that changes the background gradient to supply a different colour before and after.
* the thumb.
* @function rangeProgressHandler
* @memberof! UCASDesignFramework.rangeAction
* @param {Node} formItem - the form element
* @private
*/
function rangeProgressHandler (formItem) {
// Get the minimum and maximum possible values and the current value.
var max = formItem.max
var min = formItem.min
var value = formItem.value
// Translate the current value into a percentage value between the min and max.
var percent = ((value - min) / (max - min)) * 100
formItem.style.setProperty('--range-progress', percent + '%')
}
/**
* Handler that changes the associated input value.
* @function rangeUpdateAssociatedHandler
* @memberof! UCASDesignFramework.rangeAction
* @param {Node} formItem - the form element
* @private
*/
function rangeUpdateAssociatedHandler (formItem) {
var associatedInput = rangeInputs[formItem.id].associatedInput
if (associatedInput) {
associatedInput.value = formItem.value
}
}
/**
* Handler that changes the range input when the associated input is changed.
* @function rangeAssociatedUpdatedHandler
* @memberof! UCASDesignFramework.rangeAction
* @param {Node} associatedInput - the associated input
* @param {String} eventType - the type of event that triggered the handler
* @param {Node} [context=document] - context to limit the selector
* @private
*/
function rangeAssociatedUpdatedHandler (associatedInput, eventType, context) {
context = context || document
eventType = eventType || 'blur'
var formItem = context.getElementById(associatedInput.getAttribute('data-associated-range-id'))
var maxValue = parseInt(formItem.getAttribute('max'))
var minValue = parseInt(formItem.getAttribute('min'))
var newValue = parseInt(associatedInput.value)
if (isNaN(newValue)) {
if ((associatedInput.value.length) || ((!associatedInput.value.length) && (eventType === 'blur'))) {
newValue = formItem.value
associatedInput.value = newValue
}
} else if (newValue > maxValue) {
newValue = maxValue
associatedInput.value = newValue
} else if (newValue < minValue) {
newValue = minValue
associatedInput.value = newValue
}
if (associatedInput.value.length) {
formItem.value = newValue
rangeProgressHandler(formItem)
}
}
/**
* Creates and registers a rangeAction.
* @function create
* @memberof! UCASDesignFramework.rangeAction
* @param {Node} formItem - the form element
* @param {Node} [context=document] - context to limit the selector
* @param {Boolean} progressRequired - true if JavaScript styling is required
* @param {Boolean} associatedRequired - true if the range has an associated input
* @return {RangeProgress} - the RangeProgress object
* @example
* UCASDesignFramework.rangeAction.create($myFormItem)
*/
function createRangeActions (formItem, context, progressRequired, associatedRequired) {
progressRequired = progressRequired || !!window.getComputedStyle(formItem, '::-webkit-slider-runnable-track').getPropertyValue('background')
associatedRequired = associatedRequired || !!formItem.getAttribute('data-associated-input')
if (rangeInputs[formItem.id] === undefined) {
if (formItem.id) {
rangeInputs[formItem.id] = Object.create(RangeProgress)
rangeInputs[formItem.id].init(formItem, progressRequired, associatedRequired)
return rangeInputs[formItem.id]
} else {
_u.log.info('Form item must have an ID')
}
}
}
/**
* @function destroy
* @memberof! UCASDesignFramework.rangeAction
* @param {Node} formItem - the form element
* @example
* UCASDesignFramework.rangeAction.destroy($myFormItem)
*/
function destroyRangeProgress (formItem) {
if (rangeInputs[formItem.id]) {
rangeInputs[formItem.id].destroy(formItem)
} else {
_u.log.info('Form item must have an ID')
}
}
// Expose public methods and properties.
global.rangeAction = {
addSubscriptions: true,
init: init,
create: createRangeActions,
destroy: destroyRangeProgress,
destroyAll: destroyAll
}
})(UCASDesignFramework, UCASUtilities)