Mini Shell
;(function ( $ ) {
"use strict";
/* Defaul options */
var defaults = {
rules: {},
messages: {},
error: {
scroll: true,
highlight: true,
inBlock: false,
inTooltip: false,
underField: true,
inTooltipClass: "j-tooltip-right-top",
},
success: {
highlight: true,
},
validationEvent: {
onsubmit: true,
onchange: true,
onkeyup: true,
},
formType: {
modal: false,
multistep: false,
},
formRedirect: {
redirect: false,
address: "http://codecanyon.net/user/lazycode"
},
formHide: {
hide: false,
closeBtn: false
},
formTotalData: false,
timeoutSuccessMsg: 5000,
repeatSubmission: true,
submit: true,
debug: false,
debugArr: [],
afterInitHandler:function() { return true; },
beforeSubmitHandler:function() { return true; },
afterSubmitHandler:function() { return true; },
dict: {
debugError: "Oops! Debug errors occurred. Enable debug mode for JavaScript code",
rulesError: "User settings: You have to specify validation rules for a form",
messagesError: "User settings: You have to specify validation messages for a form",
modalOpenError: "Could not find a link/button with class='j-modal-open' to open modal form",
responseBlockError: "Could not find a block with class='j-response' for server response",
submitBtnError: "Could not find a button[type='submit'] for form submission",
totalDataBlockError: "Could not find a block with class='j-total-data' for total form data",
multistepOneStepError: "Multistep form has only one step. At least two steps are required!",
rulesUnknownRuleError: "User settings -> 'rules' array: Unknown validation rule: ",
rulesNotAllowedValueError: "User settings -> 'rules' array -> Not allowed value: ",
rulesMessagesMismatchError: "User settings: field names in 'rules' and 'messages' are mismatched",
formDataError: "FormData is not supported in this browser. Please update it to the latest version or use another browser",
disabledBtn: "Thanks!",
multistepNextBtn: "Next",
multistepPrevBtn: "Back",
errorBlockText: "Oops! The following errors occurred:",
successBlockText: "Thank you! We got your email successfully",
},
mimeTypes: {
"jpeg": "image/jpeg",
"tiff": "image/tiff",
"jpg": "image/jpg",
"png": "image/png",
"gif": "image/gif",
"ico": "image/vnd.microsoft.icon",
"doc": "application/msword",
"xls": "application/vnd.ms-excel",
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"txt": "text/plain",
"csv": "text/csv",
"zip": "application/zip",
"gzip": "application/gzip",
"rar": "application/x-rar-compressed",
"odf": ["application/vnd.oasis.opendocument.text",
"application/vnd.oasis.opendocument.spreadsheet",
"application/vnd.oasis.opendocument.presentation",
"application/vnd.oasis.opendocument.graphics"],
"pdf": "application/pdf",
"powerpoint": ["application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.presentationml.presentation"],
"mpeg": ["audio/mpeg","video/mpeg"],
"mp4": ["audio/mp4","video/mp4"],
"ogg": ["audio/ogg","video/ogg","application/ogg"],
},
};
/* File validation */
function _fileCheck( fileObj, rules, messages, mimeTypes ) {
var type, len, i,
file = fileObj[0].files[0],
required = rules.required,
validate = rules.validate,
extension = rules.extension.toLowerCase(),
size = rules.size * 1024 * 1024,
allowedTypes = [],
extensionArr = extension.split("|");
// Add required valid types
for ( type in mimeTypes ) {
if ( extensionArr.indexOf(type) === -1 ) {
continue;
}
if ( $.isArray( mimeTypes[type] ) ) {
len = mimeTypes[type].length;
for ( i=0; i<len; i++ ) {
allowedTypes.push( mimeTypes[type][i] );
}
continue;
}
allowedTypes.push( mimeTypes[type] );
}
// Validation
if ( validate || required ) {
// If file is required
if ( required ) {
// If file is empty
if ( !file ) {
return messages.required;
}
}
// If file is not required
// Validate file only if it exists
if ( file ) {
// Check allowed file types
if ( allowedTypes.indexOf(file.type) === -1 ) {
return messages.size_extension;
}
// Check allowed file size
if ( file.size > size) {
return messages.size_extension;
}
return false;
}
}
}
/* Email validation */
function _emailCheck( value ) {
var re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i;
return !re.test( value );
}
/* Url validation */
function _urlCheck( value ){
var re = /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i;
return !re.test( value );
}
/* EqualTo validation */
function _equalToCheck( value, targetValue ) {
return ( value.trim() !== targetValue.trim() ) ? true : false;
}
/* Min value validation */
function _minValueCheck( value, minVal ) {
var val = value.trim();
if ( _numberCheck( val ) ) {
return true;
}
return ( val < minVal ) ? true : false;
}
/* Max value validation */
function _maxValueCheck( value, maxVal ) {
var val = value.trim();
if ( _numberCheck( val ) ) {
return true;
}
return ( val > maxVal ) ? true : false;
}
/* Range value validation */
function _rangeValueCheck( value, rangeVal ) {
var val = value.trim();
if ( _numberCheck( val ) ) {
return true;
}
return ( val < rangeVal[0] || val > rangeVal[1] ) ? true : false;
}
/* Min length validation */
function _minLengthCheck( value, minLen ) {
return ( value.trim().length < minLen ) ? true : false;
}
/* Max length validation */
function _maxLengthCheck( value, maxLen ) {
return ( value.trim().length > maxLen ) ? true : false;
}
/* Range length validation */
function _rangeLengthCheck( value, rangeLen ) {
var val = value.trim().length;
return ( val < rangeLen[0] || val > rangeLen[1] ) ? true : false;
}
/* Integer validation */
function _integerCheck( value ) {
var re = /^-?\d+$/;
return !re.test( value );
}
/* Number validation */
function _numberCheck( value ) {
var re = /^-?\d+(?:\.\d+)?$/;
return !re.test( value );
}
/* Validate rules object */
function _rulesCheck( obj ) {
var self = this,
config = self.config,
message = self.config.dict.rulesNotAllowedValueError;
$.each(obj, function( name, value ) {
$.each(value, function( rule, val ) {
switch( rule ) {
case "required":
case "email":
case "url":
case "integer":
case "number":
case "validate":
if ( $.type( val ) !== "boolean" ) {
config.debugArr.push( _errorMsg( message, rule, val ) );
}
break;
case "minlength":
case "maxlength":
if ( _integerCheck( val ) || val < 0 ) {
config.debugArr.push( _errorMsg( message, rule, val ) );
}
break;
case "minvalue":
case "maxvalue":
if ( _numberCheck( val ) ) {
config.debugArr.push( _errorMsg( message, rule, val ) );
}
break;
case "rangelength":
if ( !$.isArray( val ) ||
val.length !== 2 ||
_integerCheck( val[0] ) ||
_integerCheck( val[1] ) ||
val[0] > val[1]
) {
config.debugArr.push( _errorMsg( message, rule, val ) );
}
break;
case "rangevalue":
if ( !$.isArray( val ) ||
val.length !== 2 ||
_numberCheck( val[0] ) ||
_numberCheck( val[1] ) ||
val[0] > val[1]
) {
config.debugArr.push( _errorMsg( message, rule, val ) );
}
break;
case "size":
if ( _numberCheck( val ) || val <= 0 ) {
config.debugArr.push( _errorMsg( message, rule, val ) );
}
break;
case "extension":
case "equalTo":
if ( $.type( val ) !== "string" ) {
config.debugArr.push( _errorMsg( message, rule, val ) );
}
break;
case "requiredFromGroup":
if ( !$.isArray( val ) ||
val.length !== 2 ||
_integerCheck( val[0] ) ||
$.type( val[1] ) !== "string"
) {
config.debugArr.push( _errorMsg( message, rule, val ) );
}
break;
default:
config.debugArr.push( config.dict.rulesUnknownRuleError + rule );
}
});
});
}
/* Validate messages object */
function _messagesCheck( obj ) {
var rKeys = Object.keys( obj.rules ).sort(),
mKeys = Object.keys( obj.messages ).sort();
if ( JSON.stringify( rKeys ) !== JSON.stringify( mKeys ) ) {
this.config.debugArr.push( this.config.dict.rulesMessagesMismatchError );
}
}
/* Config check error message */
function _errorMsg( message, rule, val ) {
return message + rule + ": " + val +" ( " + $.type(val) + " )";
}
/* Get field value */
function _getValue( obj ) {
var res;
obj.each( function() {
if ( $( this ).is( ":checkbox" ) || $( this ).is( ":radio" ) ) {
if ( $( this ).is( ":checked" ) ) {
if ( res !== undefined ) {
res += ", "+$( this ).val();
} else {
res = $( this ).val();
}
}
} else {
res = $( this ).val();
}
});
if ( $.isArray( res ) ) {
res = res.join(", ");
}
return ( res === undefined || res === null ) ? "" : res;
}
/* Check if field is hidden or has "display:none;" */
function _hiddenCheck( field ) {
if ( $( field ).css("display") === "none" ||
$( field ).css("visibility") === "hidden" ||
$( field ).is(":disabled") ){
return true;
}
return false;
}
/* Constructor */
function JustFormsPro( obj, options ) {
this.config = $.extend( true, {}, defaults, options );
this.form = obj[0];
this.$form = obj;
this.$formObj = {};
this.$formObjActiveStep = {};
this.errorExists = false;
this.errorMessages = {};
// Form elements processing
this.$formResponseBlock = this.$form.find( ".j-response" );
this.$submitBtn = this.$form.find( "button[type='submit']" );
// Form plugin init
this.init();
}
JustFormsPro.prototype = {
/* Init */
init: function() {
var self = this,
$form = self.$form,
config = self.config,
$formObj = self.$formObj,
str, $el;
// FormData support
if ( typeof window.FormData === "undefined" ) {
config.debugArr.push( config.dict.formDataError );
}
// User input validaion
if ( $.isEmptyObject( config.rules ) ) {
config.debugArr.push( config.dict.rulesError );
} else {
_rulesCheck.call( self, config.rules );
}
if ( $.isEmptyObject( config.messages ) ) {
config.debugArr.push( config.dict.messagesError );
} else {
_messagesCheck.call( self, config );
}
// Response block availability
if ( !self.$formResponseBlock.length ){
config.debugArr.push( config.dict.responseBlockError );
}
// Submit button availability
if ( !self.$submitBtn.length ){
config.debugArr.push( config.dict.submitBtnError );
}
// Hide form
if ( config.formHide.hide ) {
self.initHideForm();
}
// Modal form
if ( config.formType.modal ) {
self.initModalForm();
}
// Multistep form
if ( config.formType.multistep ) {
self.initMultistepForm();
}
// Init total form data
if ( config.formTotalData ) {
self.initTotalData();
}
// Debug result
if ( config.debugArr.length ) {
if ( !config.debug ){
alert( config.dict.debugError );
$form.on( "submit", false );
return false;
}
str = config.debugArr.join( '\n -------------- \n' );
console.warn( str );
alert( str );
config.debugArr = [];
$form.on( "submit", false );
return false;
}
// Object with html entities
$.each( config.rules, function( name, rules ) {
$el = $form.find( "[name='"+name+"']" );
if ( $el.length ) {
$formObj[name] = $el;
}
});
// Events
if ( config.validationEvent.onchange || config.validationEvent.onkeyup ) {
self.initEvent();
}
// Processing total form data
if ( config.formTotalData ) {
self.processingTotalData();
}
// Bind submit event
$form.on( "submit", self.submitForm.bind(self) );
},
/* Init events */
initEvent: function() {
var self = this,
config = self.config,
$formObj = self.$formObj,
eventsArr = [];
// Create events
if ( config.validationEvent.onchange ) {
eventsArr.push("change");
}
if ( config.validationEvent.onkeyup ) {
eventsArr.push("keyup");
}
// Add events
eventsArr.forEach( function(event) {
self.$form.on( event, ":input", function() {
self.clearField( this );
self.validateField( this );
});
$.each( config.rules, function( name, rules ) {
$.each( rules, function( rule, value ){
if ( rule === "equalTo" ) {
$( value ).on( event, function(){
self.clearField( $formObj[name][0] );
self.validateField( $formObj[name][0] );
});
}
});
});
});
},
/* Submit a form */
submitForm: function(e) {
var self = this,
config = self.config,
$prevBtn = self.$prevBtn,
$submitBtn = self.$submitBtn;
if (e) {
e.preventDefault();
e.stopPropagation();
}
// After init custom handler
if ( !config.afterInitHandler() ) {
return false;
}
// Form validation
if ( config.validationEvent.onsubmit ) {
// Buttons disabled
$submitBtn.toggleClass( "j-processing", true ).attr( "disabled", true );
if ( config.formType.multistep ) {
$prevBtn.attr( "disabled", true );
}
// Clear a form
self.clearForm();
// Validate form
self.validateForm();
}
// If errors exist
if ( self.errorExists ) {
// Scroll to first element with error
if ( config.error.scroll ) {
self.scrollToError();
}
// Show errors within an error block
if ( config.error.inBlock ) {
self.createErrorBlock( self.errorMessages );
}
// Buttons enabled
$submitBtn.toggleClass( "j-processing", false ).removeAttr( "disabled" );
if ( config.formType.multistep ) {
$prevBtn.removeAttr( "disabled" );
}
return false;
}
// Before submit custom handler
if ( !config.submit || !config.beforeSubmitHandler() ) {
// Buttons enabled
$submitBtn.toggleClass( "j-processing", false ).removeAttr( "disabled" );
if ( self.config.formType.multistep ) {
$prevBtn.removeAttr( "disabled" );
}
return false;
}
// Send form data
self.getServerResponse();
},
/* AJAX request */
sendFormDataAsync: function() {
var self = this,
form = self.form,
formData;
// Form data
formData = new FormData( form );
return $.ajax({
url: form.action,
type: form.method,
contentType: false,
processData: false,
data: formData
});
},
/* Send form data */
getServerResponse: function() {
var self = this,
config = self.config,
$prevBtn = self.$prevBtn,
$submitBtn = self.$submitBtn,
$formResponseBlock = self.$formResponseBlock,
result;
self.sendFormDataAsync().then(function( data ) {
// Server error processing
if ( config.debug ) {
console.warn( data );
}
result = $.parseJSON( data );
// Error message from server
if ( result.error ) {
// Add class "j-error-message"
$formResponseBlock.toggleClass( "j-error-message j-unit", true );
$formResponseBlock.html( result.error );
// Buttons enabled
$submitBtn.removeClass( "j-processing" ).removeAttr( "disabled" );
if ( config.formType.multistep ) {
$prevBtn.removeAttr( "disabled" );
}
}
// Success message from server
if ( result.success ) {
// Custom function applied after submission
if ( !config.afterSubmitHandler() ) {
// Buttons enabled
$submitBtn.removeClass( "j-processing" ).removeAttr( "disabled" );
if ( config.formType.multistep ) {
$prevBtn.removeAttr( "disabled" );
}
return false;
}
// Hide form after successful submitting
if ( config.formHide.hide ) {
self.processingHideForm();
return false;
}
// Redirect form after success submitting
if ( config.formRedirect.redirect ) {
self.redirectForm();
return false;
}
// Clear a form
// Show success message
self.resetForm();
self.createSuccessBlock( result.success );
}
});
},
/* Validate field */
validateField: function( field ) {
var self = this,
config = self.config,
$formObj = self.$formObj,
fieldName = $( field ).attr( "name" ),
fieldValue;
// If a form does not have a field
if ( $formObj[fieldName] === undefined || $formObj[fieldName] === null ) {
return false;
}
fieldValue = _getValue( $formObj[fieldName] );
self.validation( config.rules[fieldName], field, fieldValue, fieldName);
// Show errors within an error block
if ( config.error.inBlock ) {
self.createErrorBlock();
}
},
/* Validate all form fields */
validateForm: function() {
var self = this,
config = self.config,
$formObj, fieldValue;
// If form is mulultistep
// Get fields from "active" step only
if ( config.formType.multistep ) {
self.$formObjActiveStep = {};
self.getActiveStepField();
$formObj = self.$formObjActiveStep;
} else {
$formObj = self.$formObj;
}
$.each( config.rules, function( name, rules ) {
// If a form does not have a field
if ( $formObj[name] === undefined || $formObj[name] === null ) {
return;
}
// Skip the validation if field is hidden
if ( _hiddenCheck( $formObj[name] ) ){
return;
}
// Get field value
fieldValue = _getValue( $formObj[name] );
// Fields processing
self.validation( rules, $formObj[name], fieldValue, name );
});
},
/* Validation functions */
validation: function( rules, field, fieldValue, fieldName ) {
var self = this,
config = self.config,
count = 0,
groupObj = {},
res, groupClass, id;
// Files processing
if ( $( field ).attr( "type" ) && $( field ).attr( "type" ) === "file" ) {
if ( config.rules[fieldName].required === true ||
config.rules[fieldName].validate === true ) {
res = _fileCheck( $( field ), config.rules[fieldName],
config.messages[fieldName], config.mimeTypes );
if ( res ) {
self.setErrorState( $( field ), res );
return false;
}
self.setSuccessState( field );
}
}
// Fields processing
$.each( rules, function( rule, ruleValue ){
switch( rule ) {
case "required":
if ( ruleValue === true ) {
if ( _minLengthCheck( fieldValue, 1 ) ) {
self.setErrorState( field, config.messages[fieldName][rule] );
return false;
}
self.setSuccessState( field );
}
break;
case "email":
if ( ruleValue === true ) {
if ( _emailCheck( fieldValue ) ) {
self.setErrorState( field, config.messages[fieldName][rule] );
return false;
}
self.setSuccessState( field );
}
break;
case "url":
if ( ruleValue === true ) {
if ( _urlCheck( fieldValue ) ) {
self.setErrorState( field, config.messages[fieldName][rule] );
return false;
}
self.setSuccessState( field );
}
break;
case "integer":
if ( ruleValue === true ) {
if ( _integerCheck( fieldValue ) ) {
self.setErrorState( field, config.messages[fieldName][rule] );
return false;
}
self.setSuccessState( field );
}
break;
case "number":
if ( ruleValue === true ) {
if ( _numberCheck( fieldValue ) ) {
self.setErrorState( field, config.messages[fieldName][rule] );
return false;
}
self.setSuccessState( field );
}
break;
case "minlength":
if ( _minLengthCheck( fieldValue, config.rules[fieldName][rule] ) ) {
self.setErrorState( field, config.messages[fieldName][rule] );
return false;
}
self.setSuccessState( field );
break;
case "maxlength":
if ( _maxLengthCheck( fieldValue, config.rules[fieldName][rule] ) ) {
self.setErrorState( field, config.messages[fieldName][rule] );
return false;
}
self.setSuccessState( field );
break;
case "rangelength":
if ( _rangeLengthCheck( fieldValue, config.rules[fieldName][rule] ) ) {
self.setErrorState( field, config.messages[fieldName][rule] );
return false;
}
self.setSuccessState( field );
break;
case "minvalue":
if ( _minValueCheck( fieldValue, config.rules[fieldName][rule] ) ) {
self.setErrorState( field, config.messages[fieldName][rule] );
return false;
}
self.setSuccessState( field );
break;
case "maxvalue":
if ( _maxValueCheck( fieldValue, config.rules[fieldName][rule] ) ) {
self.setErrorState( field, config.messages[fieldName][rule] );
return false;
}
self.setSuccessState( field );
break;
case "rangevalue":
if ( _rangeValueCheck( fieldValue, config.rules[fieldName][rule] ) ) {
self.setErrorState( field, config.messages[fieldName][rule] );
return false;
}
self.setSuccessState( field );
break;
case "equalTo":
// Get id
id = ruleValue.slice(1);
// Get element's value with id
$.each( self.$formObj, function(){
if ( this.attr( "id" ) === id ) {
res = this.val();
}
});
if ( _equalToCheck( fieldValue, res ) ) {
self.setErrorState( field, config.messages[fieldName][rule] );
return false;
}
self.setSuccessState( field );
break;
case "requiredFromGroup":
// Get class name
groupClass = ruleValue[1].slice(1);
// Get elements with class
$.each( self.$formObj, function( fieldName, fieldObj ){
if ( fieldObj.hasClass( groupClass ) ) {
groupObj[fieldName] = fieldObj;
self.clearField( fieldObj );
}
});
// Validation
$.each( groupObj, function(){
if ( !_minLengthCheck( _getValue( this ), 1 ) ) {
count++;
}
});
// Add error state
if ( count < ruleValue[0] ) {
$.each( groupObj, function(){
self.setErrorState( this, config.messages[fieldName][rule] );
});
return false;
}
// Add success state
$.each( groupObj, function(){
self.setSuccessState( this );
});
break;
}
});
},
/* Set error conditions for form fields */
setErrorState: function( field, msg ) {
var self = this,
config = self.config,
$input = $( field ).closest( ".j-input" ),
elem;
// Error exists
self.errorExists = true;
/* Remove class "j-success-view" */
$input.toggleClass( "j-success-view", false );
// Add class "j-error-view"
if ( config.error.highlight ) {
$input.addClass( "j-error-view" );
}
// Show errors in tooltips
if ( config.error.inTooltip ) {
elem = $( "<span />", {
class: "j-tooltip j-error-view " + config.error.inTooltipClass,
text: msg
});
$input.append( elem );
}
// Show errors under appropriate fields
if ( config.error.underField ) {
elem = $( "<span />", {
class: "j-error-view",
text: msg
});
$input.append( elem );
}
// Add error message into an array
// For showing into error block
if ( config.error.inBlock ) {
var fieldName = $( field ).attr( "name" );
self.errorMessages[fieldName] = msg;
}
},
/* Set success conditions for form fields */
setSuccessState: function( field ) {
var self = this,
$input = $( field ).closest( ".j-input" );
// Add class "j-success-view"
if ( self.config.success.highlight ) {
$input.toggleClass( "j-success-view", true );
}
},
/* Init total form data */
initTotalData: function() {
var self = this;
self.$totalDataBlock = self.$form.find( ".j-total-data" );
if ( !self.$totalDataBlock.length ){
self.config.debugArr.push( self.config.dict.totalDataBlockError );
}
},
/* Total form data processing */
processingTotalData: function() {
var self = this,
$formObj = self.$formObj,
$totalDataBlock = self.$totalDataBlock,
$totalDataList, $totalDataValue, $fieldName, $elem;
// List of fields to insert data in
$totalDataList = $totalDataBlock.find("span[data-field]");
$.each( $totalDataList, function(){
// Get form field with the name
$fieldName = $( this ).attr("data-field");
$elem = self.$form.find( "[name='"+$fieldName+"']" );
if ( $elem.length ) {
if ( $formObj[$fieldName] === undefined || $formObj[$fieldName] === null ){
return;
}
// Add event listener
$elem.on("change", (function(obj, name){
return function(){
$totalDataValue = _getValue( obj[name] );
$totalDataBlock.find( "span[data-field='"+name+"']" ).html( $totalDataValue );
};
})($formObj, $fieldName));
}
});
},
/* Init multistep form */
initMultistepForm: function() {
var self = this,
$form = self.$form;
self.$nextBtn = $form.find( ".j-multi-next-btn" );
self.$prevBtn = $form.find( ".j-multi-prev-btn" );
self.$stepTitleList = $form.find( ".j-step-title" );
self.$contentBlock = $form.children( ".j-content" );
self.$footerBlock = $form.children( ".j-footer" );
self.$stepList = self.$contentBlock.children( "fieldset" );
self.createMultistepForm();
self.processingMultistepForm();
},
/* Make a layout for multistep form */
createMultistepForm: function() {
var self = this,
$form = self.$form,
$stepTitleBlock, $elem, $elems, $elemsLen, $tempStep, $tempStepList, i;
// Add class "j-multistep"
$form.toggleClass( "j-multistep", true );
// If a form doesn't have "next" button
if ( !self.$nextBtn.length ) {
$elem = $("<button />", {
type: "button",
class: "j-primary-btn j-multi-next-btn",
text: self.config.dict.multistepNextBtn
});
if ( self.$footerBlock.length ) {
self.$footerBlock.append( $elem );
} else {
self.$contentBlock.append( $elem );
}
self.$nextBtn = self.$form.find( ".j-multi-next-btn" );
}
// If a form doesn't have "previous" button
if ( !self.$prevBtn.length ) {
$elem = $("<button />", {
type: "button",
class: "j-secondary-btn j-multi-prev-btn",
text: self.config.dict.multistepPrevBtn
});
if ( self.$footerBlock.length ) {
self.$footerBlock.append( $elem );
} else {
self.$contentBlock.append( $elem );
}
self.$prevBtn = self.$form.find( ".j-multi-prev-btn" );
}
// If a form doesn't have fieldsets
if ( !self.$stepList.length ) {
// Find form fields
$elems = self.$contentBlock.children( ".j-unit, .j-row" ).filter(":not(.j-step-titles)");
$stepTitleBlock = self.$contentBlock.children( ".j-step-titles" );
$elemsLen = $elems.length;
// Cerate steps for every set of fields
for ( i=0; i<$elemsLen; i+=3 ) {
// Add fields to a step
$tempStep = $( "<fieldset />" ).append( $elems.slice( i, i+3 ) );
// Add step to a form
$tempStepList = self.$contentBlock.children( "fieldset" );
if ( !$tempStepList.length ){
if ( !$stepTitleBlock.length ){
self.$contentBlock.prepend( $tempStep );
continue;
}
$tempStep.insertAfter( $stepTitleBlock );
continue;
}
$tempStep.insertAfter( $tempStepList.last() );
}
self.$stepList = self.$contentBlock.children( "fieldset" );
}
},
/* Multistep form processing */
processingMultistepForm: function() {
var self = this,
config = self.config,
$stepList = self.$stepList,
$nextBtn = self.$nextBtn,
$prevBtn = self.$prevBtn,
$submitBtn = self.$submitBtn,
$stepTitleList = self.$stepTitleList,
len = self.$stepList.length;
// If multistep form has one step only
if ( len === 1 ){
config.debugArr.push( config.dict.multistepOneStepError );
$stepList.eq( 0 ).addClass( "j-active-fieldset" );
return false;
}
// Processing of the first fieldset
$stepList.eq( 0 ).addClass( "j-active-fieldset" );
$submitBtn.addClass( "j-hiddenBtn" );
$prevBtn.addClass( "j-hiddenBtn" );
// Set active step title
if ( $stepTitleList.length ){
$stepTitleList.eq(0).addClass( "j-active-step" );
}
// Click on the "next" button
$nextBtn.on( "click", function( e ){
if ( e ) {
e.preventDefault();
e.stopPropagation();
}
// Clear a form
self.clearForm();
// Form validation
self.validateForm();
// If errors don"t exist
if ( !self.errorExists ) {
// Switch the "active" class to the next fieldset
$stepList.filter( ".j-active-fieldset" ).removeClass( "j-active-fieldset" ).
next( "fieldset" ).addClass( "j-active-fieldset" );
// Switch the "active" class to the next step
if ( $stepTitleList.length ) {
$stepTitleList.filter( ".j-active-step" ).removeClass( "j-active-step" ).
addClass( "j-passed-step" ).next( ".j-step-title" ).addClass( "j-active-step" );
}
// Display "prev" button
$prevBtn.removeClass( "j-hiddenBtn" );
// If active fieldset is a last
// processing the buttons
if ( $stepList.eq( len-1 ).hasClass( "j-active-fieldset" ) ) {
$submitBtn.removeClass( "j-hiddenBtn" );
$nextBtn.addClass( "j-hiddenBtn" );
}
// If current fieldset has validation errors
} else {
// Scroll to first element with error
if ( config.error.scroll ) {
self.scrollToError();
}
// Show errors within an error block
if ( config.error.inBlock ) {
self.createErrorBlock();
}
return false;
}
});
// Click on the "prev" button
$prevBtn.on( "click", function( e ){
if ( e ) {
e.preventDefault();
e.stopPropagation();
}
// Clear a form
self.clearForm();
// Switch the "active" class to the previous fieldset
$stepList.filter( ".j-active-fieldset" ).removeClass( "j-active-fieldset" ).
prev( "fieldset" ).addClass( "j-active-fieldset" );
// Switch the "active" class to the next step
if ( $stepTitleList.length ) {
$stepTitleList.filter( ".j-active-step" ).removeClass( "j-active-step" ).
prev( ".j-step-title" ).removeClass( "j-passed-step" ).addClass( "j-active-step" );
}
// If active fieldset is a first
// processing the buttons
if ( $stepList.eq(0).hasClass( "j-active-fieldset" ) ) {
$prevBtn.addClass( "j-hiddenBtn" );
}
// If active fieldset is a penultimate
// processing the buttons
if ( $stepList.eq( len-2 ).hasClass( "j-active-fieldset" ) ) {
$submitBtn.addClass( "j-hiddenBtn" );
$nextBtn.removeClass( "j-hiddenBtn" );
}
// Get fields from active step
self.getActiveStepField();
});
},
/* Get fields from active step only */
getActiveStepField: function() {
var self = this,
config = self.config,
$form = self.$form,
$el;
if ( config.formType.multistep ) {
$.each( config.rules, function( name, rules ) {
$el = $form.find( "fieldset.j-active-fieldset [name='"+name+"']" );
if ( $el.length ) {
self.$formObjActiveStep[name] = $el;
}
});
}
},
/* Init modal form */
initModalForm: function() {
var self = this,
$form = self.$form;
self.$modalWrap = $form.closest( ".j-modal-form" );
self.$modalClose = $form.find( ".j-modal-close" );
self.$modalOpen = $( document ).find( ".j-modal-open" );
// If modal links dont exist
if ( !self.$modalOpen.length ) {
self.config.debugArr.push( self.config.dict.modalOpenError );
return false;
}
self.createModalForm();
self.processingModalForm();
},
/* Make a layout for modal form */
createModalForm: function() {
var self = this,
$form = self.$form,
$elem, $formWrap;
// If a form doesn't have modal wrapper
if ( !self.$modalWrap.length ) {
$formWrap = $form.closest( ".j-wrapper" );
// Modal wrapper
$elem = $( "<div />", {
class: "j-modal-form",
id: "j-modalwrap-" + $form.attr( "id" )
});
// Wrap a form with modal wrapper
$formWrap.wrap( $elem );
self.$modalWrap = $form.closest( ".j-modal-form" );
}
// If a form doesn't have modal close button
if ( !self.$modalClose.length ) {
// Close button
$elem = $( "<label />", {
class: "j-modal-close"
}).append( "<i></i>" );
// Add close modal button to form
$form.append( $elem );
self.$modalClose = $form.find( ".j-modal-close" );
}
},
/* Modal form processing */
processingModalForm: function() {
var self = this;
// Modal links processing
self.$modalOpen.each( function(){
$( this ).on( "click", function( e ){
if ( e ) {
e.preventDefault();
e.stopPropagation();
}
// Show modal form for approriate modal link
if ( $( this ).data( "modal-wrap" ) === self.$modalWrap.attr( "id" ) ) {
self.$modalWrap.addClass( "j-modal-visible" );
$( "body" ).addClass( "j-modal-scroll" );
}
});
});
// Close button processing
self.$modalClose.on( "click", function(){
self.$modalWrap.removeClass( "j-modal-visible" );
$( "body" ).removeClass( "j-modal-scroll" );
});
},
/* Init hide form feature */
initHideForm: function() {
var self = this;
self.$finalBlock = $( document ).find( ".j-final-block" );
self.$finalBlockCloseBtn = self.$finalBlock.find( ".j-close" );
self.createHideForm();
},
/* Make a layout for final block after form hidding */
createHideForm: function() {
var self = this,
config = self.config,
$form = self.$form,
$elem;
// If final block doesn't exist
if ( !self.$finalBlock.length ) {
$elem = $("<div />", {
class: "j-final-block j-hidden"
});
$form.after( $elem.html( config.dict.successBlockText ) );
self.$finalBlock = $form.siblings( ".j-final-block" );
}
// If "Close" btn doesn't exist
if ( !self.$finalBlockCloseBtn.length ) {
if ( config.formHide.closeBtn ) {
$elem = $( "<label />", {
class: "j-close"
}).append( "<i></i>" );
self.$finalBlock.append( $elem );
self.$finalBlockCloseBtn = self.$finalBlock.find( ".j-close" );
}
} else {
if ( !config.formHide.closeBtn ) {
self.$finalBlockCloseBtn.addClass( "j-hidden" );
}
}
},
/* Hide form processing */
processingHideForm: function() {
var self = this,
$finalBlock = self.$finalBlock,
$finalBlockCloseBtn = self.$finalBlockCloseBtn;
self.$form.addClass( "j-hidden" );
$finalBlock.removeClass( "j-hidden" );
if ( $finalBlockCloseBtn.length ) {
$finalBlockCloseBtn.on("click", function() {
if ( self.config.formType.modal ) {
var $modalForm = $( document ).find( "div.j-modal-form" );
if ( $modalForm.hasClass( "j-modal-visible" ) ) {
$modalForm.removeClass( "j-modal-visible" );
$( "body" ).removeClass( "j-modal-scroll" );
}
} else {
$finalBlock.addClass( "j-hidden" );
}
});
}
},
/* Reset form */
resetForm: function() {
var self = this,
config = self.config,
$input, $totalDataList;
// Reset a form
self.form.reset();
// Remove success condition from the fields
if ( config.success.highlight ){
$input = self.$form.find( ".j-input" );
$input.each( function(){
$( this ).toggleClass( "j-success-view", false );
});
}
// Clear total form data block
if ( config.formTotalData ){
$totalDataList = self.$totalDataBlock.find("span[data-field]");
$.each( $totalDataList, function(){
$( this ).html( "" );
});
}
},
/* Redirect form */
redirectForm: function() {
var self = this;
$( location ).attr( "href", self.config.formRedirect.address );
},
/* Scroll to first element with error */
scrollToError: function() {
var self = this,
$form = self.$form,
$err = $form.find( ".j-error-view" ).eq(0),
$modalWrap, $wrapper, $offset;
if ( !$err.length ) {
return false;
}
// Scroll modal form
if ( self.config.formType.modal ){
$modalWrap = $( document ).find( "#j-modalwrap-"+$form.attr("id") );
$wrapper = $( document ).find(".j-wrapper");
$offset = $wrapper.offset().top - $err.offset().top;
$modalWrap.animate({
scrollTop: -$offset - 60
}, 500);
return false;
}
// Scroll not modal form
$("html, body").animate({
scrollTop: $err.offset().top - 60
}, 500);
},
/* Create a block with error messages */
createErrorBlock: function() {
var self = this,
$ul = $( "<ul></ul>" ),
elems = false,
$formResponseBlock = self.$formResponseBlock,
$li;
// If error object contains error messages
$.each( self.errorMessages, function( key, val ) {
if ( val !== null ) {
elems = true;
return false;
}
});
if ( elems ) {
// Add class "j-error-message"
$formResponseBlock.toggleClass( "j-error-message j-unit", true );
$formResponseBlock.text( self.config.dict.errorBlockText ).append( $ul );
$.each( self.errorMessages, function( name, mes ) {
if ( mes !== null ) {
$li = $( "<li></li>" ).text( mes );
$ul.append( $li );
}
});
} else {
self.clearBlock();
}
},
/* Create a block with success message */
createSuccessBlock: function( msg ) {
var self = this,
config = self.config,
$submitBtn = self.$submitBtn,
$formResponseBlock = self.$formResponseBlock,
$recaptcha = self.$form.find(".g-recaptcha");
// Clear response block
if ( $formResponseBlock.hasClass( "j-error-message" ) ) {
self.clearBlock();
}
// Add success message
$formResponseBlock.addClass( "j-success-message j-unit" ).text( msg );
// Button enabled
$submitBtn.removeClass( "j-processing" );
// If repeated form submission is not allowed
if ( !config.repeatSubmission ) {
// Disable submit button
$submitBtn.addClass( "j-disabled-view" ).html( config.dict.disabledBtn );
}
setTimeout(function(){
self.clearBlock();
// Google reCaptcha reloading
if ( $recaptcha.length ) {
grecaptcha.reset();
}
// If repeated form submission is allowed
if ( config.repeatSubmission ) {
$submitBtn.removeAttr( "disabled" );
}
// Modal form processing
if ( config.formType.modal ) {
$( "div.j-modal-form" ).removeClass( "j-modal-visible" );
$( "body" ).removeClass( "j-modal-scroll" );
}
// Multistep form processing
if ( config.formType.multistep ) {
// Buttons processing
self.$prevBtn.removeAttr( "disabled" ).addClass( "j-hiddenBtn" );
self.$nextBtn.removeClass( "j-hiddenBtn" );
$submitBtn.addClass( "j-hiddenBtn" );
// Steps processing
self.$stepList.removeClass( "j-active-fieldset" ).eq( 0 ).addClass( "j-active-fieldset" );
// Step titles processing
if ( self.$stepTitleList.length ){
self.$stepTitleList.removeClass('j-active-step j-passed-step').eq(0).addClass('j-active-step');
}
}
}, config.timeoutSuccessMsg);
},
/* Delete all error messages, all "error" CSS classes */
clearForm: function() {
var self = this,
$form = self.$form,
config = self.config,
$spanErr, $tooltipErr, $input;
// Clear error variables
self.errorExists = false;
self.errorMessages = {};
// Delete error messages under the fields
if ( config.error.underField ) {
$spanErr = $form.find("span.j-error-view");
$spanErr.each(function(){
$( this ).remove();
});
}
// Delete error messages from an error block
if ( config.error.inBlock ) {
self.clearBlock();
}
// Remove error condition from the fields
if ( config.error.highlight ) {
$input = $form.find( ".j-input" );
$input.each( function(){
$( this ).toggleClass( "j-error-view", false );
});
}
// Delete tooltip error messages
if ( config.error.inTooltip ) {
$tooltipErr = $form.find( "span.j-tooltip.j-error-view" );
$tooltipErr.each(function(){
$( this ).remove();
});
}
},
/* Delete field's error message, field's "error" CSS classes */
clearField: function( field ) {
var self = this,
config = self.config,
errorMessages = self.errorMessages,
fieldName = $( field ).attr("name"),
$input, $span, $tooltip;
// If a form does not have a field
if ( self.$formObj[fieldName] === undefined || self.$formObj[fieldName] === null ){
return false;
}
self.errorExists = false;
$input = $( field ).closest( ".j-input" );
// Delete error messages under the fields
if ( config.error.underField ){
$span = $input.find( "span.j-error-view" );
if ( $span.length ) {
$span.remove();
}
}
// Remove error condition from the fields
if ( config.error.highlight ) {
$input.toggleClass( "j-error-view", false );
}
// Delete tooltip error messages
if ( config.error.inTooltip ) {
$tooltip = $input.find( "span.j-tooltip.j-error-view" );
if ( $tooltip.length ) {
$tooltip.remove();
}
}
// Delete error message from error object
if ( config.error.inBlock ){
if ( errorMessages[fieldName] !== undefined && errorMessages[fieldName] !== null ) {
errorMessages[fieldName] = null;
}
}
},
/* Clear a block with validation result */
clearBlock: function() {
var self = this;
self.$formResponseBlock.removeClass( "j-error-message j-success-message j-unit" ).html( "" );
},
};
$.fn.justFormsPro = function( options ){
new JustFormsPro( this, options );
return this;
};
}( jQuery ));
Zerion Mini Shell 1.0