The progress bar script that I made for my accessible form example. Works in modern browsers and IE9+. Read the blog post at http://sethlopez.me/article/understanding-accessibility-forms/.
/** | |
* | |
* Check out the example at http://sethlopez.me/article/understanding-accessibility-forms/. | |
* | |
* Copyright (c) 2015 Seth Lopez | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to | |
* deal in the Software without restriction, including without limitation the | |
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
* sell copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in | |
* all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
* IN THE SOFTWARE. | |
* | |
* | |
* Use like this: | |
* | |
* HTML | |
* ---------------------------------------------------------------------------- | |
* <ol class="steps"> | |
* <li class="step">Sign In</li> | |
* <li class="step">Shipping and Payment</li> | |
* <li class="step">Gift Options</li> | |
* <li class="step">Place Order</li> | |
* </ol> | |
* ---------------------------------------------------------------------------- | |
* | |
* | |
* JS | |
* ---------------------------------------------------------------------------- | |
* // the second argument is optional | |
* var progressBar = new StepProgressBar(document.querySelector('ol.steps'), { | |
* stepSelector: 'li.step' | |
* }); | |
* | |
* // then... | |
* progressBar.next(); | |
* // and... | |
* progressBar.previous(); | |
* | |
* // use a 1-based value for goToStep | |
* progressBar.goToStep(1); | |
* ---------------------------------------------------------------------------- | |
*/ | |
// polyfilled for IE9 | |
function addClass(element, className) { | |
if (element.classList) { | |
element.classList.add(className); | |
} else { | |
element.className += ' ' + className; | |
} | |
} | |
// polyfilled for IE9 | |
function removeClass(element, className) { | |
if (element.classList) { | |
element.classList.remove(className); | |
} else { | |
element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); | |
} | |
} | |
/** | |
* Initializes an accessible "step" bar and provides and API to control it. | |
* | |
* @param {HTMLElement} progressBar The progress bar container. | |
* @param {Object} options Custom options for the progress bar. | |
*/ | |
function StepProgressBar(progressBar, options) { | |
var defaults = { | |
attrPrefix: 'data-step-', | |
classPrefix: 'is-', | |
completeState: 'complete', | |
currentState: 'current', | |
incompleteState: 'incomplete', | |
initialStep: 1, | |
stepSelector: 'li', | |
useClasses: false, | |
valueText: 'Step {{current}} of {{max}}: {{content}}' | |
}; | |
// shallow object extend | |
function extend(out) { | |
out = out || {}; | |
for (var i = 1; i < arguments.length; i++) { | |
if (!arguments[i]) | |
continue; | |
for (var key in arguments[i]) { | |
if (arguments[i].hasOwnProperty(key)) | |
out[key] = arguments[i][key]; | |
} | |
} | |
return out; | |
} | |
this.progressBar = progressBar; | |
this.options = extend({}, defaults, options); | |
this.steps = [].slice.call(progressBar.querySelectorAll(this.options.stepSelector)); | |
this.currentStep = this.options.initialStep; | |
this.progressBar.setAttribute('role', 'progressbar'); | |
this.progressBar.setAttribute('aria-valuemin', 1); | |
this.progressBar.setAttribute('aria-valuemax', this.steps.length); | |
this.goToStep(this.currentStep); | |
} | |
/** | |
* Takes the valueText from the progress bar options and formats it with | |
* the current/max steps and a string for aria-valuetext. | |
* | |
* @param {String} content The content to be used for aria-valuetext. | |
* | |
* @returns {String} The formatted string that can be used for | |
* aria-valuetext. | |
*/ | |
StepProgressBar.prototype.formatValueText = function format(content) { | |
var valueText = this.options.valueText; | |
if (!valueText || valueText.indexOf('{{content}}') === -1) { | |
return content; | |
} | |
return valueText | |
.replace(/{{current}}/ig, this.currentStep) | |
.replace(/{{max}}/ig, this.steps.length) | |
.replace(/{{content}}/ig, content); | |
}; | |
/** | |
* Sets the attribute or class state of the given element. | |
* | |
* @param {HTMLElement} element The element to act on. | |
* @param {String} state The state to set. | |
*/ | |
StepProgressBar.prototype.setState = function setState(element, state) { | |
if (this.options.useClasses) { | |
removeClass(element, this.options.classPrefix + this.options.completeState); | |
removeClass(element, this.options.classPrefix + this.options.currentState); | |
removeClass(element, this.options.classPrefix + this.options.incompleteState); | |
addClass(element, this.options.classPrefix + state); | |
} else { | |
element.removeAttribute(this.options.attrPrefix + this.options.completeState); | |
element.removeAttribute(this.options.attrPrefix + this.options.currentState); | |
element.removeAttribute(this.options.attrPrefix + this.options.incompleteState); | |
element.setAttribute(this.options.attrPrefix + state, ''); | |
} | |
return true; | |
}; | |
/** | |
* Moves the progress bar to the given step. | |
* | |
* @param {Number} stepIndex The step to move to. | |
* | |
* @returns {Boolean} Whether or not the action could be | |
* performed. | |
*/ | |
StepProgressBar.prototype.goToStep = function goToStep(stepIndex) { | |
stepIndex = stepIndex - 1; | |
if (stepIndex < 0 || stepIndex > (this.steps.length - 1)) { | |
return false; | |
} | |
for (var i = 0, l = this.steps.length; i < l; i++) { | |
if (i < stepIndex) { | |
this.setState(this.steps[i], this.options.completeState); | |
} else if (i === stepIndex) { | |
this.setState(this.steps[i], this.options.currentState); | |
} else if (i > stepIndex) { | |
this.setState(this.steps[i], this.options.incompleteState); | |
} | |
} | |
this.currentStep = stepIndex + 1; | |
this.progressBar.setAttribute('aria-valuenow', stepIndex + 1); | |
this.progressBar.setAttribute('aria-valueText', this.formatValueText(this.steps[stepIndex].textContent)); | |
return true; | |
}; | |
/** | |
* Moves the progress bar to the next step. | |
* | |
* @returns {Boolean} Whether or not the action could be performed. | |
*/ | |
StepProgressBar.prototype.next = function next() { | |
return this.goToStep(this.currentStep + 1); | |
}; | |
/** | |
* Moves the progress bar to the previous step. | |
* | |
* @returns {Boolean} Whether or not the action could be performed. | |
*/ | |
StepProgressBar.prototype.previous = function previous() { | |
return this.goToStep(this.currentStep - 1); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment