PageSourceSearch

https://www.lillotheworker.com/wp-content/plugins/wpforms-lite/assets/js/wpforms.js?ver=1.5.3.1

js lillotheworker.com collected 2026-10-02 16:03:33 UTC 48,601 bytes, 1,626 lines download raw bytes

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