PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.16.3
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.16.3
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
← All changes | js/formidable.js +717 -539 6.3.1 → 6.16.3 View file →
@@ -1,84 +1,44 @@
1 -/* exported frmRecaptcha, frmAfterRecaptcha, frmUpdateField, frmDeleteEntry, frmOnSubmit, frm_resend_email */
1 +/* exported frmRecaptcha, frmAfterRecaptcha */
2 +/* eslint-disable prefer-const */
2 3
3 -var frmFrontForm;
4 -
5 4 function frmFrontFormJS() {
6 5 'use strict';
7 6
8 - /*global jQuery:false, frm_js, grecaptcha, frmProForm, tinyMCE */
7 + /*global jQuery:false, frm_js, grecaptcha, hcaptcha, turnstile, frmProForm, tinyMCE */
9 8 /*global frmThemeOverride_jsErrors, frmThemeOverride_frmPlaceError, frmThemeOverride_frmAfterSubmit */
10 9
11 - var action = '';
12 - var jsErrors = [];
10 + let jsErrors = [];
13 11
14 12 /**
15 - * Maybe add polyfills.
16 - *
17 - * @since 5.4
18 - */
19 - function maybeAddPolyfills() {
20 - var i;
21 - if ( ! Element.prototype.matches ) {
22 - // IE9 supports matches but as msMatchesSelector instead.
23 - Element.prototype.matches = Element.prototype.msMatchesSelector;
24 - }
25 -
26 - if ( ! Element.prototype.closest ) {
27 - Element.prototype.closest = function( s ) {
28 - var el = this;
29 -
30 - do {
31 - if ( el.matches( s ) ) {
32 - return el;
33 - }
34 - el = el.parentElement || el.parentNode;
35 - } while ( el !== null && el.nodeType === 1 );
36 -
37 - return null;
38 - };
39 - }
40 -
41 - // NodeList.forEach().
42 - if ( window.NodeList && ! NodeList.prototype.forEach ) {
43 - NodeList.prototype.forEach = function( callback, thisArg ) {
44 - thisArg = thisArg || window;
45 - for ( i = 0; i < this.length; i++ ) {
46 - callback.call( thisArg, this[ i ], i, this );
47 - }
48 - };
49 - }
50 - }
51 -
52 - /**
53 13 * Triggers custom JS event.
54 14 *
55 15 * @since 5.5.3
56 16 *
57 17 * @param {HTMLElement} el The HTML element.
58 - * @param {String} eventName Event name.
18 + * @param {string} eventName Event name.
59 19 * @param {mixed} data The passed data.
60 20 */
61 21 function triggerCustomEvent( el, eventName, data ) {
62 - var event;
63 -
64 - if ( typeof window.CustomEvent === 'function' ) {
65 - event = new CustomEvent( eventName );
66 - } else if ( document.createEvent ) {
67 - event = document.createEvent( 'HTMLEvents' );
68 - event.initEvent( eventName, false, true );
69 - } else {
22 + if ( typeof window.CustomEvent !== 'function' ) {
70 23 return;
71 24 }
72 25
26 + const event = new CustomEvent( eventName );
73 27 event.frmData = data;
74 28
75 29 el.dispatchEvent( event );
76 30 }
77 31
78 - /* Get the ID of the field that changed*/
32 + /**
33 + * Get the ID of the field that changed.
34 + *
35 + * @param {HTMLElement|jQuery} field
36 + * @param {boolean} fullID
37 + * @return {string|number} Field ID.
38 + */
79 39 function getFieldId( field, fullID ) {
80 - var nameParts, fieldId,
40 + let nameParts, fieldId,
81 41 isRepeating = false,
82 42 fieldName = '';
83 43 if ( field instanceof jQuery ) {
84 44 fieldName = field.attr( 'name' );
@@ -126,9 +86,9 @@
126 86 return 0;
127 87 }
128 88
129 89 // Check if 'this' is in a repeating section
130 - if ( jQuery( 'input[name="item_meta[' + fieldId + '][form]"]' ).length ) {
90 + if ( document.querySelector( 'input[name="item_meta[' + fieldId + '][form]"]' ) ) {
131 91
132 92 // this is a repeatable section with name: item_meta[repeating-section-id][row-id][field-id]
133 93 fieldId = nameParts[2].replace( '[', '' );
134 94 isRepeating = true;
@@ -161,12 +121,12 @@
161 121 * Disable the submit button for a given jQuery form object
162 122 *
163 123 * @since 2.03.02
164 124 *
165 - * @param {object} $form
125 + * @param {Object} $form
166 126 */
167 127 function disableSubmitButton( $form ) {
168 - $form.find( 'input[type="submit"], input[type="button"], button[type="submit"]' ).attr( 'disabled', 'disabled' );
128 + $form.find( 'input[type="submit"], input[type="button"], button[type="submit"], button.frm_save_draft' ).attr( 'disabled', 'disabled' );
169 129 }
170 130
171 131 /**
172 132 * Enable the submit button for a given jQuery form object
@@ -172,9 +132,9 @@
172 132 * Enable the submit button for a given jQuery form object
173 133 *
174 134 * @since 2.03.02
175 135 *
176 - * @param {object} $form
136 + * @param {Object} $form
177 137 */
178 138 function enableSubmitButton( $form ) {
179 139 $form.find( 'input[type="submit"], input[type="button"], button[type="submit"]' ).prop( 'disabled', false );
180 140 }
@@ -183,9 +143,9 @@
183 143 * Disable the save draft link for a given jQuery form object
184 144 *
185 145 * @since 4.04.03
186 146 *
187 - * @param {object} $form
147 + * @param {Object} $form
188 148 */
189 149 function disableSaveDraft( $form ) {
190 150 $form.find( 'a.frm_save_draft' ).css( 'pointer-events', 'none' );
191 151 }
@@ -194,71 +154,141 @@
194 154 * Enable the save draft link for a given jQuery form object
195 155 *
196 156 * @since 4.04.03
197 157 *
198 - * @param {object} $form
158 + * @param {jQuery} $form
199 159 */
200 160 function enableSaveDraft( $form ) {
201 - $form.find( 'a.frm_save_draft' ).css( 'pointer-events', '' );
161 + if ( ! $form.length ) {
162 + return;
163 + }
164 + $form[0].querySelectorAll( '.frm_save_draft' ).forEach( saveDraftButton => {
165 + saveDraftButton.disabled = false;
166 + saveDraftButton.style.pointerEvents = '';
167 + });
202 168 }
203 169
170 + /**
171 + * Validate form with JS.
172 + *
173 + * @param {HTMLElement|jQuery} object
174 + * @return {Array} Errors.
175 + */
204 176 function validateForm( object ) {
205 - var r, rl, n, nl, fields, field, value, requiredFields,
206 - errors = [];
177 + let errors, n, nl, fields, field;
207 178
208 - // Make sure required text field is filled in
209 - requiredFields = jQuery( object ).find(
210 - '.frm_required_field:visible input, .frm_required_field:visible select, .frm_required_field:visible textarea'
211 - ).filter( ':not(.frm_optional)' );
212 - if ( requiredFields.length ) {
213 - for ( r = 0, rl = requiredFields.length; r < rl; r++ ) {
214 - if ( hasClass( requiredFields[r], 'ed_button' ) ) {
215 - // skip rich text field buttons.
216 - continue;
179 + errors = [];
180 +
181 + const vanillaJsObject = 'function' === typeof object.get ? object.get( 0 ) : object;
182 +
183 + // Required field validation.
184 + vanillaJsObject?.querySelectorAll( '.frm_required_field' ).forEach(
185 + requiredField => {
186 + const isVisible = requiredField.offsetParent !== null;
187 + if ( ! isVisible ) {
188 + return;
217 189 }
218 - errors = checkRequiredField( requiredFields[r], errors );
190 +
191 + requiredField.querySelectorAll( 'input, select, textarea' ).forEach(
192 + requiredInput => {
193 + if ( hasClass( requiredInput, 'frm_optional' ) || hasClass( requiredInput, 'ed_button' ) ) {
194 + // skip rich text field buttons.
195 + return;
196 + }
197 +
198 + errors = checkRequiredField( requiredInput, errors );
199 + }
200 + );
219 201 }
220 - }
202 + );
221 203
222 204 fields = jQuery( object ).find( 'input,select,textarea' );
223 205 if ( fields.length ) {
224 206 for ( n = 0, nl = fields.length; n < nl; n++ ) {
225 207 field = fields[n];
226 - if ( '' !== field.value ) {
227 - validateFieldValue( field, errors );
208 + if ( '' === field.value ) {
209 + if ( 'number' === field.type ) {
210 + // A number field will return an empty string when it is invalid.
211 + checkValidity( field, errors );
212 + }
213 +
214 + const isConfirmationField = field.name && 0 === field.name.indexOf( 'item_meta[conf_' );
215 + if ( ! isConfirmationField ) {
216 + // Allow a blank confirmation field to still call validateFieldValue.
217 + // If we continue for a confirmation field there are issues with forms submitting with a blank confirmation field.
218 + continue;
219 + }
228 220 }
221 +
222 + validateFieldValue( field, errors, true );
223 + checkValidity( field, errors );
229 224 }
230 225 }
231 226
232 - errors = validateRecaptcha( object, errors );
227 + // Invisible captchas are processed after validation.
228 + // We only want to validate a visible captcha on submit.
229 + if ( ! hasInvisibleRecaptcha( object ) ) {
230 + errors = validateRecaptcha( object, errors );
231 + }
233 232
234 233 return errors;
235 234 }
236 235
237 236 /**
237 + * Check the ValidityState interface for the field.
238 + * If it is invalid, show an error for it.
239 + *
240 + * @param {HTMLElement} field
241 + * @param {Array} errors
242 + * @return {void}
243 + */
244 + function checkValidity( field, errors ) {
245 + let fieldID;
246 + if ( 'object' !== typeof field.validity || false !== field.validity.valid ) {
247 + return;
248 + }
249 +
250 + fieldID = getFieldId( field, true );
251 + if ( 'undefined' === typeof errors[ fieldID ]) {
252 + errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
253 + }
254 +
255 + if ( 'function' === typeof field.reportValidity ) {
256 + // This triggers an error pop up.
257 + field.reportValidity();
258 + }
259 + }
260 +
261 + /**
238 262 * @since 5.0.10
239 263 *
240 - * @param {object} element
264 + * @param {Object} element
241 265 * @param {string} targetClass
242 - * @returns {boolean}
266 + * @return {boolean} True if the element has the target class.
243 267 */
244 268 function hasClass( element, targetClass ) {
245 - var className = ' ' + element.className + ' ';
246 - return -1 !== className.indexOf( ' ' + targetClass + ' ' );
269 + return element.classList && element.classList.contains( targetClass );
247 270 }
248 271
272 + /**
273 + * @param {HTMLElement} field
274 + */
249 275 function maybeValidateChange( field ) {
250 276 if ( field.type === 'url' ) {
251 277 maybeAddHttpToUrl( field );
252 278 }
253 - if ( jQuery( field ).closest( 'form' ).hasClass( 'frm_js_validate' ) ) {
279 + const form = field.closest( 'form' );
280 + if ( form && hasClass( form, 'frm_js_validate' ) ) {
254 281 validateField( field );
255 282 }
256 283 }
257 284
285 + /**
286 + * @param {HTMLElement} field
287 + */
258 288 function maybeAddHttpToUrl( field ) {
259 - var url = field.value;
260 - var matches = url.match( /^(https?|ftps?|mailto|news|feed|telnet):/ );
289 + const url = field.value;
290 + const matches = url.match( /^(https?|ftps?|mailto|news|feed|telnet):/ );
261 291 if ( field.value !== '' && matches === null ) {
262 292 field.value = 'http://' + url;
263 293 }
264 294 }
@@ -263,9 +293,9 @@
263 293 }
264 294 }
265 295
266 296 function validateField( field ) {
267 - var key,
297 + let key,
268 298 errors = [],
269 299 $fieldCont = jQuery( field ).closest( '.frm_form_field' );
270 300
271 301 if ( $fieldCont.hasClass( 'frm_required_field' ) && ! jQuery( field ).hasClass( 'frm_optional' ) ) {
@@ -272,13 +302,13 @@
272 302 errors = checkRequiredField( field, errors );
273 303 }
274 304
275 305 if ( errors.length < 1 ) {
276 - validateFieldValue( field, errors );
306 + validateFieldValue( field, errors, false );
277 307 }
278 308
279 309 removeFieldError( $fieldCont );
280 - if ( Object.keys( errors ).length > 0 ) {
310 + if ( Object.keys( errors ).length > 0 ) {
281 311 for ( key in errors ) {
282 312 addFieldError( $fieldCont, key, errors );
283 313 }
284 314 }
@@ -283,17 +313,26 @@
283 313 }
284 314 }
285 315 }
286 316
287 - function validateFieldValue( field, errors ) {
317 + /**
318 + * Validates a field value.
319 + *
320 + * @since 6.15 Added `onSubmit` parameter.
321 + *
322 + * @param {HTMLElement} field Field input.
323 + * @param {Object} errors Errors data.
324 + * @param {boolean} onSubmit Is `true` if the form is being submitted.
325 + */
326 + function validateFieldValue( field, errors, onSubmit ) {
288 327 if ( field.type === 'hidden' ) {
289 328 // don't validate
290 329 } else if ( field.type === 'number' ) {
291 330 checkNumberField( field, errors );
292 331 } else if ( field.type === 'email' ) {
293 - checkEmailField( field, errors );
332 + checkEmailField( field, errors, onSubmit );
294 333 } else if ( field.type === 'password' ) {
295 - checkPasswordField( field, errors );
334 + checkPasswordField( field, errors, onSubmit );
296 335 } else if ( field.type === 'url' ) {
297 336 checkUrlField( field, errors );
298 337 } else if ( field.pattern !== null ) {
299 338 checkPatternField( field, errors );
@@ -298,16 +337,25 @@
298 337 } else if ( field.pattern !== null ) {
299 338 checkPatternField( field, errors );
300 339 }
301 340
341 + /**
342 + * @since 6.15 Added `onSubmit` to the data.
343 + */
302 344 triggerCustomEvent( document, 'frm_validate_field_value', {
303 345 field: field,
304 - errors: errors
346 + errors: errors,
347 + onSubmit: onSubmit
305 348 });
306 349 }
307 350
351 + /**
352 + * @param {HTMLElement} field
353 + * @param {Array} errors
354 + * @return {Array} Errors
355 + */
308 356 function checkRequiredField( field, errors ) {
309 - var checkGroup, tempVal, i, placeholder,
357 + let checkGroup, tempVal, i, placeholder,
310 358 val = '',
311 359 fieldID = '',
312 360 fileID = field.getAttribute( 'data-frmfile' );
313 361
@@ -358,8 +406,14 @@
358 406 } else {
359 407 fieldID = getFieldId( field, true );
360 408 }
361 409
410 + // Make sure fieldID is a string.
411 + // fieldID may be a number which doesn't include a .replace function.
412 + if ( 'function' !== typeof fieldID.replace ) {
413 + fieldID = fieldID.toString();
414 + }
415 +
362 416 if ( hasClass( field, 'frm_time_select' ) ) {
363 417 // set id for time field
364 418 fieldID = fieldID.replace( '-H', '' ).replace( '-m', '' );
365 419 } else if ( isSignatureField( field ) ) {
@@ -386,23 +440,39 @@
386 440
387 441 return errors;
388 442 }
389 443
444 + /**
445 + * @param {HTMLElement} field
446 + * @return {boolean} True if the input is a typed signature input.
447 + */
390 448 function isSignatureField( field ) {
391 - var name = field.getAttribute( 'name' );
449 + const name = field.getAttribute( 'name' );
392 450 return 'string' === typeof name && '[typed]' === name.substr( -7 );
393 451 }
394 452
453 + /**
454 + * @param {HTMLElement} field
455 + * @return {boolean} True if the field is a SSA appointment field.
456 + */
395 457 function isAppointmentField( field ) {
396 458 return hasClass( field, 'ssa_appointment_form_field_appointment_id' );
397 459 }
398 460
461 + /**
462 + * @param {HTMLElement} field
463 + * @return {boolean} True if the field is inline datepicker field.
464 + */
399 465 function isInlineDatepickerField( field ) {
400 466 return 'hidden' === field.type && '_alt' === field.id.substr( -4 ) && hasClass( field.nextElementSibling, 'frm_date_inline' );
401 467 }
402 468
469 + /**
470 + * @param {string|number} fileID
471 + * @return {string} File input value.
472 + */
403 473 function getFileVals( fileID ) {
404 - var val = '',
474 + let val = '',
405 475 fileFields = jQuery( 'input[name="file' + fileID + '"], input[name="file' + fileID + '[]"], input[name^="item_meta[' + fileID + ']"]' );
406 476
407 477 fileFields.each( function() {
408 478 if ( val === '' ) {
@@ -411,10 +481,15 @@
411 481 });
412 482 return val;
413 483 }
414 484
485 + /**
486 + * @param {HTMLElement} field
487 + * @param {Array} errors
488 + * @return {void}
489 + */
415 490 function checkUrlField( field, errors ) {
416 - var fieldID,
491 + let fieldID,
417 492 url = field.value;
418 493
419 494 if ( url !== '' && ! /^http(s)?:\/\/(?:localhost|(?:[\da-z\.-]+\.[\da-z\.-]+))/i.test( url ) ) {
420 495 fieldID = getFieldId( field, true );
@@ -423,10 +498,42 @@
423 498 }
424 499 }
425 500 }
426 501
427 - function checkEmailField( field, errors ) {
428 - var fieldID = getFieldId( field, true ),
502 + /**
503 + * Checks if the confirm field should be checked.
504 + *
505 + * @since 6.15
506 + *
507 + * @param {HTMLElement} field Field input.
508 + * @param {boolean} onSubmit Is `true` if the form is being submitted.
509 + * @return {boolean} True if we should confirm the field.
510 + */
511 + function shouldCheckConfirmField( field, onSubmit ) {
512 + if ( onSubmit ) {
513 + // Always check on submitting.
514 + return true;
515 + }
516 +
517 + if ( 0 === field.id.indexOf( 'field_conf_' ) ) {
518 + // Always check if it's the confirm field.
519 + return true;
520 + }
521 +
522 + return false;
523 + }
524 +
525 + /**
526 + * Check the email field for errors.
527 + *
528 + * @since 6.15 Added `onSubmit` parameter.
529 + *
530 + * @param {HTMLElement} field Field input.
531 + * @param {Object} errors Errors data.
532 + * @param {boolean} onSubmit Is `true` if the form is being submitted.
533 + */
534 + function checkEmailField( field, errors, onSubmit ) {
535 + const fieldID = getFieldId( field, true ),
429 536 pattern = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i;
430 537
431 538 // validate the current field we're editing first
432 539 if ( '' !== field.value && pattern.test( field.value ) === false ) {
@@ -432,17 +539,35 @@
432 539 if ( '' !== field.value && pattern.test( field.value ) === false ) {
433 540 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
434 541 }
435 542
436 - confirmField( field, errors );
543 + if ( shouldCheckConfirmField( field, onSubmit ) ) {
544 + confirmField( field, errors );
545 + }
437 546 }
438 547
439 - function checkPasswordField( field, errors ) {
440 - confirmField( field, errors );
548 + /**
549 + * Check the password field for errors.
550 + *
551 + * @since 6.15 Added `onSubmit` parameter.
552 + *
553 + * @param {HTMLElement} field Field input.
554 + * @param {Object} errors Errors data.
555 + * @param {boolean} onSubmit Is `true` if the form is being submitted.
556 + */
557 + function checkPasswordField( field, errors, onSubmit ) {
558 + if ( shouldCheckConfirmField( field, onSubmit ) ) {
559 + confirmField( field, errors );
560 + }
441 561 }
442 562
563 + /**
564 + * @param {HTMLElement} field
565 + * @param {Array} errors
566 + * @return {void}
567 + */
443 568 function confirmField( field, errors ) {
444 - var value, confirmValue, firstField,
569 + let value, confirmValue, firstField,
445 570 fieldID = getFieldId( field, true ),
446 571 strippedId = field.id.replace( 'conf_', '' ),
447 572 strippedFieldID = fieldID.replace( 'conf_', '' ),
448 573 confirmField = document.getElementById( strippedId.replace( 'field_', 'field_conf_' ) );
@@ -454,9 +579,9 @@
454 579 if ( fieldID !== strippedFieldID ) {
455 580 firstField = document.getElementById( strippedId );
456 581 value = firstField.value;
457 582 confirmValue = confirmField.value;
458 - if ( '' !== value && '' !== confirmValue && value !== confirmValue ) {
583 + if ( value !== confirmValue ) {
459 584 errors[ 'conf_' + strippedFieldID ] = getFieldValidationMessage( confirmField, 'data-confmsg' );
460 585 }
461 586 } else {
462 587 validateField( confirmField );
@@ -462,10 +587,15 @@
462 587 validateField( confirmField );
463 588 }
464 589 }
465 590
591 + /**
592 + * @param {HTMLElement} field
593 + * @param {Array} errors
594 + * @return {void}
595 + */
466 596 function checkNumberField( field, errors ) {
467 - var fieldID,
597 + let fieldID,
468 598 number = field.value;
469 599
470 600 if ( number !== '' && isNaN( number / 1 ) !== false ) {
471 601 fieldID = getFieldId( field, true );
@@ -474,10 +604,15 @@
474 604 }
475 605 }
476 606 }
477 607
608 + /**
609 + * @param {HTMLElement} field
610 + * @param {Array} errors
611 + * @return {void}
612 + */
478 613 function checkPatternField( field, errors ) {
479 - var fieldID,
614 + let fieldID,
480 615 text = field.value,
481 616 format = getFieldValidationMessage( field, 'pattern' );
482 617
483 618 if ( format !== '' && text !== '' ) {
@@ -482,18 +617,65 @@
482 617
483 618 if ( format !== '' && text !== '' ) {
484 619 fieldID = getFieldId( field, true );
485 620 if ( ! ( fieldID in errors ) ) {
486 - format = new RegExp( '^' + format + '$', 'i' );
487 - if ( format.test( text ) === false ) {
488 - errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
621 + if ( 'object' === typeof window.frmProForm && 'function' === typeof window.frmProForm.isIntlPhoneInput && window.frmProForm.isIntlPhoneInput( field ) ) {
622 + if ( ! window.frmProForm.validateIntlPhoneInput( field ) ) {
623 + errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
624 + }
625 + } else {
626 + format = new RegExp( '^' + format + '$', 'i' );
627 + if ( format.test( text ) === false ) {
628 + errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
629 + }
489 630 }
490 631 }
491 632 }
492 633 }
493 634
635 + /**
636 + * Set color for select placeholders.
637 + *
638 + * @since 6.5.1
639 + */
640 + function setSelectPlaceholderColor() {
641 + let selects = document.querySelectorAll( '.form-field select' ),
642 + styleElement = document.querySelector( '.with_frm_style' ),
643 + textColorDisabled = styleElement ? getComputedStyle( styleElement ).getPropertyValue( '--text-color-disabled' ).trim() : '',
644 + changeSelectColor;
645 +
646 + // Exit if there are no select elements or the textColorDisabled property is missing
647 + if ( ! selects.length || ! textColorDisabled ) {
648 + return;
649 + }
650 +
651 + // Function to change the color of a select element
652 + changeSelectColor = function( select ) {
653 + if ( select.options[select.selectedIndex] && hasClass( select.options[select.selectedIndex], 'frm-select-placeholder' ) ) {
654 + select.style.setProperty( 'color', textColorDisabled, 'important' );
655 + } else {
656 + select.style.color = '';
657 + }
658 + };
659 +
660 + // Use a loop to iterate through each select element
661 + Array.prototype.forEach.call( selects, function( select ) {
662 + // Apply the color change to each select element
663 + changeSelectColor( select );
664 +
665 + // Add an event listener for future changes
666 + select.addEventListener( 'change', function() {
667 + changeSelectColor( select );
668 + });
669 + });
670 + }
671 +
672 + /**
673 + * @param {HTMLElement|jQuery} object
674 + * @return {boolean} True if there is an invisible recaptcha.
675 + */
494 676 function hasInvisibleRecaptcha( object ) {
495 - var recaptcha, recaptchaID, alreadyChecked;
677 + let recaptcha, recaptchaID, alreadyChecked;
496 678
497 679 if ( isGoingToPrevPage( object ) ) {
498 680 return false;
499 681 }
@@ -503,73 +685,106 @@
503 685 recaptchaID = recaptcha.data( 'rid' );
504 686 alreadyChecked = grecaptcha.getResponse( recaptchaID );
505 687 if ( alreadyChecked.length === 0 ) {
506 688 return recaptcha;
507 - } else {
508 - return false;
509 689 }
510 - } else {
511 - return false;
512 690 }
691 + return false;
513 692 }
514 693
694 + /**
695 + * @param {jQuery} invisibleRecaptcha
696 + */
515 697 function executeInvisibleRecaptcha( invisibleRecaptcha ) {
516 - var recaptchaID = invisibleRecaptcha.data( 'rid' );
698 + const recaptchaID = invisibleRecaptcha.data( 'rid' );
517 699 grecaptcha.reset( recaptchaID );
518 700 grecaptcha.execute( recaptchaID );
519 701 }
520 702
521 703 function validateRecaptcha( form, errors ) {
522 - var recaptchaID, response, fieldContainer, fieldID,
523 - $recaptcha = jQuery( form ).find( '.frm-g-recaptcha' );
524 - if ( $recaptcha.length ) {
525 - recaptchaID = $recaptcha.data( 'rid' );
704 + let response;
526 705
527 - try {
528 - response = grecaptcha.getResponse( recaptchaID );
529 - } catch ( e ) {
530 - if ( jQuery( form ).find( 'input[name="recaptcha_checked"]' ).length ) {
531 - return errors;
532 - } else {
533 - response = '';
534 - }
706 + const $recaptcha = jQuery( form ).find( '.frm-g-recaptcha' );
707 + if ( ! $recaptcha.length ) {
708 + return errors;
709 + }
710 +
711 + const recaptchaID = $recaptcha.data( 'rid' );
712 +
713 + try {
714 + response = grecaptcha.getResponse( recaptchaID );
715 + } catch ( e ) {
716 + if ( jQuery( form ).find( 'input[name="recaptcha_checked"]' ).length ) {
717 + return errors;
535 718 }
719 + response = '';
720 + }
536 721
537 - if ( response.length === 0 ) {
538 - fieldContainer = $recaptcha.closest( '.frm_form_field' );
539 - fieldID = fieldContainer.attr( 'id' ).replace( 'frm_field_', '' ).replace( '_container', '' );
540 - errors[ fieldID ] = '';
541 - }
722 + if ( response.length === 0 ) {
723 + const fieldContainer = $recaptcha.closest( '.frm_form_field' );
724 + const fieldID = fieldContainer.attr( 'id' ).replace( 'frm_field_', '' ).replace( '_container', '' );
725 + errors[ fieldID ] = '';
542 726 }
727 +
543 728 return errors;
544 729 }
545 730
731 + /**
732 + * @param {HTMLElement} field
733 + * @param {string} messageType
734 + * @return {string} The error message to display.
735 + */
546 736 function getFieldValidationMessage( field, messageType ) {
547 - var msg, errorHtml;
548 -
549 - msg = field.getAttribute( messageType );
737 + let msg = field.getAttribute( messageType );
550 738 if ( null === msg ) {
551 739 msg = '';
552 740 }
553 741
554 742 if ( '' !== msg && shouldWrapErrorHtmlAroundMessageType( messageType ) ) {
555 - errorHtml = field.getAttribute( 'data-error-html' );
556 - if ( null !== errorHtml ) {
557 - errorHtml = errorHtml.replace( /\+/g, '%20' );
558 - msg = decodeURIComponent( errorHtml ).replace( '[error]', msg );
559 - msg = msg.replace( '[key]', getFieldId( field, false ) );
560 - }
743 + msg = wrapErrorHtml( msg, field );
561 744 }
562 745
563 746 return msg;
564 747 }
565 748
749 + /**
750 + * @param {string} msg
751 + * @param {HTMLElement} field
752 + * @return {string} The error HTML to use.
753 + */
754 + function wrapErrorHtml( msg, field ) {
755 + let errorHtml = field.getAttribute( 'data-error-html' );
756 + if ( null === errorHtml ) {
757 + return msg;
758 + }
759 +
760 + errorHtml = errorHtml.replace( /\+/g, '%20' );
761 + msg = decodeURIComponent( errorHtml ).replace( '[error]', msg );
762 + const fieldId = getFieldId( field, false );
763 + const split = fieldId.split( '-' );
764 + const fieldIdParts = field.id.split( '_' );
765 + fieldIdParts.shift(); // Drop the "field" value from the front.
766 + split[0] = fieldIdParts.join( '_' );
767 + const errorKey = split.join( '-' );
768 + return msg.replace( '[key]', errorKey );
769 + }
770 +
566 771 function shouldWrapErrorHtmlAroundMessageType( type ) {
567 772 return 'pattern' !== type;
568 773 }
569 774
775 + /**
776 + * Check if JS validation should happen.
777 + *
778 + * @param {HTMLElement|Object} object Form object.
779 + * @return {boolean} True if validation is enabled and we are not saving a draft or going to a previous page.
780 + */
570 781 function shouldJSValidate( object ) {
571 - var validate = jQuery( object ).hasClass( 'frm_js_validate' );
782 + if ( 'function' === typeof object.get ) {
783 + // Get the HTMLElement from a jQuery object.
784 + object = object.get( 0 );
785 + }
786 + let validate = hasClass( object, 'frm_js_validate' );
572 787 if ( validate && typeof frmProForm !== 'undefined' && ( frmProForm.savingDraft( object ) || frmProForm.goingToPreviousPage( object ) ) ) {
573 788 validate = false;
574 789 }
575 790
@@ -575,15 +790,16 @@
575 790
576 791 return validate;
577 792 }
578 793
794 + /**
795 + * @param {HTMLElement} object
796 + * @param {string} action
797 + * @return {void}
798 + */
579 799 function getFormErrors( object, action ) {
580 - var fieldset, data, success, error, shouldTriggerEvent;
800 + let fieldset, data, success, error, shouldTriggerEvent;
581 801
582 - if ( typeof action === 'undefined' ) {
583 - jQuery( object ).find( 'input[name="frm_action"]' ).val();
584 - }
585 -
586 802 fieldset = jQuery( object ).find( '.frm_form_field' );
587 803 fieldset.addClass( 'frm_doing_ajax' );
588 804
589 805 data = jQuery( object ).serialize() + '&action=frm_entries_' + action + '&nonce=' + frm_js.nonce; // eslint-disable-line camelcase
@@ -589,9 +805,9 @@
589 805 data = jQuery( object ).serialize() + '&action=frm_entries_' + action + '&nonce=' + frm_js.nonce; // eslint-disable-line camelcase
590 806 shouldTriggerEvent = object.classList.contains( 'frm_trigger_event_on_submit' );
591 807
592 808 success = function( response ) {
593 - var defaultResponse, formID, replaceContent, pageOrder, formReturned, contSubmit, delay,
809 + let defaultResponse, formID, replaceContent, pageOrder, formReturned, contSubmit, delay,
594 810 $fieldCont, key, inCollapsedSection, frmTrigger, newTab;
595 811
596 812 defaultResponse = {
597 813 content: '',
@@ -634,9 +850,9 @@
634 850 if ( response.content !== '' ) {
635 851 // the form or success message was returned
636 852
637 853 if ( shouldTriggerEvent ) {
638 - triggerCustomEvent( object, 'frmSubmitEvent' );
854 + triggerCustomEvent( object, 'frmSubmitEvent', { content: response.content });
639 855 return;
640 856 }
641 857
642 858 removeSubmitLoading( jQuery( object ) );
@@ -651,10 +867,12 @@
651 867 delay = maybeSlideOut( replaceContent, response.content );
652 868
653 869 setTimeout(
654 870 function() {
655 - var container, input, previousInput;
871 + let container, input, previousInput;
656 872
873 + afterFormSubmittedBeforeReplace( object, response );
874 +
657 875 replaceContent.replaceWith( response.content );
658 876
659 877 addUrlParam( response );
660 878
@@ -713,9 +931,9 @@
713 931 }
714 932 }
715 933
716 934 jQuery( object ).find( '.frm-g-recaptcha, .g-recaptcha, .h-captcha' ).each( function() {
717 - var $recaptcha = jQuery( this ),
935 + const $recaptcha = jQuery( this ),
718 936 recaptchaID = $recaptcha.data( 'rid' );
719 937
720 938 if ( typeof grecaptcha !== 'undefined' && grecaptcha ) {
721 939 if ( recaptchaID ) {
@@ -736,9 +954,9 @@
736 954
737 955 if ( contSubmit ) {
738 956 object.submit();
739 957 } else {
740 - jQuery( object ).prepend( response.error_message );
958 + object.insertAdjacentHTML( 'afterbegin', response.error_message );
741 959 checkForErrorsAndMaybeSetFocus();
742 960 }
743 961 } else {
744 962 // there may have been a plugin conflict, or the form is not set to submit with ajax
@@ -757,9 +975,9 @@
757 975 postToAjaxUrl( object, data, success, error );
758 976 }
759 977
760 978 function postToAjaxUrl( form, data, success, error ) {
761 - var ajaxUrl, action, ajaxParams;
979 + let ajaxUrl, action, ajaxParams;
762 980
763 981 ajaxUrl = frm_js.ajax_url; // eslint-disable-line camelcase
764 982 action = form.getAttribute( 'action' );
765 983
@@ -781,9 +999,9 @@
781 999 jQuery.ajax( ajaxParams );
782 1000 }
783 1001
784 1002 function afterFormSubmitted( object, response ) {
785 - var formCompleted = jQuery( response.content ).find( '.frm_message' );
1003 + const formCompleted = jQuery( response.content ).find( '.frm_message' );
786 1004 if ( formCompleted.length ) {
787 1005 jQuery( document ).trigger( 'frmFormComplete', [ object, response ]);
788 1006 } else {
789 1007 jQuery( document ).trigger( 'frmPageChanged', [ object, response ]);
@@ -789,10 +1007,26 @@
789 1007 jQuery( document ).trigger( 'frmPageChanged', [ object, response ]);
790 1008 }
791 1009 }
792 1010
1011 + /**
1012 + * Trigger an event before the form is replaced with a success message.
1013 + *
1014 + * @since 6.9
1015 + *
1016 + * @param {HTMLElement} object The form.
1017 + * @param {Object} response The response from submitting the form with AJAX.
1018 + * @return {void}
1019 + */
1020 + function afterFormSubmittedBeforeReplace( object, response ) {
1021 + const formCompleted = jQuery( response.content ).find( '.frm_message' );
1022 + if ( formCompleted.length ) {
1023 + triggerCustomEvent( document, 'frmFormCompleteBeforeReplace', { object, response });
1024 + }
1025 + }
1026 +
793 1027 function removeAddedScripts( formContainer, formID ) {
794 - var endReplace = jQuery( '.frm_end_ajax_' + formID );
1028 + const endReplace = jQuery( '.frm_end_ajax_' + formID );
795 1029 if ( endReplace.length ) {
796 1030 formContainer.nextUntil( '.frm_end_ajax_' + formID ).remove();
797 1031 endReplace.remove();
798 1032 }
@@ -798,9 +1032,9 @@
798 1032 }
799 1033 }
800 1034
801 1035 function maybeSlideOut( oldContent, newContent ) {
802 - var c,
1036 + let c,
803 1037 newClass = 'frm_slideout';
804 1038 if ( newContent.indexOf( ' frm_slide' ) !== -1 ) {
805 1039 c = oldContent.children();
806 1040 if ( newContent.indexOf( ' frm_going_back' ) !== -1 ) {
@@ -813,9 +1047,9 @@
813 1047 return 0;
814 1048 }
815 1049
816 1050 function addUrlParam( response ) {
817 - var url;
1051 + let url;
818 1052 if ( history.pushState && typeof response.page !== 'undefined' ) {
819 1053 url = addQueryVar( 'frm_page', response.page );
820 1054 window.history.pushState({ 'html': response.html }, '', '?' + url );
821 1055 }
@@ -821,9 +1055,9 @@
821 1055 }
822 1056 }
823 1057
824 1058 function addQueryVar( key, value ) {
825 - var kvp, i, x;
1059 + let kvp, i, x;
826 1060
827 1061 key = encodeURI( key );
828 1062 value = encodeURI( value );
829 1063
@@ -847,13 +1081,14 @@
847 1081 return kvp.join( '&' );
848 1082 }
849 1083
850 1084 function addFieldError( $fieldCont, key, jsErrors ) {
851 - var input, id, describedBy, roleString;
1085 + let input, id, describedBy, roleString;
852 1086 if ( $fieldCont.length && $fieldCont.is( ':visible' ) ) {
853 1087 $fieldCont.addClass( 'frm_blank_field' );
854 1088 input = $fieldCont.find( 'input, select, textarea' );
855 - id = 'frm_error_field_' + key;
1089 + id = getErrorElementId( key, input.get( 0 ) );
1090 +
856 1091 describedBy = input.attr( 'aria-describedby' );
857 1092
858 1093 if ( typeof frmThemeOverride_frmPlaceError === 'function' ) { // eslint-disable-line camelcase
859 1094 frmThemeOverride_frmPlaceError( key, jsErrors );
@@ -884,15 +1119,41 @@
884 1119 jQuery( document ).trigger( 'frmAddFieldError', [ $fieldCont, key, jsErrors ]);
885 1120 }
886 1121 }
887 1122
1123 + /**
1124 + * Get the ID to use for an error element added when submitting with AJAX.
1125 + *
1126 + * @param {string} key
1127 + * @param {HTMLElement} input
1128 + * @return {string} The ID to use for the error element.
1129 + */
1130 + function getErrorElementId( key, input ) {
1131 + if ( isNaN( key ) || ! input.id ) {
1132 + // If key isn't a number, assume it's already in the right format.
1133 + return 'frm_error_field_' + key;
1134 + }
1135 + return 'frm_error_' + input.id;
1136 + }
1137 +
1138 + /**
1139 + * Removes errors before validating with JS.
1140 + * This prevents issues with stale errors that has since been fixed.
1141 + *
1142 + * @param {Object} $fieldCont jQuery object.
1143 + * @return {void}
1144 + */
888 1145 function removeFieldError( $fieldCont ) {
889 - var errorMessage = $fieldCont.find( '.frm_error' ),
890 - errorId = errorMessage.attr( 'id' ),
891 - input = $fieldCont.find( 'input, select, textarea' ),
892 - describedBy = input.attr( 'aria-describedby' );
1146 + const errorMessage = $fieldCont.find( '.frm_error' );
1147 + const errorId = errorMessage.attr( 'id' );
1148 + const input = $fieldCont.find( 'input, select, textarea' );
1149 + let describedBy = input.attr( 'aria-describedby' );
893 1150
894 - $fieldCont.removeClass( 'frm_blank_field has-error' );
1151 + const fieldContainer = $fieldCont.get( 0 );
1152 + if ( fieldContainer && fieldContainer.classList ) {
1153 + fieldContainer.classList.remove( 'frm_blank_field', 'has-error' );
1154 + }
1155 +
895 1156 errorMessage.remove();
896 1157 input.attr( 'aria-invalid', false );
897 1158 input.removeAttr( 'aria-describedby' );
898 1159
@@ -907,13 +1168,21 @@
907 1168 jQuery( '.form-field .frm_error' ).replaceWith( '' );
908 1169 jQuery( '.frm_error_style' ).remove();
909 1170 }
910 1171
1172 + /**
1173 + * @param {HTMLElement|Object} object Form object.
1174 + * @return {void}
1175 + */
911 1176 function scrollToFirstField( object ) {
912 - var field = jQuery( object ).find( '.frm_blank_field' ).first();
913 - if ( field.length ) {
914 - frmFrontForm.scrollMsg( field, object, true );
1177 + if ( 'function' === typeof object.get ) {
1178 + // Get the HTMLElement from a jQuery object.
1179 + object = object.get( 0 );
915 1180 }
1181 + const field = object.querySelector( '.frm_blank_field' );
1182 + if ( field ) {
1183 + frmFrontForm.scrollMsg( jQuery( field ), object, true );
1184 + }
916 1185 }
917 1186
918 1187 function showSubmitLoading( $object ) {
919 1188 showLoadingIndicator( $object );
@@ -928,9 +1197,9 @@
928 1197 }
929 1198 }
930 1199
931 1200 function addLoadingClass( $object ) {
932 - var loadingClass = isGoingToPrevPage( $object ) ? 'frm_loading_prev' : 'frm_loading_form';
1201 + const loadingClass = isGoingToPrevPage( $object ) ? 'frm_loading_prev' : 'frm_loading_form';
933 1202
934 1203 $object.addClass( loadingClass );
935 1204 }
936 1205
@@ -937,10 +1206,10 @@
937 1206 function isGoingToPrevPage( $object ) {
938 1207 return ( typeof frmProForm !== 'undefined' && frmProForm.goingToPreviousPage( $object ) );
939 1208 }
940 1209
941 - function removeSubmitLoading( $object, enable, processesRunning ) {
942 - var loadingForm;
1210 + function removeSubmitLoading( _, enable, processesRunning ) {
1211 + let loadingForm;
943 1212
944 1213 if ( processesRunning > 0 ) {
945 1214 return;
946 1215 }
@@ -957,9 +1226,9 @@
957 1226 }
958 1227 }
959 1228
960 1229 function showFileLoading( object ) {
961 - var fileval,
1230 + let fileval,
962 1231 loading = document.getElementById( 'frm_loading' );
963 1232 if ( loading !== null ) {
964 1233 fileval = jQuery( object ).find( 'input[type=file]' ).val();
965 1234 if ( typeof fileval !== 'undefined' && fileval !== '' ) {
@@ -969,69 +1238,8 @@
969 1238 }
970 1239 }
971 1240 }
972 1241
973 - function clearDefault() {
974 - /*jshint validthis:true */
975 - toggleDefault( jQuery( this ), 'clear' );
976 - }
977 -
978 - function replaceDefault() {
979 - /*jshint validthis:true */
980 - toggleDefault( jQuery( this ), 'replace' );
981 - }
982 -
983 - function toggleDefault( $thisField, e ) {
984 - // TODO: Fix this for a default value that is a number or array
985 - var thisVal,
986 - v = $thisField.data( 'frmval' ).replace( /(\n|\r\n)/g, '\r' );
987 - if ( v === '' || typeof v === 'undefined' ) {
988 - return false;
989 - }
990 - thisVal = $thisField.val().replace( /(\n|\r\n)/g, '\r' );
991 -
992 - if ( 'replace' === e ) {
993 - if ( thisVal === '' ) {
994 - $thisField.addClass( 'frm_default' ).val( v );
995 - }
996 - } else if ( thisVal == v ) {
997 - $thisField.removeClass( 'frm_default' ).val( '' );
998 - }
999 - }
1000 -
1001 - function resendEmail() {
1002 - /*jshint validthis:true */
1003 - var $link = jQuery( this ),
1004 - entryId = this.getAttribute( 'data-eid' ),
1005 - formId = this.getAttribute( 'data-fid' ),
1006 - label = $link.find( '.frm_link_label' );
1007 - if ( label.length < 1 ) {
1008 - label = $link;
1009 - }
1010 - label.append( '<span class="frm-wait"></span>' );
1011 -
1012 - jQuery.ajax({
1013 - type: 'POST',
1014 - url: frm_js.ajax_url, // eslint-disable-line camelcase
1015 - data: {
1016 - action: 'frm_entries_send_email',
1017 - entry_id: entryId,
1018 - form_id: formId,
1019 - nonce: frm_js.nonce // eslint-disable-line camelcase
1020 - },
1021 - success: function( msg ) {
1022 - var admin = document.getElementById( 'wpbody' );
1023 - if ( admin === null ) {
1024 - label.html( msg );
1025 - } else {
1026 - label.html( '' );
1027 - $link.after( msg );
1028 - }
1029 - }
1030 - });
1031 - return false;
1032 - }
1033 -
1034 1242 /**********************************************
1035 1243 * General Helpers
1036 1244 *********************************************/
1037 1245
@@ -1036,68 +1244,12 @@
1036 1244 *********************************************/
1037 1245
1038 1246 function confirmClick() {
1039 1247 /*jshint validthis:true */
1040 - var message = jQuery( this ).data( 'frmconfirm' );
1248 + const message = jQuery( this ).data( 'frmconfirm' );
1041 1249 return confirm( message );
1042 1250 }
1043 1251
1044 - function toggleDiv() {
1045 - /*jshint validthis:true */
1046 - var div = jQuery( this ).data( 'frmtoggle' );
1047 - if ( jQuery( div ).is( ':visible' ) ) {
1048 - jQuery( div ).slideUp( 'fast' );
1049 - } else {
1050 - jQuery( div ).slideDown( 'fast' );
1051 - }
1052 - return false;
1053 - }
1054 -
1055 - /**********************************************
1056 - * Fallback functions
1057 - *********************************************/
1058 -
1059 - function addTrimFallbackForIE() {
1060 - if ( typeof String.prototype.trim !== 'function' ) {
1061 - String.prototype.trim = function() {
1062 - return this.replace( /^\s+|\s+$/g, '' );
1063 - };
1064 - }
1065 - }
1066 -
1067 - function addFilterFallbackForIE() {
1068 - var t, len, res, thisp, i, val;
1069 -
1070 - if ( ! Array.prototype.filter ) {
1071 -
1072 - Array.prototype.filter = function( fun /*, thisp */ ) {
1073 -
1074 - if ( this === void 0 || this === null ) {
1075 - throw new TypeError();
1076 - }
1077 -
1078 - t = Object( this );
1079 - len = t.length >>> 0;
1080 - if ( typeof fun !== 'function' ) {
1081 - throw new TypeError();
1082 - }
1083 -
1084 - res = [];
1085 - thisp = arguments[1];
1086 - for ( i = 0; i < len; i++ ) {
1087 - if ( i in t ) {
1088 - val = t[i]; // in case fun mutates this
1089 - if ( fun.call( thisp, val, i, t ) ) {
1090 - res.push( val );
1091 - }
1092 - }
1093 - }
1094 -
1095 - return res;
1096 - };
1097 - }
1098 - }
1099 -
1100 1252 /**
1101 1253 * Check for -webkit-box-shadow css value for input:-webkit-autofill selector.
1102 1254 * If this is a match, the User is autofilling the input on a Webkit browser.
1103 1255 * We want to delete the Honeypot field, otherwise it will get triggered as spam on autocomplete.
@@ -1102,9 +1254,9 @@
1102 1254 * If this is a match, the User is autofilling the input on a Webkit browser.
1103 1255 * We want to delete the Honeypot field, otherwise it will get triggered as spam on autocomplete.
1104 1256 */
1105 1257 function onHoneypotFieldChange() {
1106 - var css = jQuery( this ).css( 'box-shadow' );
1258 + const css = jQuery( this ).css( 'box-shadow' );
1107 1259 if ( css.match( /inset/ ) ) {
1108 1260 this.parentNode.removeChild( this );
1109 1261 }
1110 1262 }
@@ -1112,9 +1264,9 @@
1112 1264 function maybeMakeHoneypotFieldsUntabbable() {
1113 1265 document.addEventListener( 'keydown', handleKeyUp );
1114 1266
1115 1267 function handleKeyUp( event ) {
1116 - var code;
1268 + let code;
1117 1269
1118 1270 if ( 'undefined' !== typeof event.key ) {
1119 1271 code = event.key;
1120 1272 } else if ( 'undefined' !== typeof event.keyCode && 9 === event.keyCode ) {
@@ -1128,11 +1280,10 @@
1128 1280 }
1129 1281
1130 1282 function makeHoneypotFieldsUntabbable() {
1131 1283 document.querySelectorAll( '.frm_verify' ).forEach(
1132 - function( wrapper ) {
1133 - var input = wrapper.querySelector( 'input[id^=frm_email]' );
1134 - if ( input ) {
1284 + function( input ) {
1285 + if ( input.id && 0 === input.id.indexOf( 'frm_email_' ) ) {
1135 1286 input.setAttribute( 'tabindex', -1 );
1136 1287 }
1137 1288 }
1138 1289 );
@@ -1144,11 +1295,11 @@
1144 1295 *
1145 1296 * @since 4.10.02
1146 1297 */
1147 1298 function changeFocusWhenClickComboFieldLabel() {
1148 - var label;
1299 + let label;
1149 1300
1150 - var comboInputsContainer = document.querySelectorAll( '.frm_combo_inputs_container' );
1301 + const comboInputsContainer = document.querySelectorAll( '.frm_combo_inputs_container' );
1151 1302 comboInputsContainer.forEach( function( inputsContainer ) {
1152 1303 if ( ! inputsContainer.closest( '.frm_form_field' ) ) {
1153 1304 return;
1154 1305 }
@@ -1157,16 +1308,39 @@
1157 1308 if ( ! label ) {
1158 1309 return;
1159 1310 }
1160 1311
1161 - label.addEventListener( 'click', function( e ) {
1312 + label.addEventListener( 'click', function() {
1162 1313 inputsContainer.querySelector( '.frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea' ).focus();
1163 1314 });
1164 1315 });
1165 1316 }
1166 1317
1318 + /**
1319 + * Sets focus on a the first subfield of a combo field that has an error.
1320 + *
1321 + * @since 6.16.3
1322 + *
1323 + * @param {HTMLElement} element
1324 + * @return {boolean} True if the focus was set on a combo field.
1325 + */
1326 + function maybeFocusOnComboSubField( element ) {
1327 + if ( 'FIELDSET' !== element.nodeName ) {
1328 + return false;
1329 + }
1330 + if ( ! element.querySelector( '.frm_combo_inputs_container' ) ) {
1331 + return false;
1332 + }
1333 + const comboSubfield = element.querySelector( '[aria-invalid="true"]' );
1334 + if ( comboSubfield ) {
1335 + focusInput( comboSubfield );
1336 + return true;
1337 + }
1338 + return false;
1339 + }
1340 +
1167 1341 function checkForErrorsAndMaybeSetFocus() {
1168 - var errors, element, timeoutCallback;
1342 + let errors, element, timeoutCallback;
1169 1343
1170 1344 if ( ! frm_js.focus_first_error ) { // eslint-disable-line camelcase
1171 1345 return;
1172 1346 }
@@ -1179,16 +1353,20 @@
1179 1353 element = errors[0];
1180 1354 do {
1181 1355 element = element.previousSibling;
1182 1356 if ( -1 !== [ 'input', 'select', 'textarea' ].indexOf( element.nodeName.toLowerCase() ) ) {
1183 - element.focus();
1357 + focusInput( element );
1184 1358 break;
1185 1359 }
1186 1360
1361 + if ( maybeFocusOnComboSubField( element ) ) {
1362 + break;
1363 + }
1364 +
1187 1365 if ( 'undefined' !== typeof element.classList ) {
1188 1366 if ( element.classList.contains( 'html-active' ) ) {
1189 1367 timeoutCallback = function() {
1190 - var textarea = element.querySelector( 'textarea' );
1368 + const textarea = element.querySelector( 'textarea' );
1191 1369 if ( null !== textarea ) {
1192 1370 textarea.focus();
1193 1371 }
1194 1372 };
@@ -1206,16 +1384,21 @@
1206 1384 } while ( element.previousSibling );
1207 1385 }
1208 1386
1209 1387 /**
1210 - * Checks if is on IE browser.
1388 + * Focus a visible input, or possibly delay the focus event until the form has faded in.
1211 1389 *
1212 - * @since 5.4
1390 + * @since 6.16.3
1213 1391 *
1214 - * @return {Boolean}
1392 + * @param {HTMLElement} input
1393 + * @return {void}
1215 1394 */
1216 - function isIE() {
1217 - return navigator.userAgent.indexOf( 'MSIE' ) > -1 || navigator.userAgent.indexOf( 'Trident' ) > -1;
1395 + function focusInput( input ) {
1396 + if ( input.offsetParent !== null ) {
1397 + input.focus();
1398 + } else {
1399 + triggerCustomEvent( document, 'frmMaybeDelayFocus', { input });
1400 + }
1218 1401 }
1219 1402
1220 1403 /**
1221 1404 * Does the same as jQuery( document ).on( 'event', 'selector', handler ).
@@ -1221,12 +1404,12 @@
1221 1404 * Does the same as jQuery( document ).on( 'event', 'selector', handler ).
1222 1405 *
1223 1406 * @since 5.4
1224 1407 *
1225 - * @param {String} event Event name.
1226 - * @param {String} selector Selector.
1227 - * @param {Function} handler Handler.
1228 - * @param {Boolean|Object} options Options to be added to `addEventListener()` method. Default is `false`.
1408 + * @param {string} event Event name.
1409 + * @param {string} selector Selector.
1410 + * @param {Function} handler Handler.
1411 + * @param {boolean | Object} options Options to be added to `addEventListener()` method. Default is `false`.
1229 1412 */
1230 1413 function documentOn( event, selector, handler, options ) {
1231 1414 if ( 'undefined' === typeof options ) {
1232 1415 options = false;
@@ -1232,9 +1415,9 @@
1232 1415 options = false;
1233 1416 }
1234 1417
1235 1418 document.addEventListener( event, function( e ) {
1236 - var target;
1419 + let target;
1237 1420
1238 1421 // loop parent nodes from the target to the delegation node.
1239 1422 for ( target = e.target; target && target != this; target = target.parentNode ) {
1240 1423 if ( target && target.matches && target.matches( selector ) ) {
@@ -1245,15 +1428,15 @@
1245 1428 }, options );
1246 1429 }
1247 1430
1248 1431 function initFloatingLabels() {
1249 - var checkFloatLabel, checkDropdownLabel, checkPlaceholderIE, runOnLoad, selector, floatClass;
1432 + let checkFloatLabel, checkDropdownLabel, runOnLoad, selector, floatClass;
1250 1433
1251 1434 selector = '.frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea';
1252 1435 floatClass = 'frm_label_float_top';
1253 1436
1254 1437 checkFloatLabel = function( input ) {
1255 - var container, shouldFloatTop, firstOpt;
1438 + let container, shouldFloatTop, firstOpt;
1256 1439
1257 1440 container = input.closest( '.frm_inside_container' );
1258 1441 if ( ! container ) {
1259 1442 return;
@@ -1270,22 +1453,18 @@
1270 1453 if ( firstOpt.hasAttribute( 'data-label' ) ) {
1271 1454 firstOpt.textContent = firstOpt.getAttribute( 'data-label' );
1272 1455 firstOpt.removeAttribute( 'data-label' );
1273 1456 }
1274 - } else {
1275 - if ( firstOpt.textContent ) {
1276 - firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1277 - firstOpt.textContent = '';
1278 - }
1457 + } else if ( firstOpt.textContent ) {
1458 + firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1459 + firstOpt.textContent = '';
1279 1460 }
1280 - } else if ( isIE() ) {
1281 - checkPlaceholderIE( input );
1282 1461 }
1283 1462 };
1284 1463
1285 1464 checkDropdownLabel = function() {
1286 1465 document.querySelectorAll( '.frm-show-form .frm_inside_container:not(.' + floatClass + ') select' ).forEach( function( input ) {
1287 - var firstOpt = input.querySelector( 'option:first-child' );
1466 + const firstOpt = input.querySelector( 'option:first-child' );
1288 1467
1289 1468 if ( firstOpt.textContent ) {
1290 1469 firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1291 1470 firstOpt.textContent = '';
@@ -1292,27 +1471,8 @@
1292 1471 }
1293 1472 });
1294 1473 };
1295 1474
1296 - checkPlaceholderIE = function( input ) {
1297 - if ( input.value ) {
1298 - // Don't need to handle this case because placeholder isn't shown.
1299 - return;
1300 - }
1301 -
1302 - if ( document.activeElement === input ) {
1303 - if ( input.hasAttribute( 'data-placeholder' ) ) {
1304 - input.placeholder = input.getAttribute( 'data-placeholder' );
1305 - input.removeAttribute( 'data-placeholder' );
1306 - }
1307 - } else {
1308 - if ( input.placeholder ) {
1309 - input.setAttribute( 'data-placeholder', input.placeholder );
1310 - input.placeholder = '';
1311 - }
1312 - }
1313 - };
1314 -
1315 1475 [ 'focus', 'blur', 'change' ].forEach( function( eventName ) {
1316 1476 documentOn(
1317 1477 eventName,
1318 1478 selector,
@@ -1332,9 +1492,9 @@
1332 1492 checkFloatLabel( document.activeElement );
1333 1493 } else if ( firstLoad ) {
1334 1494 document.querySelectorAll( '.frm_inside_container' ).forEach(
1335 1495 function( container ) {
1336 - var input = container.querySelector( 'input, select, textarea' );
1496 + const input = container.querySelector( 'input, select, textarea' );
1337 1497 if ( input && '' !== input.value ) {
1338 1498 checkFloatLabel( input );
1339 1499 }
1340 1500 }
@@ -1341,14 +1501,8 @@
1341 1501 );
1342 1502 }
1343 1503
1344 1504 checkDropdownLabel();
1345 -
1346 - if ( isIE() ) {
1347 - document.querySelectorAll( selector ).forEach( function( input ) {
1348 - checkPlaceholderIE( input );
1349 - });
1350 - }
1351 1505 };
1352 1506
1353 1507 runOnLoad( true );
1354 1508
@@ -1360,10 +1514,53 @@
1360 1514 runOnLoad();
1361 1515 });
1362 1516 }
1363 1517
1518 + function shouldUpdateValidityMessage( target ) {
1519 + if ( 'INPUT' !== target.nodeName ) {
1520 + return false;
1521 + }
1522 +
1523 + if ( ! target.dataset.invmsg ) {
1524 + return false;
1525 + }
1526 +
1527 + if ( 'text' !== target.getAttribute( 'type' ) ) {
1528 + return false;
1529 + }
1530 +
1531 + if ( target.classList.contains( 'frm_verify' ) ) {
1532 + return false;
1533 + }
1534 +
1535 + return true;
1536 + }
1537 +
1538 + function maybeClearCustomValidityMessage( event, field ) {
1539 + let key,
1540 + isInvalid = false;
1541 +
1542 + if ( ! shouldUpdateValidityMessage( field ) ) {
1543 + return;
1544 + }
1545 +
1546 + for ( key in field.validity ) {
1547 + if ( 'customError' === key ) {
1548 + continue;
1549 + }
1550 + if ( 'valid' !== key && field.validity[ key ] === true ) {
1551 + isInvalid = true;
1552 + break;
1553 + }
1554 + };
1555 +
1556 + if ( ! isInvalid ) {
1557 + field.setCustomValidity( '' );
1558 + }
1559 + }
1560 +
1364 1561 function maybeShowNewTabFallbackMessage() {
1365 - var messageEl;
1562 + let messageEl;
1366 1563
1367 1564 if ( ! window.frmShowNewTabFallback ) {
1368 1565 return;
1369 1566 }
@@ -1376,9 +1573,9 @@
1376 1573 messageEl.insertAdjacentHTML( 'beforeend', ' ' + frmShowNewTabFallback.message );
1377 1574 }
1378 1575
1379 1576 function setCustomValidityMessage() {
1380 - var forms, length, index;
1577 + let forms, length, index;
1381 1578
1382 1579 forms = document.getElementsByClassName( 'frm-show-form' );
1383 1580 length = forms.length;
1384 1581
@@ -1385,37 +1582,57 @@
1385 1582 for ( index = 0; index < length; ++index ) {
1386 1583 forms[ index ].addEventListener(
1387 1584 'invalid',
1388 1585 function( event ) {
1389 - var target = event.target;
1586 + const target = event.target;
1390 1587
1391 - if ( 'INPUT' !== target.nodeName ) {
1392 - return;
1588 + if ( shouldUpdateValidityMessage( target ) ) {
1589 + target.setCustomValidity( target.dataset.invmsg );
1393 1590 }
1591 + },
1592 + true
1593 + );
1594 + }
1595 + }
1394 1596
1395 - if ( ! target.dataset.invmsg ) {
1396 - return;
1597 + function enableSubmitButtonOnBackButtonPress() {
1598 + window.addEventListener( 'pageshow', function( event ) {
1599 + if ( event.persisted ) {
1600 + document.querySelectorAll( '.frm_loading_form' ).forEach(
1601 + function( form ) {
1602 + enableSubmitButton( jQuery( form ) );
1397 1603 }
1604 + );
1605 + removeSubmitLoading();
1606 + }
1607 + });
1608 + }
1398 1609
1399 - if ( 'text' !== target.getAttribute( 'type' ) ) {
1400 - return;
1401 - }
1610 + /**
1611 + * Destroys the formidable generated global hcaptcha object since it wouldn't otherwise render.
1612 + */
1613 + function destroyhCaptcha() {
1614 + if ( ! window.hasOwnProperty( 'hcaptcha' ) || ! document.querySelector( '.frm-show-form .h-captcha' ) ) {
1615 + return;
1616 + }
1617 + window.hcaptcha = null;
1618 + }
1402 1619
1403 - if ( target.classList.contains( 'frm_verify' ) ) {
1404 - return;
1405 - }
1406 -
1407 - target.setCustomValidity( target.dataset.invmsg );
1408 - },
1409 - true
1410 - );
1411 - }
1620 + /**
1621 + * @since 6.16.3
1622 + *
1623 + * @return {string} Unique key, used for duplicate checks.
1624 + */
1625 + function getUniqueKey() {
1626 + const uniqueKey = Array.from( window.crypto.getRandomValues( new Uint8Array( 8 ) ) )
1627 + .map( b => b.toString( 16 ).padStart( 2, '0' ) )
1628 + .join( '' );
1629 + const timestamp = Date.now().toString( 16 );
1630 + return uniqueKey + '-' + timestamp;
1412 1631 }
1413 1632
1414 1633 return {
1415 1634 init: function() {
1416 - maybeAddPolyfills();
1417 -
1418 1635 jQuery( document ).off( 'submit.formidable', '.frm-show-form' );
1419 1636 jQuery( document ).on( 'submit.formidable', '.frm-show-form', frmFrontForm.submitForm );
1420 1637
1421 1638 jQuery( '.frm-show-form input[onblur], .frm-show-form textarea[onblur]' ).each( function() {
@@ -1423,14 +1640,8 @@
1423 1640 jQuery( this ).trigger( 'blur' );
1424 1641 }
1425 1642 });
1426 1643
1427 - jQuery( document ).on( 'focus', '.frm_toggle_default', clearDefault );
1428 - jQuery( document ).on( 'blur', '.frm_toggle_default', replaceDefault );
1429 - jQuery( '.frm_toggle_default' ).trigger( 'blur' );
1430 -
1431 - jQuery( document.getElementById( 'frm_resend_email' ) ).on( 'click', resendEmail );
1432 -
1433 1644 jQuery( document ).on( 'change', '.frm-show-form input[name^="item_meta"], .frm-show-form select[name^="item_meta"], .frm-show-form textarea[name^="item_meta"]', frmFrontForm.fieldValueChanged );
1434 1645
1435 1646 jQuery( document ).on( 'change', '[id^=frm_email_]', onHoneypotFieldChange );
1436 1647 maybeMakeHoneypotFieldsUntabbable();
@@ -1435,9 +1646,8 @@
1435 1646 jQuery( document ).on( 'change', '[id^=frm_email_]', onHoneypotFieldChange );
1436 1647 maybeMakeHoneypotFieldsUntabbable();
1437 1648
1438 1649 jQuery( document ).on( 'click', 'a[data-frmconfirm]', confirmClick );
1439 - jQuery( 'a[data-frmtoggle]' ).on( 'click', toggleDiv );
1440 1650
1441 1651 checkForErrorsAndMaybeSetFocus();
1442 1652
1443 1653 // Focus on the first sub field when clicking to the primary label of combo field.
@@ -1442,25 +1652,31 @@
1442 1652
1443 1653 // Focus on the first sub field when clicking to the primary label of combo field.
1444 1654 changeFocusWhenClickComboFieldLabel();
1445 1655
1446 - // Add fallbacks for IE.
1447 - addTrimFallbackForIE(); // Trim only works in IE10+.
1448 - addFilterFallbackForIE(); // Filter is not supported in any version of IE.
1449 -
1450 1656 initFloatingLabels();
1451 1657 maybeShowNewTabFallbackMessage();
1452 1658
1453 1659 jQuery( document ).on( 'frmAfterAddRow', setCustomValidityMessage );
1454 1660 setCustomValidityMessage();
1661 + jQuery( document ).on( 'frmFieldChanged', maybeClearCustomValidityMessage );
1662 +
1663 + setSelectPlaceholderColor();
1664 +
1665 + // Elementor popup show event. Fix Elementor Popup && FF Captcha field conflicts
1666 + jQuery( document ).on( 'elementor/popup/show', frmRecaptcha );
1667 +
1668 + enableSubmitButtonOnBackButtonPress();
1669 + jQuery( document ).on(
1670 + 'frmPageChanged',
1671 + destroyhCaptcha
1672 + );
1455 1673 },
1456 1674
1457 - getFieldId: function( field, fullID ) {
1458 - return getFieldId( field, fullID );
1459 - },
1675 + getFieldId,
1460 1676
1461 - renderRecaptcha: function( captcha ) {
1462 - var formID, recaptchaID,
1677 + renderCaptcha: function( captcha, captchaSelector ) {
1678 + let formID, captchaID,
1463 1679 size = captcha.getAttribute( 'data-size' ),
1464 1680 rendered = captcha.getAttribute( 'data-rid' ) !== null,
1465 1681 params = {
1466 1682 'sitekey': captcha.getAttribute( 'data-sitekey' ),
@@ -1465,9 +1681,11 @@
1465 1681 params = {
1466 1682 'sitekey': captcha.getAttribute( 'data-sitekey' ),
1467 1683 'size': size,
1468 1684 'theme': captcha.getAttribute( 'data-theme' )
1469 - };
1685 + },
1686 + activeCaptcha = getSelectedCaptcha( captchaSelector ),
1687 + captchaContainer = typeof turnstile !== 'undefined' && turnstile === activeCaptcha ? '#' + captcha.id : captcha.id;
1470 1688
1471 1689 if ( rendered ) {
1472 1690 return;
1473 1691 }
@@ -1479,20 +1697,21 @@
1479 1697 frmFrontForm.afterRecaptcha( token, formID );
1480 1698 };
1481 1699 }
1482 1700
1483 - recaptchaID = grecaptcha.render( captcha.id, params );
1484 1701
1485 - captcha.setAttribute( 'data-rid', recaptchaID );
1702 + captchaID = activeCaptcha.render( captchaContainer, params );
1703 +
1704 + captcha.setAttribute( 'data-rid', captchaID );
1486 1705 },
1487 1706
1488 1707 afterSingleRecaptcha: function() {
1489 - var object = jQuery( '.frm-show-form .g-recaptcha' ).closest( 'form' )[0];
1708 + const object = jQuery( '.frm-show-form .g-recaptcha' ).closest( 'form' )[0];
1490 1709 frmFrontForm.submitFormNow( object );
1491 1710 },
1492 1711
1493 - afterRecaptcha: function( token, formID ) {
1494 - var object = jQuery( '#frm_form_' + formID + '_container form' )[0];
1712 + afterRecaptcha: function( _, formID ) {
1713 + const object = jQuery( '#frm_form_' + formID + '_container form' )[0];
1495 1714 frmFrontForm.submitFormNow( object );
1496 1715 },
1497 1716
1498 1717 submitForm: function( e ) {
@@ -1498,10 +1717,15 @@
1498 1717 submitForm: function( e ) {
1499 1718 frmFrontForm.submitFormManual( e, this );
1500 1719 },
1501 1720
1721 + /**
1722 + * @param {Event} e
1723 + * @param {HTMLElement} object The form object that is being submitted.
1724 + * @return {void}
1725 + */
1502 1726 submitFormManual: function( e, object ) {
1503 - var isPro, errors,
1727 + let isPro, errors,
1504 1728 invisibleRecaptcha = hasInvisibleRecaptcha( object ),
1505 1729 classList = object.className.trim().split( /\s+/gi );
1506 1730
1507 1731 if ( classList && invisibleRecaptcha.length < 1 ) {
@@ -1516,31 +1740,30 @@
1516 1740 }
1517 1741
1518 1742 e.preventDefault();
1519 1743
1520 - if ( typeof frmProForm !== 'undefined' && typeof frmProForm.submitAllowed === 'function' ) {
1521 - if ( ! frmProForm.submitAllowed( object ) ) {
1522 - return;
1523 - }
1744 + if ( typeof frmProForm !== 'undefined' && typeof frmProForm.submitAllowed === 'function' && ! frmProForm.submitAllowed( object ) ) {
1745 + return;
1524 1746 }
1525 1747
1748 + errors = frmFrontForm.validateFormSubmit( object );
1749 + if ( Object.keys( errors ).length !== 0 ) {
1750 + return;
1751 + }
1752 +
1526 1753 if ( invisibleRecaptcha.length ) {
1527 1754 showLoadingIndicator( jQuery( object ) );
1528 1755 executeInvisibleRecaptcha( invisibleRecaptcha );
1529 1756 } else {
1530 1757
1531 - errors = frmFrontForm.validateFormSubmit( object );
1758 + showSubmitLoading( jQuery( object ) );
1532 1759
1533 - if ( Object.keys( errors ).length === 0 ) {
1534 - showSubmitLoading( jQuery( object ) );
1535 -
1536 - frmFrontForm.submitFormNow( object, classList );
1537 - }
1760 + frmFrontForm.submitFormNow( object, classList );
1538 1761 }
1539 1762 },
1540 1763
1541 1764 submitFormNow: function( object ) {
1542 - var hasFileFields, antispamInput,
1765 + let hasFileFields, antispamInput,
1543 1766 classList = object.className.trim().split( /\s+/gi );
1544 1767
1545 1768 if ( object.hasAttribute( 'data-token' ) && null === object.querySelector( '[name="antispam_token"]' ) ) {
1546 1769 // include the antispam token on form submit.
@@ -1550,14 +1773,21 @@
1550 1773 antispamInput.value = object.getAttribute( 'data-token' );
1551 1774 object.appendChild( antispamInput );
1552 1775 }
1553 1776
1777 + // Add a unique ID, used for duplicate checks.
1778 + const uniqueIDInput = document.createElement( 'input' );
1779 + uniqueIDInput.type = 'hidden';
1780 + uniqueIDInput.name = 'unique_id';
1781 + uniqueIDInput.value = getUniqueKey();
1782 + object.appendChild( uniqueIDInput );
1783 +
1554 1784 if ( classList.indexOf( 'frm_ajax_submit' ) > -1 ) {
1555 1785 hasFileFields = jQuery( object ).find( 'input[type="file"]' ).filter( function() {
1556 1786 return !! this.value;
1557 1787 }).length;
1558 1788 if ( hasFileFields < 1 ) {
1559 - action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
1789 + const action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
1560 1790 frmFrontForm.checkFormErrors( object, action );
1561 1791 } else {
1562 1792 object.submit();
1563 1793 }
@@ -1565,8 +1795,13 @@
1565 1795 object.submit();
1566 1796 }
1567 1797 },
1568 1798
1799 + /**
1800 + * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
1801 + *
1802 + * @return {Array} List of errors.
1803 + */
1569 1804 validateFormSubmit: function( object ) {
1570 1805 if ( typeof tinyMCE !== 'undefined' && jQuery( object ).find( '.wp-editor-wrap' ).length ) {
1571 1806 tinyMCE.triggerSave();
1572 1807 }
@@ -1583,14 +1818,18 @@
1583 1818
1584 1819 return jsErrors;
1585 1820 },
1586 1821
1822 + /**
1823 + * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
1824 + * @return {Array} List of errors.
1825 + */
1587 1826 getAjaxFormErrors: function( object ) {
1588 - var customErrors, key;
1827 + let customErrors, key;
1589 1828
1590 1829 jsErrors = validateForm( object );
1591 1830 if ( typeof frmThemeOverride_jsErrors === 'function' ) { // eslint-disable-line camelcase
1592 - action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
1831 + const action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
1593 1832 customErrors = frmThemeOverride_jsErrors( action, object );
1594 1833 if ( Object.keys( customErrors ).length ) {
1595 1834 for ( key in customErrors ) {
1596 1835 jsErrors[ key ] = customErrors[ key ];
@@ -1597,13 +1836,22 @@
1597 1836 }
1598 1837 }
1599 1838 }
1600 1839
1840 + triggerCustomEvent( document, 'frm_get_ajax_form_errors', {
1841 + formEl: object,
1842 + errors: jsErrors
1843 + });
1844 +
1601 1845 return jsErrors;
1602 1846 },
1603 1847
1848 + /**
1849 + * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
1850 + * @return {void}
1851 + */
1604 1852 addAjaxFormErrors: function( object ) {
1605 - var key, $fieldCont;
1853 + let key, $fieldCont;
1606 1854 removeAllErrors();
1607 1855
1608 1856 for ( key in jsErrors ) {
1609 1857 $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
@@ -1619,31 +1867,20 @@
1619 1867 scrollToFirstField( object );
1620 1868 checkForErrorsAndMaybeSetFocus();
1621 1869 },
1622 1870
1623 - checkFormErrors: function( object, action ) {
1624 - getFormErrors( object, action );
1625 - },
1871 + checkFormErrors: getFormErrors,
1872 + checkRequiredField,
1873 + showSubmitLoading,
1874 + removeSubmitLoading,
1626 1875
1627 - checkRequiredField: function( field, errors ) {
1628 - return checkRequiredField( field, errors );
1629 - },
1630 -
1631 - showSubmitLoading: function( $object ) {
1632 - showSubmitLoading( $object );
1633 - },
1634 -
1635 - removeSubmitLoading: function( $object, enable, processesRunning ) {
1636 - removeSubmitLoading( $object, enable, processesRunning );
1637 - },
1638 -
1639 1876 scrollToID: function( id ) {
1640 - var object = jQuery( document.getElementById( id ) );
1877 + const object = jQuery( document.getElementById( id ) );
1641 1878 frmFrontForm.scrollMsg( object, false );
1642 1879 },
1643 1880
1644 1881 scrollMsg: function( id, object, animate ) {
1645 - var newPos, m, b, screenTop, screenBottom,
1882 + let newPos, m, b, screenTop, screenBottom,
1646 1883 scrollObj = '';
1647 1884 if ( typeof object === 'undefined' ) {
1648 1885 scrollObj = jQuery( document.getElementById( 'frm_form_' + id + '_container' ) );
1649 1886 if ( scrollObj.length < 1 ) {
@@ -1686,9 +1923,9 @@
1686 1923
1687 1924 fieldValueChanged: function( e ) {
1688 1925 /*jshint validthis:true */
1689 1926
1690 - var fieldId = frmFrontForm.getFieldId( this, false );
1927 + const fieldId = frmFrontForm.getFieldId( this, false );
1691 1928 if ( ! fieldId || typeof fieldId === 'undefined' ) {
1692 1929 return;
1693 1930 }
1694 1931
@@ -1702,55 +1939,8 @@
1702 1939 maybeValidateChange( this );
1703 1940 }
1704 1941 },
1705 1942
1706 - savingDraft: function( object ) {
1707 - console.warn( 'DEPRECATED: function frmFrontForm.savingDraft in v3.0 use frmProForm.savingDraft' );
1708 - if ( typeof frmProForm !== 'undefined' ) {
1709 - return frmProForm.savingDraft( object );
1710 - }
1711 - },
1712 -
1713 - goingToPreviousPage: function( object ) {
1714 - console.warn( 'DEPRECATED: function frmFrontForm.goingToPreviousPage in v3.0 use frmProForm.goingToPreviousPage' );
1715 - if ( typeof frmProForm !== 'undefined' ) {
1716 - return frmProForm.goingToPreviousPage( object );
1717 - }
1718 - },
1719 -
1720 - hideOrShowFields: function() {
1721 - console.warn( 'DEPRECATED: function frmFrontForm.hideOrShowFields in v3.0 use frmProForm.hideOrShowFields' );
1722 - if ( typeof frmProForm !== 'undefined' ) {
1723 - frmProForm.hideOrShowFields();
1724 - }
1725 - },
1726 -
1727 - hidePreviouslyHiddenFields: function() {
1728 - console.warn( 'DEPRECATED: function frmFrontForm.hidePreviouslyHiddenFields in v3.0 use frmProForm.hidePreviouslyHiddenFields' );
1729 - if ( typeof frmProForm !== 'undefined' ) {
1730 - frmProForm.hidePreviouslyHiddenFields();
1731 - }
1732 - },
1733 -
1734 - checkDependentDynamicFields: function( ids ) {
1735 - console.warn( 'DEPRECATED: function frmFrontForm.checkDependentDynamicFields in v3.0 use frmProForm.checkDependentDynamicFields' );
1736 - if ( typeof frmProForm !== 'undefined' ) {
1737 - frmProForm.checkDependentDynamicFields( ids );
1738 - }
1739 - },
1740 -
1741 - checkDependentLookupFields: function( ids ) {
1742 - console.warn( 'DEPRECATED: function frmFrontForm.checkDependentLookupFields in v3.0 use frmProForm.checkDependentLookupFields' );
1743 - if ( typeof frmProForm !== 'undefined' ) {
1744 - frmProForm.checkDependentLookupFields( ids );
1745 - }
1746 - },
1747 -
1748 - loadGoogle: function() {
1749 - console.warn( 'DEPRECATED: function frmFrontForm.loadGoogle in v3.0 use frmProForm.loadGoogle' );
1750 - frmProForm.loadGoogle();
1751 - },
1752 -
1753 1943 escapeHtml: function( text ) {
1754 1944 return text
1755 1945 .replace( /&/g, '&amp;' )
1756 1946 .replace( /</g, '&lt;' )
@@ -1758,97 +1948,85 @@
1758 1948 .replace( /"/g, '&quot;' )
1759 1949 .replace( /'/g, '&#039;' );
1760 1950 },
1761 1951
1952 + /**
1953 + * This function was used in old back end code in v2.0.
1954 + *
1955 + * @param {string} classes
1956 + * @return {void}
1957 + */
1762 1958 invisible: function( classes ) {
1959 + console.warn( 'DEPRECATED: function frmFrontForm.invisible in v6.16.3' );
1763 1960 jQuery( classes ).css( 'visibility', 'hidden' );
1764 1961 },
1765 1962
1963 + /**
1964 + * This function was used in old back end code in v2.0.
1965 + *
1966 + * @param {string} classes
1967 + * @return {void}
1968 + */
1766 1969 visible: function( classes ) {
1970 + console.warn( 'DEPRECATED: function frmFrontForm.visible in v6.16.3' );
1767 1971 jQuery( classes ).css( 'visibility', 'visible' );
1768 - }
1972 + },
1973 +
1974 + triggerCustomEvent: triggerCustomEvent,
1975 + documentOn
1769 1976 };
1770 1977 }
1771 -frmFrontForm = frmFrontFormJS();
1772 1978
1979 +window.frmFrontForm = frmFrontFormJS();
1980 +
1773 1981 jQuery( document ).ready( function() {
1774 1982 frmFrontForm.init();
1775 1983 });
1776 1984
1777 1985 function frmRecaptcha() {
1778 - var c, cl,
1779 - captchas = jQuery( '.frm-g-recaptcha' );
1780 - for ( c = 0, cl = captchas.length; c < cl; c++ ) {
1781 - frmFrontForm.renderRecaptcha( captchas[c]);
1782 - }
1986 + frmCaptcha( '.frm-g-recaptcha' );
1783 1987 }
1784 1988
1785 -function frmAfterRecaptcha( token ) {
1786 - frmFrontForm.afterSingleRecaptcha( token );
1989 +function frmTurnstile() {
1990 + frmCaptcha( '.cf-turnstile' );
1787 1991 }
1788 1992
1789 -function frmUpdateField( entryId, fieldId, value, message, num ) {
1790 - jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).html( '<span class="frm-loading-img"></span>' );
1791 - jQuery.ajax({
1792 - type: 'POST',
1793 - url: frm_js.ajax_url, // eslint-disable-line camelcase
1794 - data: {
1795 - action: 'frm_entries_update_field_ajax',
1796 - entry_id: entryId,
1797 - field_id: fieldId,
1798 - value: value,
1799 - nonce: frm_js.nonce // eslint-disable-line camelcase
1800 - },
1801 - success: function() {
1802 - if ( message.replace( /^\s+|\s+$/g, '' ) === '' ) {
1803 - jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).fadeOut( 'slow' );
1804 - } else {
1805 - jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).replaceWith( message );
1806 - }
1993 +function frmCaptcha( captchaSelector ) {
1994 + let c;
1995 + const captchas = document.querySelectorAll( captchaSelector );
1996 + const cl = captchas.length;
1997 + for ( c = 0; c < cl; c++ ) {
1998 + const closestForm = captchas[c].closest( 'form' );
1999 + const formIsVisible = closestForm && closestForm.offsetParent !== null;
2000 + const captcha = captchas[c];
2001 + if ( ! formIsVisible ) {
2002 + // If the form is not visible, try again later in 400ms.
2003 + // This fixes issues where the form fades visible on page load.
2004 + // Or whne the form is inside of a modal.
2005 + const interval = setInterval(
2006 + function() {
2007 + if ( closestForm && closestForm.offsetParent !== null ) {
2008 + frmFrontForm.renderCaptcha( captcha, captchaSelector );
2009 + clearInterval( interval );
2010 + }
2011 + },
2012 + 400
2013 + );
2014 + continue;
1807 2015 }
1808 - });
2016 + frmFrontForm.renderCaptcha( captcha, captchaSelector );
2017 + }
1809 2018 }
1810 2019
1811 -function frmDeleteEntry( entryId, prefix ) {
1812 - console.warn( 'DEPRECATED: function frmDeleteEntry in v2.0.13 use frmFrontForm.deleteEntry' );
1813 - jQuery( document.getElementById( 'frm_delete_' + entryId ) ).replaceWith( '<span class="frm-loading-img" id="frm_delete_' + entryId + '"></span>' );
1814 - jQuery.ajax({
1815 - type: 'POST',
1816 - url: frm_js.ajax_url, // eslint-disable-line camelcase
1817 - data: {
1818 - action: 'frm_entries_destroy',
1819 - entry: entryId,
1820 - nonce: frm_js.nonce // eslint-disable-line camelcase
1821 - },
1822 - success: function( html ) {
1823 - if ( html.replace( /^\s+|\s+$/g, '' ) === 'success' ) {
1824 - jQuery( document.getElementById( prefix + entryId ) ).fadeOut( 'slow' );
1825 - } else {
1826 - jQuery( document.getElementById( 'frm_delete_' + entryId ) ).replaceWith( html );
1827 - }
1828 - }
1829 - });
2020 +function getSelectedCaptcha( captchaSelector ) {
2021 + if ( captchaSelector === '.frm-g-recaptcha' ) {
2022 + return grecaptcha;
2023 + }
2024 + if ( document.querySelector( '.cf-turnstile' ) ) {
2025 + return turnstile;
2026 + }
2027 + return hcaptcha;
1830 2028 }
1831 2029
1832 -function frmOnSubmit( e ) {
1833 - console.warn( 'DEPRECATED: function frmOnSubmit in v2.0 use frmFrontForm.submitForm' );
1834 - frmFrontForm.submitForm( e, this );
1835 -}
1836 -
1837 -function frm_resend_email( entryId, formId ) { // eslint-disable-line camelcase
1838 - var $link = jQuery( document.getElementById( 'frm_resend_email' ) );
1839 - console.warn( 'DEPRECATED: function frm_resend_email in v2.0' );
1840 - $link.append( '<span class="spinner" style="display:inline"></span>' );
1841 - jQuery.ajax({
1842 - type: 'POST',
1843 - url: frm_js.ajax_url, // eslint-disable-line camelcase
1844 - data: {
1845 - action: 'frm_entries_send_email',
1846 - entry_id: entryId,
1847 - form_id: formId,
1848 - nonce: frm_js.nonce // eslint-disable-line camelcase
1849 - },
1850 - success: function( msg ) {
1851 - $link.replaceWith( msg );
1852 - }
1853 - });
2030 +function frmAfterRecaptcha( token ) {
2031 + frmFrontForm.afterSingleRecaptcha( token );
1854 2032 }