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