|
|
;(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 ));
|