XH_Digital_Management/static/js/pages/minimal-form/stepsForm.js

278 lines
8.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* stepsForm.js v1.0.0
* http://www.codrops.com
*
* Licensed under the MIT license.
* http://www.opensource.org/licenses/mit-license.php
*
* Copyright 2014, Codrops
* http://www.codrops.com
*/
;( function( window ) {
'use strict';
var transEndEventNames = {
'WebkitTransition': 'webkitTransitionEnd',
'MozTransition': 'transitionend',
'OTransition': 'oTransitionEnd',
'msTransition': 'MSTransitionEnd',
'transition': 'transitionend'
},
transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ],
support = { transitions : Modernizr.csstransitions };
function extend( a, b ) {
for( var key in b ) {
if( b.hasOwnProperty( key ) ) {
a[key] = b[key];
}
}
return a;
}
function stepsForm( el, options ) {
this.el = el;
this.options = extend( {}, this.options );
extend( this.options, options );
this._init();
}
// generates a unique id
function randomID() {
var id = Math.random().toString(36).substr(2, 9);
if (document.getElementById(id)) {
return randomID();
}
return id;
}
stepsForm.prototype.options = {
onSubmit : function() { return false; }
};
stepsForm.prototype._init = function() {
// current question
this.current = 0;
// questions
this.questions = [].slice.call( this.el.querySelectorAll( 'ol.questions > li' ) );
// total questions
this.questionsCount = this.questions.length;
// show first question
classie.addClass( this.questions[0], 'current' );
// next question control
this.ctrlNext = this.el.querySelector( 'button.next' );
this.ctrlNext.setAttribute( 'aria-label', 'Next' );
// progress bar
this.progress = this.el.querySelector( 'div.progress' );
// set progressbar attributes
this.progress.setAttribute( 'role', 'progressbar' );
this.progress.setAttribute( 'aria-readonly', 'true' );
this.progress.setAttribute( 'aria-valuemin', '0' );
this.progress.setAttribute( 'aria-valuemax', '100' );
this.progress.setAttribute( 'aria-valuenow', '0' );
// question number status
this.questionStatus = this.el.querySelector( 'span.number' );
// give the questions status an id
this.questionStatus.id = this.questionStatus.id || randomID();
// associate "x / y" with the input via aria-describedby
for (var i = this.questions.length - 1; i >= 0; i--) {
var formElement = this.questions[i].querySelector( 'input, textarea, select' );
formElement.setAttribute( 'aria-describedby', this.questionStatus.id );
};
// current question placeholder
this.currentNum = this.questionStatus.querySelector( 'span.number-current' );
this.currentNum.innerHTML = Number( this.current + 1 );
// total questions placeholder
this.totalQuestionNum = this.questionStatus.querySelector( 'span.number-total' );
this.totalQuestionNum.innerHTML = this.questionsCount;
// error message
this.error = this.el.querySelector( 'span.error-message' );
// checks for HTML5 Form Validation support
// a cleaner solution might be to add form validation to the custom Modernizr script
this.supportsHTML5Forms = typeof document.createElement("input").checkValidity === 'function';
// init events
this._initEvents();
};
stepsForm.prototype._initEvents = function() {
var self = this,
// first input
firstElInput = this.questions[ this.current ].querySelector( 'input, textarea, select' ),
// focus
onFocusStartFn = function() {
firstElInput.removeEventListener( 'focus', onFocusStartFn );
classie.addClass( self.ctrlNext, 'show' );
};
// show the next question control first time the input gets focused
firstElInput.addEventListener( 'focus', onFocusStartFn );
// show next question
this.ctrlNext.addEventListener( 'click', function( ev ) {
ev.preventDefault();
self._nextQuestion();
} );
// pressing enter will jump to next question
this.el.addEventListener( 'keydown', function( ev ) {
var keyCode = ev.keyCode || ev.which;
// enter
if( keyCode === 13 ) {
ev.preventDefault();
self._nextQuestion();
}
} );
};
stepsForm.prototype._nextQuestion = function() {
if( !this._validate() ) {
return false;
}
// checks HTML5 validation
if ( this.supportsHTML5Forms ) {
var input = this.questions[ this.current ].querySelector( 'input, textarea, select' );
// clear any previous error messages
input.setCustomValidity( '' );
// checks input against the validation constraint
if ( !input.checkValidity() ) {
// Optionally, set a custom HTML5 valiation message
// comment or remove this line to use the browser default message
input.setCustomValidity( 'Whoops, that\'s not an email address!' );
// display the HTML5 error message
this._showError( input.validationMessage );
// prevent the question from changing
return false;
}
}
// check if form is filled
if( this.current === this.questionsCount - 1 ) {
this.isFilled = true;
}
// clear any previous error messages
this._clearError();
// current question
var currentQuestion = this.questions[ this.current ];
// increment current question iterator
++this.current;
// update progress bar
this._progress();
if( !this.isFilled ) {
// change the current question number/status
this._updateQuestionNumber();
// add class "show-next" to form element (start animations)
classie.addClass( this.el, 'show-next' );
// remove class "current" from current question and add it to the next one
// current question
var nextQuestion = this.questions[ this.current ];
classie.removeClass( currentQuestion, 'current' );
classie.addClass( nextQuestion, 'current' );
}
// after animation ends, remove class "show-next" from form element and change current question placeholder
var self = this,
onEndTransitionFn = function( ev ) {
if( support.transitions ) {
this.removeEventListener( transEndEventName, onEndTransitionFn );
}
if( self.isFilled ) {
self._submit();
}
else {
classie.removeClass( self.el, 'show-next' );
self.currentNum.innerHTML = self.nextQuestionNum.innerHTML;
self.questionStatus.removeChild( self.nextQuestionNum );
// force the focus on the next input
nextQuestion.querySelector( 'input, textarea, select' ).focus();
}
};
if( support.transitions ) {
this.progress.addEventListener( transEndEventName, onEndTransitionFn );
}
else {
onEndTransitionFn();
}
}
// updates the progress bar by setting its width
stepsForm.prototype._progress = function() {
var currentProgress = this.current * ( 100 / this.questionsCount );
this.progress.style.width = currentProgress + '%';
// update the progressbar's aria-valuenow attribute
this.progress.setAttribute('aria-valuenow', currentProgress);
}
// changes the current question number
stepsForm.prototype._updateQuestionNumber = function() {
// first, create next question number placeholder
this.nextQuestionNum = document.createElement( 'span' );
this.nextQuestionNum.className = 'number-next';
this.nextQuestionNum.innerHTML = Number( this.current + 1 );
// insert it in the DOM
this.questionStatus.appendChild( this.nextQuestionNum );
}
// submits the form
stepsForm.prototype._submit = function() {
this.options.onSubmit( this.el );
}
// TODO (next version..)
// the validation function
stepsForm.prototype._validate = function() {
// current question´s input
var input = this.questions[ this.current ].querySelector( 'input, textarea, select' ).value;
if( input === '' ) {
this._showError( 'EMPTYSTR' );
return false;
}
return true;
}
// TODO (next version..)
stepsForm.prototype._showError = function( err ) {
var message = '';
switch( err ) {
case 'EMPTYSTR' :
message = 'Please fill the field before continuing';
break;
case 'INVALIDEMAIL' :
message = 'Please fill a valid email address';
break;
// ...
default :
message = err;
};
this.error.innerHTML = message;
classie.addClass( this.error, 'show' );
}
// clears/hides the current error message
stepsForm.prototype._clearError = function() {
classie.removeClass( this.error, 'show' );
}
// add to global namespace
window.stepsForm = stepsForm;
})( window );