PageSourceSearch

https://www.youtiantech.com/wp-content/plugins/wpforms/assets/js/wpforms.js?ver=1.6.5

js youtiantech.com collected 2026-10-02 01:51:16 UTC 64,094 bytes, 2,144 lines download raw bytes

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