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