/*! * classie - class helper functions * from bonzo https://github.com/ded/bonzo * * classie.has( elem, 'my-class' ) -> true/false * classie.add( elem, 'my-new-class' ) * classie.remove( elem, 'my-unwanted-class' ) * classie.toggle( elem, 'my-class' ) */ /*jshint browser: true, strict: true, undef: true */ /*global define: false */ ( function( window ) { 'use strict'; // class helper functions from bonzo https://github.com/ded/bonzo function classReg( className ) { return new RegExp("(^|\\s+)" + className + "(\\s+|$)"); } // classList support for class management // altho to be fair, the api sucks because it won't accept multiple classes at once var hasClass, addClass, removeClass; if ( 'classList' in document.documentElement ) { hasClass = function( elem, c ) { return elem.classList.contains( c ); }; addClass = function( elem, c ) { elem.classList.add( c ); }; removeClass = function( elem, c ) { elem.classList.remove( c ); }; } else { hasClass = function( elem, c ) { return classReg( c ).test( elem.className ); }; addClass = function( elem, c ) { if ( !hasClass( elem, c ) ) { elem.className = elem.className + ' ' + c; } }; removeClass = function( elem, c ) { elem.className = elem.className.replace( classReg( c ), ' ' ); }; } function toggleClass( elem, c ) { var fn = hasClass( elem, c ) ? removeClass : addClass; fn( elem, c ); } var classie = { // full names hasClass: hasClass, addClass: addClass, removeClass: removeClass, toggleClass: toggleClass, // short names has: hasClass, add: addClass, remove: removeClass, toggle: toggleClass }; // transport if ( typeof define === 'function' && define.amd ) { // AMD define( classie ); } else { // browser global window.classie = classie; } })( window ); /** * 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(); } 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' ); // progress bar this.progress = this.el.querySelector( 'div.progress' ); // question number status this.questionStatus = this.el.querySelector( 'span.number' ); // 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' ); // init events this._initEvents(); }; stepsForm.prototype._initEvents = function() { var self = this, // first input firstElInput = this.questions[ this.current ].querySelector( 'input' ), // 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 document.addEventListener( 'keydown', function( ev ) { var keyCode = ev.keyCode || ev.which; // enter if( keyCode === 13 ) { ev.preventDefault(); self._nextQuestion(); } } ); // disable tab this.el.addEventListener( 'keydown', function( ev ) { var keyCode = ev.keyCode || ev.which; // tab if( keyCode === 9 ) { ev.preventDefault(); } } ); }; stepsForm.prototype._nextQuestion = function() { if( !this._validade() ) { 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' ).focus(); } }; if( support.transitions ) { this.progress.addEventListener( transEndEventName, onEndTransitionFn ); } else { onEndTransitionFn(); } } // updates the progress bar by setting its width stepsForm.prototype._progress = function() { this.progress.style.width = this.current * ( 100 / this.questionsCount ) + '%'; } // 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._validade = function() { // current questionВґs input var input = this.questions[ this.current ].querySelector( 'input' ).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; // ... }; 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 ); var theForm = document.getElementById( 'theForm' ); new stepsForm( theForm, { onSubmit : function( form ) { // hide form classie.addClass( theForm.querySelector( '.simform-inner' ), 'hide' ); /* form.submit() or AJAX request (maybe show loading indicator while we don't have an answer..) */ // let's just simulate something... var messageEl = theForm.querySelector( '.final-message' ); messageEl.innerHTML = 'Thank you! We\'ll be in touch.'; classie.addClass( messageEl, 'show' ); } } );