PageSourceSearch

https://scienceinyoruba.org/wp-content/plugins/wpforms-lite/assets/js/wpforms.js?ver=1.6.2.3

js scienceinyoruba.org collected 2026-10-07 00:11:15 UTC 58,982 bytes, 1,942 lines download raw bytes

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