PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.11.1
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.11.1
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / js / formidable.js

formidable.js in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.11.1, at js/formidable.js

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