PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.16.1
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.16.1
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 +458 -489 6.5.2 → 6.16.1 View file →
@@ -1,76 +1,31 @@
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 action = '';
11 + let jsErrors = [];
13 12
14 13 /**
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 14 * Triggers custom JS event.
54 15 *
55 16 * @since 5.5.3
56 17 *
57 18 * @param {HTMLElement} el The HTML element.
58 - * @param {String} eventName Event name.
19 + * @param {string} eventName Event name.
59 20 * @param {mixed} data The passed data.
60 21 */
61 22 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 {
23 + if ( typeof window.CustomEvent !== 'function' ) {
70 24 return;
71 25 }
72 26
27 + const event = new CustomEvent( eventName );
73 28 event.frmData = data;
74 29
75 30 el.dispatchEvent( event );
76 31 }
@@ -76,9 +31,9 @@
76 31 }
77 32
78 33 /* Get the ID of the field that changed*/
79 34 function getFieldId( field, fullID ) {
80 - var nameParts, fieldId,
35 + let nameParts, fieldId,
81 36 isRepeating = false,
82 37 fieldName = '';
83 38 if ( field instanceof jQuery ) {
84 39 fieldName = field.attr( 'name' );
@@ -161,12 +116,12 @@
161 116 * Disable the submit button for a given jQuery form object
162 117 *
163 118 * @since 2.03.02
164 119 *
165 - * @param {object} $form
120 + * @param {Object} $form
166 121 */
167 122 function disableSubmitButton( $form ) {
168 - $form.find( 'input[type="submit"], input[type="button"], button[type="submit"]' ).attr( 'disabled', 'disabled' );
123 + $form.find( 'input[type="submit"], input[type="button"], button[type="submit"], button.frm_save_draft' ).attr( 'disabled', 'disabled' );
169 124 }
170 125
171 126 /**
172 127 * Enable the submit button for a given jQuery form object
@@ -172,9 +127,9 @@
172 127 * Enable the submit button for a given jQuery form object
173 128 *
174 129 * @since 2.03.02
175 130 *
176 - * @param {object} $form
131 + * @param {Object} $form
177 132 */
178 133 function enableSubmitButton( $form ) {
179 134 $form.find( 'input[type="submit"], input[type="button"], button[type="submit"]' ).prop( 'disabled', false );
180 135 }
@@ -183,9 +138,9 @@
183 138 * Disable the save draft link for a given jQuery form object
184 139 *
185 140 * @since 4.04.03
186 141 *
187 - * @param {object} $form
142 + * @param {Object} $form
188 143 */
189 144 function disableSaveDraft( $form ) {
190 145 $form.find( 'a.frm_save_draft' ).css( 'pointer-events', 'none' );
191 146 }
@@ -194,18 +149,25 @@
194 149 * Enable the save draft link for a given jQuery form object
195 150 *
196 151 * @since 4.04.03
197 152 *
198 - * @param {object} $form
153 + * @param {Object} $form
199 154 */
200 155 function enableSaveDraft( $form ) {
201 - $form.find( 'a.frm_save_draft' ).css( 'pointer-events', '' );
156 + if ( ! $form.length ) {
157 + return;
158 + }
159 + $form[0].querySelectorAll( '.frm_save_draft' ).forEach( saveDraftButton => {
160 + saveDraftButton.disabled = false;
161 + saveDraftButton.style.pointerEvents = '';
162 + });
202 163 }
203 164
204 165 function validateForm( object ) {
205 - var r, rl, n, nl, fields, field, value, requiredFields,
206 - errors = [];
166 + let errors, r, rl, n, nl, fields, field, requiredFields;
207 167
168 + errors = [];
169 +
208 170 // Make sure required text field is filled in
209 171 requiredFields = jQuery( object ).find(
210 172 '.frm_required_field:visible input, .frm_required_field:visible select, .frm_required_field:visible textarea'
211 173 ).filter( ':not(.frm_optional)' );
@@ -222,29 +184,70 @@
222 184 fields = jQuery( object ).find( 'input,select,textarea' );
223 185 if ( fields.length ) {
224 186 for ( n = 0, nl = fields.length; n < nl; n++ ) {
225 187 field = fields[n];
226 - if ( '' !== field.value ) {
227 - validateFieldValue( field, errors );
188 + if ( '' === field.value ) {
189 + if ( 'number' === field.type ) {
190 + // A number field will return an empty string when it is invalid.
191 + checkValidity( field, errors );
192 + }
193 +
194 + const isConfirmationField = field.name && 0 === field.name.indexOf( 'item_meta[conf_' );
195 + if ( ! isConfirmationField ) {
196 + // Allow a blank confirmation field to still call validateFieldValue.
197 + // If we continue for a confirmation field there are issues with forms submitting with a blank confirmation field.
198 + continue;
199 + }
228 200 }
201 +
202 + validateFieldValue( field, errors, true );
203 + checkValidity( field, errors );
229 204 }
230 205 }
231 206
232 - errors = validateRecaptcha( object, errors );
207 + // Invisible captchas are processed after validation.
208 + // We only want to validate a visible captcha on submit.
209 + if ( ! hasInvisibleRecaptcha( object ) ) {
210 + errors = validateRecaptcha( object, errors );
211 + }
233 212
234 213 return errors;
235 214 }
236 215
237 216 /**
217 + * Check the ValidityState interface for the field.
218 + * If it is invalid, show an error for it.
219 + *
220 + * @param {HTMLElement} field
221 + * @param {Array} errors
222 + * @return {void}
223 + */
224 + function checkValidity( field, errors ) {
225 + let fieldID;
226 + if ( 'object' !== typeof field.validity || false !== field.validity.valid ) {
227 + return;
228 + }
229 +
230 + fieldID = getFieldId( field, true );
231 + if ( 'undefined' === typeof errors[ fieldID ]) {
232 + errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
233 + }
234 +
235 + if ( 'function' === typeof field.reportValidity ) {
236 + // This triggers an error pop up.
237 + field.reportValidity();
238 + }
239 + }
240 +
241 + /**
238 242 * @since 5.0.10
239 243 *
240 - * @param {object} element
244 + * @param {Object} element
241 245 * @param {string} targetClass
242 - * @returns {boolean}
246 + * @return {boolean} True if the element has the target class.
243 247 */
244 248 function hasClass( element, targetClass ) {
245 - var className = ' ' + element.className + ' ';
246 - return -1 !== className.indexOf( ' ' + targetClass + ' ' );
249 + return element.classList && element.classList.contains( targetClass );
247 250 }
248 251
249 252 function maybeValidateChange( field ) {
250 253 if ( field.type === 'url' ) {
@@ -255,10 +258,10 @@
255 258 }
256 259 }
257 260
258 261 function maybeAddHttpToUrl( field ) {
259 - var url = field.value;
260 - var matches = url.match( /^(https?|ftps?|mailto|news|feed|telnet):/ );
262 + const url = field.value;
263 + const matches = url.match( /^(https?|ftps?|mailto|news|feed|telnet):/ );
261 264 if ( field.value !== '' && matches === null ) {
262 265 field.value = 'http://' + url;
263 266 }
264 267 }
@@ -263,9 +266,9 @@
263 266 }
264 267 }
265 268
266 269 function validateField( field ) {
267 - var key,
270 + let key,
268 271 errors = [],
269 272 $fieldCont = jQuery( field ).closest( '.frm_form_field' );
270 273
271 274 if ( $fieldCont.hasClass( 'frm_required_field' ) && ! jQuery( field ).hasClass( 'frm_optional' ) ) {
@@ -272,13 +275,13 @@
272 275 errors = checkRequiredField( field, errors );
273 276 }
274 277
275 278 if ( errors.length < 1 ) {
276 - validateFieldValue( field, errors );
279 + validateFieldValue( field, errors, false );
277 280 }
278 281
279 282 removeFieldError( $fieldCont );
280 - if ( Object.keys( errors ).length > 0 ) {
283 + if ( Object.keys( errors ).length > 0 ) {
281 284 for ( key in errors ) {
282 285 addFieldError( $fieldCont, key, errors );
283 286 }
284 287 }
@@ -283,17 +286,26 @@
283 286 }
284 287 }
285 288 }
286 289
287 - function validateFieldValue( field, errors ) {
290 + /**
291 + * Validates a field value.
292 + *
293 + * @since 6.15 Added `onSubmit` parameter.
294 + *
295 + * @param {HTMLElement} field Field input.
296 + * @param {Object} errors Errors data.
297 + * @param {boolean} onSubmit Is `true` if the form is being submitted.
298 + */
299 + function validateFieldValue( field, errors, onSubmit ) {
288 300 if ( field.type === 'hidden' ) {
289 301 // don't validate
290 302 } else if ( field.type === 'number' ) {
291 303 checkNumberField( field, errors );
292 304 } else if ( field.type === 'email' ) {
293 - checkEmailField( field, errors );
305 + checkEmailField( field, errors, onSubmit );
294 306 } else if ( field.type === 'password' ) {
295 - checkPasswordField( field, errors );
307 + checkPasswordField( field, errors, onSubmit );
296 308 } else if ( field.type === 'url' ) {
297 309 checkUrlField( field, errors );
298 310 } else if ( field.pattern !== null ) {
299 311 checkPatternField( field, errors );
@@ -298,16 +310,20 @@
298 310 } else if ( field.pattern !== null ) {
299 311 checkPatternField( field, errors );
300 312 }
301 313
314 + /**
315 + * @since 6.15 Added `onSubmit` to the data.
316 + */
302 317 triggerCustomEvent( document, 'frm_validate_field_value', {
303 318 field: field,
304 - errors: errors
319 + errors: errors,
320 + onSubmit: onSubmit
305 321 });
306 322 }
307 323
308 324 function checkRequiredField( field, errors ) {
309 - var checkGroup, tempVal, i, placeholder,
325 + let checkGroup, tempVal, i, placeholder,
310 326 val = '',
311 327 fieldID = '',
312 328 fileID = field.getAttribute( 'data-frmfile' );
313 329
@@ -358,8 +374,14 @@
358 374 } else {
359 375 fieldID = getFieldId( field, true );
360 376 }
361 377
378 + // Make sure fieldID is a string.
379 + // fieldID may be a number which doesn't include a .replace function.
380 + if ( 'function' !== typeof fieldID.replace ) {
381 + fieldID = fieldID.toString();
382 + }
383 +
362 384 if ( hasClass( field, 'frm_time_select' ) ) {
363 385 // set id for time field
364 386 fieldID = fieldID.replace( '-H', '' ).replace( '-m', '' );
365 387 } else if ( isSignatureField( field ) ) {
@@ -387,9 +409,9 @@
387 409 return errors;
388 410 }
389 411
390 412 function isSignatureField( field ) {
391 - var name = field.getAttribute( 'name' );
413 + const name = field.getAttribute( 'name' );
392 414 return 'string' === typeof name && '[typed]' === name.substr( -7 );
393 415 }
394 416
395 417 function isAppointmentField( field ) {
@@ -400,9 +422,9 @@
400 422 return 'hidden' === field.type && '_alt' === field.id.substr( -4 ) && hasClass( field.nextElementSibling, 'frm_date_inline' );
401 423 }
402 424
403 425 function getFileVals( fileID ) {
404 - var val = '',
426 + let val = '',
405 427 fileFields = jQuery( 'input[name="file' + fileID + '"], input[name="file' + fileID + '[]"], input[name^="item_meta[' + fileID + ']"]' );
406 428
407 429 fileFields.each( function() {
408 430 if ( val === '' ) {
@@ -412,9 +434,9 @@
412 434 return val;
413 435 }
414 436
415 437 function checkUrlField( field, errors ) {
416 - var fieldID,
438 + let fieldID,
417 439 url = field.value;
418 440
419 441 if ( url !== '' && ! /^http(s)?:\/\/(?:localhost|(?:[\da-z\.-]+\.[\da-z\.-]+))/i.test( url ) ) {
420 442 fieldID = getFieldId( field, true );
@@ -423,10 +445,41 @@
423 445 }
424 446 }
425 447 }
426 448
427 - function checkEmailField( field, errors ) {
428 - var fieldID = getFieldId( field, true ),
449 + /**
450 + * Checks if the confirm field should be checked.
451 + *
452 + * @since 6.15
453 + *
454 + * @param {HTMLElement} field Field input.
455 + * @param {boolean} onSubmit Is `true` if the form is being submitted.
456 + */
457 + function shouldCheckConfirmField( field, onSubmit ) {
458 + if ( onSubmit ) {
459 + // Always check on submitting.
460 + return true;
461 + }
462 +
463 + if ( 0 === field.id.indexOf( 'field_conf_' ) ) {
464 + // Always check if it's the confirm field.
465 + return true;
466 + }
467 +
468 + return false;
469 + }
470 +
471 + /**
472 + * Check the email field for errors.
473 + *
474 + * @since 6.15 Added `onSubmit` parameter.
475 + *
476 + * @param {HTMLElement} field Field input.
477 + * @param {Object} errors Errors data.
478 + * @param {boolean} onSubmit Is `true` if the form is being submitted.
479 + */
480 + function checkEmailField( field, errors, onSubmit ) {
481 + const fieldID = getFieldId( field, true ),
429 482 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 483
431 484 // validate the current field we're editing first
432 485 if ( '' !== field.value && pattern.test( field.value ) === false ) {
@@ -432,17 +485,30 @@
432 485 if ( '' !== field.value && pattern.test( field.value ) === false ) {
433 486 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
434 487 }
435 488
436 - confirmField( field, errors );
489 + if ( shouldCheckConfirmField( field, onSubmit ) ) {
490 + confirmField( field, errors );
491 + }
437 492 }
438 493
439 - function checkPasswordField( field, errors ) {
440 - confirmField( field, errors );
494 + /**
495 + * Check the password field for errors.
496 + *
497 + * @since 6.15 Added `onSubmit` parameter.
498 + *
499 + * @param {HTMLElement} field Field input.
500 + * @param {Object} errors Errors data.
501 + * @param {boolean} onSubmit Is `true` if the form is being submitted.
502 + */
503 + function checkPasswordField( field, errors, onSubmit ) {
504 + if ( shouldCheckConfirmField( field, onSubmit ) ) {
505 + confirmField( field, errors );
506 + }
441 507 }
442 508
443 509 function confirmField( field, errors ) {
444 - var value, confirmValue, firstField,
510 + let value, confirmValue, firstField,
445 511 fieldID = getFieldId( field, true ),
446 512 strippedId = field.id.replace( 'conf_', '' ),
447 513 strippedFieldID = fieldID.replace( 'conf_', '' ),
448 514 confirmField = document.getElementById( strippedId.replace( 'field_', 'field_conf_' ) );
@@ -454,9 +520,9 @@
454 520 if ( fieldID !== strippedFieldID ) {
455 521 firstField = document.getElementById( strippedId );
456 522 value = firstField.value;
457 523 confirmValue = confirmField.value;
458 - if ( '' !== value && '' !== confirmValue && value !== confirmValue ) {
524 + if ( value !== confirmValue ) {
459 525 errors[ 'conf_' + strippedFieldID ] = getFieldValidationMessage( confirmField, 'data-confmsg' );
460 526 }
461 527 } else {
462 528 validateField( confirmField );
@@ -463,9 +529,9 @@
463 529 }
464 530 }
465 531
466 532 function checkNumberField( field, errors ) {
467 - var fieldID,
533 + let fieldID,
468 534 number = field.value;
469 535
470 536 if ( number !== '' && isNaN( number / 1 ) !== false ) {
471 537 fieldID = getFieldId( field, true );
@@ -475,9 +541,9 @@
475 541 }
476 542 }
477 543
478 544 function checkPatternField( field, errors ) {
479 - var fieldID,
545 + let fieldID,
480 546 text = field.value,
481 547 format = getFieldValidationMessage( field, 'pattern' );
482 548
483 549 if ( format !== '' && text !== '' ) {
@@ -482,11 +548,17 @@
482 548
483 549 if ( format !== '' && text !== '' ) {
484 550 fieldID = getFieldId( field, true );
485 551 if ( ! ( fieldID in errors ) ) {
486 - format = new RegExp( '^' + format + '$', 'i' );
487 - if ( format.test( text ) === false ) {
488 - errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
552 + if ( 'object' === typeof window.frmProForm && 'function' === typeof window.frmProForm.isIntlPhoneInput && window.frmProForm.isIntlPhoneInput( field ) ) {
553 + if ( ! window.frmProForm.validateIntlPhoneInput( field ) ) {
554 + errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
555 + }
556 + } else {
557 + format = new RegExp( '^' + format + '$', 'i' );
558 + if ( format.test( text ) === false ) {
559 + errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
560 + }
489 561 }
490 562 }
491 563 }
492 564 }
@@ -496,9 +568,9 @@
496 568 *
497 569 * @since 6.5.1
498 570 */
499 571 function setSelectPlaceholderColor() {
500 - var selects = document.querySelectorAll( '.form-field select' ),
572 + let selects = document.querySelectorAll( '.form-field select' ),
501 573 styleElement = document.querySelector( '.with_frm_style' ),
502 574 textColorDisabled = styleElement ? getComputedStyle( styleElement ).getPropertyValue( '--text-color-disabled' ).trim() : '',
503 575 changeSelectColor;
504 576
@@ -508,9 +580,9 @@
508 580 }
509 581
510 582 // Function to change the color of a select element
511 583 changeSelectColor = function( select ) {
512 - if ( hasClass( select.options[select.selectedIndex], 'frm-select-placeholder' ) ) {
584 + if ( select.options[select.selectedIndex] && hasClass( select.options[select.selectedIndex], 'frm-select-placeholder' ) ) {
513 585 select.style.setProperty( 'color', textColorDisabled, 'important' );
514 586 } else {
515 587 select.style.color = '';
516 588 }
@@ -528,9 +600,9 @@
528 600 });
529 601 }
530 602
531 603 function hasInvisibleRecaptcha( object ) {
532 - var recaptcha, recaptchaID, alreadyChecked;
604 + let recaptcha, recaptchaID, alreadyChecked;
533 605
534 606 if ( isGoingToPrevPage( object ) ) {
535 607 return false;
536 608 }
@@ -540,73 +612,103 @@
540 612 recaptchaID = recaptcha.data( 'rid' );
541 613 alreadyChecked = grecaptcha.getResponse( recaptchaID );
542 614 if ( alreadyChecked.length === 0 ) {
543 615 return recaptcha;
544 - } else {
545 - return false;
546 616 }
547 - } else {
548 - return false;
549 617 }
618 + return false;
550 619 }
551 620
552 621 function executeInvisibleRecaptcha( invisibleRecaptcha ) {
553 - var recaptchaID = invisibleRecaptcha.data( 'rid' );
622 + const recaptchaID = invisibleRecaptcha.data( 'rid' );
554 623 grecaptcha.reset( recaptchaID );
555 624 grecaptcha.execute( recaptchaID );
556 625 }
557 626
558 627 function validateRecaptcha( form, errors ) {
559 - var recaptchaID, response, fieldContainer, fieldID,
560 - $recaptcha = jQuery( form ).find( '.frm-g-recaptcha' );
561 - if ( $recaptcha.length ) {
562 - recaptchaID = $recaptcha.data( 'rid' );
628 + let response;
563 629
564 - try {
565 - response = grecaptcha.getResponse( recaptchaID );
566 - } catch ( e ) {
567 - if ( jQuery( form ).find( 'input[name="recaptcha_checked"]' ).length ) {
568 - return errors;
569 - } else {
570 - response = '';
571 - }
630 + const $recaptcha = jQuery( form ).find( '.frm-g-recaptcha' );
631 + if ( ! $recaptcha.length ) {
632 + return errors;
633 + }
634 +
635 + const recaptchaID = $recaptcha.data( 'rid' );
636 +
637 + try {
638 + response = grecaptcha.getResponse( recaptchaID );
639 + } catch ( e ) {
640 + if ( jQuery( form ).find( 'input[name="recaptcha_checked"]' ).length ) {
641 + return errors;
572 642 }
643 + response = '';
644 + }
573 645
574 - if ( response.length === 0 ) {
575 - fieldContainer = $recaptcha.closest( '.frm_form_field' );
576 - fieldID = fieldContainer.attr( 'id' ).replace( 'frm_field_', '' ).replace( '_container', '' );
577 - errors[ fieldID ] = '';
578 - }
646 + if ( response.length === 0 ) {
647 + const fieldContainer = $recaptcha.closest( '.frm_form_field' );
648 + const fieldID = fieldContainer.attr( 'id' ).replace( 'frm_field_', '' ).replace( '_container', '' );
649 + errors[ fieldID ] = '';
579 650 }
651 +
580 652 return errors;
581 653 }
582 654
655 + /**
656 + * @param {HTMLElement} field
657 + * @param {string} messageType
658 + * @return {string} The error message to display.
659 + */
583 660 function getFieldValidationMessage( field, messageType ) {
584 - var msg, errorHtml;
585 -
586 - msg = field.getAttribute( messageType );
661 + let msg = field.getAttribute( messageType );
587 662 if ( null === msg ) {
588 663 msg = '';
589 664 }
590 665
591 666 if ( '' !== msg && shouldWrapErrorHtmlAroundMessageType( messageType ) ) {
592 - errorHtml = field.getAttribute( 'data-error-html' );
593 - if ( null !== errorHtml ) {
594 - errorHtml = errorHtml.replace( /\+/g, '%20' );
595 - msg = decodeURIComponent( errorHtml ).replace( '[error]', msg );
596 - msg = msg.replace( '[key]', getFieldId( field, false ) );
597 - }
667 + msg = wrapErrorHtml( msg, field );
598 668 }
599 669
600 670 return msg;
601 671 }
602 672
673 + /**
674 + * @param {string} msg
675 + * @param {HTMLElement} field
676 + * @return {string} The error HTML to use.
677 + */
678 + function wrapErrorHtml( msg, field ) {
679 + let errorHtml = field.getAttribute( 'data-error-html' );
680 + if ( null === errorHtml ) {
681 + return msg;
682 + }
683 +
684 + errorHtml = errorHtml.replace( /\+/g, '%20' );
685 + msg = decodeURIComponent( errorHtml ).replace( '[error]', msg );
686 + const fieldId = getFieldId( field, false );
687 + const split = fieldId.split( '-' );
688 + const fieldIdParts = field.id.split( '_' );
689 + fieldIdParts.shift(); // Drop the "field" value from the front.
690 + split[0] = fieldIdParts.join( '_' );
691 + const errorKey = split.join( '-' );
692 + return msg.replace( '[key]', errorKey );
693 + }
694 +
603 695 function shouldWrapErrorHtmlAroundMessageType( type ) {
604 696 return 'pattern' !== type;
605 697 }
606 698
699 + /**
700 + * Check if JS validation should happen.
701 + *
702 + * @param {HTMLElement|Object} object Form object.
703 + * @return {boolean} True if validation is enabled and we are not saving a draft or going to a previous page.
704 + */
607 705 function shouldJSValidate( object ) {
608 - var validate = jQuery( object ).hasClass( 'frm_js_validate' );
706 + if ( 'function' === typeof object.get ) {
707 + // Get the HTMLElement from a jQuery object.
708 + object = object.get( 0 );
709 + }
710 + let validate = hasClass( object, 'frm_js_validate' );
609 711 if ( validate && typeof frmProForm !== 'undefined' && ( frmProForm.savingDraft( object ) || frmProForm.goingToPreviousPage( object ) ) ) {
610 712 validate = false;
611 713 }
612 714
@@ -612,10 +714,15 @@
612 714
613 715 return validate;
614 716 }
615 717
718 + /**
719 + * @param {HTMLElement} object
720 + * @param {string|undefined} action
721 + * @return {void}
722 + */
616 723 function getFormErrors( object, action ) {
617 - var fieldset, data, success, error, shouldTriggerEvent;
724 + let fieldset, data, success, error, shouldTriggerEvent;
618 725
619 726 if ( typeof action === 'undefined' ) {
620 727 jQuery( object ).find( 'input[name="frm_action"]' ).val();
621 728 }
@@ -626,9 +733,9 @@
626 733 data = jQuery( object ).serialize() + '&action=frm_entries_' + action + '&nonce=' + frm_js.nonce; // eslint-disable-line camelcase
627 734 shouldTriggerEvent = object.classList.contains( 'frm_trigger_event_on_submit' );
628 735
629 736 success = function( response ) {
630 - var defaultResponse, formID, replaceContent, pageOrder, formReturned, contSubmit, delay,
737 + let defaultResponse, formID, replaceContent, pageOrder, formReturned, contSubmit, delay,
631 738 $fieldCont, key, inCollapsedSection, frmTrigger, newTab;
632 739
633 740 defaultResponse = {
634 741 content: '',
@@ -671,9 +778,9 @@
671 778 if ( response.content !== '' ) {
672 779 // the form or success message was returned
673 780
674 781 if ( shouldTriggerEvent ) {
675 - triggerCustomEvent( object, 'frmSubmitEvent' );
782 + triggerCustomEvent( object, 'frmSubmitEvent', { content: response.content });
676 783 return;
677 784 }
678 785
679 786 removeSubmitLoading( jQuery( object ) );
@@ -688,10 +795,12 @@
688 795 delay = maybeSlideOut( replaceContent, response.content );
689 796
690 797 setTimeout(
691 798 function() {
692 - var container, input, previousInput;
799 + let container, input, previousInput;
693 800
801 + afterFormSubmittedBeforeReplace( object, response );
802 +
694 803 replaceContent.replaceWith( response.content );
695 804
696 805 addUrlParam( response );
697 806
@@ -750,9 +859,9 @@
750 859 }
751 860 }
752 861
753 862 jQuery( object ).find( '.frm-g-recaptcha, .g-recaptcha, .h-captcha' ).each( function() {
754 - var $recaptcha = jQuery( this ),
863 + const $recaptcha = jQuery( this ),
755 864 recaptchaID = $recaptcha.data( 'rid' );
756 865
757 866 if ( typeof grecaptcha !== 'undefined' && grecaptcha ) {
758 867 if ( recaptchaID ) {
@@ -773,9 +882,9 @@
773 882
774 883 if ( contSubmit ) {
775 884 object.submit();
776 885 } else {
777 - jQuery( object ).prepend( response.error_message );
886 + object.insertAdjacentHTML( 'afterbegin', response.error_message );
778 887 checkForErrorsAndMaybeSetFocus();
779 888 }
780 889 } else {
781 890 // there may have been a plugin conflict, or the form is not set to submit with ajax
@@ -794,9 +903,9 @@
794 903 postToAjaxUrl( object, data, success, error );
795 904 }
796 905
797 906 function postToAjaxUrl( form, data, success, error ) {
798 - var ajaxUrl, action, ajaxParams;
907 + let ajaxUrl, action, ajaxParams;
799 908
800 909 ajaxUrl = frm_js.ajax_url; // eslint-disable-line camelcase
801 910 action = form.getAttribute( 'action' );
802 911
@@ -818,9 +927,9 @@
818 927 jQuery.ajax( ajaxParams );
819 928 }
820 929
821 930 function afterFormSubmitted( object, response ) {
822 - var formCompleted = jQuery( response.content ).find( '.frm_message' );
931 + const formCompleted = jQuery( response.content ).find( '.frm_message' );
823 932 if ( formCompleted.length ) {
824 933 jQuery( document ).trigger( 'frmFormComplete', [ object, response ]);
825 934 } else {
826 935 jQuery( document ).trigger( 'frmPageChanged', [ object, response ]);
@@ -826,10 +935,26 @@
826 935 jQuery( document ).trigger( 'frmPageChanged', [ object, response ]);
827 936 }
828 937 }
829 938
939 + /**
940 + * Trigger an event before the form is replaced with a success message.
941 + *
942 + * @since 6.9
943 + *
944 + * @param {HTMLElement} object The form.
945 + * @param {Object} response The response from submitting the form with AJAX.
946 + * @return {void}
947 + */
948 + function afterFormSubmittedBeforeReplace( object, response ) {
949 + const formCompleted = jQuery( response.content ).find( '.frm_message' );
950 + if ( formCompleted.length ) {
951 + triggerCustomEvent( document, 'frmFormCompleteBeforeReplace', { object, response });
952 + }
953 + }
954 +
830 955 function removeAddedScripts( formContainer, formID ) {
831 - var endReplace = jQuery( '.frm_end_ajax_' + formID );
956 + const endReplace = jQuery( '.frm_end_ajax_' + formID );
832 957 if ( endReplace.length ) {
833 958 formContainer.nextUntil( '.frm_end_ajax_' + formID ).remove();
834 959 endReplace.remove();
835 960 }
@@ -835,9 +960,9 @@
835 960 }
836 961 }
837 962
838 963 function maybeSlideOut( oldContent, newContent ) {
839 - var c,
964 + let c,
840 965 newClass = 'frm_slideout';
841 966 if ( newContent.indexOf( ' frm_slide' ) !== -1 ) {
842 967 c = oldContent.children();
843 968 if ( newContent.indexOf( ' frm_going_back' ) !== -1 ) {
@@ -850,9 +975,9 @@
850 975 return 0;
851 976 }
852 977
853 978 function addUrlParam( response ) {
854 - var url;
979 + let url;
855 980 if ( history.pushState && typeof response.page !== 'undefined' ) {
856 981 url = addQueryVar( 'frm_page', response.page );
857 982 window.history.pushState({ 'html': response.html }, '', '?' + url );
858 983 }
@@ -858,9 +983,9 @@
858 983 }
859 984 }
860 985
861 986 function addQueryVar( key, value ) {
862 - var kvp, i, x;
987 + let kvp, i, x;
863 988
864 989 key = encodeURI( key );
865 990 value = encodeURI( value );
866 991
@@ -884,13 +1009,14 @@
884 1009 return kvp.join( '&' );
885 1010 }
886 1011
887 1012 function addFieldError( $fieldCont, key, jsErrors ) {
888 - var input, id, describedBy, roleString;
1013 + let input, id, describedBy, roleString;
889 1014 if ( $fieldCont.length && $fieldCont.is( ':visible' ) ) {
890 1015 $fieldCont.addClass( 'frm_blank_field' );
891 1016 input = $fieldCont.find( 'input, select, textarea' );
892 - id = 'frm_error_field_' + key;
1017 + id = getErrorElementId( key, input.get( 0 ) );
1018 +
893 1019 describedBy = input.attr( 'aria-describedby' );
894 1020
895 1021 if ( typeof frmThemeOverride_frmPlaceError === 'function' ) { // eslint-disable-line camelcase
896 1022 frmThemeOverride_frmPlaceError( key, jsErrors );
@@ -921,15 +1047,41 @@
921 1047 jQuery( document ).trigger( 'frmAddFieldError', [ $fieldCont, key, jsErrors ]);
922 1048 }
923 1049 }
924 1050
1051 + /**
1052 + * Get the ID to use for an error element added when submitting with AJAX.
1053 + *
1054 + * @param {string} key
1055 + * @param {HTMLElement} input
1056 + * @return {string} The ID to use for the error element.
1057 + */
1058 + function getErrorElementId( key, input ) {
1059 + if ( isNaN( key ) || ! input.id ) {
1060 + // If key isn't a number, assume it's already in the right format.
1061 + return 'frm_error_field_' + key;
1062 + }
1063 + return 'frm_error_' + input.id;
1064 + }
1065 +
1066 + /**
1067 + * Removes errors before validating with JS.
1068 + * This prevents issues with stale errors that has since been fixed.
1069 + *
1070 + * @param {Object} $fieldCont jQuery object.
1071 + * @return {void}
1072 + */
925 1073 function removeFieldError( $fieldCont ) {
926 - var errorMessage = $fieldCont.find( '.frm_error' ),
927 - errorId = errorMessage.attr( 'id' ),
928 - input = $fieldCont.find( 'input, select, textarea' ),
929 - describedBy = input.attr( 'aria-describedby' );
1074 + const errorMessage = $fieldCont.find( '.frm_error' );
1075 + const errorId = errorMessage.attr( 'id' );
1076 + const input = $fieldCont.find( 'input, select, textarea' );
1077 + let describedBy = input.attr( 'aria-describedby' );
930 1078
931 - $fieldCont.removeClass( 'frm_blank_field has-error' );
1079 + const fieldContainer = $fieldCont.get( 0 );
1080 + if ( fieldContainer && fieldContainer.classList ) {
1081 + fieldContainer.classList.remove( 'frm_blank_field', 'has-error' );
1082 + }
1083 +
932 1084 errorMessage.remove();
933 1085 input.attr( 'aria-invalid', false );
934 1086 input.removeAttr( 'aria-describedby' );
935 1087
@@ -944,13 +1096,21 @@
944 1096 jQuery( '.form-field .frm_error' ).replaceWith( '' );
945 1097 jQuery( '.frm_error_style' ).remove();
946 1098 }
947 1099
1100 + /**
1101 + * @param {HTMLElement|Object} object Form object.
1102 + * @return {void}
1103 + */
948 1104 function scrollToFirstField( object ) {
949 - var field = jQuery( object ).find( '.frm_blank_field' ).first();
950 - if ( field.length ) {
951 - frmFrontForm.scrollMsg( field, object, true );
1105 + if ( 'function' === typeof object.get ) {
1106 + // Get the HTMLElement from a jQuery object.
1107 + object = object.get( 0 );
952 1108 }
1109 + const field = object.querySelector( '.frm_blank_field' );
1110 + if ( field ) {
1111 + frmFrontForm.scrollMsg( jQuery( field ), object, true );
1112 + }
953 1113 }
954 1114
955 1115 function showSubmitLoading( $object ) {
956 1116 showLoadingIndicator( $object );
@@ -965,9 +1125,9 @@
965 1125 }
966 1126 }
967 1127
968 1128 function addLoadingClass( $object ) {
969 - var loadingClass = isGoingToPrevPage( $object ) ? 'frm_loading_prev' : 'frm_loading_form';
1129 + const loadingClass = isGoingToPrevPage( $object ) ? 'frm_loading_prev' : 'frm_loading_form';
970 1130
971 1131 $object.addClass( loadingClass );
972 1132 }
973 1133
@@ -975,9 +1135,9 @@
975 1135 return ( typeof frmProForm !== 'undefined' && frmProForm.goingToPreviousPage( $object ) );
976 1136 }
977 1137
978 1138 function removeSubmitLoading( $object, enable, processesRunning ) {
979 - var loadingForm;
1139 + let loadingForm;
980 1140
981 1141 if ( processesRunning > 0 ) {
982 1142 return;
983 1143 }
@@ -994,9 +1154,9 @@
994 1154 }
995 1155 }
996 1156
997 1157 function showFileLoading( object ) {
998 - var fileval,
1158 + let fileval,
999 1159 loading = document.getElementById( 'frm_loading' );
1000 1160 if ( loading !== null ) {
1001 1161 fileval = jQuery( object ).find( 'input[type=file]' ).val();
1002 1162 if ( typeof fileval !== 'undefined' && fileval !== '' ) {
@@ -1006,69 +1166,8 @@
1006 1166 }
1007 1167 }
1008 1168 }
1009 1169
1010 - function clearDefault() {
1011 - /*jshint validthis:true */
1012 - toggleDefault( jQuery( this ), 'clear' );
1013 - }
1014 -
1015 - function replaceDefault() {
1016 - /*jshint validthis:true */
1017 - toggleDefault( jQuery( this ), 'replace' );
1018 - }
1019 -
1020 - function toggleDefault( $thisField, e ) {
1021 - // TODO: Fix this for a default value that is a number or array
1022 - var thisVal,
1023 - v = $thisField.data( 'frmval' ).replace( /(\n|\r\n)/g, '\r' );
1024 - if ( v === '' || typeof v === 'undefined' ) {
1025 - return false;
1026 - }
1027 - thisVal = $thisField.val().replace( /(\n|\r\n)/g, '\r' );
1028 -
1029 - if ( 'replace' === e ) {
1030 - if ( thisVal === '' ) {
1031 - $thisField.addClass( 'frm_default' ).val( v );
1032 - }
1033 - } else if ( thisVal == v ) {
1034 - $thisField.removeClass( 'frm_default' ).val( '' );
1035 - }
1036 - }
1037 -
1038 - function resendEmail() {
1039 - /*jshint validthis:true */
1040 - var $link = jQuery( this ),
1041 - entryId = this.getAttribute( 'data-eid' ),
1042 - formId = this.getAttribute( 'data-fid' ),
1043 - label = $link.find( '.frm_link_label' );
1044 - if ( label.length < 1 ) {
1045 - label = $link;
1046 - }
1047 - label.append( '<span class="frm-wait"></span>' );
1048 -
1049 - jQuery.ajax({
1050 - type: 'POST',
1051 - url: frm_js.ajax_url, // eslint-disable-line camelcase
1052 - data: {
1053 - action: 'frm_entries_send_email',
1054 - entry_id: entryId,
1055 - form_id: formId,
1056 - nonce: frm_js.nonce // eslint-disable-line camelcase
1057 - },
1058 - success: function( msg ) {
1059 - var admin = document.getElementById( 'wpbody' );
1060 - if ( admin === null ) {
1061 - label.html( msg );
1062 - } else {
1063 - label.html( '' );
1064 - $link.after( msg );
1065 - }
1066 - }
1067 - });
1068 - return false;
1069 - }
1070 -
1071 1170 /**********************************************
1072 1171 * General Helpers
1073 1172 *********************************************/
1074 1173
@@ -1073,68 +1172,12 @@
1073 1172 *********************************************/
1074 1173
1075 1174 function confirmClick() {
1076 1175 /*jshint validthis:true */
1077 - var message = jQuery( this ).data( 'frmconfirm' );
1176 + const message = jQuery( this ).data( 'frmconfirm' );
1078 1177 return confirm( message );
1079 1178 }
1080 1179
1081 - function toggleDiv() {
1082 - /*jshint validthis:true */
1083 - var div = jQuery( this ).data( 'frmtoggle' );
1084 - if ( jQuery( div ).is( ':visible' ) ) {
1085 - jQuery( div ).slideUp( 'fast' );
1086 - } else {
1087 - jQuery( div ).slideDown( 'fast' );
1088 - }
1089 - return false;
1090 - }
1091 -
1092 - /**********************************************
1093 - * Fallback functions
1094 - *********************************************/
1095 -
1096 - function addTrimFallbackForIE() {
1097 - if ( typeof String.prototype.trim !== 'function' ) {
1098 - String.prototype.trim = function() {
1099 - return this.replace( /^\s+|\s+$/g, '' );
1100 - };
1101 - }
1102 - }
1103 -
1104 - function addFilterFallbackForIE() {
1105 - var t, len, res, thisp, i, val;
1106 -
1107 - if ( ! Array.prototype.filter ) {
1108 -
1109 - Array.prototype.filter = function( fun /*, thisp */ ) {
1110 -
1111 - if ( this === void 0 || this === null ) {
1112 - throw new TypeError();
1113 - }
1114 -
1115 - t = Object( this );
1116 - len = t.length >>> 0;
1117 - if ( typeof fun !== 'function' ) {
1118 - throw new TypeError();
1119 - }
1120 -
1121 - res = [];
1122 - thisp = arguments[1];
1123 - for ( i = 0; i < len; i++ ) {
1124 - if ( i in t ) {
1125 - val = t[i]; // in case fun mutates this
1126 - if ( fun.call( thisp, val, i, t ) ) {
1127 - res.push( val );
1128 - }
1129 - }
1130 - }
1131 -
1132 - return res;
1133 - };
1134 - }
1135 - }
1136 -
1137 1180 /**
1138 1181 * Check for -webkit-box-shadow css value for input:-webkit-autofill selector.
1139 1182 * If this is a match, the User is autofilling the input on a Webkit browser.
1140 1183 * We want to delete the Honeypot field, otherwise it will get triggered as spam on autocomplete.
@@ -1139,9 +1182,9 @@
1139 1182 * If this is a match, the User is autofilling the input on a Webkit browser.
1140 1183 * We want to delete the Honeypot field, otherwise it will get triggered as spam on autocomplete.
1141 1184 */
1142 1185 function onHoneypotFieldChange() {
1143 - var css = jQuery( this ).css( 'box-shadow' );
1186 + const css = jQuery( this ).css( 'box-shadow' );
1144 1187 if ( css.match( /inset/ ) ) {
1145 1188 this.parentNode.removeChild( this );
1146 1189 }
1147 1190 }
@@ -1149,9 +1192,9 @@
1149 1192 function maybeMakeHoneypotFieldsUntabbable() {
1150 1193 document.addEventListener( 'keydown', handleKeyUp );
1151 1194
1152 1195 function handleKeyUp( event ) {
1153 - var code;
1196 + let code;
1154 1197
1155 1198 if ( 'undefined' !== typeof event.key ) {
1156 1199 code = event.key;
1157 1200 } else if ( 'undefined' !== typeof event.keyCode && 9 === event.keyCode ) {
@@ -1180,11 +1223,11 @@
1180 1223 *
1181 1224 * @since 4.10.02
1182 1225 */
1183 1226 function changeFocusWhenClickComboFieldLabel() {
1184 - var label;
1227 + let label;
1185 1228
1186 - var comboInputsContainer = document.querySelectorAll( '.frm_combo_inputs_container' );
1229 + const comboInputsContainer = document.querySelectorAll( '.frm_combo_inputs_container' );
1187 1230 comboInputsContainer.forEach( function( inputsContainer ) {
1188 1231 if ( ! inputsContainer.closest( '.frm_form_field' ) ) {
1189 1232 return;
1190 1233 }
@@ -1193,9 +1236,9 @@
1193 1236 if ( ! label ) {
1194 1237 return;
1195 1238 }
1196 1239
1197 - label.addEventListener( 'click', function( e ) {
1240 + label.addEventListener( 'click', function() {
1198 1241 inputsContainer.querySelector( '.frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea' ).focus();
1199 1242 });
1200 1243 });
1201 1244 }
@@ -1200,9 +1243,9 @@
1200 1243 });
1201 1244 }
1202 1245
1203 1246 function checkForErrorsAndMaybeSetFocus() {
1204 - var errors, element, timeoutCallback;
1247 + let errors, element, timeoutCallback;
1205 1248
1206 1249 if ( ! frm_js.focus_first_error ) { // eslint-disable-line camelcase
1207 1250 return;
1208 1251 }
@@ -1222,9 +1265,9 @@
1222 1265
1223 1266 if ( 'undefined' !== typeof element.classList ) {
1224 1267 if ( element.classList.contains( 'html-active' ) ) {
1225 1268 timeoutCallback = function() {
1226 - var textarea = element.querySelector( 'textarea' );
1269 + const textarea = element.querySelector( 'textarea' );
1227 1270 if ( null !== textarea ) {
1228 1271 textarea.focus();
1229 1272 }
1230 1273 };
@@ -1242,27 +1285,16 @@
1242 1285 } while ( element.previousSibling );
1243 1286 }
1244 1287
1245 1288 /**
1246 - * Checks if is on IE browser.
1247 - *
1248 - * @since 5.4
1249 - *
1250 - * @return {Boolean}
1251 - */
1252 - function isIE() {
1253 - return navigator.userAgent.indexOf( 'MSIE' ) > -1 || navigator.userAgent.indexOf( 'Trident' ) > -1;
1254 - }
1255 -
1256 - /**
1257 1289 * Does the same as jQuery( document ).on( 'event', 'selector', handler ).
1258 1290 *
1259 1291 * @since 5.4
1260 1292 *
1261 - * @param {String} event Event name.
1262 - * @param {String} selector Selector.
1263 - * @param {Function} handler Handler.
1264 - * @param {Boolean|Object} options Options to be added to `addEventListener()` method. Default is `false`.
1293 + * @param {string} event Event name.
1294 + * @param {string} selector Selector.
1295 + * @param {Function} handler Handler.
1296 + * @param {boolean | Object} options Options to be added to `addEventListener()` method. Default is `false`.
1265 1297 */
1266 1298 function documentOn( event, selector, handler, options ) {
1267 1299 if ( 'undefined' === typeof options ) {
1268 1300 options = false;
@@ -1268,9 +1300,9 @@
1268 1300 options = false;
1269 1301 }
1270 1302
1271 1303 document.addEventListener( event, function( e ) {
1272 - var target;
1304 + let target;
1273 1305
1274 1306 // loop parent nodes from the target to the delegation node.
1275 1307 for ( target = e.target; target && target != this; target = target.parentNode ) {
1276 1308 if ( target && target.matches && target.matches( selector ) ) {
@@ -1281,15 +1313,15 @@
1281 1313 }, options );
1282 1314 }
1283 1315
1284 1316 function initFloatingLabels() {
1285 - var checkFloatLabel, checkDropdownLabel, checkPlaceholderIE, runOnLoad, selector, floatClass;
1317 + let checkFloatLabel, checkDropdownLabel, runOnLoad, selector, floatClass;
1286 1318
1287 1319 selector = '.frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea';
1288 1320 floatClass = 'frm_label_float_top';
1289 1321
1290 1322 checkFloatLabel = function( input ) {
1291 - var container, shouldFloatTop, firstOpt;
1323 + let container, shouldFloatTop, firstOpt;
1292 1324
1293 1325 container = input.closest( '.frm_inside_container' );
1294 1326 if ( ! container ) {
1295 1327 return;
@@ -1306,22 +1338,18 @@
1306 1338 if ( firstOpt.hasAttribute( 'data-label' ) ) {
1307 1339 firstOpt.textContent = firstOpt.getAttribute( 'data-label' );
1308 1340 firstOpt.removeAttribute( 'data-label' );
1309 1341 }
1310 - } else {
1311 - if ( firstOpt.textContent ) {
1312 - firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1313 - firstOpt.textContent = '';
1314 - }
1342 + } else if ( firstOpt.textContent ) {
1343 + firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1344 + firstOpt.textContent = '';
1315 1345 }
1316 - } else if ( isIE() ) {
1317 - checkPlaceholderIE( input );
1318 1346 }
1319 1347 };
1320 1348
1321 1349 checkDropdownLabel = function() {
1322 1350 document.querySelectorAll( '.frm-show-form .frm_inside_container:not(.' + floatClass + ') select' ).forEach( function( input ) {
1323 - var firstOpt = input.querySelector( 'option:first-child' );
1351 + const firstOpt = input.querySelector( 'option:first-child' );
1324 1352
1325 1353 if ( firstOpt.textContent ) {
1326 1354 firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1327 1355 firstOpt.textContent = '';
@@ -1328,27 +1356,8 @@
1328 1356 }
1329 1357 });
1330 1358 };
1331 1359
1332 - checkPlaceholderIE = function( input ) {
1333 - if ( input.value ) {
1334 - // Don't need to handle this case because placeholder isn't shown.
1335 - return;
1336 - }
1337 -
1338 - if ( document.activeElement === input ) {
1339 - if ( input.hasAttribute( 'data-placeholder' ) ) {
1340 - input.placeholder = input.getAttribute( 'data-placeholder' );
1341 - input.removeAttribute( 'data-placeholder' );
1342 - }
1343 - } else {
1344 - if ( input.placeholder ) {
1345 - input.setAttribute( 'data-placeholder', input.placeholder );
1346 - input.placeholder = '';
1347 - }
1348 - }
1349 - };
1350 -
1351 1360 [ 'focus', 'blur', 'change' ].forEach( function( eventName ) {
1352 1361 documentOn(
1353 1362 eventName,
1354 1363 selector,
@@ -1368,9 +1377,9 @@
1368 1377 checkFloatLabel( document.activeElement );
1369 1378 } else if ( firstLoad ) {
1370 1379 document.querySelectorAll( '.frm_inside_container' ).forEach(
1371 1380 function( container ) {
1372 - var input = container.querySelector( 'input, select, textarea' );
1381 + const input = container.querySelector( 'input, select, textarea' );
1373 1382 if ( input && '' !== input.value ) {
1374 1383 checkFloatLabel( input );
1375 1384 }
1376 1385 }
@@ -1377,14 +1386,8 @@
1377 1386 );
1378 1387 }
1379 1388
1380 1389 checkDropdownLabel();
1381 -
1382 - if ( isIE() ) {
1383 - document.querySelectorAll( selector ).forEach( function( input ) {
1384 - checkPlaceholderIE( input );
1385 - });
1386 - }
1387 1390 };
1388 1391
1389 1392 runOnLoad( true );
1390 1393
@@ -1417,9 +1420,9 @@
1417 1420 return true;
1418 1421 }
1419 1422
1420 1423 function maybeClearCustomValidityMessage( event, field ) {
1421 - var key,
1424 + let key,
1422 1425 isInvalid = false;
1423 1426
1424 1427 if ( ! shouldUpdateValidityMessage( field ) ) {
1425 1428 return;
@@ -1440,9 +1443,9 @@
1440 1443 }
1441 1444 }
1442 1445
1443 1446 function maybeShowNewTabFallbackMessage() {
1444 - var messageEl;
1447 + let messageEl;
1445 1448
1446 1449 if ( ! window.frmShowNewTabFallback ) {
1447 1450 return;
1448 1451 }
@@ -1455,9 +1458,9 @@
1455 1458 messageEl.insertAdjacentHTML( 'beforeend', ' ' + frmShowNewTabFallback.message );
1456 1459 }
1457 1460
1458 1461 function setCustomValidityMessage() {
1459 - var forms, length, index;
1462 + let forms, length, index;
1460 1463
1461 1464 forms = document.getElementsByClassName( 'frm-show-form' );
1462 1465 length = forms.length;
1463 1466
@@ -1464,9 +1467,9 @@
1464 1467 for ( index = 0; index < length; ++index ) {
1465 1468 forms[ index ].addEventListener(
1466 1469 'invalid',
1467 1470 function( event ) {
1468 - var target = event.target;
1471 + const target = event.target;
1469 1472
1470 1473 if ( shouldUpdateValidityMessage( target ) ) {
1471 1474 target.setCustomValidity( target.dataset.invmsg );
1472 1475 }
@@ -1475,12 +1478,33 @@
1475 1478 );
1476 1479 }
1477 1480 }
1478 1481
1482 + function enableSubmitButtonOnBackButtonPress() {
1483 + window.addEventListener( 'pageshow', function( event ) {
1484 + if ( event.persisted ) {
1485 + document.querySelectorAll( '.frm_loading_form' ).forEach(
1486 + function( form ) {
1487 + enableSubmitButton( jQuery( form ) );
1488 + }
1489 + );
1490 + removeSubmitLoading();
1491 + }
1492 + });
1493 + }
1494 +
1495 + /**
1496 + * Destroys the formidable generated global hcaptcha object since it wouldn't otherwise render.
1497 + */
1498 + function destroyhCaptcha() {
1499 + if ( ! window.hasOwnProperty( 'hcaptcha' ) || ! document.querySelector( '.frm-show-form .h-captcha' ) ) {
1500 + return;
1501 + }
1502 + window.hcaptcha = null;
1503 + }
1504 +
1479 1505 return {
1480 1506 init: function() {
1481 - maybeAddPolyfills();
1482 -
1483 1507 jQuery( document ).off( 'submit.formidable', '.frm-show-form' );
1484 1508 jQuery( document ).on( 'submit.formidable', '.frm-show-form', frmFrontForm.submitForm );
1485 1509
1486 1510 jQuery( '.frm-show-form input[onblur], .frm-show-form textarea[onblur]' ).each( function() {
@@ -1488,14 +1512,8 @@
1488 1512 jQuery( this ).trigger( 'blur' );
1489 1513 }
1490 1514 });
1491 1515
1492 - jQuery( document ).on( 'focus', '.frm_toggle_default', clearDefault );
1493 - jQuery( document ).on( 'blur', '.frm_toggle_default', replaceDefault );
1494 - jQuery( '.frm_toggle_default' ).trigger( 'blur' );
1495 -
1496 - jQuery( document.getElementById( 'frm_resend_email' ) ).on( 'click', resendEmail );
1497 -
1498 1516 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 );
1499 1517
1500 1518 jQuery( document ).on( 'change', '[id^=frm_email_]', onHoneypotFieldChange );
1501 1519 maybeMakeHoneypotFieldsUntabbable();
@@ -1500,9 +1518,8 @@
1500 1518 jQuery( document ).on( 'change', '[id^=frm_email_]', onHoneypotFieldChange );
1501 1519 maybeMakeHoneypotFieldsUntabbable();
1502 1520
1503 1521 jQuery( document ).on( 'click', 'a[data-frmconfirm]', confirmClick );
1504 - jQuery( 'a[data-frmtoggle]' ).on( 'click', toggleDiv );
1505 1522
1506 1523 checkForErrorsAndMaybeSetFocus();
1507 1524
1508 1525 // Focus on the first sub field when clicking to the primary label of combo field.
@@ -1507,12 +1524,8 @@
1507 1524
1508 1525 // Focus on the first sub field when clicking to the primary label of combo field.
1509 1526 changeFocusWhenClickComboFieldLabel();
1510 1527
1511 - // Add fallbacks for IE.
1512 - addTrimFallbackForIE(); // Trim only works in IE10+.
1513 - addFilterFallbackForIE(); // Filter is not supported in any version of IE.
1514 -
1515 1528 initFloatingLabels();
1516 1529 maybeShowNewTabFallbackMessage();
1517 1530
1518 1531 jQuery( document ).on( 'frmAfterAddRow', setCustomValidityMessage );
@@ -1522,8 +1535,14 @@
1522 1535 setSelectPlaceholderColor();
1523 1536
1524 1537 // Elementor popup show event. Fix Elementor Popup && FF Captcha field conflicts
1525 1538 jQuery( document ).on( 'elementor/popup/show', frmRecaptcha );
1539 +
1540 + enableSubmitButtonOnBackButtonPress();
1541 + jQuery( document ).on(
1542 + 'frmPageChanged',
1543 + destroyhCaptcha
1544 + );
1526 1545 },
1527 1546
1528 1547 getFieldId: function( field, fullID ) {
1529 1548 return getFieldId( field, fullID );
@@ -1528,10 +1547,10 @@
1528 1547 getFieldId: function( field, fullID ) {
1529 1548 return getFieldId( field, fullID );
1530 1549 },
1531 1550
1532 - renderRecaptcha: function( captcha ) {
1533 - var formID, recaptchaID,
1551 + renderCaptcha: function( captcha, captchaSelector ) {
1552 + let formID, captchaID,
1534 1553 size = captcha.getAttribute( 'data-size' ),
1535 1554 rendered = captcha.getAttribute( 'data-rid' ) !== null,
1536 1555 params = {
1537 1556 'sitekey': captcha.getAttribute( 'data-sitekey' ),
@@ -1536,9 +1555,11 @@
1536 1555 params = {
1537 1556 'sitekey': captcha.getAttribute( 'data-sitekey' ),
1538 1557 'size': size,
1539 1558 'theme': captcha.getAttribute( 'data-theme' )
1540 - };
1559 + },
1560 + activeCaptcha = getSelectedCaptcha( captchaSelector ),
1561 + captchaContainer = typeof turnstile !== 'undefined' && turnstile === activeCaptcha ? '#' + captcha.id : captcha.id;
1541 1562
1542 1563 if ( rendered ) {
1543 1564 return;
1544 1565 }
@@ -1550,20 +1571,21 @@
1550 1571 frmFrontForm.afterRecaptcha( token, formID );
1551 1572 };
1552 1573 }
1553 1574
1554 - recaptchaID = grecaptcha.render( captcha.id, params );
1555 1575
1556 - captcha.setAttribute( 'data-rid', recaptchaID );
1576 + captchaID = activeCaptcha.render( captchaContainer, params );
1577 +
1578 + captcha.setAttribute( 'data-rid', captchaID );
1557 1579 },
1558 1580
1559 1581 afterSingleRecaptcha: function() {
1560 - var object = jQuery( '.frm-show-form .g-recaptcha' ).closest( 'form' )[0];
1582 + const object = jQuery( '.frm-show-form .g-recaptcha' ).closest( 'form' )[0];
1561 1583 frmFrontForm.submitFormNow( object );
1562 1584 },
1563 1585
1564 - afterRecaptcha: function( token, formID ) {
1565 - var object = jQuery( '#frm_form_' + formID + '_container form' )[0];
1586 + afterRecaptcha: function( _, formID ) {
1587 + const object = jQuery( '#frm_form_' + formID + '_container form' )[0];
1566 1588 frmFrontForm.submitFormNow( object );
1567 1589 },
1568 1590
1569 1591 submitForm: function( e ) {
@@ -1569,10 +1591,15 @@
1569 1591 submitForm: function( e ) {
1570 1592 frmFrontForm.submitFormManual( e, this );
1571 1593 },
1572 1594
1595 + /**
1596 + * @param {Event} e
1597 + * @param {HTMLElement} object The form object that is being submitted.
1598 + * @return {void}
1599 + */
1573 1600 submitFormManual: function( e, object ) {
1574 - var isPro, errors,
1601 + let isPro, errors,
1575 1602 invisibleRecaptcha = hasInvisibleRecaptcha( object ),
1576 1603 classList = object.className.trim().split( /\s+/gi );
1577 1604
1578 1605 if ( classList && invisibleRecaptcha.length < 1 ) {
@@ -1587,31 +1614,30 @@
1587 1614 }
1588 1615
1589 1616 e.preventDefault();
1590 1617
1591 - if ( typeof frmProForm !== 'undefined' && typeof frmProForm.submitAllowed === 'function' ) {
1592 - if ( ! frmProForm.submitAllowed( object ) ) {
1593 - return;
1594 - }
1618 + if ( typeof frmProForm !== 'undefined' && typeof frmProForm.submitAllowed === 'function' && ! frmProForm.submitAllowed( object ) ) {
1619 + return;
1595 1620 }
1596 1621
1622 + errors = frmFrontForm.validateFormSubmit( object );
1623 + if ( Object.keys( errors ).length !== 0 ) {
1624 + return;
1625 + }
1626 +
1597 1627 if ( invisibleRecaptcha.length ) {
1598 1628 showLoadingIndicator( jQuery( object ) );
1599 1629 executeInvisibleRecaptcha( invisibleRecaptcha );
1600 1630 } else {
1601 1631
1602 - errors = frmFrontForm.validateFormSubmit( object );
1632 + showSubmitLoading( jQuery( object ) );
1603 1633
1604 - if ( Object.keys( errors ).length === 0 ) {
1605 - showSubmitLoading( jQuery( object ) );
1606 -
1607 - frmFrontForm.submitFormNow( object, classList );
1608 - }
1634 + frmFrontForm.submitFormNow( object, classList );
1609 1635 }
1610 1636 },
1611 1637
1612 1638 submitFormNow: function( object ) {
1613 - var hasFileFields, antispamInput,
1639 + let hasFileFields, antispamInput,
1614 1640 classList = object.className.trim().split( /\s+/gi );
1615 1641
1616 1642 if ( object.hasAttribute( 'data-token' ) && null === object.querySelector( '[name="antispam_token"]' ) ) {
1617 1643 // include the antispam token on form submit.
@@ -1636,8 +1662,13 @@
1636 1662 object.submit();
1637 1663 }
1638 1664 },
1639 1665
1666 + /**
1667 + * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
1668 + *
1669 + * @return {Array} List of errors.
1670 + */
1640 1671 validateFormSubmit: function( object ) {
1641 1672 if ( typeof tinyMCE !== 'undefined' && jQuery( object ).find( '.wp-editor-wrap' ).length ) {
1642 1673 tinyMCE.triggerSave();
1643 1674 }
@@ -1654,10 +1685,14 @@
1654 1685
1655 1686 return jsErrors;
1656 1687 },
1657 1688
1689 + /**
1690 + * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
1691 + * @return {Array} List of errors.
1692 + */
1658 1693 getAjaxFormErrors: function( object ) {
1659 - var customErrors, key;
1694 + let customErrors, key;
1660 1695
1661 1696 jsErrors = validateForm( object );
1662 1697 if ( typeof frmThemeOverride_jsErrors === 'function' ) { // eslint-disable-line camelcase
1663 1698 action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
@@ -1668,13 +1703,22 @@
1668 1703 }
1669 1704 }
1670 1705 }
1671 1706
1707 + triggerCustomEvent( document, 'frm_get_ajax_form_errors', {
1708 + formEl: object,
1709 + errors: jsErrors
1710 + });
1711 +
1672 1712 return jsErrors;
1673 1713 },
1674 1714
1715 + /**
1716 + * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
1717 + * @return {void}
1718 + */
1675 1719 addAjaxFormErrors: function( object ) {
1676 - var key, $fieldCont;
1720 + let key, $fieldCont;
1677 1721 removeAllErrors();
1678 1722
1679 1723 for ( key in jsErrors ) {
1680 1724 $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
@@ -1707,14 +1751,14 @@
1707 1751 removeSubmitLoading( $object, enable, processesRunning );
1708 1752 },
1709 1753
1710 1754 scrollToID: function( id ) {
1711 - var object = jQuery( document.getElementById( id ) );
1755 + const object = jQuery( document.getElementById( id ) );
1712 1756 frmFrontForm.scrollMsg( object, false );
1713 1757 },
1714 1758
1715 1759 scrollMsg: function( id, object, animate ) {
1716 - var newPos, m, b, screenTop, screenBottom,
1760 + let newPos, m, b, screenTop, screenBottom,
1717 1761 scrollObj = '';
1718 1762 if ( typeof object === 'undefined' ) {
1719 1763 scrollObj = jQuery( document.getElementById( 'frm_form_' + id + '_container' ) );
1720 1764 if ( scrollObj.length < 1 ) {
@@ -1757,9 +1801,9 @@
1757 1801
1758 1802 fieldValueChanged: function( e ) {
1759 1803 /*jshint validthis:true */
1760 1804
1761 - var fieldId = frmFrontForm.getFieldId( this, false );
1805 + const fieldId = frmFrontForm.getFieldId( this, false );
1762 1806 if ( ! fieldId || typeof fieldId === 'undefined' ) {
1763 1807 return;
1764 1808 }
1765 1809
@@ -1773,55 +1817,8 @@
1773 1817 maybeValidateChange( this );
1774 1818 }
1775 1819 },
1776 1820
1777 - savingDraft: function( object ) {
1778 - console.warn( 'DEPRECATED: function frmFrontForm.savingDraft in v3.0 use frmProForm.savingDraft' );
1779 - if ( typeof frmProForm !== 'undefined' ) {
1780 - return frmProForm.savingDraft( object );
1781 - }
1782 - },
1783 -
1784 - goingToPreviousPage: function( object ) {
1785 - console.warn( 'DEPRECATED: function frmFrontForm.goingToPreviousPage in v3.0 use frmProForm.goingToPreviousPage' );
1786 - if ( typeof frmProForm !== 'undefined' ) {
1787 - return frmProForm.goingToPreviousPage( object );
1788 - }
1789 - },
1790 -
1791 - hideOrShowFields: function() {
1792 - console.warn( 'DEPRECATED: function frmFrontForm.hideOrShowFields in v3.0 use frmProForm.hideOrShowFields' );
1793 - if ( typeof frmProForm !== 'undefined' ) {
1794 - frmProForm.hideOrShowFields();
1795 - }
1796 - },
1797 -
1798 - hidePreviouslyHiddenFields: function() {
1799 - console.warn( 'DEPRECATED: function frmFrontForm.hidePreviouslyHiddenFields in v3.0 use frmProForm.hidePreviouslyHiddenFields' );
1800 - if ( typeof frmProForm !== 'undefined' ) {
1801 - frmProForm.hidePreviouslyHiddenFields();
1802 - }
1803 - },
1804 -
1805 - checkDependentDynamicFields: function( ids ) {
1806 - console.warn( 'DEPRECATED: function frmFrontForm.checkDependentDynamicFields in v3.0 use frmProForm.checkDependentDynamicFields' );
1807 - if ( typeof frmProForm !== 'undefined' ) {
1808 - frmProForm.checkDependentDynamicFields( ids );
1809 - }
1810 - },
1811 -
1812 - checkDependentLookupFields: function( ids ) {
1813 - console.warn( 'DEPRECATED: function frmFrontForm.checkDependentLookupFields in v3.0 use frmProForm.checkDependentLookupFields' );
1814 - if ( typeof frmProForm !== 'undefined' ) {
1815 - frmProForm.checkDependentLookupFields( ids );
1816 - }
1817 - },
1818 -
1819 - loadGoogle: function() {
1820 - console.warn( 'DEPRECATED: function frmFrontForm.loadGoogle in v3.0 use frmProForm.loadGoogle' );
1821 - frmProForm.loadGoogle();
1822 - },
1823 -
1824 1821 escapeHtml: function( text ) {
1825 1822 return text
1826 1823 .replace( /&/g, '&amp;' )
1827 1824 .replace( /</g, '&lt;' )
@@ -1837,91 +1834,63 @@
1837 1834 visible: function( classes ) {
1838 1835 jQuery( classes ).css( 'visibility', 'visible' );
1839 1836 },
1840 1837
1841 - triggerCustomEvent: triggerCustomEvent
1838 + triggerCustomEvent: triggerCustomEvent,
1839 + documentOn
1842 1840 };
1843 1841 }
1844 -frmFrontForm = frmFrontFormJS();
1845 1842
1843 +window.frmFrontForm = frmFrontFormJS();
1844 +
1846 1845 jQuery( document ).ready( function() {
1847 1846 frmFrontForm.init();
1848 1847 });
1849 1848
1850 1849 function frmRecaptcha() {
1851 - var c, cl,
1852 - captchas = jQuery( '.frm-g-recaptcha' );
1853 - for ( c = 0, cl = captchas.length; c < cl; c++ ) {
1854 - frmFrontForm.renderRecaptcha( captchas[c]);
1855 - }
1850 + frmCaptcha( '.frm-g-recaptcha' );
1856 1851 }
1857 1852
1858 -function frmAfterRecaptcha( token ) {
1859 - frmFrontForm.afterSingleRecaptcha( token );
1853 +function frmTurnstile() {
1854 + frmCaptcha( '.cf-turnstile' );
1860 1855 }
1861 1856
1862 -function frmUpdateField( entryId, fieldId, value, message, num ) {
1863 - jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).html( '<span class="frm-loading-img"></span>' );
1864 - jQuery.ajax({
1865 - type: 'POST',
1866 - url: frm_js.ajax_url, // eslint-disable-line camelcase
1867 - data: {
1868 - action: 'frm_entries_update_field_ajax',
1869 - entry_id: entryId,
1870 - field_id: fieldId,
1871 - value: value,
1872 - nonce: frm_js.nonce // eslint-disable-line camelcase
1873 - },
1874 - success: function() {
1875 - if ( message.replace( /^\s+|\s+$/g, '' ) === '' ) {
1876 - jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).fadeOut( 'slow' );
1877 - } else {
1878 - jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).replaceWith( message );
1879 - }
1857 +function frmCaptcha( captchaSelector ) {
1858 + let c;
1859 + const captchas = document.querySelectorAll( captchaSelector );
1860 + const cl = captchas.length;
1861 + for ( c = 0; c < cl; c++ ) {
1862 + const closestForm = captchas[c].closest( 'form' );
1863 + const formIsVisible = closestForm && closestForm.offsetParent !== null;
1864 + const captcha = captchas[c];
1865 + if ( ! formIsVisible ) {
1866 + // If the form is not visible, try again later in 400ms.
1867 + // This fixes issues where the form fades visible on page load.
1868 + // Or whne the form is inside of a modal.
1869 + const interval = setInterval(
1870 + function() {
1871 + if ( closestForm && closestForm.offsetParent !== null ) {
1872 + frmFrontForm.renderCaptcha( captcha, captchaSelector );
1873 + clearInterval( interval );
1874 + }
1875 + },
1876 + 400
1877 + );
1878 + continue;
1880 1879 }
1881 - });
1880 + frmFrontForm.renderCaptcha( captcha, captchaSelector );
1881 + }
1882 1882 }
1883 1883
1884 -function frmDeleteEntry( entryId, prefix ) {
1885 - console.warn( 'DEPRECATED: function frmDeleteEntry in v2.0.13 use frmFrontForm.deleteEntry' );
1886 - jQuery( document.getElementById( 'frm_delete_' + entryId ) ).replaceWith( '<span class="frm-loading-img" id="frm_delete_' + entryId + '"></span>' );
1887 - jQuery.ajax({
1888 - type: 'POST',
1889 - url: frm_js.ajax_url, // eslint-disable-line camelcase
1890 - data: {
1891 - action: 'frm_entries_destroy',
1892 - entry: entryId,
1893 - nonce: frm_js.nonce // eslint-disable-line camelcase
1894 - },
1895 - success: function( html ) {
1896 - if ( html.replace( /^\s+|\s+$/g, '' ) === 'success' ) {
1897 - jQuery( document.getElementById( prefix + entryId ) ).fadeOut( 'slow' );
1898 - } else {
1899 - jQuery( document.getElementById( 'frm_delete_' + entryId ) ).replaceWith( html );
1900 - }
1901 - }
1902 - });
1884 +function getSelectedCaptcha( captchaSelector ) {
1885 + if ( captchaSelector === '.frm-g-recaptcha' ) {
1886 + return grecaptcha;
1887 + }
1888 + if ( document.querySelector( '.cf-turnstile' ) ) {
1889 + return turnstile;
1890 + }
1891 + return hcaptcha;
1903 1892 }
1904 1893
1905 -function frmOnSubmit( e ) {
1906 - console.warn( 'DEPRECATED: function frmOnSubmit in v2.0 use frmFrontForm.submitForm' );
1907 - frmFrontForm.submitForm( e, this );
1908 -}
1909 -
1910 -function frm_resend_email( entryId, formId ) { // eslint-disable-line camelcase
1911 - var $link = jQuery( document.getElementById( 'frm_resend_email' ) );
1912 - console.warn( 'DEPRECATED: function frm_resend_email in v2.0' );
1913 - $link.append( '<span class="spinner" style="display:inline"></span>' );
1914 - jQuery.ajax({
1915 - type: 'POST',
1916 - url: frm_js.ajax_url, // eslint-disable-line camelcase
1917 - data: {
1918 - action: 'frm_entries_send_email',
1919 - entry_id: entryId,
1920 - form_id: formId,
1921 - nonce: frm_js.nonce // eslint-disable-line camelcase
1922 - },
1923 - success: function( msg ) {
1924 - $link.replaceWith( msg );
1925 - }
1926 - });
1894 +function frmAfterRecaptcha( token ) {
1895 + frmFrontForm.afterSingleRecaptcha( token );
1927 1896 }