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