PageSourceSearch

https://lawstreetmedia.com/wp-content/plugins/wpforms/assets/js/wpforms.js?ver=1.5.6.2

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