/* global wpforms_settings, grecaptcha, wpformsRecaptchaCallback, wpforms_validate, wpforms_datepicker, wpforms_timepicker, Mailcheck */
'use strict';
var wpforms = window.wpforms || ( function( document, window, $ ) {
var app = {
/**
* Start the engine.
*
* @since 1.2.3
*/
init: function() {
// Document ready.
$( document ).ready( app.ready );
// Page load.
$( window ).on( 'load', app.load );
app.bindUIActions();
app.bindOptinMonster();
},
/**
* Document ready.
*
* @since 1.2.3
*/
ready: function() {
// Clear URL - remove wpforms_form_id.
app.clearUrlQuery();
// Set user identifier.
app.setUserIndentifier();
app.loadValidation();
app.loadDatePicker();
app.loadTimePicker();
app.loadInputMask();
app.loadSmartPhoneField();
app.loadPayments();
app.loadMailcheck();
// Randomize elements.
$( '.wpforms-randomize' ).each( function() {
var $list = $( this ),
$listItems = $list.children();
while ( $listItems.length ) {
$list.append( $listItems.splice( Math.floor( Math.random() * $listItems.length ), 1 )[0] );
}
} );
$( document ).trigger( 'wpformsReady' );
},
/**
* Page load.
*
* @since 1.2.3
*/
load: function() {
},
//--------------------------------------------------------------------//
// Initializing
//--------------------------------------------------------------------//
/**
* Remove wpforms_form_id from URL.
*
* @since 1.5.2
*/
clearUrlQuery: function() {
var loc = window.location,
query = loc.search;
if ( query.indexOf( 'wpforms_form_id=' ) !== -1 ) {
query = query.replace( /([&?]wpforms_form_id=[0-9]*$|wpforms_form_id=[0-9]*&|[?&]wpforms_form_id=[0-9]*(?=#))/, '' );
history.replaceState( {}, null, loc.origin + loc.pathname + query );
}
},
/**
* Load jQuery Validation.
*
* @since 1.2.3
*/
loadValidation: function() {
// Only load if jQuery validation library exists.
if ( typeof $.fn.validate !== 'undefined' ) {
// jQuery Validation library will not correctly validate
// fields that do not have a name attribute, so we use the
// `wpforms-input-temp-name` class to add a temporary name
// attribute before validation is initialized, then remove it
// before the form submits.
$( '.wpforms-input-temp-name' ).each( function( index, el ) {
var random = Math.floor( Math.random() * 9999 ) + 1;
$( this ).attr( 'name', 'wpf-temp-' + random );
} );
// Prepend URL field contents with http:// if user input doesn't contain a schema.
$( '.wpforms-validate input[type=url]' ).change( function() {
var url = $( this ).val();
if ( ! url ) {
return false;
}
if ( url.substr( 0, 7 ) !== 'http://' && url.substr( 0, 8 ) !== 'https://' ) {
$( this ).val( 'http://' + url );
}
} );
$.validator.messages.required = wpforms_settings.val_required;
$.validator.messages.url = wpforms_settings.val_url;
$.validator.messages.email = wpforms_settings.val_email;
$.validator.messages.number = wpforms_settings.val_number;
// Payments: Validate method for Credit Card Number.
if ( typeof $.fn.payment !== 'undefined' ) {
$.validator.addMethod( 'creditcard', function( value, element ) {
//var type = $.payment.cardType(value);
var valid = $.payment.validateCardNumber( value );
return this.optional( element ) || valid;
}, wpforms_settings.val_creditcard );
// @todo validate CVC and expiration
}
// Validate method for file extensions.
$.validator.addMethod( 'extension', function( value, element, param ) {
param = 'string' === typeof param ? param.replace( /,/g, '|' ) : 'png|jpe?g|gif';
return this.optional( element ) || value.match( new RegExp( '\\.(' + param + ')$', 'i' ) );
}, wpforms_settings.val_fileextension );
// Validate method for file size.
$.validator.addMethod( 'maxsize', function( value, element, param ) {
var maxSize = param,
optionalValue = this.optional( element ),
i, len, file;
if ( optionalValue ) {
return optionalValue;
}
if ( element.files && element.files.length ) {
i = 0;
len = element.files.length;
for ( ; i < len; i++ ) {
file = element.files[i];
if ( file.size > maxSize ) {
return false;
}
}
}
return true;
}, wpforms_settings.val_filesize );
// Validate email addresses.
$.validator.methods.email = function( value, element ) {
return this.optional( element ) || /^[a-z0-9.!#$%&'*+\/=?^_`{|}~-]+@((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/i.test( value );
};
// Validate confirmations.
$.validator.addMethod( 'confirm', function( value, element, param ) {
return $.validator.methods.equalTo.call( this, value, element, param );
}, wpforms_settings.val_confirm );
// Validate required payments.
$.validator.addMethod( 'required-payment', function( value, element ) {
return app.amountSanitize( value ) > 0;
}, wpforms_settings.val_requiredpayment );
// Validate 12-hour time.
$.validator.addMethod( 'time12h', function( value, element ) {
return this.optional( element ) || /^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test( value );
}, wpforms_settings.val_time12h );
// Validate 24-hour time.
$.validator.addMethod( 'time24h', function( value, element ) {
return this.optional( element ) || /^(([0-1]?[0-9])|([2][0-3])):([0-5]?[0-9])(\ ?[AP]M)?$/i.test( value );
}, wpforms_settings.val_time24h );
// Validate checkbox choice limit.
$.validator.addMethod( 'check-limit', function( value, element ) {
var $ul = $( element ).closest( 'ul' ),
$checked = $ul.find( 'input[type="checkbox"]:checked' ),
choiceLimit = parseInt( $ul.attr( 'data-choice-limit' ) || 0, 10 );
if ( 0 === choiceLimit ) {
return true;
}
return $checked.length <= choiceLimit;
}, function( params, element ) {
var choiceLimit = parseInt( $( element ).closest( 'ul' ).attr( 'data-choice-limit' ) || 0, 10 );
return wpforms_settings.val_checklimit.replace( '{#}', choiceLimit );
} );
// Validate Smart Phone Field.
if ( typeof $.fn.intlTelInput !== 'undefined' ) {
$.validator.addMethod( 'smart-phone-field', function( value, element ) {
return this.optional( element ) || $( element ).intlTelInput( 'isValidNumber' );
}, wpforms_settings.val_phone );
}
// Validate US Phone Field.
$.validator.addMethod( 'us-phone-field', function( value, element ) {
return this.optional( element ) || value.replace( /[^\d]/g, '' ).length === 10;
}, wpforms_settings.val_phone );
// Validate International Phone Field.
$.validator.addMethod( 'int-phone-field', function( value, element ) {
return this.optional( element ) || value.replace( /[^\d]/g, '' ).length > 0;
}, wpforms_settings.val_phone );
// Finally load jQuery Validation library for our forms.
$( '.wpforms-validate' ).each( function() {
var form = $( this ),
formID = form.data( 'formid' ),
properties;
// TODO: cleanup this BC with wpforms_validate.
if ( typeof window['wpforms_' + formID] !== 'undefined' && window['wpforms_' + formID].hasOwnProperty( 'validate' ) ) {
properties = window['wpforms_' + formID].validate;
} else if ( typeof wpforms_validate !== 'undefined' ) {
properties = wpforms_validate;
} else {
properties = {
errorClass: 'wpforms-error',
validClass: 'wpforms-valid',
errorPlacement: function( error, element ) {
if ( 'radio' === element.attr( 'type' ) || 'checkbox' === element.attr( 'type' ) ) {
if ( element.hasClass( 'wpforms-likert-scale-option' ) ) {
if ( element.closest( 'table' ).hasClass( 'single-row' ) ) {
element.closest( 'table' ).after( error );
} else {
element.closest( 'tr' ).find( 'th' ).append( error );
}
} else if ( element.hasClass( 'wpforms-net-promoter-score-option' ) ) {
element.closest( 'table' ).after( error );
} else {
element.closest( '.wpforms-field-checkbox' ).find( 'label.wpforms-error' ).remove();
element.parent().parent().parent().append( error );
}
} else if ( element.is( 'select' ) && element.attr( 'class' ).match( /date-month|date-day|date-year/ ) ) {
if ( 0 === element.parent().find( 'label.wpforms-error:visible' ).length ) {
element.parent().find( 'select:last' ).after( error );
}
} else if ( element.hasClass( 'wpforms-smart-phone-field' ) ) {
element.parent().after( error );
} else {
error.insertAfter( element );
}
},
highlight: function( element, errorClass, validClass ) {
var $element = $( element ),
$field = $element.closest( '.wpforms-field' ),
inputName = $element.attr( 'name' );
if ( 'radio' === $element.attr( 'type' ) || 'checkbox' === $element.attr( 'type' ) ) {
$field.find( 'input[name=\'' + inputName + '\']' ).addClass( errorClass ).removeClass( validClass );
} else {
$element.addClass( errorClass ).removeClass( validClass );
}
$field.addClass( 'wpforms-has-error' );
},
unhighlight: function( element, errorClass, validClass ) {
var $element = $( element ),
$field = $element.closest( '.wpforms-field' ),
inputName = $element.attr( 'name' );
if ( 'radio' === $element.attr( 'type' ) || 'checkbox' === $element.attr( 'type' ) ) {
$field.find( 'input[name=\'' + inputName + '\']' ).addClass( validClass ).removeClass( errorClass );
} else {
$element.addClass( validClass ).removeClass( errorClass );
}
$field.removeClass( 'wpforms-has-error' );
},
submitHandler: function( form ) {
var $form = $( form ),
$submit = $form.find( '.wpforms-submit' ),
altText = $submit.data( 'alt-text' ),
recaptchaID = $submit.get( 0 ).recaptchaID;
$submit.prop( 'disabled', true );
$form.find( '#wpforms-field_recaptcha-error' ).remove();
if ( ! app.empty( recaptchaID ) || recaptchaID === 0 ) {
// Form contains invisible reCAPTCHA.
grecaptcha.execute( recaptchaID ).then( null, function( reason ) {
reason = ( null === reason ) ? '' : '
' + reason;
$form.find( '.wpforms-recaptcha-container' ).append( '' );
$submit.prop( 'disabled', false );
} );
return false;
}
// Normal form.
if ( altText ) {
$submit.text( altText );
}
// Remove name attributes if needed.
$( '.wpforms-input-temp-name' ).removeAttr( 'name' );
app.formSubmit( $form );
},
invalidHandler: function( event, validator ) {
if ( typeof validator.errorList[0] !== 'undefined' ) {
app.scrollToError( $( validator.errorList[0].element ) );
}
},
onkeyup: function( element, event ) {
// This code is copied from JQuery Validate 'onkeyup' method with only one change: 'wpforms-novalidate-onkeyup' class check.
var excludedKeys = [ 16, 17, 18, 20, 35, 36, 37, 38, 39, 40, 45, 144, 225 ];
if ( $( element ).hasClass( 'wpforms-novalidate-onkeyup' ) ) {
return; // Disable onkeyup validation for some elements (e.g. remote calls).
}
if ( 9 === event.which && '' === this.elementValue( element ) || $.inArray( event.keyCode, excludedKeys ) !== -1 ) {
return;
} else if ( element.name in this.submitted || element.name in this.invalid ) {
this.element( element );
}
},
onfocusout: function( element ) {
// This code is copied from JQuery Validate 'onfocusout' method with only one change: 'wpforms-novalidate-onkeyup' class check.
var validate = false;
if ( $( element ).hasClass( 'wpforms-novalidate-onkeyup' ) && ! element.value ) {
validate = true; // Empty value error handling for elements with onkeyup validation disabled.
}
if ( ! this.checkable( element ) && ( element.name in this.submitted || ! this.optional( element ) ) ) {
validate = true;
}
if ( validate ) {
this.element( element );
}
},
onclick: function( element ) {
var validate = false,
type = ( element || {} ).type,
$el = $( element );
if ( [ 'checkbox', 'radio' ].indexOf( type ) > -1 ) {
if ( $el.hasClass( 'wpforms-likert-scale-option' ) ) {
$el = $el.closest( 'tr' );
} else {
$el = $el.closest( '.wpforms-field' );
}
$el.find( 'label.wpforms-error' ).remove();
validate = true;
}
if ( validate ) {
this.element( element );
}
},
};
}
form.validate( properties );
} );
}
},
/**
* Load jQuery Date Picker.
*
* @since 1.2.3
*/
loadDatePicker: function() {
// Only load if jQuery datepicker library exists.
if ( typeof $.fn.flatpickr !== 'undefined' ) {
$( '.wpforms-datepicker' ).each( function() {
var element = $( this ),
form = element.closest( '.wpforms-form' ),
formID = form.data( 'formid' ),
fieldID = element.closest( '.wpforms-field' ).data( 'field-id' ),
properties;
if ( typeof window['wpforms_' + formID + '_' + fieldID] !== 'undefined' && window['wpforms_' + formID + '_' + fieldID].hasOwnProperty( 'datepicker' ) ) {
properties = window['wpforms_' + formID + '_' + fieldID].datepicker;
} else if ( typeof window['wpforms_' + formID] !== 'undefined' && window['wpforms_' + formID].hasOwnProperty( 'datepicker' ) ) {
properties = window['wpforms_' + formID].datepicker;
} else if ( typeof wpforms_datepicker !== 'undefined' ) {
properties = wpforms_datepicker;
} else {
properties = {
disableMobile: true,
};
}
// Redefine locale only if user doesn't do that manually and we have the locale.
if (
! properties.hasOwnProperty( 'locale' ) &&
typeof wpforms_settings !== 'undefined' &&
wpforms_settings.hasOwnProperty( 'locale' )
) {
properties.locale = wpforms_settings.locale;
}
element.flatpickr( properties );
} );
}
},
/**
* Load jQuery Time Picker.
*
* @since 1.2.3
*/
loadTimePicker: function() {
// Only load if jQuery timepicker library exists.
if ( typeof $.fn.timepicker !== 'undefined' ) {
$( '.wpforms-timepicker' ).each( function() {
var element = $( this ),
form = element.closest( '.wpforms-form' ),
formID = form.data( 'formid' ),
fieldID = element.closest( '.wpforms-field' ).data( 'field-id' ),
properties;
if (
typeof window['wpforms_' + formID + '_' + fieldID] !== 'undefined' &&
window['wpforms_' + formID + '_' + fieldID].hasOwnProperty( 'timepicker' )
) {
properties = window['wpforms_' + formID + '_' + fieldID].timepicker;
} else if (
typeof window['wpforms_' + formID] !== 'undefined' &&
window['wpforms_' + formID].hasOwnProperty( 'timepicker' )
) {
properties = window['wpforms_' + formID].timepicker;
} else if ( typeof wpforms_timepicker !== 'undefined' ) {
properties = wpforms_timepicker;
} else {
properties = {
scrollDefault: 'now',
forceRoundTime: true,
};
}
element.timepicker( properties );
} );
}
},
/**
* Load jQuery input masks.
*
* @since 1.2.3
*/
loadInputMask: function() {
// Only load if jQuery input mask library exists.
if ( typeof $.fn.inputmask === 'undefined' ) {
return;
}
$( '.wpforms-masked-input' ).inputmask();
},
/**
* Load smart phone field.
*
* @since 1.5.2
*/
loadSmartPhoneField: function() {
// Only load if library exists.
if ( typeof $.fn.intlTelInput === 'undefined' ) {
return;
}
var inputOptions = {};
// Determine the country by IP if no GDPR restrictions enabled.
if ( ! wpforms_settings.gdpr ) {
inputOptions.geoIpLookup = app.currentIpToCountry;
}
// Try to kick in an alternative solution if GDPR restrictions are enabled.
if ( wpforms_settings.gdpr ) {
var lang = this.getFirstBrowserLanguage(),
countryCode = lang.indexOf( '-' ) > -1 ? lang.split( '-' ).pop() : '';
}
// Make sure the library recognizes browser country code to avoid console error.
if ( countryCode ) {
var countryData = window.intlTelInputGlobals.getCountryData();
countryData = countryData.filter( function( country ) {
return country.iso2 === countryCode.toLowerCase();
} );
countryCode = countryData.length ? countryCode : '';
}
// Set default country.
inputOptions.initialCountry = wpforms_settings.gdpr && countryCode ? countryCode : 'auto';
$( '.wpforms-smart-phone-field' ).each( function( i, el ) {
var $el = $( el );
// Hidden input allows to include country code into submitted data.
inputOptions.hiddenInput = $el.closest( '.wpforms-field-phone' ).data( 'field-id' );
inputOptions.utilsScript = wpforms_settings.wpforms_plugin_url + 'pro/assets/js/vendor/jquery.intl-tel-input-utils.js';
$el.intlTelInput( inputOptions );
// Remove original input name not to interfere with a hidden input.
$el.removeAttr( 'name' );
// Instantly update a hidden form input with a correct data.
// Previously "blur" only was used, which is broken in case Enter was used to submit the form.
$el.on( 'blur keydown', function() {
if ( $el.intlTelInput( 'isValidNumber' ) ) {
$el.siblings( 'input[type="hidden"]' ).val( $el.intlTelInput( 'getNumber' ) );
}
} );
} );
},
/**
* Payments: Do various payment-related tasks on load.
*
* @since 1.2.6
*/
loadPayments: function() {
// Update Total field(s) with latest calculation.
$( '.wpforms-payment-total' ).each( function( index, el ) {
app.amountTotal( this );
} );
// Credit card validation.
if ( typeof $.fn.payment !== 'undefined' ) {
$( '.wpforms-field-credit-card-cardnumber' ).payment( 'formatCardNumber' );
$( '.wpforms-field-credit-card-cardcvc' ).payment( 'formatCardCVC' );
}
},
/**
* Load mailcheck.
*
* @since 1.5.3
*/
loadMailcheck: function() {
// Skip loading if `wpforms_mailcheck_enabled` filter return false.
if ( ! wpforms_settings.mailcheck_enabled ) {
return;
}
// Only load if library exists.
if ( typeof $.fn.mailcheck === 'undefined' ) {
return;
}
if ( wpforms_settings.mailcheck_domains.length > 0 ) {
Mailcheck.defaultDomains = Mailcheck.defaultDomains.concat( wpforms_settings.mailcheck_domains );
}
if ( wpforms_settings.mailcheck_toplevel_domains.length > 0 ) {
Mailcheck.defaultTopLevelDomains = Mailcheck.defaultTopLevelDomains.concat( wpforms_settings.mailcheck_toplevel_domains );
}
// Mailcheck suggestion.
$( document ).on( 'blur', '.wpforms-field-email input', function() {
var $t = $( this ),
id = $t.attr( 'id' );
$t.mailcheck( {
suggested: function( el, suggestion ) {
$( '#' + id + '_suggestion' ).remove();
var sugg = '' + suggestion.full + '';
sugg = wpforms_settings.val_email_suggestion.replace( '{suggestion}', sugg );
$( el ).after( '' );
},
empty: function() {
$( '#' + id + '_suggestion' ).remove();
},
} );
} );
// Apply Mailcheck suggestion.
$( document ).on( 'click', '.wpforms-field-email .mailcheck-suggestion', function( e ) {
var $t = $( this ),
id = $t.attr( 'data-id' );
e.preventDefault();
$( '#' + id ).val( $t.text() );
$t.parent().remove();
} );
},
//--------------------------------------------------------------------//
// Binds.
//--------------------------------------------------------------------//
/**
* Element bindings.
*
* @since 1.2.3
*/
bindUIActions: function() {
// Pagebreak navigation.
$( document ).on( 'click', '.wpforms-page-button', function( event ) {
event.preventDefault();
app.pagebreakNav( this );
} );
// Payments: Update Total field(s) when latest calculation.
$( document ).on( 'change input', '.wpforms-payment-price', function() {
app.amountTotal( this, true );
} );
// Payments: Restrict user input payment fields.
$( document ).on( 'input', '.wpforms-payment-user-input', function() {
var $this = $( this ),
amount = $this.val();
$this.val( amount.replace( /[^0-9.,]/g, '' ) );
} );
// Payments: Sanitize/format user input amounts.
$( document ).on( 'focusout', '.wpforms-payment-user-input', function() {
var $this = $( this ),
amount = $this.val(),
sanitized = app.amountSanitize( amount ),
formatted = app.amountFormat( sanitized );
$this.val( formatted );
} );
// Payments: Update Total field(s) when conditials are processed.
$( document ).on( 'wpformsProcessConditionals', function( e, el ) {
app.amountTotal( el, true );
} );
// Payment radio/checkbox fields: preselect the selected payment (from dynamic/fallback population).
$( document ).ready( function() {
// Radios.
$( '.wpforms-field-radio .wpforms-image-choices-item input:checked' ).change();
$( '.wpforms-field-payment-multiple .wpforms-image-choices-item input:checked' ).change();
// Checkboxes.
$( '.wpforms-field-checkbox .wpforms-image-choices-item input' ).change();
$( '.wpforms-field-payment-checkbox .wpforms-image-choices-item input' ).change();
} );
// Rating field: hover effect.
$( '.wpforms-field-rating-item' ).hover(
function() {
$( this ).parent().find( '.wpforms-field-rating-item' ).removeClass( 'selected hover' );
$( this ).prevAll().andSelf().addClass( 'hover' );
},
function() {
$( this ).parent().find( '.wpforms-field-rating-item' ).removeClass( 'selected hover' );
$( this ).parent().find( 'input:checked' ).parent().prevAll().andSelf().addClass( 'selected' );
}
);
// Rating field: toggle selected state.
$( document ).on( 'change', '.wpforms-field-rating-item input', function() {
var $this = $( this ),
$wrap = $this.closest( '.wpforms-field-rating-items' ),
$items = $wrap.find( '.wpforms-field-rating-item' );
$items.removeClass( 'hover selected' );
$this.parent().prevAll().andSelf().addClass( 'selected' );
} );
// Rating field: preselect the selected rating (from dynamic/fallback population).
$( document ).ready( function() {
$( '.wpforms-field-rating-item input:checked' ).change();
} );
// Checkbox/Radio/Payment checkbox: make labels keyboard-accessible.
$( document ).on( 'keypress', '.wpforms-image-choices-item label', function( event ) {
var $this = $( this ),
$field = $this.closest( '.wpforms-field' );
if ( $field.hasClass( 'wpforms-conditional-hide' ) ) {
event.preventDefault();
return false;
}
// Cause the input to be clicked when clicking the label.
if ( 13 === event.which ) {
$( '#' + $this.attr( 'for' ) ).click();
}
} );
// IE: Click on the `image choice` image should trigger the click event on the input (checkbox or radio) field.
$( document ).on( 'click', '.wpforms-image-choices-item img', function( e ) {
e.preventDefault();
$( this ).closest( 'label' ).find( 'input' ).click();
} );
$( document ).on( 'change', '.wpforms-field-checkbox input, .wpforms-field-radio input, .wpforms-field-payment-multiple input, .wpforms-field-payment-checkbox input, .wpforms-field-gdpr-checkbox input', function( event ) {
var $this = $( this ),
$field = $this.closest( '.wpforms-field' );
if ( $field.hasClass( 'wpforms-conditional-hide' ) ) {
event.preventDefault();
return false;
}
switch ( $this.attr( 'type' ) ) {
case 'radio':
$this.closest( 'ul' ).find( 'li' ).removeClass( 'wpforms-selected' ).find( 'input[type=radio]' ).removeProp( 'checked' );
$this
.prop( 'checked', true )
.closest( 'li' ).addClass( 'wpforms-selected' );
break;
case 'checkbox':
if ( $this.is( ':checked' ) ) {
$this.closest( 'li' ).addClass( 'wpforms-selected' );
$this.prop( 'checked', true );
} else {
$this.closest( 'li' ).removeClass( 'wpforms-selected' );
$this.prop( 'checked', false );
}
break;
}
} );
// Upload fields: Check combined file size.
$( document ).on( 'change', '.wpforms-field-file-upload input[type=file]:not(".dropzone-input")', function() {
var $this = $( this ),
$uploads = $this.closest( 'form.wpforms-form' ).find( '.wpforms-field-file-upload input:not(".dropzone-input")' ),
totalSize = 0,
postMaxSize = Number( wpforms_settings.post_max_size ),
errorMsg = '