PageSourceSearch

https://hydrogen-central.com/wp-content/plugins/11wpforms1/assets/js/wpforms.js?ver=1.6.6

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