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