PageSourceSearch

https://kai4u.co.nz/online-order/wp-content/plugins/wpforms-lite/assets/js/wpforms.js?ver=1.6.0.1

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