/* global jQuery */
'use strict';
/**
* Adds multistep functionality to forms using jQuery.
* @namespace multistep
* @memberof UCASDesignFramework
* @param {object} global - UCASDesignFramework object.
* @param {object} $ - jQuery.
*/
(function (global, $) {
global.multistep = {}
// @private
var multistepForm = {}
// @private
var submit = {}
// @private
var panels = []
// @private
var progress = {}
// @private
var settings = {}
// @private
var defaultSettings = {
panel: '.form-multistep--panel',
progress: false,
nextButtonEnabled: false,
prevButtonEnabled: true,
enableStepNav: false
}
/**
* Initialise the multistep form.
* @function init
* @memberof! UCASDesignFramework.multistep
* @public
* @param {object} context - DOM element containing the multistep form
*/
function init (context) {
// Set the form.
// Make sure there's only one. We may add support for multiple forms one day, you never know.
multistepForm = $('[data-multistep-settings]:first-of-type')
// Read in the settings.
var options = multistepForm.data('multistep-settings')
// Extend the options passed via the initialisation call with some sensible defaults.
settings = $.extend({}, defaultSettings, options)
// Set the panels.
panels = $(multistepForm).find(settings.panel)
// Insert the progress bar if it's enabled.
if (settings.progress) {
initProgressBar()
}
// Set up the initial panel.
panels.each(initPanel)
// Disable the submit button by default.
// It is re-enabled on the last panel.
submit = multistepForm.find('[type="submit"]')
disableSubmit()
// Update the height on window resize.
$(window).on('resize', function () {
updateHeight()
})
// Resize the container to fit the content.
updateHeight()
}
/**
* Initialise the progress bar.
* @private
*/
function initProgressBar () {
// Insert the progress bar.
progress = $('<ul>').addClass('progress-indicator js-progress-indicator--disabled').insertBefore(multistepForm)
for (var i = 0; i < panels.length; i++) {
var progressItem = $('<li>').html('Step' + i).appendTo(progress)
if (i === 0) {
progressItem.addClass('active')
}
}
// Attach click handlers to the progress bar.
enableProgressBar()
}
/**
* Enable the progress bar.
* @private
*/
function enableProgressBar () {
if (settings.enableStepNav) {
$('li', progress).each(function (i) {
$(this).on('click', function () {
goto(i)
})
})
progress.removeClass('js-progress-indicator--disabled')
}
}
/**
* Disable the progress bar.
* @private
*/
function disableProgressBar () {
$('li', progress).each(function (i) {
$(this).off('click')
})
progress.addClass('js-progress-indicator--disabled')
}
/**
* Enable the submit button.
* @function enableSubmit
* @memberof! UCASDesignFramework.multistep
* @public
*/
function enableSubmit () {
submit.off('click.submit')
}
/**
* Disable the submit button.
* @function disableSubmit
* @memberof! UCASDesignFramework.multistep
* @public
*/
function disableSubmit () {
submit.off('.submit')
submit.on('click.submit', function (e) {
e.preventDefault()
})
}
/**
* Initialise a panel.
* @private
* @param {number} index - the index of the panel in the panels array.
* @param {object} panel - the panel node
*/
function initPanel (index, panel) {
var formItemActions = $(panel).find('.form-item__multistep-actions')
// Check to see if form multistep actions appears on this panel.
// If not, create it.
if (formItemActions.length === 0) {
formItemActions = '<div class="form-item form-item__actions form-item__multistep-actions">' +
'<div class="buttons">' +
'<div class="buttons__group"></div>' +
'<div class="buttons__group"></div>' +
'</div>' +
'</div>'
$(panel).append($(formItemActions))
}
// Add a next button.
if (index < panels.length - 1) {
var nextButton = $('<button>').html('Next').addClass('button next button--primary').attr('type', 'button').prependTo($(panel).find('.buttons__group:last-child'))
nextButton.on('click', function (e) {
e.preventDefault()
})
}
// Add a previous button.
if (index > 0) {
var prevButton = $('<button>').html('Previous').addClass('button previous').attr('type', 'button').prependTo($(panel).find('.buttons__group:first-child'))
prevButton.on('click', function (e) {
e.preventDefault()
})
}
// Setup the initial panel with events.
if (index === 0) {
$(panel).addClass('current')
enablePanel($(panel))
}
// Set the tabindex so the panel can receive focus.
$(panel).attr('tabindex', -1)
}
/**
* Disable a panel by switching off its click handlers.
* @private
* @param {object} [panel=The current panel] - the panel node
* @fires multistepPanelDisabled
*/
function disablePanel (panel) {
panel = panel || getCurrent()
// Switch off click handlers for the panel.
disableNavigation(panel)
// Set the correct class on the panel we're leaving.
panel.removeClass('current').removeClass('first')
// @event multistepPanelDisabled
multistepForm.trigger('multistepPanelDisabled', [panel])
}
/**
* Disable navigation.
* @function disableNavigation
* @memberof! UCASDesignFramework.multistep
* @public
* @param {object} [panel=The current panel] - the panel node
* @fires multistepNavigationDisabled
*/
function disableNavigation (panel) {
panel = panel || getCurrent()
// Switch off click handlers for the panel.
panel.find('.form-item__actions button.next').off('click.next')
panel.find('.form-item__actions button.previous').off('click.prev')
// Switch off click handlers for the progress bar.
if (settings.progress) {
disableProgressBar()
}
// @event multistepNavigationDisabled
multistepForm.trigger('multistepNavigationDisabled', [panel])
}
/**
* Enable a panel by adding click handlers.
* @private
* @param {object} [panel=The current panel] - the panel node
* @fires multistepPanelEnabled
*/
function enablePanel (panel) {
panel = panel || getCurrent()
// Add click handlers for the panel.
enableNavigation(panel)
// Set the correct class on the panel.
panel.removeClass('next').removeClass('prev').addClass('current')
// Give the panel focus to help keyboard navigation.
panel.focus()
// @event multistepPanelEnabled
multistepForm.trigger('multistepPanelEnabled', [panel])
}
/**
* Enable navigation.
* @function enableNavigation
* @memberof! UCASDesignFramework.multistep
* @public
* @param {object} [panel=The current panel] - the panel node
* @fires multistepNavigationEnabled
* @fires multistepNextTriggered
* @fires multistepPrevTriggered
*/
function enableNavigation (panel) {
panel = panel || getCurrent()
var index = getIndex(panel)
// Add click handlers for the panel.
panel.find('.form-item__actions button.next').on('click.next', function () {
if (settings.nextButtonEnabled) {
next()
}
// @event multistepNextTriggered
multistepForm.trigger('multistepNextTriggered', { 'panel': panel, 'currentIndex': index })
})
panel.find('.form-item__actions button.previous').on('click.prev', function () {
if (settings.prevButtonEnabled) {
prev()
}
// @event multistepPrevTriggered
multistepForm.trigger('multistepPrevTriggered', { 'panel': panel, 'currentIndex': index })
})
// Switch on click handlers for the progress bar.
if (settings.progress) {
enableProgressBar()
}
// @event multistepNavigationEnabled
multistepForm.trigger('multistepNavigationEnabled', [panel])
}
/**
* Calculate the container height.
* @private
* @param {object} panel - the panel node
* @returns {number} - the height in pixels
*/
function calculateContainerHeight (panel) {
var height = panel.outerHeight(true)
if (settings.progress && multistepForm.find('.progress-indicator').length > 0) {
height += multistepForm.find('.progress-indicator').outerHeight(true)
}
return height
}
/**
* Update the container height.
* @function updateHeight
* @memberof! UCASDesignFramework.multistep
* @param {object} [panel=The current panel] - the panel node
* @public
*/
function updateHeight (panel) {
panel = panel || getCurrent()
multistepForm.height(calculateContainerHeight(panel))
}
/**
* Get the current panel.
* @private
* @returns {object} The current panel object.
*/
function getCurrent () {
return multistepForm.find('.current')
}
/**
* Get a panel index.
* @function getIndex
* @memberof! UCASDesignFramework.multistep
* @public
* @param {object} [panel=A panel] - a panel node
* @returns {object} The current panel object.
*/
function getIndex (panel) {
panel = panel || getCurrent()
return panels.index(panel)
}
/**
* Callback for successful navigation to another panel.
* @callback onSuccessCallback
* @memberof! UCASDesignFramework.multistep
* @param {int} index - the index of the panel being navigated to.
*/
/**
* Callback for failed navigation to another panel.
* @callback onFailureCallback
* @memberof! UCASDesignFramework.multistep
* @param {string} message - failure message.
*/
/**
* Go to the next panel.
* @function next
* @memberof! UCASDesignFramework.multistep
* @public
* @param {onSuccessCallback} onSuccess - success callback
* @param {onFailureCallback} onFailure - failure callback
*/
function next (onSuccess, onFailure) {
var index = getIndex() + 1
goto(index, onSuccess, onFailure)
}
/**
* Go to the previous panel.
* @function prev
* @memberof! UCASDesignFramework.multistep
* @public
* @param {onSuccessCallback} onSuccess - success callback
* @param {onFailureCallback} onFailure - failure callback
*/
function prev (onSuccess, onFailure) {
var index = getIndex() - 1
goto(index, onSuccess, onFailure)
}
/**
* Go to a panel.
* @function goto
* @memberof! UCASDesignFramework.multistep
* @public
* @param {number} index - the index of the panel in the panels array.
* @param {onSuccessCallback} onSuccess - success callback
* @param {onFailureCallback} onFailure - failure callback
* @fires multistepPanelLoaded
*/
function goto (index, onSuccess, onFailure) {
// Check that we have a valid index.
if (index < 0 || index >= panels.length) {
if (typeof onFailure === 'function') {
onFailure('Attempted to go to non-existant panel (' + index + ').')
}
return
} else {
var current = getCurrent()
var goto = panels.eq(index)
// Switch off any click handlers for the panel we're leaving.
disablePanel(current)
// Switch on the click handlers for the panel we're going to.
enablePanel(goto)
// Enable the submit button if we're on the last panel.
index === panels.length - 1 ? enableSubmit() : disableSubmit()
// Resize the container to fit the content.
updateHeight(goto)
// @todo This could be used elsewhere.
// @link http://stackoverflow.com/a/22480938
var isScrolledIntoView = function (el) {
var elemTop = el.getBoundingClientRect().top
var isVisible = (elemTop >= $(window).scrollTop()) && (elemTop <= window.innerHeight)
return isVisible
}
// Make sure we are at least at the top of the form.
if (!isScrolledIntoView(multistepForm.parent()[0])) {
$(window).scrollTop(multistepForm.parent().offset().top)
}
// Update the progress bar.
if (settings.progress) {
progress.find('li').removeClass('active')
progress.find('li:lt(' + (index + 1) + ')').addClass('active')
}
// @event multistepPanelLoaded
multistepForm.trigger('multistepPanelLoaded', [goto])
if (typeof onSuccess === 'function') {
onSuccess(index)
}
}
}
/**
* Assign public methods.
*/
global.multistep = {
init: init,
next: next,
prev: prev,
goto: goto,
updateHeight: updateHeight,
disableNavigation: disableNavigation,
enableNavigation: enableNavigation,
disableSubmit: disableSubmit,
enableSubmit: enableSubmit,
getIndex: getIndex
}
})(UCASDesignFramework, jQuery)