Skip to content

Instantly share code, notes, and snippets.

@sethlopezme
Last active August 29, 2015 14:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sethlopezme/db6f0dfd67f5c02e729e to your computer and use it in GitHub Desktop.
Save sethlopezme/db6f0dfd67f5c02e729e to your computer and use it in GitHub Desktop.
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