PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.12
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.12
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 +392 -476 6.3.1 → 6.12 View file →
@@ -1,76 +1,31 @@
1 -/* exported frmRecaptcha, frmAfterRecaptcha, frmUpdateField, frmDeleteEntry, frmOnSubmit, frm_resend_email */
1 +/* exported frmRecaptcha, frmAfterRecaptcha, frmUpdateField */
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,11 +184,18 @@
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 + continue;
228 194 }
195 +
196 + validateFieldValue( field, errors );
197 + checkValidity( field, errors );
229 198 }
230 199 }
231 200
232 201 errors = validateRecaptcha( object, errors );
@@ -234,17 +203,41 @@
234 203 return errors;
235 204 }
236 205
237 206 /**
207 + * Check the ValidityState interface for the field.
208 + * If it is invalid, show an error for it.
209 + *
210 + * @param {HTMLElement} field
211 + * @param {Array} errors
212 + * @return {void}
213 + */
214 + function checkValidity( field, errors ) {
215 + let fieldID;
216 + if ( 'object' !== typeof field.validity || false !== field.validity.valid ) {
217 + return;
218 + }
219 +
220 + fieldID = getFieldId( field, true );
221 + if ( 'undefined' === typeof errors[ fieldID ]) {
222 + errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
223 + }
224 +
225 + if ( 'function' === typeof field.reportValidity ) {
226 + // This triggers an error pop up.
227 + field.reportValidity();
228 + }
229 + }
230 +
231 + /**
238 232 * @since 5.0.10
239 233 *
240 - * @param {object} element
234 + * @param {Object} element
241 235 * @param {string} targetClass
242 - * @returns {boolean}
236 + * @return {boolean} True if the element has the target class.
243 237 */
244 238 function hasClass( element, targetClass ) {
245 - var className = ' ' + element.className + ' ';
246 - return -1 !== className.indexOf( ' ' + targetClass + ' ' );
239 + return element.classList && element.classList.contains( targetClass );
247 240 }
248 241
249 242 function maybeValidateChange( field ) {
250 243 if ( field.type === 'url' ) {
@@ -255,10 +248,10 @@
255 248 }
256 249 }
257 250
258 251 function maybeAddHttpToUrl( field ) {
259 - var url = field.value;
260 - var matches = url.match( /^(https?|ftps?|mailto|news|feed|telnet):/ );
252 + const url = field.value;
253 + const matches = url.match( /^(https?|ftps?|mailto|news|feed|telnet):/ );
261 254 if ( field.value !== '' && matches === null ) {
262 255 field.value = 'http://' + url;
263 256 }
264 257 }
@@ -263,9 +256,9 @@
263 256 }
264 257 }
265 258
266 259 function validateField( field ) {
267 - var key,
260 + let key,
268 261 errors = [],
269 262 $fieldCont = jQuery( field ).closest( '.frm_form_field' );
270 263
271 264 if ( $fieldCont.hasClass( 'frm_required_field' ) && ! jQuery( field ).hasClass( 'frm_optional' ) ) {
@@ -276,9 +269,9 @@
276 269 validateFieldValue( field, errors );
277 270 }
278 271
279 272 removeFieldError( $fieldCont );
280 - if ( Object.keys( errors ).length > 0 ) {
273 + if ( Object.keys( errors ).length > 0 ) {
281 274 for ( key in errors ) {
282 275 addFieldError( $fieldCont, key, errors );
283 276 }
284 277 }
@@ -305,9 +298,9 @@
305 298 });
306 299 }
307 300
308 301 function checkRequiredField( field, errors ) {
309 - var checkGroup, tempVal, i, placeholder,
302 + let checkGroup, tempVal, i, placeholder,
310 303 val = '',
311 304 fieldID = '',
312 305 fileID = field.getAttribute( 'data-frmfile' );
313 306
@@ -387,9 +380,9 @@
387 380 return errors;
388 381 }
389 382
390 383 function isSignatureField( field ) {
391 - var name = field.getAttribute( 'name' );
384 + const name = field.getAttribute( 'name' );
392 385 return 'string' === typeof name && '[typed]' === name.substr( -7 );
393 386 }
394 387
395 388 function isAppointmentField( field ) {
@@ -400,9 +393,9 @@
400 393 return 'hidden' === field.type && '_alt' === field.id.substr( -4 ) && hasClass( field.nextElementSibling, 'frm_date_inline' );
401 394 }
402 395
403 396 function getFileVals( fileID ) {
404 - var val = '',
397 + let val = '',
405 398 fileFields = jQuery( 'input[name="file' + fileID + '"], input[name="file' + fileID + '[]"], input[name^="item_meta[' + fileID + ']"]' );
406 399
407 400 fileFields.each( function() {
408 401 if ( val === '' ) {
@@ -412,9 +405,9 @@
412 405 return val;
413 406 }
414 407
415 408 function checkUrlField( field, errors ) {
416 - var fieldID,
409 + let fieldID,
417 410 url = field.value;
418 411
419 412 if ( url !== '' && ! /^http(s)?:\/\/(?:localhost|(?:[\da-z\.-]+\.[\da-z\.-]+))/i.test( url ) ) {
420 413 fieldID = getFieldId( field, true );
@@ -424,9 +417,9 @@
424 417 }
425 418 }
426 419
427 420 function checkEmailField( field, errors ) {
428 - var fieldID = getFieldId( field, true ),
421 + const fieldID = getFieldId( field, true ),
429 422 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 423
431 424 // validate the current field we're editing first
432 425 if ( '' !== field.value && pattern.test( field.value ) === false ) {
@@ -440,9 +433,9 @@
440 433 confirmField( field, errors );
441 434 }
442 435
443 436 function confirmField( field, errors ) {
444 - var value, confirmValue, firstField,
437 + let value, confirmValue, firstField,
445 438 fieldID = getFieldId( field, true ),
446 439 strippedId = field.id.replace( 'conf_', '' ),
447 440 strippedFieldID = fieldID.replace( 'conf_', '' ),
448 441 confirmField = document.getElementById( strippedId.replace( 'field_', 'field_conf_' ) );
@@ -454,9 +447,9 @@
454 447 if ( fieldID !== strippedFieldID ) {
455 448 firstField = document.getElementById( strippedId );
456 449 value = firstField.value;
457 450 confirmValue = confirmField.value;
458 - if ( '' !== value && '' !== confirmValue && value !== confirmValue ) {
451 + if ( value !== confirmValue ) {
459 452 errors[ 'conf_' + strippedFieldID ] = getFieldValidationMessage( confirmField, 'data-confmsg' );
460 453 }
461 454 } else {
462 455 validateField( confirmField );
@@ -463,9 +456,9 @@
463 456 }
464 457 }
465 458
466 459 function checkNumberField( field, errors ) {
467 - var fieldID,
460 + let fieldID,
468 461 number = field.value;
469 462
470 463 if ( number !== '' && isNaN( number / 1 ) !== false ) {
471 464 fieldID = getFieldId( field, true );
@@ -475,9 +468,9 @@
475 468 }
476 469 }
477 470
478 471 function checkPatternField( field, errors ) {
479 - var fieldID,
472 + let fieldID,
480 473 text = field.value,
481 474 format = getFieldValidationMessage( field, 'pattern' );
482 475
483 476 if ( format !== '' && text !== '' ) {
@@ -482,18 +475,61 @@
482 475
483 476 if ( format !== '' && text !== '' ) {
484 477 fieldID = getFieldId( field, true );
485 478 if ( ! ( fieldID in errors ) ) {
486 - format = new RegExp( '^' + format + '$', 'i' );
487 - if ( format.test( text ) === false ) {
488 - errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
479 + if ( 'object' === typeof window.frmProForm && 'function' === typeof window.frmProForm.isIntlPhoneInput && window.frmProForm.isIntlPhoneInput( field ) ) {
480 + if ( ! window.frmProForm.validateIntlPhoneInput( field ) ) {
481 + errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
482 + }
483 + } else {
484 + format = new RegExp( '^' + format + '$', 'i' );
485 + if ( format.test( text ) === false ) {
486 + errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
487 + }
489 488 }
490 489 }
491 490 }
492 491 }
493 492
493 + /**
494 + * Set color for select placeholders.
495 + *
496 + * @since 6.5.1
497 + */
498 + function setSelectPlaceholderColor() {
499 + let selects = document.querySelectorAll( '.form-field select' ),
500 + styleElement = document.querySelector( '.with_frm_style' ),
501 + textColorDisabled = styleElement ? getComputedStyle( styleElement ).getPropertyValue( '--text-color-disabled' ).trim() : '',
502 + changeSelectColor;
503 +
504 + // Exit if there are no select elements or the textColorDisabled property is missing
505 + if ( ! selects.length || ! textColorDisabled ) {
506 + return;
507 + }
508 +
509 + // Function to change the color of a select element
510 + changeSelectColor = function( select ) {
511 + if ( select.options[select.selectedIndex] && hasClass( select.options[select.selectedIndex], 'frm-select-placeholder' ) ) {
512 + select.style.setProperty( 'color', textColorDisabled, 'important' );
513 + } else {
514 + select.style.color = '';
515 + }
516 + };
517 +
518 + // Use a loop to iterate through each select element
519 + Array.prototype.forEach.call( selects, function( select ) {
520 + // Apply the color change to each select element
521 + changeSelectColor( select );
522 +
523 + // Add an event listener for future changes
524 + select.addEventListener( 'change', function() {
525 + changeSelectColor( select );
526 + });
527 + });
528 + }
529 +
494 530 function hasInvisibleRecaptcha( object ) {
495 - var recaptcha, recaptchaID, alreadyChecked;
531 + let recaptcha, recaptchaID, alreadyChecked;
496 532
497 533 if ( isGoingToPrevPage( object ) ) {
498 534 return false;
499 535 }
@@ -503,24 +539,21 @@
503 539 recaptchaID = recaptcha.data( 'rid' );
504 540 alreadyChecked = grecaptcha.getResponse( recaptchaID );
505 541 if ( alreadyChecked.length === 0 ) {
506 542 return recaptcha;
507 - } else {
508 - return false;
509 543 }
510 - } else {
511 - return false;
512 544 }
545 + return false;
513 546 }
514 547
515 548 function executeInvisibleRecaptcha( invisibleRecaptcha ) {
516 - var recaptchaID = invisibleRecaptcha.data( 'rid' );
549 + const recaptchaID = invisibleRecaptcha.data( 'rid' );
517 550 grecaptcha.reset( recaptchaID );
518 551 grecaptcha.execute( recaptchaID );
519 552 }
520 553
521 554 function validateRecaptcha( form, errors ) {
522 - var recaptchaID, response, fieldContainer, fieldID,
555 + let recaptchaID, response, fieldContainer, fieldID,
523 556 $recaptcha = jQuery( form ).find( '.frm-g-recaptcha' );
524 557 if ( $recaptcha.length ) {
525 558 recaptchaID = $recaptcha.data( 'rid' );
526 559
@@ -528,11 +561,10 @@
528 561 response = grecaptcha.getResponse( recaptchaID );
529 562 } catch ( e ) {
530 563 if ( jQuery( form ).find( 'input[name="recaptcha_checked"]' ).length ) {
531 564 return errors;
532 - } else {
533 - response = '';
534 565 }
566 + response = '';
535 567 }
536 568
537 569 if ( response.length === 0 ) {
538 570 fieldContainer = $recaptcha.closest( '.frm_form_field' );
@@ -542,34 +574,64 @@
542 574 }
543 575 return errors;
544 576 }
545 577
578 + /**
579 + * @param {HTMLElement} field
580 + * @param {string} messageType
581 + * @return {string} The error message to display.
582 + */
546 583 function getFieldValidationMessage( field, messageType ) {
547 - var msg, errorHtml;
548 -
549 - msg = field.getAttribute( messageType );
584 + let msg = field.getAttribute( messageType );
550 585 if ( null === msg ) {
551 586 msg = '';
552 587 }
553 588
554 589 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 - }
590 + msg = wrapErrorHtml( msg, field );
561 591 }
562 592
563 593 return msg;
564 594 }
565 595
596 + /**
597 + * @param {string} msg
598 + * @param {HTMLElement} field
599 + * @return {string} The error HTML to use.
600 + */
601 + function wrapErrorHtml( msg, field ) {
602 + let errorHtml = field.getAttribute( 'data-error-html' );
603 + if ( null === errorHtml ) {
604 + return msg;
605 + }
606 +
607 + errorHtml = errorHtml.replace( /\+/g, '%20' );
608 + msg = decodeURIComponent( errorHtml ).replace( '[error]', msg );
609 + const fieldId = getFieldId( field, false );
610 + const split = fieldId.split( '-' );
611 + const fieldIdParts = field.id.split( '_' );
612 + fieldIdParts.shift(); // Drop the "field" value from the front.
613 + split[0] = fieldIdParts.join( '_' );
614 + const errorKey = split.join( '-' );
615 + return msg.replace( '[key]', errorKey );
616 + }
617 +
566 618 function shouldWrapErrorHtmlAroundMessageType( type ) {
567 619 return 'pattern' !== type;
568 620 }
569 621
622 + /**
623 + * Check if JS validation should happen.
624 + *
625 + * @param {HTMLElement|Object} object Form object.
626 + * @return {boolean} True if validation is enabled and we are not saving a draft or going to a previous page.
627 + */
570 628 function shouldJSValidate( object ) {
571 - var validate = jQuery( object ).hasClass( 'frm_js_validate' );
629 + if ( 'function' === typeof object.get ) {
630 + // Get the HTMLElement from a jQuery object.
631 + object = object.get( 0 );
632 + }
633 + let validate = hasClass( object, 'frm_js_validate' );
572 634 if ( validate && typeof frmProForm !== 'undefined' && ( frmProForm.savingDraft( object ) || frmProForm.goingToPreviousPage( object ) ) ) {
573 635 validate = false;
574 636 }
575 637
@@ -576,9 +638,9 @@
576 638 return validate;
577 639 }
578 640
579 641 function getFormErrors( object, action ) {
580 - var fieldset, data, success, error, shouldTriggerEvent;
642 + let fieldset, data, success, error, shouldTriggerEvent;
581 643
582 644 if ( typeof action === 'undefined' ) {
583 645 jQuery( object ).find( 'input[name="frm_action"]' ).val();
584 646 }
@@ -589,9 +651,9 @@
589 651 data = jQuery( object ).serialize() + '&action=frm_entries_' + action + '&nonce=' + frm_js.nonce; // eslint-disable-line camelcase
590 652 shouldTriggerEvent = object.classList.contains( 'frm_trigger_event_on_submit' );
591 653
592 654 success = function( response ) {
593 - var defaultResponse, formID, replaceContent, pageOrder, formReturned, contSubmit, delay,
655 + let defaultResponse, formID, replaceContent, pageOrder, formReturned, contSubmit, delay,
594 656 $fieldCont, key, inCollapsedSection, frmTrigger, newTab;
595 657
596 658 defaultResponse = {
597 659 content: '',
@@ -634,9 +696,9 @@
634 696 if ( response.content !== '' ) {
635 697 // the form or success message was returned
636 698
637 699 if ( shouldTriggerEvent ) {
638 - triggerCustomEvent( object, 'frmSubmitEvent' );
700 + triggerCustomEvent( object, 'frmSubmitEvent', { content: response.content });
639 701 return;
640 702 }
641 703
642 704 removeSubmitLoading( jQuery( object ) );
@@ -651,10 +713,12 @@
651 713 delay = maybeSlideOut( replaceContent, response.content );
652 714
653 715 setTimeout(
654 716 function() {
655 - var container, input, previousInput;
717 + let container, input, previousInput;
656 718
719 + afterFormSubmittedBeforeReplace( object, response );
720 +
657 721 replaceContent.replaceWith( response.content );
658 722
659 723 addUrlParam( response );
660 724
@@ -713,9 +777,9 @@
713 777 }
714 778 }
715 779
716 780 jQuery( object ).find( '.frm-g-recaptcha, .g-recaptcha, .h-captcha' ).each( function() {
717 - var $recaptcha = jQuery( this ),
781 + const $recaptcha = jQuery( this ),
718 782 recaptchaID = $recaptcha.data( 'rid' );
719 783
720 784 if ( typeof grecaptcha !== 'undefined' && grecaptcha ) {
721 785 if ( recaptchaID ) {
@@ -736,9 +800,9 @@
736 800
737 801 if ( contSubmit ) {
738 802 object.submit();
739 803 } else {
740 - jQuery( object ).prepend( response.error_message );
804 + object.insertAdjacentHTML( 'afterbegin', response.error_message );
741 805 checkForErrorsAndMaybeSetFocus();
742 806 }
743 807 } else {
744 808 // there may have been a plugin conflict, or the form is not set to submit with ajax
@@ -757,9 +821,9 @@
757 821 postToAjaxUrl( object, data, success, error );
758 822 }
759 823
760 824 function postToAjaxUrl( form, data, success, error ) {
761 - var ajaxUrl, action, ajaxParams;
825 + let ajaxUrl, action, ajaxParams;
762 826
763 827 ajaxUrl = frm_js.ajax_url; // eslint-disable-line camelcase
764 828 action = form.getAttribute( 'action' );
765 829
@@ -781,9 +845,9 @@
781 845 jQuery.ajax( ajaxParams );
782 846 }
783 847
784 848 function afterFormSubmitted( object, response ) {
785 - var formCompleted = jQuery( response.content ).find( '.frm_message' );
849 + const formCompleted = jQuery( response.content ).find( '.frm_message' );
786 850 if ( formCompleted.length ) {
787 851 jQuery( document ).trigger( 'frmFormComplete', [ object, response ]);
788 852 } else {
789 853 jQuery( document ).trigger( 'frmPageChanged', [ object, response ]);
@@ -789,10 +853,26 @@
789 853 jQuery( document ).trigger( 'frmPageChanged', [ object, response ]);
790 854 }
791 855 }
792 856
857 + /**
858 + * Trigger an event before the form is replaced with a success message.
859 + *
860 + * @since 6.9
861 + *
862 + * @param {HTMLElement} object The form.
863 + * @param {Object} response The response from submitting the form with AJAX.
864 + * @return {void}
865 + */
866 + function afterFormSubmittedBeforeReplace( object, response ) {
867 + const formCompleted = jQuery( response.content ).find( '.frm_message' );
868 + if ( formCompleted.length ) {
869 + triggerCustomEvent( document, 'frmFormCompleteBeforeReplace', { object, response });
870 + }
871 + }
872 +
793 873 function removeAddedScripts( formContainer, formID ) {
794 - var endReplace = jQuery( '.frm_end_ajax_' + formID );
874 + const endReplace = jQuery( '.frm_end_ajax_' + formID );
795 875 if ( endReplace.length ) {
796 876 formContainer.nextUntil( '.frm_end_ajax_' + formID ).remove();
797 877 endReplace.remove();
798 878 }
@@ -798,9 +878,9 @@
798 878 }
799 879 }
800 880
801 881 function maybeSlideOut( oldContent, newContent ) {
802 - var c,
882 + let c,
803 883 newClass = 'frm_slideout';
804 884 if ( newContent.indexOf( ' frm_slide' ) !== -1 ) {
805 885 c = oldContent.children();
806 886 if ( newContent.indexOf( ' frm_going_back' ) !== -1 ) {
@@ -813,9 +893,9 @@
813 893 return 0;
814 894 }
815 895
816 896 function addUrlParam( response ) {
817 - var url;
897 + let url;
818 898 if ( history.pushState && typeof response.page !== 'undefined' ) {
819 899 url = addQueryVar( 'frm_page', response.page );
820 900 window.history.pushState({ 'html': response.html }, '', '?' + url );
821 901 }
@@ -821,9 +901,9 @@
821 901 }
822 902 }
823 903
824 904 function addQueryVar( key, value ) {
825 - var kvp, i, x;
905 + let kvp, i, x;
826 906
827 907 key = encodeURI( key );
828 908 value = encodeURI( value );
829 909
@@ -847,13 +927,14 @@
847 927 return kvp.join( '&' );
848 928 }
849 929
850 930 function addFieldError( $fieldCont, key, jsErrors ) {
851 - var input, id, describedBy, roleString;
931 + let input, id, describedBy, roleString;
852 932 if ( $fieldCont.length && $fieldCont.is( ':visible' ) ) {
853 933 $fieldCont.addClass( 'frm_blank_field' );
854 934 input = $fieldCont.find( 'input, select, textarea' );
855 - id = 'frm_error_field_' + key;
935 + id = getErrorElementId( key, input.get( 0 ) );
936 +
856 937 describedBy = input.attr( 'aria-describedby' );
857 938
858 939 if ( typeof frmThemeOverride_frmPlaceError === 'function' ) { // eslint-disable-line camelcase
859 940 frmThemeOverride_frmPlaceError( key, jsErrors );
@@ -884,15 +965,38 @@
884 965 jQuery( document ).trigger( 'frmAddFieldError', [ $fieldCont, key, jsErrors ]);
885 966 }
886 967 }
887 968
969 + /**
970 + * Get the ID to use for an error element added when submitting with AJAX.
971 + *
972 + * @param {string} key
973 + * @param {HTMLElement} input
974 + * @return {string} The ID to use for the error element.
975 + */
976 + function getErrorElementId( key, input ) {
977 + if ( isNaN( key ) || ! input.id ) {
978 + // If key isn't a number, assume it's already in the right format.
979 + return 'frm_error_field_' + key;
980 + }
981 + return 'frm_error_' + input.id;
982 + }
983 +
984 + /**
985 + * Removes errors before validating with JS.
986 + * This prevents issues with stale errors that has since been fixed.
987 + *
988 + * @param {Object} $fieldCont jQuery object.
989 + * @return {void}
990 + */
888 991 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' );
992 + const errorMessage = $fieldCont.find( '.frm_error' );
993 + const errorId = errorMessage.attr( 'id' );
994 + const input = $fieldCont.find( 'input, select, textarea' );
995 + let describedBy = input.attr( 'aria-describedby' );
893 996
894 - $fieldCont.removeClass( 'frm_blank_field has-error' );
997 + $fieldCont.get( 0 ).classList.remove( 'frm_blank_field', 'has-error' );
998 +
895 999 errorMessage.remove();
896 1000 input.attr( 'aria-invalid', false );
897 1001 input.removeAttr( 'aria-describedby' );
898 1002
@@ -907,12 +1011,16 @@
907 1011 jQuery( '.form-field .frm_error' ).replaceWith( '' );
908 1012 jQuery( '.frm_error_style' ).remove();
909 1013 }
910 1014
1015 + /**
1016 + * @param {HTMLElement} object Form object.
1017 + * @return {void}
1018 + */
911 1019 function scrollToFirstField( object ) {
912 - var field = jQuery( object ).find( '.frm_blank_field' ).first();
913 - if ( field.length ) {
914 - frmFrontForm.scrollMsg( field, object, true );
1020 + const field = object.querySelector( '.frm_blank_field' );
1021 + if ( field ) {
1022 + frmFrontForm.scrollMsg( jQuery( field ), object, true );
915 1023 }
916 1024 }
917 1025
918 1026 function showSubmitLoading( $object ) {
@@ -928,9 +1036,9 @@
928 1036 }
929 1037 }
930 1038
931 1039 function addLoadingClass( $object ) {
932 - var loadingClass = isGoingToPrevPage( $object ) ? 'frm_loading_prev' : 'frm_loading_form';
1040 + const loadingClass = isGoingToPrevPage( $object ) ? 'frm_loading_prev' : 'frm_loading_form';
933 1041
934 1042 $object.addClass( loadingClass );
935 1043 }
936 1044
@@ -938,9 +1046,9 @@
938 1046 return ( typeof frmProForm !== 'undefined' && frmProForm.goingToPreviousPage( $object ) );
939 1047 }
940 1048
941 1049 function removeSubmitLoading( $object, enable, processesRunning ) {
942 - var loadingForm;
1050 + let loadingForm;
943 1051
944 1052 if ( processesRunning > 0 ) {
945 1053 return;
946 1054 }
@@ -957,9 +1065,9 @@
957 1065 }
958 1066 }
959 1067
960 1068 function showFileLoading( object ) {
961 - var fileval,
1069 + let fileval,
962 1070 loading = document.getElementById( 'frm_loading' );
963 1071 if ( loading !== null ) {
964 1072 fileval = jQuery( object ).find( 'input[type=file]' ).val();
965 1073 if ( typeof fileval !== 'undefined' && fileval !== '' ) {
@@ -969,69 +1077,8 @@
969 1077 }
970 1078 }
971 1079 }
972 1080
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 1081 /**********************************************
1035 1082 * General Helpers
1036 1083 *********************************************/
1037 1084
@@ -1036,68 +1083,12 @@
1036 1083 *********************************************/
1037 1084
1038 1085 function confirmClick() {
1039 1086 /*jshint validthis:true */
1040 - var message = jQuery( this ).data( 'frmconfirm' );
1087 + const message = jQuery( this ).data( 'frmconfirm' );
1041 1088 return confirm( message );
1042 1089 }
1043 1090
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 1091 /**
1101 1092 * Check for -webkit-box-shadow css value for input:-webkit-autofill selector.
1102 1093 * If this is a match, the User is autofilling the input on a Webkit browser.
1103 1094 * We want to delete the Honeypot field, otherwise it will get triggered as spam on autocomplete.
@@ -1102,9 +1093,9 @@
1102 1093 * If this is a match, the User is autofilling the input on a Webkit browser.
1103 1094 * We want to delete the Honeypot field, otherwise it will get triggered as spam on autocomplete.
1104 1095 */
1105 1096 function onHoneypotFieldChange() {
1106 - var css = jQuery( this ).css( 'box-shadow' );
1097 + const css = jQuery( this ).css( 'box-shadow' );
1107 1098 if ( css.match( /inset/ ) ) {
1108 1099 this.parentNode.removeChild( this );
1109 1100 }
1110 1101 }
@@ -1112,9 +1103,9 @@
1112 1103 function maybeMakeHoneypotFieldsUntabbable() {
1113 1104 document.addEventListener( 'keydown', handleKeyUp );
1114 1105
1115 1106 function handleKeyUp( event ) {
1116 - var code;
1107 + let code;
1117 1108
1118 1109 if ( 'undefined' !== typeof event.key ) {
1119 1110 code = event.key;
1120 1111 } else if ( 'undefined' !== typeof event.keyCode && 9 === event.keyCode ) {
@@ -1128,11 +1119,10 @@
1128 1119 }
1129 1120
1130 1121 function makeHoneypotFieldsUntabbable() {
1131 1122 document.querySelectorAll( '.frm_verify' ).forEach(
1132 - function( wrapper ) {
1133 - var input = wrapper.querySelector( 'input[id^=frm_email]' );
1134 - if ( input ) {
1123 + function( input ) {
1124 + if ( input.id && 0 === input.id.indexOf( 'frm_email_' ) ) {
1135 1125 input.setAttribute( 'tabindex', -1 );
1136 1126 }
1137 1127 }
1138 1128 );
@@ -1144,11 +1134,11 @@
1144 1134 *
1145 1135 * @since 4.10.02
1146 1136 */
1147 1137 function changeFocusWhenClickComboFieldLabel() {
1148 - var label;
1138 + let label;
1149 1139
1150 - var comboInputsContainer = document.querySelectorAll( '.frm_combo_inputs_container' );
1140 + const comboInputsContainer = document.querySelectorAll( '.frm_combo_inputs_container' );
1151 1141 comboInputsContainer.forEach( function( inputsContainer ) {
1152 1142 if ( ! inputsContainer.closest( '.frm_form_field' ) ) {
1153 1143 return;
1154 1144 }
@@ -1157,9 +1147,9 @@
1157 1147 if ( ! label ) {
1158 1148 return;
1159 1149 }
1160 1150
1161 - label.addEventListener( 'click', function( e ) {
1151 + label.addEventListener( 'click', function() {
1162 1152 inputsContainer.querySelector( '.frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea' ).focus();
1163 1153 });
1164 1154 });
1165 1155 }
@@ -1164,9 +1154,9 @@
1164 1154 });
1165 1155 }
1166 1156
1167 1157 function checkForErrorsAndMaybeSetFocus() {
1168 - var errors, element, timeoutCallback;
1158 + let errors, element, timeoutCallback;
1169 1159
1170 1160 if ( ! frm_js.focus_first_error ) { // eslint-disable-line camelcase
1171 1161 return;
1172 1162 }
@@ -1186,9 +1176,9 @@
1186 1176
1187 1177 if ( 'undefined' !== typeof element.classList ) {
1188 1178 if ( element.classList.contains( 'html-active' ) ) {
1189 1179 timeoutCallback = function() {
1190 - var textarea = element.querySelector( 'textarea' );
1180 + const textarea = element.querySelector( 'textarea' );
1191 1181 if ( null !== textarea ) {
1192 1182 textarea.focus();
1193 1183 }
1194 1184 };
@@ -1206,27 +1196,16 @@
1206 1196 } while ( element.previousSibling );
1207 1197 }
1208 1198
1209 1199 /**
1210 - * Checks if is on IE browser.
1211 - *
1212 - * @since 5.4
1213 - *
1214 - * @return {Boolean}
1215 - */
1216 - function isIE() {
1217 - return navigator.userAgent.indexOf( 'MSIE' ) > -1 || navigator.userAgent.indexOf( 'Trident' ) > -1;
1218 - }
1219 -
1220 - /**
1221 1200 * Does the same as jQuery( document ).on( 'event', 'selector', handler ).
1222 1201 *
1223 1202 * @since 5.4
1224 1203 *
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`.
1204 + * @param {string} event Event name.
1205 + * @param {string} selector Selector.
1206 + * @param {Function} handler Handler.
1207 + * @param {boolean | Object} options Options to be added to `addEventListener()` method. Default is `false`.
1229 1208 */
1230 1209 function documentOn( event, selector, handler, options ) {
1231 1210 if ( 'undefined' === typeof options ) {
1232 1211 options = false;
@@ -1232,9 +1211,9 @@
1232 1211 options = false;
1233 1212 }
1234 1213
1235 1214 document.addEventListener( event, function( e ) {
1236 - var target;
1215 + let target;
1237 1216
1238 1217 // loop parent nodes from the target to the delegation node.
1239 1218 for ( target = e.target; target && target != this; target = target.parentNode ) {
1240 1219 if ( target && target.matches && target.matches( selector ) ) {
@@ -1245,15 +1224,15 @@
1245 1224 }, options );
1246 1225 }
1247 1226
1248 1227 function initFloatingLabels() {
1249 - var checkFloatLabel, checkDropdownLabel, checkPlaceholderIE, runOnLoad, selector, floatClass;
1228 + let checkFloatLabel, checkDropdownLabel, runOnLoad, selector, floatClass;
1250 1229
1251 1230 selector = '.frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea';
1252 1231 floatClass = 'frm_label_float_top';
1253 1232
1254 1233 checkFloatLabel = function( input ) {
1255 - var container, shouldFloatTop, firstOpt;
1234 + let container, shouldFloatTop, firstOpt;
1256 1235
1257 1236 container = input.closest( '.frm_inside_container' );
1258 1237 if ( ! container ) {
1259 1238 return;
@@ -1270,22 +1249,18 @@
1270 1249 if ( firstOpt.hasAttribute( 'data-label' ) ) {
1271 1250 firstOpt.textContent = firstOpt.getAttribute( 'data-label' );
1272 1251 firstOpt.removeAttribute( 'data-label' );
1273 1252 }
1274 - } else {
1275 - if ( firstOpt.textContent ) {
1276 - firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1277 - firstOpt.textContent = '';
1278 - }
1253 + } else if ( firstOpt.textContent ) {
1254 + firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1255 + firstOpt.textContent = '';
1279 1256 }
1280 - } else if ( isIE() ) {
1281 - checkPlaceholderIE( input );
1282 1257 }
1283 1258 };
1284 1259
1285 1260 checkDropdownLabel = function() {
1286 1261 document.querySelectorAll( '.frm-show-form .frm_inside_container:not(.' + floatClass + ') select' ).forEach( function( input ) {
1287 - var firstOpt = input.querySelector( 'option:first-child' );
1262 + const firstOpt = input.querySelector( 'option:first-child' );
1288 1263
1289 1264 if ( firstOpt.textContent ) {
1290 1265 firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1291 1266 firstOpt.textContent = '';
@@ -1292,27 +1267,8 @@
1292 1267 }
1293 1268 });
1294 1269 };
1295 1270
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 1271 [ 'focus', 'blur', 'change' ].forEach( function( eventName ) {
1316 1272 documentOn(
1317 1273 eventName,
1318 1274 selector,
@@ -1332,9 +1288,9 @@
1332 1288 checkFloatLabel( document.activeElement );
1333 1289 } else if ( firstLoad ) {
1334 1290 document.querySelectorAll( '.frm_inside_container' ).forEach(
1335 1291 function( container ) {
1336 - var input = container.querySelector( 'input, select, textarea' );
1292 + const input = container.querySelector( 'input, select, textarea' );
1337 1293 if ( input && '' !== input.value ) {
1338 1294 checkFloatLabel( input );
1339 1295 }
1340 1296 }
@@ -1341,14 +1297,8 @@
1341 1297 );
1342 1298 }
1343 1299
1344 1300 checkDropdownLabel();
1345 -
1346 - if ( isIE() ) {
1347 - document.querySelectorAll( selector ).forEach( function( input ) {
1348 - checkPlaceholderIE( input );
1349 - });
1350 - }
1351 1301 };
1352 1302
1353 1303 runOnLoad( true );
1354 1304
@@ -1360,10 +1310,53 @@
1360 1310 runOnLoad();
1361 1311 });
1362 1312 }
1363 1313
1314 + function shouldUpdateValidityMessage( target ) {
1315 + if ( 'INPUT' !== target.nodeName ) {
1316 + return false;
1317 + }
1318 +
1319 + if ( ! target.dataset.invmsg ) {
1320 + return false;
1321 + }
1322 +
1323 + if ( 'text' !== target.getAttribute( 'type' ) ) {
1324 + return false;
1325 + }
1326 +
1327 + if ( target.classList.contains( 'frm_verify' ) ) {
1328 + return false;
1329 + }
1330 +
1331 + return true;
1332 + }
1333 +
1334 + function maybeClearCustomValidityMessage( event, field ) {
1335 + let key,
1336 + isInvalid = false;
1337 +
1338 + if ( ! shouldUpdateValidityMessage( field ) ) {
1339 + return;
1340 + }
1341 +
1342 + for ( key in field.validity ) {
1343 + if ( 'customError' === key ) {
1344 + continue;
1345 + }
1346 + if ( 'valid' !== key && field.validity[ key ] === true ) {
1347 + isInvalid = true;
1348 + break;
1349 + }
1350 + };
1351 +
1352 + if ( ! isInvalid ) {
1353 + field.setCustomValidity( '' );
1354 + }
1355 + }
1356 +
1364 1357 function maybeShowNewTabFallbackMessage() {
1365 - var messageEl;
1358 + let messageEl;
1366 1359
1367 1360 if ( ! window.frmShowNewTabFallback ) {
1368 1361 return;
1369 1362 }
@@ -1376,9 +1369,9 @@
1376 1369 messageEl.insertAdjacentHTML( 'beforeend', ' ' + frmShowNewTabFallback.message );
1377 1370 }
1378 1371
1379 1372 function setCustomValidityMessage() {
1380 - var forms, length, index;
1373 + let forms, length, index;
1381 1374
1382 1375 forms = document.getElementsByClassName( 'frm-show-form' );
1383 1376 length = forms.length;
1384 1377
@@ -1385,37 +1378,44 @@
1385 1378 for ( index = 0; index < length; ++index ) {
1386 1379 forms[ index ].addEventListener(
1387 1380 'invalid',
1388 1381 function( event ) {
1389 - var target = event.target;
1382 + const target = event.target;
1390 1383
1391 - if ( 'INPUT' !== target.nodeName ) {
1392 - return;
1384 + if ( shouldUpdateValidityMessage( target ) ) {
1385 + target.setCustomValidity( target.dataset.invmsg );
1393 1386 }
1387 + },
1388 + true
1389 + );
1390 + }
1391 + }
1394 1392
1395 - if ( ! target.dataset.invmsg ) {
1396 - return;
1393 + function enableSubmitButtonOnBackButtonPress() {
1394 + window.addEventListener( 'pageshow', function( event ) {
1395 + if ( event.persisted ) {
1396 + document.querySelectorAll( '.frm_loading_form' ).forEach(
1397 + function( form ) {
1398 + enableSubmitButton( jQuery( form ) );
1397 1399 }
1400 + );
1401 + removeSubmitLoading();
1402 + }
1403 + });
1404 + }
1398 1405
1399 - if ( 'text' !== target.getAttribute( 'type' ) ) {
1400 - return;
1401 - }
1402 -
1403 - if ( target.classList.contains( 'frm_verify' ) ) {
1404 - return;
1405 - }
1406 -
1407 - target.setCustomValidity( target.dataset.invmsg );
1408 - },
1409 - true
1410 - );
1406 + /**
1407 + * Destroys the formidable generated global hcaptcha object since it wouldn't otherwise render.
1408 + */
1409 + function destroyhCaptcha() {
1410 + if ( ! window.hasOwnProperty( 'hcaptcha' ) || ! document.querySelector( '.frm-show-form .h-captcha' ) ) {
1411 + return;
1411 1412 }
1413 + window.hcaptcha = null;
1412 1414 }
1413 1415
1414 1416 return {
1415 1417 init: function() {
1416 - maybeAddPolyfills();
1417 -
1418 1418 jQuery( document ).off( 'submit.formidable', '.frm-show-form' );
1419 1419 jQuery( document ).on( 'submit.formidable', '.frm-show-form', frmFrontForm.submitForm );
1420 1420
1421 1421 jQuery( '.frm-show-form input[onblur], .frm-show-form textarea[onblur]' ).each( function() {
@@ -1423,14 +1423,8 @@
1423 1423 jQuery( this ).trigger( 'blur' );
1424 1424 }
1425 1425 });
1426 1426
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 1427 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 1428
1435 1429 jQuery( document ).on( 'change', '[id^=frm_email_]', onHoneypotFieldChange );
1436 1430 maybeMakeHoneypotFieldsUntabbable();
@@ -1435,9 +1429,8 @@
1435 1429 jQuery( document ).on( 'change', '[id^=frm_email_]', onHoneypotFieldChange );
1436 1430 maybeMakeHoneypotFieldsUntabbable();
1437 1431
1438 1432 jQuery( document ).on( 'click', 'a[data-frmconfirm]', confirmClick );
1439 - jQuery( 'a[data-frmtoggle]' ).on( 'click', toggleDiv );
1440 1433
1441 1434 checkForErrorsAndMaybeSetFocus();
1442 1435
1443 1436 // Focus on the first sub field when clicking to the primary label of combo field.
@@ -1442,17 +1435,25 @@
1442 1435
1443 1436 // Focus on the first sub field when clicking to the primary label of combo field.
1444 1437 changeFocusWhenClickComboFieldLabel();
1445 1438
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 1439 initFloatingLabels();
1451 1440 maybeShowNewTabFallbackMessage();
1452 1441
1453 1442 jQuery( document ).on( 'frmAfterAddRow', setCustomValidityMessage );
1454 1443 setCustomValidityMessage();
1444 + jQuery( document ).on( 'frmFieldChanged', maybeClearCustomValidityMessage );
1445 +
1446 + setSelectPlaceholderColor();
1447 +
1448 + // Elementor popup show event. Fix Elementor Popup && FF Captcha field conflicts
1449 + jQuery( document ).on( 'elementor/popup/show', frmRecaptcha );
1450 +
1451 + enableSubmitButtonOnBackButtonPress();
1452 + jQuery( document ).on(
1453 + 'frmPageChanged',
1454 + destroyhCaptcha
1455 + );
1455 1456 },
1456 1457
1457 1458 getFieldId: function( field, fullID ) {
1458 1459 return getFieldId( field, fullID );
@@ -1457,10 +1458,10 @@
1457 1458 getFieldId: function( field, fullID ) {
1458 1459 return getFieldId( field, fullID );
1459 1460 },
1460 1461
1461 - renderRecaptcha: function( captcha ) {
1462 - var formID, recaptchaID,
1462 + renderCaptcha: function( captcha, captchaSelector ) {
1463 + let formID, captchaID,
1463 1464 size = captcha.getAttribute( 'data-size' ),
1464 1465 rendered = captcha.getAttribute( 'data-rid' ) !== null,
1465 1466 params = {
1466 1467 'sitekey': captcha.getAttribute( 'data-sitekey' ),
@@ -1465,9 +1466,11 @@
1465 1466 params = {
1466 1467 'sitekey': captcha.getAttribute( 'data-sitekey' ),
1467 1468 'size': size,
1468 1469 'theme': captcha.getAttribute( 'data-theme' )
1469 - };
1470 + },
1471 + activeCaptcha = getSelectedCaptcha( captchaSelector ),
1472 + captchaContainer = typeof turnstile !== 'undefined' && turnstile === activeCaptcha ? '#' + captcha.id : captcha.id;
1470 1473
1471 1474 if ( rendered ) {
1472 1475 return;
1473 1476 }
@@ -1479,20 +1482,21 @@
1479 1482 frmFrontForm.afterRecaptcha( token, formID );
1480 1483 };
1481 1484 }
1482 1485
1483 - recaptchaID = grecaptcha.render( captcha.id, params );
1484 1486
1485 - captcha.setAttribute( 'data-rid', recaptchaID );
1487 + captchaID = activeCaptcha.render( captchaContainer, params );
1488 +
1489 + captcha.setAttribute( 'data-rid', captchaID );
1486 1490 },
1487 1491
1488 1492 afterSingleRecaptcha: function() {
1489 - var object = jQuery( '.frm-show-form .g-recaptcha' ).closest( 'form' )[0];
1493 + const object = jQuery( '.frm-show-form .g-recaptcha' ).closest( 'form' )[0];
1490 1494 frmFrontForm.submitFormNow( object );
1491 1495 },
1492 1496
1493 - afterRecaptcha: function( token, formID ) {
1494 - var object = jQuery( '#frm_form_' + formID + '_container form' )[0];
1497 + afterRecaptcha: function( _, formID ) {
1498 + const object = jQuery( '#frm_form_' + formID + '_container form' )[0];
1495 1499 frmFrontForm.submitFormNow( object );
1496 1500 },
1497 1501
1498 1502 submitForm: function( e ) {
@@ -1499,9 +1503,9 @@
1499 1503 frmFrontForm.submitFormManual( e, this );
1500 1504 },
1501 1505
1502 1506 submitFormManual: function( e, object ) {
1503 - var isPro, errors,
1507 + let isPro, errors,
1504 1508 invisibleRecaptcha = hasInvisibleRecaptcha( object ),
1505 1509 classList = object.className.trim().split( /\s+/gi );
1506 1510
1507 1511 if ( classList && invisibleRecaptcha.length < 1 ) {
@@ -1516,12 +1520,10 @@
1516 1520 }
1517 1521
1518 1522 e.preventDefault();
1519 1523
1520 - if ( typeof frmProForm !== 'undefined' && typeof frmProForm.submitAllowed === 'function' ) {
1521 - if ( ! frmProForm.submitAllowed( object ) ) {
1522 - return;
1523 - }
1524 + if ( typeof frmProForm !== 'undefined' && typeof frmProForm.submitAllowed === 'function' && ! frmProForm.submitAllowed( object ) ) {
1525 + return;
1524 1526 }
1525 1527
1526 1528 if ( invisibleRecaptcha.length ) {
1527 1529 showLoadingIndicator( jQuery( object ) );
@@ -1538,9 +1540,9 @@
1538 1540 }
1539 1541 },
1540 1542
1541 1543 submitFormNow: function( object ) {
1542 - var hasFileFields, antispamInput,
1544 + let hasFileFields, antispamInput,
1543 1545 classList = object.className.trim().split( /\s+/gi );
1544 1546
1545 1547 if ( object.hasAttribute( 'data-token' ) && null === object.querySelector( '[name="antispam_token"]' ) ) {
1546 1548 // include the antispam token on form submit.
@@ -1565,8 +1567,13 @@
1565 1567 object.submit();
1566 1568 }
1567 1569 },
1568 1570
1571 + /**
1572 + * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
1573 + *
1574 + * @return {Array} List of errors.
1575 + */
1569 1576 validateFormSubmit: function( object ) {
1570 1577 if ( typeof tinyMCE !== 'undefined' && jQuery( object ).find( '.wp-editor-wrap' ).length ) {
1571 1578 tinyMCE.triggerSave();
1572 1579 }
@@ -1584,9 +1591,9 @@
1584 1591 return jsErrors;
1585 1592 },
1586 1593
1587 1594 getAjaxFormErrors: function( object ) {
1588 - var customErrors, key;
1595 + let customErrors, key;
1589 1596
1590 1597 jsErrors = validateForm( object );
1591 1598 if ( typeof frmThemeOverride_jsErrors === 'function' ) { // eslint-disable-line camelcase
1592 1599 action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
@@ -1601,9 +1608,9 @@
1601 1608 return jsErrors;
1602 1609 },
1603 1610
1604 1611 addAjaxFormErrors: function( object ) {
1605 - var key, $fieldCont;
1612 + let key, $fieldCont;
1606 1613 removeAllErrors();
1607 1614
1608 1615 for ( key in jsErrors ) {
1609 1616 $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
@@ -1636,14 +1643,14 @@
1636 1643 removeSubmitLoading( $object, enable, processesRunning );
1637 1644 },
1638 1645
1639 1646 scrollToID: function( id ) {
1640 - var object = jQuery( document.getElementById( id ) );
1647 + const object = jQuery( document.getElementById( id ) );
1641 1648 frmFrontForm.scrollMsg( object, false );
1642 1649 },
1643 1650
1644 1651 scrollMsg: function( id, object, animate ) {
1645 - var newPos, m, b, screenTop, screenBottom,
1652 + let newPos, m, b, screenTop, screenBottom,
1646 1653 scrollObj = '';
1647 1654 if ( typeof object === 'undefined' ) {
1648 1655 scrollObj = jQuery( document.getElementById( 'frm_form_' + id + '_container' ) );
1649 1656 if ( scrollObj.length < 1 ) {
@@ -1686,9 +1693,9 @@
1686 1693
1687 1694 fieldValueChanged: function( e ) {
1688 1695 /*jshint validthis:true */
1689 1696
1690 - var fieldId = frmFrontForm.getFieldId( this, false );
1697 + const fieldId = frmFrontForm.getFieldId( this, false );
1691 1698 if ( ! fieldId || typeof fieldId === 'undefined' ) {
1692 1699 return;
1693 1700 }
1694 1701
@@ -1702,55 +1709,8 @@
1702 1709 maybeValidateChange( this );
1703 1710 }
1704 1711 },
1705 1712
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 1713 escapeHtml: function( text ) {
1754 1714 return text
1755 1715 .replace( /&/g, '&amp;' )
1756 1716 .replace( /</g, '&lt;' )
@@ -1764,91 +1724,47 @@
1764 1724 },
1765 1725
1766 1726 visible: function( classes ) {
1767 1727 jQuery( classes ).css( 'visibility', 'visible' );
1768 - }
1728 + },
1729 +
1730 + triggerCustomEvent: triggerCustomEvent,
1731 + documentOn
1769 1732 };
1770 1733 }
1771 -frmFrontForm = frmFrontFormJS();
1772 1734
1735 +window.frmFrontForm = frmFrontFormJS();
1736 +
1773 1737 jQuery( document ).ready( function() {
1774 1738 frmFrontForm.init();
1775 1739 });
1776 1740
1777 1741 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 - }
1742 + frmCaptcha( '.frm-g-recaptcha' );
1783 1743 }
1784 1744
1785 -function frmAfterRecaptcha( token ) {
1786 - frmFrontForm.afterSingleRecaptcha( token );
1745 +function frmTurnstile() {
1746 + frmCaptcha( '.cf-turnstile' );
1787 1747 }
1788 1748
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 - }
1807 - }
1808 - });
1749 +function frmCaptcha( captchaSelector ) {
1750 + let c;
1751 + const captchas = document.querySelectorAll( captchaSelector );
1752 + const cl = captchas.length;
1753 + for ( c = 0; c < cl; c++ ) {
1754 + frmFrontForm.renderCaptcha( captchas[c], captchaSelector );
1755 + }
1809 1756 }
1810 1757
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 - });
1758 +function getSelectedCaptcha( captchaSelector ) {
1759 + if ( captchaSelector === '.frm-g-recaptcha' ) {
1760 + return grecaptcha;
1761 + }
1762 + if ( document.querySelector( '.cf-turnstile' ) ) {
1763 + return turnstile;
1764 + }
1765 + return hcaptcha;
1830 1766 }
1831 1767
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 - });
1768 +function frmAfterRecaptcha( token ) {
1769 + frmFrontForm.afterSingleRecaptcha( token );
1854 1770 }