Файл: sngine-v2.8/Script/includes/assets/js/sngine/installer/fullscreenForm.js
Строк: 570
<?php
/**
* fullscreenForm.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 support = { animations : Modernizr.cssanimations },
animEndEventNames = { 'WebkitAnimation' : 'webkitAnimationEnd', 'OAnimation' : 'oAnimationEnd', 'msAnimation' : 'MSAnimationEnd', 'animation' : 'animationend' },
// animation end event name
animEndEventName = animEndEventNames[ Modernizr.prefixed( 'animation' ) ];
/**
* extend obj function
*/
function extend( a, b ) {
for( var key in b ) {
if( b.hasOwnProperty( key ) ) {
a[key] = b[key];
}
}
return a;
}
/**
* createElement function
* creates an element with tag = tag, className = opt.cName, innerHTML = opt.inner and appends it to opt.appendTo
*/
function createElement( tag, opt ) {
var el = document.createElement( tag )
if( opt ) {
if( opt.cName ) {
el.className = opt.cName;
}
if( opt.inner ) {
el.innerHTML = opt.inner;
}
if( opt.appendTo ) {
opt.appendTo.appendChild( el );
}
}
return el;
}
/**
* FForm function
*/
function FForm( el, options ) {
this.el = el;
this.options = extend( {}, this.options );
extend( this.options, options );
this._init();
}
/**
* FForm options
*/
FForm.prototype.options = {
// show progress bar
ctrlProgress : true,
// show navigation dots
ctrlNavDots : true,
// show [current field]/[total fields] status
ctrlNavPosition : true,
// reached the review and submit step
onReview : function() { return false; }
};
/**
* init function
* initialize and cache some vars
*/
FForm.prototype._init = function() {
// the form element
this.formEl = this.el.querySelector( 'form' );
// list of fields
this.fieldsList = this.formEl.querySelector( 'ol.fs-fields' );
// current field position
this.current = 0;
// all fields
this.fields = [].slice.call( this.fieldsList.children );
// total fields
this.fieldsCount = this.fields.length;
// show first field
classie.add( this.fields[ this.current ], 'fs-current' );
// create/add controls
this._addControls();
// create/add messages
this._addErrorMsg();
// init events
this._initEvents();
};
/**
* addControls function
* create and insert the structure for the controls
*/
FForm.prototype._addControls = function() {
// main controls wrapper
this.ctrls = createElement( 'div', { cName : 'fs-controls', appendTo : this.el } );
// continue button (jump to next field)
this.ctrlContinue = createElement( 'button', { cName : 'fs-continue', inner : 'Continue', appendTo : this.ctrls } );
this._showCtrl( this.ctrlContinue );
// navigation dots
if( this.options.ctrlNavDots ) {
this.ctrlNav = createElement( 'nav', { cName : 'fs-nav-dots', appendTo : this.ctrls } );
var dots = '';
for( var i = 0; i < this.fieldsCount; ++i ) {
dots += i === this.current ? '<button class="fs-dot-current"></button>' : '<button disabled></button>';
}
this.ctrlNav.innerHTML = dots;
this._showCtrl( this.ctrlNav );
this.ctrlNavDots = [].slice.call( this.ctrlNav.children );
}
// field number status
if( this.options.ctrlNavPosition ) {
this.ctrlFldStatus = createElement( 'span', { cName : 'fs-numbers', appendTo : this.ctrls } );
// current field placeholder
this.ctrlFldStatusCurr = createElement( 'span', { cName : 'fs-number-current', inner : Number( this.current + 1 ) } );
this.ctrlFldStatus.appendChild( this.ctrlFldStatusCurr );
// total fields placeholder
this.ctrlFldStatusTotal = createElement( 'span', { cName : 'fs-number-total', inner : this.fieldsCount } );
this.ctrlFldStatus.appendChild( this.ctrlFldStatusTotal );
this._showCtrl( this.ctrlFldStatus );
}
// progress bar
if( this.options.ctrlProgress ) {
this.ctrlProgress = createElement( 'div', { cName : 'fs-progress', appendTo : this.ctrls } );
this._showCtrl( this.ctrlProgress );
}
}
/**
* addErrorMsg function
* create and insert the structure for the error message
*/
FForm.prototype._addErrorMsg = function() {
// error message
this.msgError = createElement( 'span', { cName : 'fs-message-error', appendTo : this.el } );
}
/**
* init events
*/
FForm.prototype._initEvents = function() {
var self = this;
// show next field
this.ctrlContinue.addEventListener( 'click', function() {
self._nextField();
} );
// navigation dots
if( this.options.ctrlNavDots ) {
this.ctrlNavDots.forEach( function( dot, pos ) {
dot.addEventListener( 'click', function() {
self._showField( pos );
} );
} );
}
// jump to next field without clicking the continue button (for fields/list items with the attribute "data-input-trigger")
this.fields.forEach( function( fld ) {
if( fld.hasAttribute( 'data-input-trigger' ) ) {
var input = fld.querySelector( 'input[type="radio"]' ) || /*fld.querySelector( '.cs-select' ) ||*/ fld.querySelector( 'select' ); // assuming only radio and select elements (TODO: exclude multiple selects)
if( !input ) return;
switch( input.tagName.toLowerCase() ) {
case 'select' :
input.addEventListener( 'change', function() { self._nextField(); } );
break;
case 'input' :
[].slice.call( fld.querySelectorAll( 'input[type="radio"]' ) ).forEach( function( inp ) {
inp.addEventListener( 'change', function(ev) { self._nextField(); } );
} );
break;
/*
// for our custom select we would do something like:
case 'div' :
[].slice.call( fld.querySelectorAll( 'ul > li' ) ).forEach( function( inp ) {
inp.addEventListener( 'click', function(ev) { self._nextField(); } );
} );
break;
*/
}
}
} );
// keyboard navigation events - jump to next field when pressing enter
document.addEventListener( 'keydown', function( ev ) {
if( !self.isLastStep && ev.target.tagName.toLowerCase() !== 'textarea' ) {
var keyCode = ev.keyCode || ev.which;
if( keyCode === 13 ) {
ev.preventDefault();
self._nextField();
}
}
} );
};
/**
* nextField function
* jumps to the next field
*/
FForm.prototype._nextField = function( backto ) {
if( this.isLastStep || !this._validade() || this.isAnimating ) {
return false;
}
this.isAnimating = true;
// check if on last step
this.isLastStep = this.current === this.fieldsCount - 1 && backto === undefined ? true : false;
// clear any previous error messages
this._clearError();
// current field
var currentFld = this.fields[ this.current ];
// save the navigation direction
this.navdir = backto !== undefined ? backto < this.current ? 'prev' : 'next' : 'next';
// update current field
this.current = backto !== undefined ? backto : this.current + 1;
if( backto === undefined ) {
// update progress bar (unless we navigate backwards)
this._progress();
// save farthest position so far
this.farthest = this.current;
}
// add class "fs-display-next" or "fs-display-prev" to the list of fields
classie.add( this.fieldsList, 'fs-display-' + this.navdir );
// remove class "fs-current" from current field and add it to the next one
// also add class "fs-show" to the next field and the class "fs-hide" to the current one
classie.remove( currentFld, 'fs-current' );
classie.add( currentFld, 'fs-hide' );
if( !this.isLastStep ) {
// update nav
this._updateNav();
// change the current field number/status
this._updateFieldNumber();
var nextField = this.fields[ this.current ];
classie.add( nextField, 'fs-current' );
classie.add( nextField, 'fs-show' );
}
// after animation ends remove added classes from fields
var self = this,
onEndAnimationFn = function( ev ) {
if( support.animations ) {
this.removeEventListener( animEndEventName, onEndAnimationFn );
}
classie.remove( self.fieldsList, 'fs-display-' + self.navdir );
classie.remove( currentFld, 'fs-hide' );
if( self.isLastStep ) {
// show the complete form and hide the controls
self._hideCtrl( self.ctrlNav );
self._hideCtrl( self.ctrlProgress );
self._hideCtrl( self.ctrlContinue );
self._hideCtrl( self.ctrlFldStatus );
// replace class fs-form-full with fs-form-overview
classie.remove( self.formEl, 'fs-form-full' );
classie.add( self.formEl, 'fs-form-overview' );
classie.add( self.formEl, 'fs-show' );
// callback
self.options.onReview();
}
else {
classie.remove( nextField, 'fs-show' );
if( self.options.ctrlNavPosition ) {
self.ctrlFldStatusCurr.innerHTML = self.ctrlFldStatusNew.innerHTML;
self.ctrlFldStatus.removeChild( self.ctrlFldStatusNew );
classie.remove( self.ctrlFldStatus, 'fs-show-' + self.navdir );
}
}
self.isAnimating = false;
};
if( support.animations ) {
if( this.navdir === 'next' ) {
if( this.isLastStep ) {
currentFld.querySelector( '.fs-anim-upper' ).addEventListener( animEndEventName, onEndAnimationFn );
}
else {
nextField.querySelector( '.fs-anim-lower' ).addEventListener( animEndEventName, onEndAnimationFn );
}
}
else {
nextField.querySelector( '.fs-anim-upper' ).addEventListener( animEndEventName, onEndAnimationFn );
}
}
else {
onEndAnimationFn();
}
}
/**
* showField function
* jumps to the field at position pos
*/
FForm.prototype._showField = function( pos ) {
if( pos === this.current || pos < 0 || pos > this.fieldsCount - 1 ) {
return false;
}
this._nextField( pos );
}
/**
* updateFieldNumber function
* changes the current field number
*/
FForm.prototype._updateFieldNumber = function() {
if( this.options.ctrlNavPosition ) {
// first, create next field number placeholder
this.ctrlFldStatusNew = document.createElement( 'span' );
this.ctrlFldStatusNew.className = 'fs-number-new';
this.ctrlFldStatusNew.innerHTML = Number( this.current + 1 );
// insert it in the DOM
this.ctrlFldStatus.appendChild( this.ctrlFldStatusNew );
// add class "fs-show-next" or "fs-show-prev" depending on the navigation direction
var self = this;
setTimeout( function() {
classie.add( self.ctrlFldStatus, self.navdir === 'next' ? 'fs-show-next' : 'fs-show-prev' );
}, 25 );
}
}
/**
* progress function
* updates the progress bar by setting its width
*/
FForm.prototype._progress = function() {
if( this.options.ctrlProgress ) {
this.ctrlProgress.style.width = this.current * ( 100 / this.fieldsCount ) + '%';
}
}
/**
* updateNav function
* updates the navigation dots
*/
FForm.prototype._updateNav = function() {
if( this.options.ctrlNavDots ) {
classie.remove( this.ctrlNav.querySelector( 'button.fs-dot-current' ), 'fs-dot-current' );
classie.add( this.ctrlNavDots[ this.current ], 'fs-dot-current' );
this.ctrlNavDots[ this.current ].disabled = false;
}
}
/**
* showCtrl function
* shows a control
*/
FForm.prototype._showCtrl = function( ctrl ) {
classie.add( ctrl, 'fs-show' );
}
/**
* hideCtrl function
* hides a control
*/
FForm.prototype._hideCtrl = function( ctrl ) {
classie.remove( ctrl, 'fs-show' );
}
// TODO: this is a very basic validation function. Only checks for required fields..
FForm.prototype._validade = function() {
var fld = this.fields[ this.current ],
input = fld.querySelector( 'input[required]' ) || fld.querySelector( 'textarea[required]' ) || fld.querySelector( 'select[required]' ),
error;
if( !input ) return true;
switch( input.tagName.toLowerCase() ) {
case 'input' :
if( input.type === 'radio' || input.type === 'checkbox' ) {
var checked = 0;
[].slice.call( fld.querySelectorAll( 'input[type="' + input.type + '"]' ) ).forEach( function( inp ) {
if( inp.checked ) {
++checked;
}
} );
if( !checked ) {
error = 'NOVAL';
}
}
else if( input.value === '' ) {
error = 'NOVAL';
}
break;
case 'select' :
// assuming here '' or '-1' only
if( input.value === '' || input.value === '-1' ) {
error = 'NOVAL';
}
break;
case 'textarea' :
if( input.value === '' ) {
error = 'NOVAL';
}
break;
}
if( error != undefined ) {
this._showError( error );
return false;
}
return true;
}
// TODO
FForm.prototype._showError = function( err ) {
var message = '';
switch( err ) {
case 'NOVAL' :
message = 'Please fill the field before continuing';
break;
case 'INVALIDEMAIL' :
message = 'Please fill a valid email address';
break;
// ...
};
this.msgError.innerHTML = message;
this._showCtrl( this.msgError );
}
// clears/hides the current error message
FForm.prototype._clearError = function() {
this._hideCtrl( this.msgError );
}
// add to global namespace
window.FForm = FForm;
})( window );
?>