PageSourceSearch

https://courtemaux.fr/wp-content/plugins/wpforms-lite/assets/js/wpforms.js

js courtemaux.fr collected 2026-10-03 02:46:30 UTC 58,056 bytes, 1,911 lines download raw bytes

1/* global wpforms_settings, grecaptcha, wpformsRecaptchaCallback, wpforms_validate, wpforms_datepicker, wpforms_timepicker, Mailcheck, Choices */
2
3'use strict';
4
5var wpforms = window.wpforms || ( function( document, window, $ ) {
6
7	var app = {
8
9		/**
10		 * Start the engine.
11		 *
12		 * @since 1.2.3
13		 */
14		init: function() {
15
16			// Document ready.
17			$( app.ready );
18
19			// Page load.
20			$( window ).on( 'load', app.load );
21
22			app.bindUIActions();
23			app.bindOptinMonster();
24		},
25
26		/**
27		 * Document ready.
28		 *
29		 * @since 1.2.3
30		 */
31		ready: function() {
32
33			// Clear URL - remove wpforms_form_id.
34			app.clearUrlQuery();
35
36			// Set user identifier.
37			app.setUserIndentifier();
38
39			app.loadValidation();
40			app.loadDatePicker();
41			app.loadTimePicker();
42			app.loadInputMask();
43			app.loadSmartPhoneField();
44			app.loadPayments();
45			app.loadMailcheck();
46			app.loadChoicesJS();
47
48			// Randomize elements.
49			$( '.wpforms-randomize' ).each( function() {
50				var $list      = $( this ),
51					$listItems = $list.children();
52				while ( $listItems.length ) {
53					$list.append( $listItems.splice( Math.floor( Math.random() * $listItems.length ), 1 )[0] );
54				}
55			} );
56
57			$( document ).trigger( 'wpformsReady' );
58		},
59
60		/**
61		 * Page load.
62		 *
63		 * @since 1.2.3
64		 */
65		load: function() {
66
67		},
68
69		//--------------------------------------------------------------------//
70		// Initializing
71		//--------------------------------------------------------------------//
72
73		/**
74		 * Remove wpforms_form_id from URL.
75		 *
76		 * @since 1.5.2
77		 */
78		clearUrlQuery: function() {
79			var loc   = window.location,
80				query = loc.search;
81
82			if ( query.indexOf( 'wpforms_form_id=' ) !== -1 ) {
83				query = query.replace( /([&?]wpforms_form_id=[0-9]*$|wpforms_form_id=[0-9]*&|[?&]wpforms_form_id=[0-9]*(?=#))/, '' );
84				history.replaceState( {}, null, loc.origin + loc.pathname + query );
85			}
86		},
87
88
89		/**
90		 * Load jQuery Validation.
91		 *
92		 * @since 1.2.3
93		 */
94		loadValidation: function() {
95
96			// Only load if jQuery validation library exists.
97			if ( typeof $.fn.validate !== 'undefined' ) {
98
99				// jQuery Validation library will not correctly validate
100				// fields that do not have a name attribute, so we use the
101				// `wpforms-input-temp-name` class to add a temporary name
102				// attribute before validation is initialized, then remove it
103				// before the form submits.
104				$( '.wpforms-input-temp-name' ).each( function( index, el ) {
105					var random = Math.floor( Math.random() * 9999 ) + 1;
106					$( this ).attr( 'name', 'wpf-temp-' + random );
107				} );
108
109				// Prepend URL field contents with http:// if user input doesn't contain a schema.
110				$( '.wpforms-validate input[type=url]' ).change( function() {
111					var url = $( this ).val();
112					if ( ! url ) {
113						return false;
114					}
115					if ( url.substr( 0, 7 ) !== 'http://' && url.substr( 0, 8 ) !== 'https://' ) {
116						$( this ).val( 'http://' + url );
117					}
118				} );
119
120				$.validator.messages.required = wpforms_settings.val_required;
121				$.validator.messages.url      = wpforms_settings.val_url;
122				$.validator.messages.email    = wpforms_settings.val_email;
123				$.validator.messages.number   = wpforms_settings.val_number;
124
125				// Payments: Validate method for Credit Card Number.
126				if ( typeof $.fn.payment !== 'undefined' ) {
127					$.validator.addMethod( 'creditcard', function( value, element ) {
128
129						//var type  = $.payment.cardType(value);
130						var valid = $.payment.validateCardNumber( value );
131						return this.optional( element ) || valid;
132					}, wpforms_settings.val_creditcard );
133
134					// @todo validate CVC and expiration
135				}
136
137				// Validate method for file extensions.
138				$.validator.addMethod( 'extension', function( value, element, param ) {
139					param = 'string' === typeof param ? param.replace( /,/g, '|' ) : 'png|jpe?g|gif';
140					return this.optional( element ) || value.match( new RegExp( '\\.(' + param + ')$', 'i' ) );
141				}, wpforms_settings.val_fileextension );
142
143				// Validate method for file size.
144				$.validator.addMethod( 'maxsize', function( value, element, param ) {
145					var maxSize = param,
146						optionalValue = this.optional( element ),
147						i, len, file;
148					if ( optionalValue ) {
149						return optionalValue;
150					}
151					if ( element.files && element.files.length ) {
152						i = 0;
153						len = element.files.length;
154						for ( ; i < len; i++ ) {
155							file = element.files[i];
156							if ( file.size > maxSize ) {
157								return false;
158							}
159						}
160					}
161					return true;
162				}, wpforms_settings.val_filesize );
163
164				// Validate email addresses.
165				$.validator.methods.email = function( value, element ) {
166					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 );
167				};
168
169				// Validate confirmations.
170				$.validator.addMethod( 'confirm', function( value, element, param ) {
171					return $.validator.methods.equalTo.call( this, value, element, param );
172				}, wpforms_settings.val_confirm );
173
174				// Validate required payments.
175				$.validator.addMethod( 'required-payment', function( value, element ) {
176					return app.amountSanitize( value ) > 0;
177				}, wpforms_settings.val_requiredpayment );
178
179				// Validate 12-hour time.
180				$.validator.addMethod( 'time12h', function( value, element ) {
181					return this.optional( element ) || /^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test( value );
182				}, wpforms_settings.val_time12h );
183
184				// Validate 24-hour time.
185				$.validator.addMethod( 'time24h', function( value, element ) {
186					return this.optional( element ) || /^(([0-1]?[0-9])|([2][0-3])):([0-5]?[0-9])(\ ?[AP]M)?$/i.test( value );
187				}, wpforms_settings.val_time24h );
188
189				// Validate checkbox choice limit.
190				$.validator.addMethod( 'check-limit', function( value, element ) {
191					var $ul = $( element ).closest( 'ul' ),
192						$checked = $ul.find( 'input[type="checkbox"]:checked' ),
193						choiceLimit = parseInt( $ul.attr( 'data-choice-limit' ) || 0, 10 );
194
195					if ( 0 === choiceLimit ) {
196						return true;
197					}
198					return $checked.length <= choiceLimit;
199				}, function( params, element ) {
200					var	choiceLimit = parseInt( $( element ).closest( 'ul' ).attr( 'data-choice-limit' ) || 0, 10 );
201					return wpforms_settings.val_checklimit.replace( '{#}', choiceLimit );
202				} );
203
204				// Validate Smart Phone Field.
205				if ( typeof $.fn.intlTelInput !== 'undefined' ) {
206					$.validator.addMethod( 'smart-phone-field', function( value, element ) {
207						if ( value.match( /[^\d()\-+\s]/ ) ) {
208							return false;
209						}
210						return this.optional( element ) || $( element ).intlTelInput( 'isValidNumber' );
211					}, wpforms_settings.val_phone );
212				}
213
214				// Validate Input Mask minimum length.
215				$.validator.addMethod( 'empty-blanks', function( value, element ) {
216					if ( typeof $.fn.inputmask === 'undefined' ) {
217						return true;
218					}
219					return ! ( value.indexOf( element.inputmask.opts.placeholder ) + 1 );
220				}, wpforms_settings.val_empty_blanks );
221
222				// Validate US Phone Field.
223				$.validator.addMethod( 'us-phone-field', function( value, element ) {
224					if ( value.match( /[^\d()\-+\s]/ ) ) {
225						return false;
226					}
227					return this.optional( element ) || value.replace( /[^\d]/g, '' ).length === 10;
228				}, wpforms_settings.val_phone );
229
230				// Validate International Phone Field.
231				$.validator.addMethod( 'int-phone-field', function( value, element ) {
232					if ( value.match( /[^\d()\-+\s]/ ) ) {
233						return false;
234					}
235					return this.optional( element ) || value.replace( /[^\d]/g, '' ).length > 0;
236				}, wpforms_settings.val_phone );
237
238				// Finally load jQuery Validation library for our forms.
239				$( '.wpforms-validate' ).each( function() {
240					var form   = $( this ),
241						formID = form.data( 'formid' ),
242						properties;
243
244					// TODO: cleanup this BC with wpforms_validate.
245					if ( typeof window['wpforms_' + formID] !== 'undefined' && window['wpforms_' + formID].hasOwnProperty( 'validate' ) ) {
246						properties = window['wpforms_' + formID].validate;
247					} else if ( typeof wpforms_validate !== 'undefined' ) {
248						properties = wpforms_validate;
249					} else {
250						properties = {
251							errorClass: 'wpforms-error',
252							validClass: 'wpforms-valid',
253							errorPlacement: function( error, element ) {
254								if ( 'radio' === element.attr( 'type' ) || 'checkbox' === element.attr( 'type' ) ) {
255									if ( element.hasClass( 'wpforms-likert-scale-option' ) ) {
256										if ( element.closest( 'table' ).hasClass( 'single-row' ) ) {
257											element.closest( 'table' ).after( error );
258										} else {
259											element.closest( 'tr' ).find( 'th' ).append( error );
260										}
261									} else if ( element.hasClass( 'wpforms-net-promoter-score-option' ) ) {
262										element.closest( 'table' ).after( error );
263									} else {
264										element.closest( '.wpforms-field-checkbox' ).find( 'label.wpforms-error' ).remove();
265										element.parent().parent().parent().append( error );
266									}
267								} else if ( element.is( 'select' ) && element.attr( 'class' ).match( /date-month|date-day|date-year/ ) ) {
268									if ( 0 === element.parent().find( 'label.wpforms-error:visible' ).length ) {
269										element.parent().find( 'select:last' ).after( error );
270									}
271								} else if ( element.hasClass( 'wpforms-smart-phone-field' ) ) {
272									element.parent().after( error );
273								} else if ( element.hasClass( 'wpforms-validation-group-member' ) ) {
274									element.closest( '.wpforms-field' ).append( error );
275								} else {
276									error.insertAfter( element );
277								}
278							},
279							highlight: function( element, errorClass, validClass ) {
280								var $element  = $( element ),
281									$field    = $element.closest( '.wpforms-field' ),
282									inputName = $element.attr( 'name' );
283								if ( 'radio' === $element.attr( 'type' ) || 'checkbox' === $element.attr( 'type' ) ) {
284									$field.find( 'input[name="' + inputName + '"]' ).addClass( errorClass ).removeClass( validClass );
285								} else {
286									$element.addClass( errorClass ).removeClass( validClass );
287								}
288								$field.addClass( 'wpforms-has-error' );
289							},
290							unhighlight: function( element, errorClass, validClass ) {
291								var $element  = $( element ),
292									$field    = $element.closest( '.wpforms-field' ),
293									inputName = $element.attr( 'name' );
294								if ( 'radio' === $element.attr( 'type' ) || 'checkbox' === $element.attr( 'type' ) ) {
295									$field.find( 'input[name="' + inputName + '"]' ).addClass( validClass ).removeClass( errorClass );
296								} else {
297									$element.addClass( validClass ).removeClass( errorClass );
298								}
299								$field.removeClass( 'wpforms-has-error' );
300							},
301							submitHandler: function( form ) {
302
303								var $form       = $( form ),
304									$submit     = $form.find( '.wpforms-submit' ),
305									altText     = $submit.data( 'alt-text' ),
306									recaptchaID = $submit.get( 0 ).recaptchaID;
307
308								if ( $form.data( 'token' ) && 0 === $( '.wpforms-token', $form ).length ) {
309									$( '<input type="hidden" class="wpforms-token" name="wpforms[token]" />' )
310										.val( $form.data( 'token' ) )
311										.appendTo( $form );
312								}
313
314								$submit.prop( 'disabled', true );
315								$form.find( '#wpforms-field_recaptcha-error' ).remove();
316
317								// Display processing text.
318								if ( altText ) {
319									$submit.text( altText );
320								}
321
322								if ( ! app.empty( recaptchaID ) || recaptchaID === 0 ) {
323
324									// Form contains invisible reCAPTCHA.
325									grecaptcha.execute( recaptchaID ).then( null, function( reason ) {
326
327										reason = ( null === reason ) ? '' : '<br>' + reason;
328										$form.find( '.wpforms-recaptcha-container' ).append( '<label id="wpforms-field_recaptcha-error" class="wpforms-error"> ' + wpforms_settings.val_recaptcha_fail_msg + reason + '</label>' );
329										$submit.prop( 'disabled', false );
330									} );
331									return false;
332								}
333
334								// Remove name attributes if needed.
335								$( '.wpforms-input-temp-name' ).removeAttr( 'name' );
336
337								app.formSubmit( $form );
338							},
339							invalidHandler: function( event, validator ) {
340
341								if ( typeof validator.errorList[0] !== 'undefined' ) {
342									app.scrollToError( $( validator.errorList[0].element ) );
343								}
344							},
345							onkeyup: function( element, event ) {
346
347								// This code is copied from JQuery Validate 'onkeyup' method with only one change: 'wpforms-novalidate-onkeyup' class check.
348								var excludedKeys = [ 16, 17, 18, 20, 35, 36, 37, 38, 39, 40, 45, 144, 225 ];
349
350								if ( $( element ).hasClass( 'wpforms-novalidate-onkeyup' ) ) {
351									return; // Disable onkeyup validation for some elements (e.g. remote calls).
352								}
353
354								if ( 9 === event.which && '' === this.elementValue( element ) || $.inArray( event.keyCode, excludedKeys ) !== -1 ) {
355									return;
356								} else if ( element.name in this.submitted || element.name in this.invalid ) {
357									this.element( element );
358								}
359							},
360							onfocusout: function( element ) {
361
362								// This code is copied from JQuery Validate 'onfocusout' method with only one change: 'wpforms-novalidate-onkeyup' class check.
363								var validate = false;
364
365								if ( $( element ).hasClass( 'wpforms-novalidate-onkeyup' ) && ! element.value ) {
366									validate = true; // Empty value error handling for elements with onkeyup validation disabled.
367								}
368
369								if ( ! this.checkable( element ) && ( element.name in this.submitted || ! this.optional( element ) ) ) {
370									validate = true;
371								}
372
373								if ( validate ) {
374									this.element( element );
375								}
376							},
377							onclick: function( element ) {
378								var validate = false,
379									type = ( element || {} ).type,
380									$el = $( element );
381
382								if ( [ 'checkbox', 'radio' ].indexOf( type ) > -1 ) {
383									if ( $el.hasClass( 'wpforms-likert-scale-option' ) ) {
384										$el = $el.closest( 'tr' );
385									} else {
386										$el = $el.closest( '.wpforms-field' );
387									}
388									$el.find( 'label.wpforms-error' ).remove();
389									validate = true;
390								}
391
392								if ( validate ) {
393									this.element( element );
394								}
395							},
396						};
397					}
398					form.validate( properties );
399				} );
400			}
401		},
402
403		/**
404		 * Load jQuery Date Picker.
405		 *
406		 * @since 1.2.3
407		 */
408		loadDatePicker: function() {
409
410			// Only load if jQuery datepicker library exists.
411			if ( typeof $.fn.flatpickr !== 'undefined' ) {
412				$( '.wpforms-datepicker-wrap' ).each( function() {
413					var element = $( this ),
414						form    = element.closest( '.wpforms-form' ),
415						formID  = form.data( 'formid' ),
416						fieldID = element.closest( '.wpforms-field' ).data( 'field-id' ),
417						properties;
418
419					if ( typeof window['wpforms_' + formID + '_' + fieldID] !== 'undefined' && window['wpforms_' + formID + '_' + fieldID].hasOwnProperty( 'datepicker' ) ) {
420						properties = window['wpforms_' + formID + '_' + fieldID].datepicker;
421					} else if ( typeof window['wpforms_' + formID] !== 'undefined' && window['wpforms_' + formID].hasOwnProperty( 'datepicker' ) ) {
422						properties = window['wpforms_' + formID].datepicker;
423					} else if ( typeof wpforms_datepicker !== 'undefined' ) {
424						properties = wpforms_datepicker;
425					} else {
426						properties = {
427							disableMobile: true,
428						};
429					}
430
431					// Redefine locale only if user doesn't do that manually and we have the locale.
432					if (
433						! properties.hasOwnProperty( 'locale' ) &&
434						typeof wpforms_settings !== 'undefined' &&
435						wpforms_settings.hasOwnProperty( 'locale' )
436					) {
437						properties.locale = wpforms_settings.locale;
438					}
439
440					properties.wrap = true;
441					properties.dateFormat = element.find( 'input' ).data( 'date-format' );
442
443					// Toggle clear date icon.
444					properties.onChange = function( selectedDates, dateStr, instance ) {
445
446						var display = dateStr === '' ? 'none' : 'block';
447						element.find( '.wpforms-datepicker-clear' ).css( 'display', display );
448					};
449
450					element.flatpickr( properties );
451				} );
452			}
453		},
454
455		/**
456		 * Load jQuery Time Picker.
457		 *
458		 * @since 1.2.3
459		 */
460		loadTimePicker: function() {
461
462			// Only load if jQuery timepicker library exists.
463			if ( typeof $.fn.timepicker !== 'undefined' ) {
464				$( '.wpforms-timepicker' ).each( function() {
465					var element = $( this ),
466						form    = element.closest( '.wpforms-form' ),
467						formID  = form.data( 'formid' ),
468						fieldID = element.closest( '.wpforms-field' ).data( 'field-id' ),
469						properties;
470
471					if (
472						typeof window['wpforms_' + formID + '_' + fieldID] !== 'undefined' &&
473						window['wpforms_' + formID + '_' + fieldID].hasOwnProperty( 't
473imepicker' )
474					) {
475						properties = window['wpforms_' + formID + '_' + fieldID].timepicker;
476					} else if (
477						typeof window['wpforms_' + formID] !== 'undefined' &&
478						window['wpforms_' + formID].hasOwnProperty( 'timepicker' )
479					) {
480						properties = window['wpforms_' + formID].timepicker;
481					} else if ( typeof wpforms_timepicker !== 'undefined' ) {
482						properties = wpforms_timepicker;
483					} else {
484						properties = {
485							scrollDefault: 'now',
486							forceRoundTime: true,
487						};
488					}
489
490					element.timepicker( properties );
491				} );
492			}
493		},
494
495		/**
496		 * Load jQuery input masks.
497		 *
498		 * @since 1.2.3
499		 */
500		loadInputMask: function() {
501
502			// Only load if jQuery input mask library exists.
503			if ( typeof $.fn.inputmask === 'undefined' ) {
504				return;
505			}
506
507			$( '.wpforms-masked-input' ).inputmask();
508		},
509
510		/**
511		 * Load smart phone field.
512		 *
513		 * @since 1.5.2
514		 */
515		loadSmartPhoneField: function() {
516
517			// Only load if library exists.
518			if ( typeof $.fn.intlTelInput === 'undefined' ) {
519				return;
520			}
521
522			var inputOptions = {};
523
524			// Determine the country by IP if no GDPR restrictions enabled.
525			if ( ! wpforms_settings.gdpr ) {
526				inputOptions.geoIpLookup = app.currentIpToCountry;
527			}
528
529			// Try to kick in an alternative solution if GDPR restrictions are enabled.
530			if ( wpforms_settings.gdpr ) {
531				var lang = this.getFirstBrowserLanguage(),
532					countryCode = lang.indexOf( '-' ) > -1 ? lang.split( '-' ).pop() : '';
533			}
534
535			// Make sure the library recognizes browser country code to avoid console error.
536			if ( countryCode ) {
537				var countryData = window.intlTelInputGlobals.getCountryData();
538
539				countryData = countryData.filter( function( country ) {
540					return country.iso2 === countryCode.toLowerCase();
541				} );
542				countryCode = countryData.length ? countryCode : '';
543			}
544
545			// Set default country.
546			inputOptions.initialCountry = wpforms_settings.gdpr && countryCode ? countryCode : 'auto';
547
548			$( '.wpforms-smart-phone-field' ).each( function( i, el ) {
549
550				var $el = $( el );
551
552				// Hidden input allows to include country code into submitted data.
553				inputOptions.hiddenInput = $el.closest( '.wpforms-field-phone' ).data( 'field-id' );
554				inputOptions.utilsScript = wpforms_settings.wpforms_plugin_url + 'pro/assets/js/vendor/jquery.intl-tel-input-utils.js';
555
556				$el.intlTelInput( inputOptions );
557
558				// For proper validation, we should preserve the name attribute of the input field.
559				// But we need to modify original input name not to interfere with a hidden input.
560				$el.attr( 'name', 'wpf-temp-' + $el.attr( 'name' ) );
561
562				// Add special class to remove name attribute before submitting.
563				// So, only the hidden input value will be submitted.
564				$el.addClass( 'wpforms-input-temp-name' );
565
566				// Instantly update a hidden form input with a correct data.
567				// Previously "blur" only was used, which is broken in case Enter was used to submit the form.
568				$el.on( 'blur input', function() {
569					if ( $el.intlTelInput( 'isValidNumber' ) || ! app.empty( window.WPFormsEditEntry ) ) {
570						$el.siblings( 'input[type="hidden"]' ).val( $el.intlTelInput( 'getNumber' ) );
571					}
572				} );
573			} );
574
575			// Update hidden input of the `Smart` phone field to be sure the latest value will be submitted.
576			$( '.wpforms-form' ).on( 'wpformsBeforeFormSubmit', function() {
577
578				$( this ).find( '.wpforms-smart-phone-field' ).trigger( 'input' );
579			} );
580		},
581
582		/**
583		 * Payments: Do various payment-related tasks on load.
584		 *
585		 * @since 1.2.6
586		 */
587		loadPayments: function() {
588
589			// Update Total field(s) with latest calculation.
590			$( '.wpforms-payment-total' ).each( function( index, el ) {
591				app.amountTotal( this );
592			} );
593
594			// Credit card validation.
595			if ( typeof $.fn.payment !== 'undefined' ) {
596				$( '.wpforms-field-credit-card-cardnumber' ).payment( 'formatCardNumber' );
597				$( '.wpforms-field-credit-card-cardcvc' ).payment( 'formatCardCVC' );
598			}
599		},
600
601		/**
602		 * Load mailcheck.
603		 *
604		 * @since 1.5.3
605		 */
606		loadMailcheck: function() {
607
608			// Skip loading if `wpforms_mailcheck_enabled` filter return false.
609			if ( ! wpforms_settings.mailcheck_enabled ) {
610				return;
611			}
612
613			// Only load if library exists.
614			if ( typeof $.fn.mailcheck === 'undefined' ) {
615				return;
616			}
617
618			if ( wpforms_settings.mailcheck_domains.length > 0 ) {
619				Mailcheck.defaultDomains = Mailcheck.defaultDomains.concat( wpforms_settings.mailcheck_domains );
620			}
621			if ( wpforms_settings.mailcheck_toplevel_domains.length > 0 ) {
622				Mailcheck.defaultTopLevelDomains = Mailcheck.defaultTopLevelDomains.concat( wpforms_settings.mailcheck_toplevel_domains );
623			}
624
625			// Mailcheck suggestion.
626			$( document ).on( 'blur', '.wpforms-field-email input', function() {
627				var $t = $( this ),
628					id = $t.attr( 'id' );
629
630				$t.mailcheck( {
631					suggested: function( el, suggestion ) {
632						$( '#' + id + '_suggestion' ).remove();
633						var sugg = '<a href="#" class="mailcheck-suggestion" data-id="' + id + '" title="' + wpforms_settings.val_email_suggestion_title + '">' + suggestion.full + '</a>';
634						sugg = wpforms_settings.val_email_suggestion.replace( '{suggestion}', sugg );
635						$( el ).after( '<label class="wpforms-error mailcheck-error" id="' + id + '_suggestion">' + sugg + '</label>' );
636					},
637					empty: function() {
638						$( '#' + id + '_suggestion' ).remove();
639					},
640				} );
641			} );
642
643			// Apply Mailcheck suggestion.
644			$( document ).on( 'click', '.wpforms-field-email .mailcheck-suggestion', function( e ) {
645				var $t = $( this ),
646					id = $t.attr( 'data-id' );
647				e.preventDefault();
648				$( '#' + id ).val( $t.text() );
649				$t.parent().remove();
650			} );
651
652		},
653
654		/**
655		 * Load Choices.js library for all Modern style Dropdown fields (<select>).
656		 *
657		 * @since 1.6.1
658		 */
659		loadChoicesJS: function() {
660
661			// Loads if function exists.
662			if ( ! $.isFunction( window.Choices ) ) {
663
664				return;
665			}
666
667			$( '.wpforms-field-select-style-modern .choicesjs-select, .wpforms-field-payment-select .choicesjs-select' ).each( function( idx, el ) {
668
669				var args = window.wpforms_choicesjs_config || {};
670
671				args.callbackOnInit = function() {
672
673					var self      = this,
674						$element  = $( self.passedElement.element ),
675						$input    = $( self.input.element ),
676						sizeClass = $element.data( 'size-class' );
677
678					// Add CSS-class for size.
679					if ( sizeClass ) {
680						$( self.containerOuter.element ).addClass( sizeClass );
681					}
682
683					/**
684					 * If a multiple select has selected choices - hide a placeholder input.
685					 * We use custom styles like `.screen-reader-text` for it,
686					 * because it avoids an issue with closing a dropdown.
687					 */
688					if ( $element.prop( 'multiple' ) ) {
689
690						// On init event.
691						if ( self.getValue( true ).length ) {
692							$input.addClass( self.config.classNames.input + '--hidden' );
693						}
694
695						// On change event.
696						$element.on( 'change', function() {
697
698							self.getValue( true ).length ? $input.addClass( self.config.classNames.input + '--hidden' ) : $input.removeClass( self.config.classNames.input + '--hidden' );
699						} );
700					}
701				};
702
703				args.callbackOnCreateTemplates = function() {
704
705					var self      = this,
706						$element  = $( self.passedElement.element );
707
708					return {
709
710						// Change default template for option.
711						option: function( item ) {
712
713							var opt = Choices.defaults.templates.option.call( this, item );
714
715							// Add a `.placeholder` class for placeholder option - it needs for WPForm CL.
716							if ( 'undefined' !== typeof item.placeholder && true === item.placeholder ) {
717								opt.classList.add( 'placeholder' );
718							}
719
720							// Add a `data-amount` attribute for payment dropdown.
721							// It will be copy from a Choices.js `data-custom-properties` attribute.
722							if ( $element.hasClass( 'wpforms-payment-price' ) && 'undefined' !== typeof item.customProperties && null !== item.customProperties ) {
723								opt.dataset.amount = item.customProperties;
724							}
725
726							return opt;
727						},
728					};
729				};
730
731				// Save choicesjs instance for future access.
732				$( el ).data( 'choicesjs', new Choices( el, args ) );
733			} );
734		},
735
736		//--------------------------------------------------------------------//
737		// Binds.
738		//--------------------------------------------------------------------//
739
740		/**
741		 * Element bindings.
742		 *
743		 * @since 1.2.3
744		 */
745		bindUIActions: function() {
746
747			// Pagebreak navigation.
748			$( document ).on( 'click', '.wpforms-page-button', function( event ) {
749				event.preventDefault();
750				app.pagebreakNav( this );
751			} );
752
753			// Payments: Update Total field(s) when latest calculation.
754			$( document ).on( 'change input', '.wpforms-payment-price', function() {
755				app.amountTotal( this, true );
756			} );
757
758			// Payments: Restrict user input payment fields.
759			$( document ).on( 'input', '.wpforms-payment-user-input', function() {
760				var $this = $( this ),
761					amount = $this.val();
762				$this.val( amount.replace( /[^0-9.,]/g, '' ) );
763			} );
764
765			// Payments: Sanitize/format user input amounts.
766			$( document ).on( 'focusout', '.wpforms-payment-user-input', function() {
767				var $this     = $( this ),
768					amount    = $this.val(),
769					sanitized = app.amountSanitize( amount ),
770					formatted = app.amountFormat( sanitized );
771				$this.val( formatted );
772			} );
773
774			// Payments: Update Total field(s) when conditials are processed.
775			$( document ).on( 'wpformsProcessConditionals', function( e, el ) {
776				app.amountTotal( el, true );
777			} );
778
779			// Payment radio/checkbox fields: preselect the selected payment (from dynamic/fallback population).
780			$( function() {
781
782				// Radios.
783				$( '.wpforms-field-radio .wpforms-image-choices-item input:checked' ).change();
784				$( '.wpforms-field-payment-multiple .wpforms-image-choices-item input:checked' ).change();
785
786				// Checkboxes.
787				$( '.wpforms-field-checkbox .wpforms-image-choices-item input' ).change();
788				$( '.wpforms-field-payment-checkbox .wpforms-image-choices-item input' ).change();
789			} );
790
791			// Rating field: hover effect.
792			$( '.wpforms-field-rating-item' ).hover(
793				function() {
794					$( this ).parent().find( '.wpforms-field-rating-item' ).removeClass( 'selected hover' );
795					$( this ).prevAll().addBack().addClass( 'hover' );
796				},
797				function() {
798					$( this ).parent().find( '.wpforms-field-rating-item' ).removeClass( 'selected hover' );
799					$( this ).parent().find( 'input:checked' ).parent().prevAll().addBack().addClass( 'selected' );
800				}
801			);
802
803			// Rating field: toggle selected state.
804			$( document ).on( 'change', '.wpforms-field-rating-item input', function() {
805
806				var $this  = $( this ),
807					$wrap  = $this.closest( '.wpforms-field-rating-items' ),
808					$items = $wrap.find( '.wpforms-field-rating-item' );
809
810				$items.removeClass( 'hover selected' );
811				$this.parent().prevAll().addBack().addClass( 'selected' );
812			} );
813
814			// Rating field: preselect the selected rating (from dynamic/fallback population).
815			$( function() {
816				$( '.wpforms-field-rating-item input:checked' ).change();
817			} );
818
819			// Checkbox/Radio/Payment checkbox: make labels keyboard-accessible.
820			$( document ).on( 'keypress', '.wpforms-image-choices-item label', function( event ) {
821				var $this  = $( this ),
822					$field = $this.closest( '.wpforms-field' );
823
824				if ( $field.hasClass( 'wpforms-conditional-hide' ) ) {
825					event.preventDefault();
826					return false;
827				}
828
829				// Cause the input to be clicked when clicking the label.
830				if ( 13 === event.which ) {
831					$( '#' + $this.attr( 'for' ) ).click();
832				}
833			} );
834
835			// IE: Click on the `image choice` image should trigger the click event on the input (checkbox or radio) field.
836			if ( window.document.documentMode ) {
837				$( document ).on( 'click', '.wpforms-image-choices-item img', function() {
838
839					$( this ).closest( 'label' ).find( 'input' ).click();
840				} );
841			}
842
843			$( 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 ) {
844
845				var $this  = $( this ),
846					$field = $this.closest( '.wpforms-field' );
847
848				if ( $field.hasClass( 'wpforms-conditional-hide' ) ) {
849					event.preventDefault();
850					return false;
851				}
852
853				switch ( $this.attr( 'type' ) ) {
854					case 'radio':
855						$this.closest( 'ul' ).find( 'li' ).removeClass( 'wpforms-selected' ).find( 'input[type=radio]' ).removeProp( 'checked' );
856						$this
857							.prop( 'checked', true )
858							.closest( 'li' ).addClass( 'wpforms-selected' );
859						break;
860
861					case 'checkbox':
862						if ( $this.is( ':checked' ) ) {
863							$this.closest( 'li' ).addClass( 'wpforms-selected' );
864							$this.prop( 'checked', true );
865						} else {
866							$this.closest( 'li' ).removeClass( 'wpforms-selected' );
867							$this.prop( 'checked', false );
868						}
869						break;
870				}
871			} );
872
873			// Upload fields: Check combined file size.
874			$( document ).on( 'change', '.wpforms-field-file-upload input[type=file]:not(".dropzone-input")', function() {
875				var $this       = $( this ),
876					$uploads    = $this.closest( 'form.wpforms-form' ).find( '.wpforms-field-file-upload input:not(".dropzone-input")' ),
877					totalSize   = 0,
878					postMaxSize = Number( wpforms_settings.post_max_size ),
879					errorMsg    = '<div class="wpforms-error-container-post_max_size">' + wpforms_settings.val_post_max_size + '</div>',
880					errorCntTpl = '<div class="wpforms-error-container">{errorMsg}</span></div>',
881					$submitCnt  = $this.closest( 'form.wpforms-form' ).find( '.wpforms-submit-container' ),
882					$submitBtn  = $submitCnt.find( 'button.wpforms-submit' ),
883					$errorCnt   = $submitCnt.prev();
884
885				// Calculating totalSize.
886				$uploads.each( function() {
887					var $upload = $( this ),
888						i = 0,
889						len = $upload[0].files.length;
890					for ( ; i < len; i++ ) {
891						totalSize += $upload[0].files[i].size;
892					}
893				} );
894
895				// Checking totalSize.
896				if ( totalSize > postMaxSize ) {
897
898					// Convert sizes to Mb.
899					totalSize = Number( ( totalSize / 1048576 ).toFixed( 3 ) );
900					postMaxSize = Number( ( postMaxSize / 1048576 ).toFixed( 3 ) );
901
902					// Preparing error message.
903					errorMsg = errorMsg.replace( /{totalSize}/, totalSize ).replace( /{maxSize}/, postMaxSize );
904
905					// Output error message.
906					if ( $errorCnt.hasClass( 'wpforms-error-container' ) ) {
907						$errorCnt.find( '.wpforms-error-container-post_max_size' ).remove();
908						$errorCnt.append( errorMsg );
909					} else {
910						$submitCnt.before( errorCntTpl.replace( /{errorMsg}/, errorMsg ) );
911					}
912
913					// Disable submit button.
914					$submitBtn.prop( 'disabled', true );
915				} else {
916
917					// Remove error and release submit button.
918					$errorCnt.find( '.wpforms-error-container-post_max_size' ).remove();
919					$submitBtn.prop( 'disabled', false );
920				}
921
922			} );
923
924			// Number Slider field: update hints.
925			$( document ).on( 'change input', '.wpforms-field-number-slider input[type=range]', function( event ) {
926				var hintEl = $( event.target ).siblings( '.wpforms-field-number-slider-hint' );
927
928				hintEl.html( hintEl.data( 'hint' ).replace( '{value}', '<b>' + event.target.value + '</b>' ) );
929			} );
930
931			// Enter key event.
932			$( document ).on( 'keydown', '.wpforms-form input', function( e ) {
933
934				if ( e.keyCode !== 13 ) {
935					return;
936				}
937
938				var $t = $( this ),
939					$page = $t.closest( '.wpforms-page' );
940
941				if ( $page.length === 0 ) {
942					return;
943				}
944
945				if ( [ 'text', 'tel', 'number', 'email', 'url', 'radio', 'checkbox' ].indexOf( $t.attr( 'type' ) ) < 0 ) {
946					return;
947				}
948
949				if ( $t.hasClass( 'wpforms-datepicker' ) ) {
950					$t.flatpickr( 'close' );
951				}
952
953				e.preventDefault();
954
955				if ( $page.hasClass( 'last' ) ) {
956					$page.closest( '.wpforms-form' ).find( '.wpforms-submit' ).click();
957					return;
958				}
959
960				$page.find( '.wpforms-page-next' ).click();
961			} );
962
963			// Allow only numbers, minus and decimal point to be entered into the Numbers field.
964			$( document ).on( 'keypress', '.wpforms-field-number input', function( e ) {
965
966				return /^[-0-9.]+$/.test( String.fromCharCode( e.keyCode || e.which ) );
967			} );
968		},
969
970		/**
971		 * Scroll to and focus on the field with error.
972		 *
973		 * @since 1.5.8
974		 *
975		 * @param {jQuery} $el Form, container or input element jQuery object.
976		 */
977		scrollToError: function( $el ) {
978
979			if ( $el.length === 0 ) {
980				return;
981			}
982
983			// Look for a field with an error inside an $el.
984			var $field = $el.find( '.wpforms-field.wpforms-has-error' );
985
986			// Look outside in not found inside.
987			if ( $field.length === 0 ) {
988				$field = $el.closest( '.wpforms-field' );
989			}
990
991			if ( $field.length === 0 ) {
992				return;
993			}
994
995			var offset = $field.offset();
996
997			if ( typeof offset === 'undefined' ) {
998				return;
999			}
1000
1001			app.animateScrollTop( offset.top - 75, 750 ).done( function() {
1002				var $error = $field.find( '.wpforms-error' ).first();
1003				if ( app.isFunction( $error.focus ) ) {
1004					$error.focus();
1005				}
1006			} );
1007		},
1008
1009		/**
1010		 * Update Pagebreak navigation.
1011		 *
1012		 * @since 1.2.2
1013		 *
1014		 * @param {jQuery} el jQuery element object.
1015		 */
1016		pagebreakNav: function( el ) {
1017
1018			var $this      = $( el ),
1019				valid      = true,
1020				action     = $this.data( 'action' ),
1021				page       = $this.data( 'page' ),
1022				page2      = page,
1023				next       = page + 1,
1024				prev       = page - 1,
1025				formID     = $this.data( 'formid' ),
1026				$form      = $this.closest( '.wpforms-form' ),
1027				$page      = $form.find( '.wpforms-page-' + page ),
1028				$submit    = $form.find( '.wpforms-submit-container' ),
1029				$indicator = $form.find( '.wpforms-page-indicator' ),
1030				$reCAPTCHA = $form.find( '.wpforms-recaptcha-container' ),
1031				pageScroll = false;
1032
1033			// Page scroll.
1034			// TODO: cleanup this BC with wpform_pageScroll.
1035			if ( false === window.wpforms_pageScroll ) {
1036				pageScroll = false;
1037			} else if ( ! app.empty( window.wpform_pageScroll ) ) {
1038				pageScroll = window.wpform_pageScroll;
1039			} else {
1040				pageScroll = $indicator.data( 'scroll' ) !== 0 ? 75 : false;
1041			}
1042
1043			// Toggling between the pages.
1044			if ( 'next' === action ) {
1045
1046				// Validate.
1047				if ( typeof $.fn.validate !== 'undefined' ) {
1048					$page.find( ':input' ).each( function( index, el ) {
1049						if ( ! $( el ).valid() ) {
1050							valid = false;
1051						}
1052					} );
1053
1054					// Scroll to first/top error on page.
1055					app.scrollToError( $page );
1056				}
1057
1058				// Move to the next page.
1059				if ( valid ) {
1060					page2 = next;
1061					$page.hide();
1062					var $nextPage = $form.find( '.wpforms-page-' + next );
1063					$nextPage.show();
1064					if ( $nextPage.hasClass( 'last' ) ) {
1065						$reCAPTCHA.show();
1066						$submit.show();
1067					}
1068					if ( pageScroll ) {
1069
1070						// Scroll to top of the form.
1071						app.animateScrollTop( $form.offset().top - pageScroll, 750 );
1072					}
1073					$this.trigger( 'wpformsPageChange', [ page2, $form ] );
1074				}
1075			} else if ( 'prev' === action ) {
1076
1077				// Move to the prev page.
1078				page2 = prev;
1079				$page.hide();
1080				$form.find( '.wpforms-page-' + prev ).show();
1081				$reCAPTCHA.hide();
1082				$submit.hide();
1083				if ( pageScroll ) {
1084
1085					// Scroll to the top of the form.
1086					app.animateScrollTop( $form.offset().top - pageScroll );
1087				}
1088				$this.trigger( 'wpformsPageChange', [ page2, $form ] );
1089			}
1090
1091			if ( $indicator ) {
1092				var theme = $indicator.data( 'indicator' ),
1093					color = $indicator.data( 'indicator-color' );
1094				if ( 'connector' === theme || 'circles' === theme ) {
1095					$indicator.find( '.wpforms-page-indicator-page' ).removeClass( 'active' );
1096					$indicator.find( '.wpforms-page-indicator-page-' + page2 ).addClass( 'active' );
1097					$indicator.find( '.wpforms-page-indicator-page-number' ).removeAttr( 'style' );
1098					$indicator.find( '.active .wpforms-page-indicator-page-number' ).css( 'background-color', color );
1099					if ( 'connector' === theme ) {
1100						$indicator.find( '.wpforms-page-indicator-page-triangle' ).removeAttr( 'style' );
1101						$indicator.find( '.active .wpforms-page-indicator-page-triangle' ).css( 'border-top-color', color );
1102					}
1103				} else if ( 'progress' === theme ) {
1104					var $pageTitle = $indicator.find( '.wpforms-page-indicator-page-title' ),
1105						$pageSep   = $indicator.find( '.wpforms-page-indicator-page-title-sep' ),
1106						totalPages = $form.find( '.wpforms-page' ).length,
1107						width = ( page2 / totalPages ) * 100;
1108					$indicator.find( '.wpforms-page-indicator-page-progress' ).css( 'width', width + '%' );
1109					$indicator.find( '.wpforms-page-indicator-steps-current' ).text( page2 );
1110					if ( $pageTitle.data( 'page-' + page2 + '-title' ) ) {
1111						$pageTitle.css( 'display', 'inline' ).text( $pageTitle.data( 'page-' + page2 + '-title' ) );
1112						$pageSep.css( 'display', 'inline' );
1113					} else {
1114						$pageTitle.css( 'display', 'none' );
1115						$pageSep.css( 'display', 'none' );
1116					}
1117				}
1118			}
1119		},
1120
1121		/**
1122		 * OptinMonster compatibility.
1123		 *
1124		 * Re-initialize after OptinMonster loads to accommodate changes that
1125		 * have occurred to the DOM.
1126		 *
1127		 * @since 1.5.0
1128		 */
1129		bindOptinMonster: function() {
1130
1131			// OM v5.
1132			document.addEventListener( 'om.Campaign.load', function( event ) {
1133				app.ready();
1134				app.optinMonsterRecaptchaReset( event.detail.Campaign.data.id );
1135			} );
1136
1137			// OM Legacy.
1138			$( document ).on( 'OptinMonsterOnShow', function( event, data, object ) {
1139				app.ready();
1140				app.optinMonsterRecaptchaReset( data.optin );
1141			} );
1142		},
1143
1144		/**
1145		 * Reset/recreate reCAPTCHA v2 inside OptinMonster.
1146		 *
1147		 * @since 1.5.0
1148		 */
1149		optinMonsterRecaptchaReset: function( optinId ) {
1150
1151			var $form               = $( '#om-' + optinId ).find( '.wpforms-form' ),
1152				$recaptchaContainer = $form.find( '.wpforms-recaptcha-container' ),
1153				$recaptcha          = $form.find( '.g-recaptcha' ),
1154				recaptchaSiteKey    = $recaptcha.attr( 'data-sitekey' ),
1155				recaptchaID         = 'recaptcha-' + Date.now();
1156
1157			if ( $form.length && $recaptcha.length ) {
1158
1159				$recaptcha.remove();
1160				$recaptchaContainer.prepend( '<div class="g-recaptcha" id="' + recaptchaID + '" data-sitekey="' + recaptchaSiteKey + '"></div>' );
1161
1162				grecaptcha.render(
1163					recaptchaID,
1164					{
1165						sitekey: recaptchaSiteKey,
1166						callback: function() {
1167							wpformsRecaptchaCallback( $( '#' + recaptchaID ) );
1168						},
1169					}
1170				);
1171			}
1172		},
1173
1174		//--------------------------------------------------------------------//
1175		// Other functions.
1176		//--------------------------------------------------------------------//
1177
1178		/**
1179		 * Payments: Calculate total.
1180		 *
1181		 * @since 1.2.3
1182		 * @since 1.5.1 Added support for payment-checkbox field.
1183		 */
1184		amountTotal: function( el, validate ) {
1185
1186			validate = validate || false;
1187
1188			var $form = $( el ).closest( '.wpforms-form' ),
1189				total = 0,
1190				totalFormatted,
1191				totalFormattedSymbol,
1192				currency = app.getCurrency();
1193
1194			$( '.wpforms-payment-price', $form ).each( function( index, el ) {
1195
1196				var amount = 0,
1197					$this = $( this );
1198
1199				if ( $this.closest( '.wpforms-field-payment-single' ).hasClass( 'wpforms-conditional-hide' ) ) {
1200					return;
1201				}
1202				if ( 'text' === $this.attr( 'type' ) || 'hidden' === $this.attr( 'type' ) ) {
1203					amount = $this.val();
1204				} else if ( ( 'radio' === $this.attr( 'type' ) || 'checkbox' === $this.attr( 'type' ) ) && $this.is( ':checked' ) ) {
1205					amount = $this.data( 'amount' );
1206				} else if ( $this.is( 'select' ) && $this.find( 'option:selected' ).length > 0 ) {
1207					amount = $this.find( 'option:selected' ).data( 'amount' );
1208				}
1209				if ( ! app.empty( amount ) ) {
1210					amount = app.amountSanitize( amount );
1211					total = Number( total ) + Number( amount );
1212				}
1213			} );
1214
1215			totalFormatted = app.amountFormat( total );
1216
1217			if ( 'left' === currency.symbol_pos ) {
1218				totalFormattedSymbol = currency.symbol + ' ' + totalFormatted;
1219			} else {
1220				totalFormattedSymbol = totalFormatted + ' ' + currency.symbol;
1221			}
1222
1223			$form.find( '.wpforms-payment-total' ).each( function( index, el ) {
1224				if ( 'hidden' === $( this ).attr( 'type' ) || 'text' === $( this ).attr( 'type' ) ) {
1225					$( this ).val( totalFormattedSymbol );
1226					if ( 'text' === $( this ).attr( 'type' ) && validate && $form.data( 'validator' ) ) {
1227						$( this ).valid();
1228					}
1229				} else {
1230					$( this ).text( totalFormattedSymbol );
1231				}
1232			} );
1233		},
1234
1235		/**
1236		 * Sanitize amount and convert to standard format for calculations.
1237		 *
1238		 * @since 1.2.6
1239		 */
1240		amountSanitize: function( amount ) {
1241
1242			var currency = app.getCurrency();
1243
1244			amount = amount.toString().replace( /[^0-9.,]/g, '' );
1245
1246			if ( ',' === currency.decimal_sep && ( amount.indexOf( currency.decimal_sep ) !== -1 ) ) {
1247				if ( '.' === currency.thousands_sep && amount.indexOf( currency.thousands_sep ) !== -1 ) {
1248					amount = amount.replace( currency.thousands_sep, '' );
1249				} else if ( '' === currency.thousands_sep && amount.indexOf( '.' ) !== -1 ) {
1250					amount = amount.replace( '.', '' );
1251				}
1252				amount = amount.replace( currency.decimal_sep, '.' );
1253			} else if ( ',' === currency.thousands_sep && ( amount.indexOf( currency.thousands_sep ) !== -1 ) ) {
1254				amount = amount.replace( currency.thousands_sep, '' );
1255			}
1256
1257			return app.numberFormat( amount, 2, '.', '' );
1258		},
1259
1260		/**
1261		 * Format amount.
1262		 *
1263		 * @since 1.2.6
1264		 */
1265		amountFormat: function( amount ) {
1266
1267			var currency = app.getCurrency();
1268
1269			amount = String( amount );
1270
1271			// Format the amount
1272			if ( ',' === currency.decimal_sep && ( amount.indexOf( currency.decimal_sep ) !== -1 ) ) {
1273				var sepFound = amount.indexOf( currency.decimal_sep ),
1274					whole    = amount.substr( 0, sepFound ),
1275					part     = amount.substr( sepFound + 1, amount.strlen - 1 );
1276				amount = whole + '.' + part;
1277			}
1278
1279			// Strip , from the amount (if set as the thousands separator)
1280			if ( ',' === currency.thousands_sep && ( amount.indexOf( currency.thousands_sep ) !== -1 ) ) {
1281				amount = amount.replace( ',', '' );
1282			}
1283
1284			if ( app.empty( amount ) ) {
1285				amount = 0;
1286			}
1287
1288			return app.numberFormat( amount, 2, currency.decimal_sep, currency.thousands_sep );
1289		},
1290
1291		/**
1292		 * Get site currency settings.
1293		 *
1294		 * @since 1.2.6
1295		 */
1296		getCurrency: function() {
1297
1298			var currency = {
1299				code: 'USD',
1300				thousands_sep: ',',
1301				decimal_sep: '.',
1302				symbol: '$',
1303				symbol_pos: 'left',
1304			};
1305
1306			// Backwards compatibility.
1307			if ( typeof wpforms_settings.currency_code !== 'undefined' ) {
1308				currency.code = wpforms_settings.currency_code;
1309			}
1310			if ( typeof wpforms_settings.currency_thousands !== 'undefined' ) {
1311				currency.thousands_sep = wpforms_settings.currency_thousands;
1312			}
1313			if ( typeof wpforms_settings.currency_decimal !== 'undefined' ) {
1314				currency.decimal_sep = wpforms_settings.currency_decimal;
1315			}
1316			if ( typeof wpforms_settings.currency_symbol !== 'undefined' ) {
1317				currency.symbol = wpforms_settings.currency_symbol;
1318			}
1319			if ( typeof wpforms_settings.currency_symbol_pos !== 'undefined' ) {
1320				currency.symbol_pos = wpforms_settings.currency_symbol_pos;
1321			}
1322
1323			return currency;
1324		},
1325
1326		/**
1327		 * Format number.
1328		 *
1329		 * @link http://locutus.io/php/number_format/
1330		 * @since 1.2.6
1331		 */
1332		numberFormat: function( number, decimals, decimalSep, thousandsSep ) {
1333
1334			number = ( number + '' ).replace( /[^0-9+\-Ee.]/g, '' );
1335			var n = ! isFinite( +number ) ? 0 : +number;
1336			var prec = ! isFinite( +decimals ) ? 0 : Math.abs( decimals );
1337			var sep = ( 'undefined' === typeof thousandsSep ) ? ',' : thousandsSep;
1338			var dec = ( 'undefined' === typeof decimalSep ) ? '.' : decimalSep;
1339			var s;
1340
1341			var toFixedFix = function( n, prec ) {
1342				var k = Math.pow( 10, prec );
1343				return '' + ( Math.round( n * k ) / k ).toFixed( prec );
1344			};
1345
1346			// @todo: for IE parseFloat(0.55).toFixed(0) = 0;
1347			s = ( prec ? toFixedFix( n, prec ) : '' + Math.round( n ) ).split( '.' );
1348			if ( s[0].length > 3 ) {
1349				s[0] = s[0].replace( /\B(?=(?:\d{3})+(?!\d))/g, sep );
1350			}
1351			if ( ( s[1] || '' ).length < prec ) {
1352				s[1] = s[1] || '';
1353				s[1] += new Array( prec - s[1].length + 1 ).join( '0' );
1354			}
1355
1356			return s.join( dec );
1357		},
1358
1359		/**
1360		 * Empty check similar to PHP.
1361		 *
1362		 * @link http://locutus.io/php/empty/
1363		 * @since 1.2.6
1364		 */
1365		empty: function( mixedVar ) {
1366
1367			var undef;
1368			var key;
1369			var i;
1370			var len;
1371			var emptyValues = [ undef, null, false, 0, '', '0' ];
1372
1373			for ( i = 0, len = emptyValues.length; i < len; i++ ) {
1374				if ( mixedVar === emptyValues[i] ) {
1375					return true;
1376				}
1377			}
1378
1379			if ( 'object' === typeof mixedVar ) {
1380				for ( key in mixedVar ) {
1381					if ( mixedVar.hasOwnProperty( key ) ) {
1382						return false;
1383					}
1384				}
1385				return true;
1386			}
1387
1388			return false;
1389		},
1390
1391		/**
1392		 * Set cookie container user UUID.
1393		 *
1394		 * @since 1.3.3
1395		 */
1396		setUserIndentifier: function() {
1397
1398			if ( ( ( ! window.hasRequiredConsent && typeof wpforms_settings !== 'undefined' && wpforms_settings.uuid_cookie ) || ( window.hasRequiredConsent && window.hasRequiredConsent() ) ) && ! app.getCookie( '_wpfuuid' ) ) {
1399
1400				// Generate UUID - http://stackoverflow.com/a/873856/1489528
1401				var s         = new Array( 36 ),
1402					hexDigits = '0123456789abcdef',
1403					uuid;
1404
1405				for ( var i = 0; i < 36; i++ ) {
1406					s[i] = hexDigits.substr( Math.floor( Math.random() * 0x10 ), 1 );
1407				}
1408				s[14] = '4';
1409				s[19] = hexDigits.substr( ( s[19] & 0x3 ) | 0x8, 1 );
1410				s[8]  = s[13] = s[18] = s[23] = '-';
1411
1412				uuid = s.join( '' );
1413
1414				app.createCookie( '_wpfuuid', uuid, 3999 );
1415			}
1416		},
1417
1418		/**
1419		 * Create cookie.
1420		 *
1421		 * @since 1.3.3
1422		 *
1423		 * @param {string} name  Cookie name.
1424		 * @param {string} value Cookie value.
1425		 * @param {string} days  Whether it should expire and when.
1426		 */
1427		createCookie: function( name, value, days ) {
1428
1429			var expires = '';
1430
1431			// If we have a days value, set it in the expiry of the cookie.
1432			if ( days ) {
1433
1434				// If -1 is our value, set a session based cookie instead of a persistent cookie.
1435				if ( '-1' === days ) {
1436					expires = '';
1437				} else {
1438					var date = new Date();
1439					date.setTime( date.getTime() + ( days * 24 * 60 * 60 * 1000 ) );
1440					expires = '; expires=' + date.toGMTString();
1441				}
1442			} else {
1443				expires = '; expires=Thu, 01 Jan 1970 00:00:01 GMT';
1444			}
1445
1446			// Write the cookie.
1447			document.cookie = name + '=' + value + expires + '; path=/; samesite=strict';
1448		},
1449
1450		/**
1451		 * Retrieve cookie.
1452		 *
1453		 * @since 1.3.3
1454		 *
1455		 * @param {string} name Cookie name.
1456		 *
1457		 * @returns {string|null} Cookie value or null when it doesn't exist.
1458		 */
1459		getCookie: function( name ) {
1460
1461			var nameEQ = name + '=',
1462				ca     = document.cookie.split( ';' );
1463
1464			for ( var i = 0; i < ca.length; i++ ) {
1465				var c = ca[i];
1466				while ( ' ' === c.charAt( 0 ) ) {
1467					c = c.substring( 1, c.length );
1468				}
1469				if ( 0 === c.indexOf( nameEQ ) ) {
1470					return c.substring( nameEQ.length, c.length );
1471				}
1472			}
1473
1474			return null;
1475		},
1476
1477		/**
1478		 * Delete cookie.
1479		 *
1480		 * @since 1.3.3
1481		 *
1482		 * @param {string} name Cookie name.
1483		 */
1484		removeCookie: function( name ) {
1485
1486			app.createCookie( name, '', -1 );
1487		},
1488
1489		/**
1490		 * Get user browser preferred language.
1491		 *
1492		 * @since 1.5.2
1493		 *
1494		 * @returns {String} Language code.
1495		 */
1496		getFirstBrowserLanguage: function() {
1497			var nav = window.navigator,
1498				browserLanguagePropertyKeys = [ 'language', 'browserLanguage', 'systemLanguage', 'userLanguage' ],
1499				i,
1500				language;
1501
1502			// Support for HTML 5.1 "navigator.languages".
1503			if ( Array.isArray( nav.languages ) ) {
1504				for ( i = 0; i < nav.languages.length; i++ ) {
1505					language = nav.languages[ i ];
1506					if ( language && language.length ) {
1507						return language;
1508					}
1509				}
1510			}
1511
1512			// Support for other well known properties in browsers.
1513			for ( i = 0; i < browserLanguagePropertyKeys.length; i++ ) {
1514				language = nav[ browserLanguagePropertyKeys[ i ] ];
1515				if ( language && language.length ) {
1516					return language;
1517				}
1518			}
1519
1520			return '';
1521		},
1522
1523		/**
1524		 * Asynchronously fetches country code using current IP
1525		 * and executes a callback provided with a country code parameter.
1526		 *
1527		 * @since 1.5.2
1528		 *
1529		 * @param {Function} callback Executes once the fetch is completed.
1530		 */
1531		currentIpToCountry: function( callback ) {
1532
1533			var fallback = function() {
1534
1535				$.get( 'https://ipapi.co/jsonp', function() {}, 'jsonp' )
1536					.always( function( resp ) {
1537						var countryCode = ( resp && resp.country ) ? resp.country : '';
1538						if ( ! countryCode ) {
1539							var lang = app.getFirstBrowserLanguage();
1540							countryCode = lang.indexOf( '-' ) > -1 ? lang.split( '-' ).pop() : '';
1541						}
1542						callback( countryCode );
1543					} );
1544			};
1545
1546			$.get( 'https://geo.wpforms.com/v3/geolocate/json' )
1547				.done( function( resp ) {
1548					if ( resp && resp.country_iso ) {
1549						callback( resp.country_iso );
1550					} else {
1551						fallback();
1552					}
1553				} )
1554				.fail( function( resp ) {
1555					fallback();
1556				} );
1557		},
1558
1559		/**
1560		 * Form submit.
1561		 *
1562		 * @since 1.5.3
1563		 *
1564		 * @param {jQuery} $form Form element.
1565		 */
1566		formSubmit: function( $form ) {
1567
1568			$form.trigger( 'wpformsBeforeFormSubmit' );
1569
1570			if ( $form.hasClass( 'wpforms-ajax-form' ) && typeof FormData !== 'undefined' ) {
1571				app.formSubmitAjax( $form );
1572			} else {
1573				app.formSubmitNormal( $form );
1574			}
1575		},
1576
1577		/**
1578		 * Normal form submit with page reload.
1579		 *
1580		 * @since 1.5.3
1581		 *
1582		 * @param {jQuery} $form Form element.
1583		 */
1584		formSubmitNormal: function( $form ) {
1585
1586			if ( ! $form.length ) {
1587				return;
1588			}
1589
1590			var $submit     = $form.find( '.wpforms-submit' ),
1591				recaptchaID = $submit.get( 0 ).recaptchaID;
1592
1593			if ( ! app.empty( recaptchaID ) || recaptchaID === 0 ) {
1594				$submit.get( 0 ).recaptchaID = false;
1595			}
1596
1597			$form.get( 0 ).submit();
1598		},
1599
1600		/**
1601		 * Reset form recaptcha.
1602		 *
1603		 * @since 1.5.3
1604		 *
1605		 * @param {jQuery} $form Form element.
1606		 */
1607		resetFormRecaptcha: function( $form ) {
1608
1609			if ( ! $form || ! $form.length ) {
1610				return;
1611			}
1612
1613			if ( typeof grecaptcha === 'undefined' ) {
1614				return;
1615			}
1616
1617			var recaptchaID;
1618
1619			// Check for invisible recaptcha first.
1620			recaptchaID = $form.find( '.wpforms-submit' ).get( 0 ).recaptchaID;
1621
1622			// Check for v2 recaptcha if invisible recaptcha is not found.
1623			if ( app.empty( recaptchaID ) && recaptchaID !== 0 ) {
1624				recaptchaID = $form.find( '.g-recaptcha' ).data( 'recaptcha-id' );
1625			}
1626
1627			// Reset recaptcha.
1628			if ( ! app.empty( recaptchaID ) || recaptchaID === 0 ) {
1629				grecaptcha.reset( recaptchaID );
1630			}
1631		},
1632
1633		/**
1634		 * Console log AJAX error.
1635		 *
1636		 * @since 1.5.3
1637		 *
1638		 * @param {string} error Error text (optional).
1639		 */
1640		consoleLogAjaxError: function( error ) {
1641
1642			if ( error ) {
1643				console.error( 'WPForms AJAX submit error:\n%s', error ); // eslint-disable-line no-console
1644			} else {
1645				console.error( 'WPForms AJAX submit error' ); // eslint-disable-line no-console
1646			}
1647		},
1648
1649		/**
1650		 * Display form AJAX errors.
1651		 *
1652		 * @since 1.5.3
1653		 *
1654		 * @param {jQuery} $form Form element.
1655		 * @param {object} errors Errors in format { general: { generalErrors }, field: { fieldErrors } }.
1656		 */
1657		displayFormAjaxErrors: function( $form, errors ) {
1658
1659			if ( 'string' === typeof errors ) {
1660				app.displayFormAjaxGeneralErrors( $form, errors );
1661				return;
1662			}
1663
1664			errors = errors && ( 'errors' in errors ) ? errors.errors : null;
1665
1666			if ( app.empty( errors ) || ( app.empty( errors.general ) && app.empty( errors.field ) ) ) {
1667				app.consoleLogAjaxError();
1668				return;
1669			}
1670
1671			if ( ! app.empty( errors.general ) ) {
1672				app.displayFormAjaxGeneralErrors( $form, errors.general );
1673			}
1674
1675			if ( ! app.empty( errors.field ) ) {
1676				app.displayFormAjaxFieldErrors( $form, errors.field );
1677			}
1678		},
1679
1680		/**
1681		 * Display form AJAX general errors that cannot be displayed using jQuery Validation plugin.
1682		 *
1683		 * @since 1.5.3
1684		 *
1685		 * @param {jQuery} $form Form element.
1686		 * @param {object} errors Errors in format { errorType: errorText }.
1687		 */
1688		displayFormAjaxGeneralErrors: function( $form, errors ) {
1689
1690			if ( ! $form || ! $form.length ) {
1691				return;
1692			}
1693
1694			if ( app.empty( errors ) ) {
1695				return;
1696			}
1697
1698			// Safety net for random errors thrown by a third-party code. Should never be used intentionally.
1699			if ( 'string' === typeof errors ) {
1700				$form.find( '.wpforms-submit-container' ).before( '<div class="wpforms-error-container">' + errors + '</div>' );
1701				return;
1702			}
1703
1704			$.each( errors, function( type, html ) {
1705				switch ( type ) {
1706					case 'header':
1707						$form.prepend( html );
1708						break;
1709					case 'footer':
1710						$form.find( '.wpforms-submit-container' ).before( html );
1711						break;
1712					case 'recaptcha':
1713						$form.find( '.wpforms-recaptcha-container' ).append( html );
1714						break;
1715				}
1716			} );
1717		},
1718
1719		/**
1720		 * Clear forms AJAX general errors that cannot be cleared using jQuery Validation plugin.
1721		 *
1722		 * @since 1.5.3
1723		 *
1724		 * @param {jQuery} $form Form element.
1725		 */
1726		clearFormAjaxGeneralErrors: function( $form ) {
1727
1728			$form.find( '.wpforms-error-container' ).remove();
1729			$form.find( '#wpforms-field_recaptcha-error' ).remove();
1730		},
1731
1732		/**
1733		 * Display form AJAX field errors using jQuery Validation plugin.
1734		 *
1735		 * @since 1.5.3
1736		 *
1737		 * @param {jQuery} $form Form element.
1738		 * @param {object} errors Errors in format { fieldName: errorText }.
1739		 */
1740		displayFormAjaxFieldErrors: function( $form, errors ) {
1741
1742			if ( ! $form || ! $form.length ) {
1743				return;
1744			}
1745
1746			if ( app.empty( errors ) ) {
1747				return;
1748			}
1749
1750			var validator = $form.data( 'validator' );
1751
1752			if ( ! validator ) {
1753				return;
1754			}
1755
1756			validator.showErrors( errors );
1757			validator.focusInvalid();
1758		},
1759
1760		/**
1761		 * Submit a form using AJAX.
1762		 *
1763		 * @since 1.5.3
1764		 *
1765		 * @param {jQuery} $form Form element.
1766		 *
1767		 * @returns {JQueryXHR|JQueryDeferred} Promise like object for async callbacks.
1768		 */
1769		formSubmitAjax: function( $form ) {
1770
1771			if ( ! $form.length ) {
1772				return $.Deferred().reject(); // eslint-disable-line new-cap
1773			}
1774
1775			var $container = $form.closest( '.wpforms-container' ),
1776				$spinner = $form.find( '.wpforms-submit-spinner' ),
1777				$confirmationScroll,
1778				formData,
1779				args;
1780
1781			$container.css( 'opacity', 0.6 );
1782			$spinner.show();
1783
1784			app.clearFormAjaxGeneralErrors( $form );
1785
1786			formData = new FormData( $form.get( 0 ) );
1787			formData.append( 'action', 'wpforms_submit' );
1788			formData.append( 'page_url', window.location.href );
1789
1790			args = {
1791				type       : 'post',
1792				dataType   : 'json',
1793				url        : wpforms_settings.ajaxurl,
1794				data       : formData,
1795				cache      : false,
1796				contentType: false,
1797				processData: false,
1798			};
1799
1800			args.success = function( json ) {
1801
1802				if ( ! json ) {
1803					app.consoleLogAjaxError();
1804					return;
1805				}
1806
1807				if ( json.data && json.data.action_required ) {
1808					$form.trigger( 'wpformsAjaxSubmitActionRequired', json );
1809					return;
1810				}
1811
1812				if ( ! json.success ) {
1813					app.resetFormRecaptcha( $form );
1814					app.displayFormAjaxErrors( $form, json.data );
1815					$form.trigger( 'wpformsAjaxSubmitFailed', json );
1816					return;
1817				}
1818
1819				$form.trigger( 'wpformsAjaxSubmitSuccess', json );
1820
1821				if ( ! json.data ) {
1822					return;
1823				}
1824
1825				if ( json.data.redirect_url ) {
1826					$form.trigger( 'wpformsAjaxSubmitBeforeRedirect', json );
1827					window.location = json.data.redirect_url;
1828					return;
1829				}
1830
1831				if ( json.data.confirmation ) {
1832					$container.html( json.data.confirmation );
1833					$confirmationScroll = $container.find( 'div.wpforms-confirmation-scroll' );
1834					if ( $confirmationScroll.length ) {
1835						app.animateScrollTop( $confirmationScroll.offset().top - 100 );
1836					}
1837				}
1838			};
1839
1840			args.error = function( jqHXR, textStatus, error ) {
1841
1842				app.consoleLogAjaxError( error );
1843
1844				$form.trigger( 'wpformsAjaxSubmitError', [ jqHXR, textStatus, error ] );
1845			};
1846
1847			args.complete = function( jqHXR, textStatus ) {
1848
1849				// Do not make form active if the action is required.
1850				if ( jqHXR.responseJSON && jqHXR.responseJSON.data && jqHXR.responseJSON.data.action_required ) {
1851					return;
1852				}
1853
1854				var $submit     = $form.find( '.wpforms-submit' ),
1855					submitText  = $submit.data( 'submit-text' );
1856
1857				if ( submitText ) {
1858					$submit.text( submitText );
1859				}
1860				$submit.prop( 'disabled', false );
1861
1862				$container.css( 'opacity', '' );
1863				$spinner.hide();
1864
1865				$form.trigger( 'wpformsAjaxSubmitCompleted', [ jqHXR, textStatus ] );
1866			};
1867
1868			$form.trigger( 'wpformsAjaxBeforeSubmit' );
1869
1870			return $.ajax( args );
1871		},
1872
1873		/**
1874		 * Scroll to position with animation.
1875		 *
1876		 * @since 1.5.3
1877		 *
1878		 * @param {number} position Position (in pixels) to scroll to,
1879		 * @param {number} duration Animation duration.
1880		 * @param {Function} complete Function to execute after animation is complete.
1881		 *
1882		 * @returns {JQueryPromise} Promise object for async callbacks.
1883		 */
1884		animateScrollTop: function( position, duration, complete ) {
1885
1886			duration = duration || 1000;
1887			complete = app.isFunction( complete ) ? complete : function() {};
1888			return $( 'html, body' ).animate( { scrollTop: parseInt( position, 10 ) }, { duration: duration, complete: complete } ).promise();
1889		},
1890
1891		/**
1892		 * Check if object is a function.
1893		 *
1894		 * @since 1.5.8
1895		 *
1896		 * @param {mixed} object Object to check if it is function.
1897		 *
1898		 * @returns {boolean} True if object is a function.
1899		 */
1900		isFunction: function( object ) {
1901
1902			return !! ( object && object.constructor && object.call && object.apply );
1903		},
1904	};
1905
1906	return app;
1907
1908}( document, window, jQuery ) );
1909
1910// Initialize.
1911wpforms.init();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.