PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 5.0
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v5.0
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 +791 -1912 6.355.0 View file →
@@ -1,58 +1,53 @@
1 -/* exported frmRecaptcha, frmAfterRecaptcha */
1 +/* exported frmRecaptcha, frmAfterRecaptcha, frmUpdateField, frmDeleteEntry, frmOnSubmit, frm_resend_email */
2 2
3 +var frmFrontForm;
4 +
3 5 function frmFrontFormJS() {
4 6 'use strict';
5 7
6 - let jsErrors = [];
8 + /*global jQuery:false, frm_js, grecaptcha, frmProForm, tinyMCE */
9 + /*global frmThemeOverride_jsErrors, frmThemeOverride_frmPlaceError, frmThemeOverride_frmAfterSubmit */
7 10
8 - /**
9 - * Triggers custom JS event.
10 - *
11 - * @since 5.5.3
12 - *
13 - * @param {HTMLElement} el The HTML element.
14 - * @param {string} eventName Event name.
15 - * @param {*} data The passed data.
16 - */
17 - function triggerCustomEvent( el, eventName, data ) {
18 - if ( typeof window.CustomEvent !== 'function' ) {
19 - return;
20 - }
11 + var action = '';
12 + var jsErrors = [];
21 13
22 - const event = new CustomEvent( eventName );
23 - event.frmData = data;
14 + function maybeShowLabel() {
15 + /*jshint validthis:true */
16 + var $field = jQuery( this ),
17 + $label = $field.closest( '.frm_inside_container' ).find( '.frm_primary_label' ),
18 + val = $field.val();
24 19
25 - el.dispatchEvent( event );
20 + if ( val !== null && val.length > 0 ) {
21 + $label.addClass( 'frm_visible' );
22 + } else {
23 + $label.removeClass( 'frm_visible' );
24 + }
26 25 }
27 26
28 - /**
29 - * Get the ID of the field that changed.
30 - *
31 - * @param {HTMLElement|jQuery} field
32 - * @param {boolean} fullID
33 - * @return {string|number} Field ID.
34 - */
27 + /* Get the ID of the field that changed*/
35 28 function getFieldId( field, fullID ) {
36 - let nameParts;
37 - let fieldId;
38 - let isRepeating = false;
39 - let fieldName = '';
40 -
29 + var nameParts, fieldId,
30 + isRepeating = false,
31 + fieldName = '';
41 32 if ( field instanceof jQuery ) {
42 - field = field.get( 0 );
33 + fieldName = field.attr( 'name' );
34 + } else {
35 + fieldName = field.name;
43 36 }
44 37
45 - fieldName = field.name;
46 -
47 - if ( fieldName === undefined ) {
38 + if ( typeof fieldName === 'undefined' ) {
48 39 fieldName = '';
49 40 }
50 41
51 42 if ( fieldName === '' ) {
52 - fieldName = field.getAttribute( 'data-name' );
43 + if ( field instanceof jQuery ) {
44 + fieldName = field.data( 'name' );
45 + } else {
46 + fieldName = field.getAttribute( 'data-name' );
47 + }
53 48
54 - if ( fieldName === undefined ) {
49 + if ( typeof fieldName === 'undefined' ) {
55 50 fieldName = '';
56 51 }
57 52
58 53 if ( fieldName !== '' && fieldName ) {
@@ -67,24 +62,25 @@
67 62 return 0;
68 63 }
69 64 nameParts = nameParts.filter( function( n ) {
70 65 return n !== '';
71 - } );
66 + });
72 67
73 - fieldId = nameParts[ 0 ];
68 + fieldId = nameParts[0];
74 69
75 70 if ( nameParts.length === 1 ) {
76 71 return fieldId;
77 72 }
78 73
79 - if ( nameParts[ 1 ] === '[form' || nameParts[ 1 ] === '[row_ids' ) {
74 + if ( nameParts[1] === '[form' || nameParts[1] === '[row_ids' ) {
80 75 return 0;
81 76 }
82 77
83 78 // Check if 'this' is in a repeating section
84 - if ( document.querySelector( `input[name="item_meta[${ fieldId }][form]"]` ) ) {
79 + if ( jQuery( 'input[name="item_meta[' + fieldId + '][form]"]' ).length ) {
80 +
85 81 // this is a repeatable section with name: item_meta[repeating-section-id][row-id][field-id]
86 - fieldId = nameParts[ 2 ].replace( '[', '' );
82 + fieldId = nameParts[2].replace( '[', '' );
87 83 isRepeating = true;
88 84 }
89 85
90 86 // Check if 'this' is an other text field and get field ID for it
@@ -90,21 +86,21 @@
90 86 // Check if 'this' is an other text field and get field ID for it
91 87 if ( 'other' === fieldId ) {
92 88 if ( isRepeating ) {
93 89 // name for other fields: item_meta[370][0][other][414]
94 - fieldId = nameParts[ 3 ].replace( '[', '' );
90 + fieldId = nameParts[3].replace( '[', '' );
95 91 } else {
96 92 // Other field name: item_meta[other][370]
97 - fieldId = nameParts[ 1 ].replace( '[', '' );
93 + fieldId = nameParts[1].replace( '[', '' );
98 94 }
99 95 }
100 96
101 97 if ( fullID === true ) {
102 98 // For use in the container div id
103 - if ( fieldId === nameParts[ 0 ] ) {
104 - fieldId = `${ fieldId }-${ nameParts[ 1 ].replace( '[', '' ) }`;
99 + if ( fieldId === nameParts[0]) {
100 + fieldId = fieldId + '-' + nameParts[1].replace( '[', '' );
105 101 } else {
106 - fieldId = `${ fieldId }-${ nameParts[ 0 ] }-${ nameParts[ 1 ].replace( '[', '' ) }`;
102 + fieldId = fieldId + '-' + nameParts[0] + '-' + nameParts[1].replace( '[', '' );
107 103 }
108 104 }
109 105
110 106 return fieldId;
@@ -114,18 +110,12 @@
114 110 * Disable the submit button for a given jQuery form object
115 111 *
116 112 * @since 2.03.02
117 113 *
118 - * @param {Object} $form
119 - */
114 + * @param {object} $form
115 + */
120 116 function disableSubmitButton( $form ) {
121 - const form = $form instanceof jQuery ? $form.get( 0 ) : $form;
122 - if ( ! form ) {
123 - return;
124 - }
125 - form.querySelectorAll( 'input[type="submit"], input[type="button"], button[type="submit"], button.frm_save_draft' ).forEach(
126 - button => button.disabled = true
127 - );
117 + $form.find( 'input[type="submit"], input[type="button"], button[type="submit"]' ).attr( 'disabled', 'disabled' );
128 118 }
129 119
130 120 /**
131 121 * Enable the submit button for a given jQuery form object
@@ -131,16 +121,12 @@
131 121 * Enable the submit button for a given jQuery form object
132 122 *
133 123 * @since 2.03.02
134 124 *
135 - * @param {HTMLElement} form
136 - *
137 - * @return {void}
138 - */
139 - function enableSubmitButton( form ) {
140 - form.querySelectorAll( 'input[type="submit"], input[type="button"], button[type="submit"]' ).forEach(
141 - button => button.disabled = false
142 - );
125 + * @param {object} $form
126 + */
127 + function enableSubmitButton( $form ) {
128 + $form.find( 'input[type="submit"], input[type="button"], button[type="submit"]' ).prop( 'disabled', false );
143 129 }
144 130
145 131 /**
146 132 * Disable the save draft link for a given jQuery form object
@@ -146,294 +132,148 @@
146 132 * Disable the save draft link for a given jQuery form object
147 133 *
148 134 * @since 4.04.03
149 135 *
150 - * @param {Object} $form
136 + * @param {object} $form
151 137 */
152 138 function disableSaveDraft( $form ) {
153 - const form = $form instanceof jQuery ? $form.get( 0 ) : $form;
154 - if ( ! form ) {
155 - return;
156 - }
157 - form.querySelectorAll( 'a.frm_save_draft' ).forEach(
158 - link => link.style.pointerEvents = 'none'
159 - );
139 + $form.find( 'a.frm_save_draft' ).css( 'pointer-events', 'none' );
160 140 }
161 141
162 142 /**
163 - * Enable the save draft link for a given form object.
143 + * Enable the save draft link for a given jQuery form object
164 144 *
165 145 * @since 4.04.03
166 146 *
167 - * @param {jQuery|HTMLElement} $form
147 + * @param {object} $form
168 148 */
169 149 function enableSaveDraft( $form ) {
170 - const form = $form instanceof jQuery ? $form.get( 0 ) : $form;
171 - if ( ! form ) {
172 - return;
173 - }
174 - form.querySelectorAll( '.frm_save_draft' ).forEach( saveDraftButton => {
175 - saveDraftButton.disabled = false;
176 - saveDraftButton.style.pointerEvents = '';
177 - } );
150 + $form.find( 'a.frm_save_draft' ).css( 'pointer-events', '' );
178 151 }
179 152
180 - /**
181 - * Validate form with JS.
182 - *
183 - * @param {HTMLElement|jQuery} object
184 - * @return {Array} Errors.
185 - */
186 153 function validateForm( object ) {
187 - let errors = [];
154 + var r, rl, n, nl, fields, field, value, requiredFields,
155 + errors = [];
188 156
189 - const vanillaJsObject = 'function' === typeof object.get ? object.get( 0 ) : object;
190 -
191 - // Required field validation.
192 - vanillaJsObject?.querySelectorAll( '.frm_required_field' ).forEach(
193 - requiredField => {
194 - const isVisible = requiredField.offsetParent !== null;
195 - if ( ! isVisible ) {
196 - return;
197 - }
198 -
199 - requiredField.querySelectorAll( 'input, select, textarea' ).forEach(
200 - requiredInput => {
201 - if ( hasClass( requiredInput, 'frm_optional' ) || hasClass( requiredInput, 'ed_button' ) ) {
202 - // skip rich text field buttons.
203 - return;
204 - }
205 -
206 - errors = checkRequiredField( requiredInput, errors );
207 - }
208 - );
157 + // Make sure required text field is filled in
158 + requiredFields = jQuery( object ).find(
159 + '.frm_required_field:visible input, .frm_required_field:visible select, .frm_required_field:visible textarea'
160 + ).filter( ':not(.frm_optional)' );
161 + if ( requiredFields.length ) {
162 + for ( r = 0, rl = requiredFields.length; r < rl; r++ ) {
163 + errors = checkRequiredField( requiredFields[r], errors );
209 164 }
210 - );
165 + }
211 166
212 - vanillaJsObject?.querySelectorAll( 'input,select,textarea' ).forEach(
213 - field => {
214 - if ( '' === field.value ) {
215 - if ( 'number' === field.type ) {
216 - // A number field will return an empty string when it is invalid.
217 - checkValidity( field, errors );
167 + fields = jQuery( object ).find( 'input,select,textarea' );
168 + if ( fields.length ) {
169 + for ( n = 0, nl = fields.length; n < nl; n++ ) {
170 + field = fields[n];
171 + value = field.value;
172 + if ( value !== '' ) {
173 + if ( field.type === 'hidden' ) {
174 + // don't validate
175 + } else if ( field.type === 'number' ) {
176 + errors = checkNumberField( field, errors );
177 + } else if ( field.type === 'email' ) {
178 + errors = checkEmailField( field, errors );
179 + } else if ( field.type === 'password' ) {
180 + errors = checkPasswordField( field, errors );
181 + } else if ( field.type === 'url' ) {
182 + errors = checkUrlField( field, errors );
183 + } else if ( field.pattern !== null ) {
184 + errors = checkPatternField( field, errors );
218 185 }
219 -
220 - const isConfirmationField = field.name && 0 === field.name.indexOf( 'item_meta[conf_' );
221 - if ( ! isConfirmationField ) {
222 - // Allow a blank confirmation field to still call validateFieldValue.
223 - // If we continue for a confirmation field there are issues with forms submitting with a blank confirmation field.
224 - return;
225 - }
226 186 }
227 -
228 - validateFieldValue( field, errors, true );
229 - checkValidity( field, errors );
230 187 }
231 - );
188 + }
232 189
233 - // Invisible captchas are processed after validation.
234 - // We only want to validate a visible captcha on submit.
235 - if ( ! hasInvisibleRecaptcha( object ) ) {
236 - errors = validateRecaptcha( object, errors );
237 - }
190 + errors = validateRecaptcha( object, errors );
238 191
239 192 return errors;
240 193 }
241 194
242 - /**
243 - * Check the ValidityState interface for the field.
244 - * If it is invalid, show an error for it.
245 - *
246 - * @param {HTMLElement} field
247 - * @param {Array} errors
248 - * @return {void}
249 - */
250 - function checkValidity( field, errors ) {
251 - if ( 'object' !== typeof field.validity || false !== field.validity.valid ) {
252 - return;
253 - }
254 -
255 - const fieldID = getFieldId( field, true );
256 - if ( errors[ fieldID ] === undefined ) {
257 - errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
258 - }
259 -
260 - if ( 'function' === typeof field.reportValidity ) {
261 - // This triggers an error pop up.
262 - field.reportValidity();
263 - }
264 - }
265 -
266 - /**
267 - * @since 5.0.10
268 - *
269 - * @param {Object} element
270 - * @param {string} targetClass
271 - * @return {boolean} True if the element has the target class.
272 - */
273 - function hasClass( element, targetClass ) {
274 - return element.classList && element.classList.contains( targetClass );
275 - }
276 -
277 - /**
278 - * @param {HTMLElement} field
279 - */
280 - function maybeValidateChange( field ) {
195 + function maybeValidateChange( fieldId, field ) {
281 196 if ( field.type === 'url' ) {
282 - maybeAddHttpsToUrl( field );
197 + maybeAddHttpToUrl( field );
283 198 }
284 - const form = field.closest( 'form' );
285 - if ( ! form ) {
286 - return;
199 + if ( jQuery( field ).closest( 'form' ).hasClass( 'frm_js_validate' ) ) {
200 + validateField( fieldId, field );
287 201 }
288 -
289 - // Removing stale errors is universal. Adding errors only happens when JS validation is enabled.
290 - validateField( field, hasClass( form, 'frm_js_validate' ) );
291 202 }
292 203
293 - /**
294 - * @param {HTMLElement} field
295 - */
296 - function maybeAddHttpsToUrl( field ) {
297 - const url = field.value;
298 - const matches = url.match( /^(https?|ftps?|mailto|news|feed|telnet):/ );
204 + function maybeAddHttpToUrl( field ) {
205 + var url = field.value;
206 + var matches = url.match( /^(https?|ftps?|mailto|news|feed|telnet):/ );
299 207 if ( field.value !== '' && matches === null ) {
300 - field.value = `https://${ url }`;
208 + field.value = 'http://' + url;
301 209 }
302 210 }
303 211
304 - /**
305 - * Validate a field with JS.
306 - *
307 - * Removing stale errors is universal. Adding errors only happens when JS validation is enabled.
308 - *
309 - * @since 6.32 Added the `addErrors` parameter.
310 - *
311 - * @param {HTMLElement} field
312 - * @param {boolean} addErrors Whether to add new errors. Defaults to `true`.
313 - *
314 - * @return {void}
315 - */
316 - function validateField( field, addErrors = true ) {
317 - let errors;
318 - let key;
212 + function validateField( fieldId, field ) {
213 + var key,
214 + errors = [];
319 215
320 - errors = [];
321 - const fieldContainer = field.closest( '.frm_form_field' );
322 -
323 - if ( ! fieldContainer ) {
324 - // Hidden fields do not have a field container and do not require JS validation.
325 - return;
326 - }
327 -
328 - if ( hasClass( fieldContainer, 'frm_required_field' ) && ! hasClass( field, 'frm_optional' ) ) {
216 + var $fieldCont = jQuery( field ).closest( '.frm_form_field' );
217 + if ( $fieldCont.hasClass( 'frm_required_field' ) && ! jQuery( field ).hasClass( 'frm_optional' ) ) {
329 218 errors = checkRequiredField( field, errors );
330 219 }
331 220
332 221 if ( errors.length < 1 ) {
333 - validateFieldValue( field, errors, false );
222 + if ( field.type === 'email' ) {
223 + errors = checkEmailField( field, errors );
224 + } else if ( field.type === 'password' ) {
225 + errors = checkPasswordField( field, errors );
226 + } else if ( field.type === 'number' ) {
227 + errors = checkNumberField( field, errors );
228 + } else if ( field.type === 'url' ) {
229 + errors = checkUrlField( field, errors );
230 + } else if ( field.pattern !== null ) {
231 + errors = checkPatternField( field, errors );
232 + }
334 233 }
335 234
336 - const hasErrors = Object.keys( errors ).length > 0;
337 -
338 - if ( addErrors ) {
339 - removeFieldError( fieldContainer );
340 - if ( hasErrors ) {
341 - for ( key in errors ) {
342 - addFieldError( fieldContainer, key, errors );
343 - }
235 + removeFieldError( $fieldCont );
236 + if ( Object.keys( errors ).length > 0 ) {
237 + for ( key in errors ) {
238 + addFieldError( $fieldCont, key, errors );
344 239 }
345 - } else if ( ! hasErrors ) {
346 - // JS validation is off, so only remove existing errors once the field passes validation.
347 - removeFieldError( fieldContainer );
348 240 }
349 241 }
350 242
351 - /**
352 - * Validates a field value.
353 - *
354 - * @since 6.15 Added `onSubmit` parameter.
355 - *
356 - * @param {HTMLElement} field Field input.
357 - * @param {Object} errors Errors data.
358 - * @param {boolean} onSubmit Is `true` if the form is being submitted.
359 - */
360 - function validateFieldValue( field, errors, onSubmit ) {
361 - if ( field.type === 'hidden' ) {
362 - // don't validate
363 - } else if ( field.type === 'number' ) {
364 - checkNumberField( field, errors );
365 - } else if ( field.type === 'email' ) {
366 - checkEmailField( field, errors, onSubmit );
367 - } else if ( field.type === 'password' ) {
368 - checkPasswordField( field, errors, onSubmit );
369 - } else if ( field.type === 'url' ) {
370 - checkUrlField( field, errors );
371 - } else if ( field.pattern !== null ) {
372 - checkPatternField( field, errors );
373 - }
374 -
375 - if ( 'tel' === field.type && shouldCheckConfirmField( field, onSubmit ) ) {
376 - confirmField( field, errors );
377 - }
378 -
379 - /**
380 - * @since 6.15 Added `onSubmit` to the data.
381 - */
382 - triggerCustomEvent( document, 'frm_validate_field_value', {
383 - field,
384 - errors,
385 - onSubmit
386 - } );
387 - }
388 -
389 - /**
390 - * @param {HTMLElement} field
391 - * @param {Array} errors
392 - * @return {Array} Errors
393 - */
394 243 function checkRequiredField( field, errors ) {
395 - let tempVal;
396 - let i;
397 - let placeholder;
398 - let val = '';
399 - let fieldID = '';
400 - let fileID = field.getAttribute( 'data-frmfile' );
244 + var checkGroup, fieldClasses, tempVal, i, placeholder,
245 + val = '',
246 + fieldID = '',
247 + fileID = field.getAttribute( 'data-frmfile' );
401 248
402 - if ( field.type === 'hidden' && fileID === null && ! isAppointmentField( field ) && ! isInlineDatepickerField( field ) ) {
249 + if ( field.type === 'hidden' && fileID === null ) {
403 250 return errors;
404 251 }
405 252
406 253 if ( field.type === 'checkbox' || field.type === 'radio' ) {
407 - document.querySelectorAll( `input[name="${ field.name }"]` ).forEach( function( input ) {
408 - const requiredField = input.closest( '.frm_required_field' );
409 - if ( ! requiredField ) {
410 - return;
411 - }
412 -
413 - const checkedInputs = requiredField.querySelectorAll( 'input:checked' );
414 - checkedInputs.forEach( function( checkedInput ) {
415 - val = checkedInput.value;
416 - } );
417 - } );
254 + checkGroup = jQuery( 'input[name="' + field.name + '"]' ).closest( '.frm_required_field' ).find( 'input:checked' );
255 + jQuery( checkGroup ).each( function() {
256 + val = this.value;
257 + });
418 258 } else if ( field.type === 'file' || fileID ) {
419 - if ( fileID === undefined ) {
259 + if ( typeof fileID === 'undefined' ) {
420 260 fileID = getFieldId( field, true );
421 261 fileID = fileID.replace( 'file', '' );
422 262 }
423 263
424 - if ( errors[ fileID ] === undefined ) {
264 + if ( typeof errors[ fileID ] === 'undefined' ) {
425 265 val = getFileVals( fileID );
426 266 }
427 267 fieldID = fileID;
428 268 } else {
429 - if ( hasClass( field, 'frm_pos_none' ) ) {
269 + fieldClasses = field.className;
270 + if ( fieldClasses.indexOf( 'frm_pos_none' ) !== -1 ) {
430 271 // skip hidden other fields
431 272 return errors;
432 273 }
433 274
434 - val = jQuery( field ).val(); // eslint-disable-line no-jquery/no-val
435 -
275 + val = jQuery( field ).val();
436 276 if ( val === null ) {
437 277 val = '';
438 278 } else if ( typeof val !== 'string' ) {
439 279 tempVal = val;
@@ -438,40 +278,23 @@
438 278 } else if ( typeof val !== 'string' ) {
439 279 tempVal = val;
440 280 val = '';
441 281 for ( i = 0; i < tempVal.length; i++ ) {
442 - if ( tempVal[ i ] !== '' ) {
443 - val = tempVal[ i ];
282 + if ( tempVal[i] !== '' ) {
283 + val = tempVal[i];
444 284 }
445 285 }
446 286 }
447 287
448 - if ( hasClass( field, 'frm_other_input' ) ) {
288 + if ( fieldClasses.indexOf( 'frm_other_input' ) === -1 ) {
289 + fieldID = getFieldId( field, true );
290 + } else {
449 291 fieldID = getFieldId( field, false );
450 -
451 - if ( val === '' ) {
452 - field = document.getElementById( field.id.replace( '-otext', '' ) );
453 - }
454 - } else {
455 - fieldID = getFieldId( field, true );
456 292 }
457 293
458 - // Make sure fieldID is a string.
459 - // fieldID may be a number which doesn't include a .replace function.
460 - if ( 'function' !== typeof fieldID.replace ) {
461 - fieldID = fieldID.toString();
462 - }
463 -
464 - if ( hasClass( field, 'frm_time_select' ) ) {
294 + if ( fieldClasses.indexOf( 'frm_time_select' ) !== -1 ) {
465 295 // set id for time field
466 296 fieldID = fieldID.replace( '-H', '' ).replace( '-m', '' );
467 - } else if ( isSignatureField( field ) ) {
468 - if ( val === '' ) {
469 - const fieldContainer = field.closest( '.frm_form_field' );
470 - const outputField = fieldContainer ? fieldContainer.querySelector( `[name="${ field.getAttribute( 'name' ).replace( '[typed]', '[output]' ) }"]` ) : null;
471 - val = outputField ? outputField.value : '';
472 - }
473 - fieldID = fieldID.replace( '-typed', '' );
474 297 }
475 298
476 299 placeholder = field.getAttribute( 'data-frmplaceholder' );
477 300 if ( placeholder !== null && val === placeholder ) {
@@ -490,57 +313,23 @@
490 313
491 314 return errors;
492 315 }
493 316
494 - /**
495 - * @param {HTMLElement} field
496 - * @return {boolean} True if the input is a typed signature input.
497 - */
498 - function isSignatureField( field ) {
499 - const name = field.getAttribute( 'name' );
500 - return 'string' === typeof name && '[typed]' === name.substr( -7 );
501 - }
502 -
503 - /**
504 - * @param {HTMLElement} field
505 - * @return {boolean} True if the field is a SSA appointment field.
506 - */
507 - function isAppointmentField( field ) {
508 - return hasClass( field, 'ssa_appointment_form_field_appointment_id' );
509 - }
510 -
511 - /**
512 - * @param {HTMLElement} field
513 - * @return {boolean} True if the field is inline datepicker field.
514 - */
515 - function isInlineDatepickerField( field ) {
516 - return 'hidden' === field.type && '_alt' === field.id.substr( -4 ) && hasClass( field.nextElementSibling, 'frm_date_inline' );
517 - }
518 -
519 - /**
520 - * @param {string|number} fileID
521 - * @return {string} File input value.
522 - */
523 317 function getFileVals( fileID ) {
524 - let val = '';
525 - const fileFields = document.querySelectorAll( `input[name="file${ fileID }"], input[name="file${ fileID }[]"], input[name^="item_meta[${ fileID }]"]` );
318 + var val = '',
319 + fileFields = jQuery( 'input[name="file' + fileID + '"], input[name="file' + fileID + '[]"], input[name^="item_meta[' + fileID + ']"]' );
526 320
527 - fileFields.forEach( function( field ) {
321 + fileFields.each( function() {
528 322 if ( val === '' ) {
529 - val = field.value;
323 + val = this.value;
530 324 }
531 - } );
325 + });
532 326 return val;
533 327 }
534 328
535 - /**
536 - * @param {HTMLElement} field
537 - * @param {Array} errors
538 - * @return {void}
539 - */
540 329 function checkUrlField( field, errors ) {
541 - let fieldID;
542 - const url = field.value;
330 + var fieldID,
331 + url = field.value;
543 332
544 333 if ( url !== '' && ! /^http(s)?:\/\/(?:localhost|(?:[\da-z\.-]+\.[\da-z\.-]+))/i.test( url ) ) {
545 334 fieldID = getFieldId( field, true );
546 335 if ( ! ( fieldID in errors ) ) {
@@ -546,106 +335,55 @@
546 335 if ( ! ( fieldID in errors ) ) {
547 336 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
548 337 }
549 338 }
339 + return errors;
550 340 }
551 341
552 - /**
553 - * Checks if the confirm field should be checked.
554 - *
555 - * @since 6.15
556 - *
557 - * @param {HTMLElement} field Field input.
558 - * @param {boolean} onSubmit Is `true` if the form is being submitted.
559 - * @return {boolean} True if we should confirm the field.
560 - */
561 - function shouldCheckConfirmField( field, onSubmit ) {
562 - if ( onSubmit ) {
563 - // Always check on submitting.
564 - return true;
565 - }
342 + function checkEmailField( field, errors ) {
343 + var fieldID = getFieldId( field, true ),
344 + 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;
566 345
567 - if ( 0 === field.id.indexOf( 'field_conf_' ) ) {
568 - // Always check if it's the confirm field.
569 - return true;
570 - }
571 -
572 - return false;
573 - }
574 -
575 - /**
576 - * Check the email field for errors.
577 - *
578 - * @since 6.15 Added `onSubmit` parameter.
579 - *
580 - * @param {HTMLElement} field Field input.
581 - * @param {Object} errors Errors data.
582 - * @param {boolean} onSubmit Is `true` if the form is being submitted.
583 - */
584 - function checkEmailField( field, errors, onSubmit ) {
585 - const fieldID = getFieldId( field, true );
586 - const 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;
587 -
588 346 // validate the current field we're editing first
589 347 if ( '' !== field.value && pattern.test( field.value ) === false ) {
590 348 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
591 349 }
592 350
593 - if ( shouldCheckConfirmField( field, onSubmit ) ) {
594 - confirmField( field, errors );
595 - }
351 + confirmField( field, errors );
352 + return errors;
596 353 }
597 354
598 - /**
599 - * Check the password field for errors.
600 - *
601 - * @since 6.15 Added `onSubmit` parameter.
602 - *
603 - * @param {HTMLElement} field Field input.
604 - * @param {Object} errors Errors data.
605 - * @param {boolean} onSubmit Is `true` if the form is being submitted.
606 - */
607 - function checkPasswordField( field, errors, onSubmit ) {
608 - if ( shouldCheckConfirmField( field, onSubmit ) ) {
609 - confirmField( field, errors );
610 - }
355 + function checkPasswordField( field, errors ) {
356 + confirmField( field, errors );
357 + return errors;
611 358 }
612 359
613 - /**
614 - * @param {HTMLElement} field
615 - * @param {Array} errors
616 - * @return {void}
617 - */
618 360 function confirmField( field, errors ) {
619 - const fieldID = getFieldId( field, true );
620 - const strippedId = field.id.replace( 'conf_', '' );
621 - const strippedFieldID = fieldID.replace( 'conf_', '' );
622 - const confirmField = document.getElementById( strippedId.replace( 'field_', 'field_conf_' ) );
361 + var value, confirmValue, firstField,
362 + fieldID = getFieldId( field, true ),
363 + strippedId = field.id.replace( 'conf_', '' ),
364 + strippedFieldID = fieldID.replace( 'conf_', '' ),
365 + confirmField = document.getElementById( strippedId.replace( 'field_', 'field_conf_' ) );
623 366
624 - if ( ! confirmField || errors[ `conf_${ strippedFieldID }` ] !== undefined ) {
367 + if ( confirmField === null || typeof errors[ 'conf_' + strippedFieldID ] !== 'undefined' ) {
625 368 return;
626 369 }
627 370
628 371 if ( fieldID !== strippedFieldID ) {
629 - const firstField = document.getElementById( strippedId );
630 - const { value } = firstField;
631 - const confirmValue = confirmField.value;
632 - if ( value !== confirmValue ) {
633 - errors[ `conf_${ strippedFieldID }` ] = getFieldValidationMessage( confirmField, 'data-confmsg' );
372 + firstField = document.getElementById( strippedId );
373 + value = firstField.value;
374 + confirmValue = confirmField.value;
375 + if ( '' !== value && '' !== confirmValue && value !== confirmValue ) {
376 + errors[ 'conf_' + strippedFieldID ] = getFieldValidationMessage( confirmField, 'data-confmsg' );
634 377 }
635 378 } else {
636 - validateField( confirmField );
379 + validateField( 'conf_' + strippedFieldID, confirmField );
637 380 }
638 381 }
639 382
640 - /**
641 - * @param {HTMLElement} field
642 - * @param {Array} errors
643 - * @return {void}
644 - */
645 383 function checkNumberField( field, errors ) {
646 - let fieldID;
647 - const number = field.value;
384 + var fieldID,
385 + number = field.value;
648 386
649 387 if ( number !== '' && isNaN( number / 1 ) !== false ) {
650 388 fieldID = getFieldId( field, true );
651 389 if ( ! ( fieldID in errors ) ) {
@@ -651,200 +389,90 @@
651 389 if ( ! ( fieldID in errors ) ) {
652 390 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
653 391 }
654 392 }
393 + return errors;
655 394 }
656 395
657 - /**
658 - * @param {HTMLElement} field
659 - * @param {Array} errors
660 - * @return {void}
661 - */
662 396 function checkPatternField( field, errors ) {
663 - let fieldID;
664 - const text = field.value;
665 - let format = getFieldValidationMessage( field, 'pattern' );
397 + var fieldID,
398 + text = field.value,
399 + format = getFieldValidationMessage( field, 'pattern' );
666 400
667 401 if ( format !== '' && text !== '' ) {
668 402 fieldID = getFieldId( field, true );
669 403 if ( ! ( fieldID in errors ) ) {
670 - if ( 'object' === typeof window.frmProForm && 'function' === typeof window.frmProForm.isIntlPhoneInput && window.frmProForm.isIntlPhoneInput( field ) ) {
671 - if ( ! window.frmProForm.validateIntlPhoneInput( field ) ) {
672 - errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
673 - }
674 - } else {
675 - format = new RegExp( `^${ format }$`, 'i' );
676 - if ( format.test( text ) === false ) {
677 - errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
678 - }
404 + format = new RegExp( '^' + format + '$', 'i' );
405 + if ( format.test( text ) === false ) {
406 + errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
679 407 }
680 408 }
681 409 }
410 + return errors;
682 411 }
683 412
684 - /**
685 - * Set color for select placeholders.
686 - *
687 - * @since 6.5.1
688 - */
689 - function setSelectPlaceholderColor() {
690 - const selects = document.querySelectorAll( '.form-field select' );
691 - const styleElement = document.querySelector( '.with_frm_style' );
692 - const textColorDisabled = styleElement ? getComputedStyle( styleElement ).getPropertyValue( '--text-color-disabled' ).trim() : '';
693 - // Exit if there are no select elements or the textColorDisabled property is missing
694 - if ( ! selects.length || ! textColorDisabled ) {
695 - return;
696 - }
413 + function hasInvisibleRecaptcha( object ) {
414 + var recaptcha, recaptchaID, alreadyChecked;
697 415
698 - // Function to change the color of a select element
699 - const changeSelectColor = function( select ) {
700 - if ( select.options[ select.selectedIndex ] && hasClass( select.options[ select.selectedIndex ], 'frm-select-placeholder' ) ) {
701 - select.style.setProperty( 'color', textColorDisabled, 'important' );
702 - } else {
703 - select.style.color = '';
704 - }
705 - };
706 -
707 - // Use a loop to iterate through each select element
708 - Array.prototype.forEach.call( selects, function( select ) {
709 - // Apply the color change to each select element
710 - changeSelectColor( select );
711 -
712 - // Add an event listener for future changes
713 - select.addEventListener( 'change', function() {
714 - changeSelectColor( select );
715 - } );
716 - } );
717 - }
718 -
719 - /**
720 - * @param {HTMLElement|jQuery} object
721 - *
722 - * @return {HTMLElement|false} Captcha element if there is an invisible recaptcha.
723 - */
724 - function hasInvisibleRecaptcha( object ) {
725 416 if ( isGoingToPrevPage( object ) ) {
726 417 return false;
727 418 }
728 419
729 - const form = object instanceof jQuery ? object.get( 0 ) : object;
730 - if ( ! form ) {
731 - return false;
732 - }
733 -
734 - const recaptcha = form.querySelector( '.frm-g-recaptcha[data-size="invisible"], .g-recaptcha[data-size="invisible"]' );
735 - if ( recaptcha ) {
736 - const recaptchaID = recaptcha.dataset.rid;
737 - const alreadyChecked = grecaptcha.getResponse( recaptchaID );
420 + recaptcha = jQuery( object ).find( '.frm-g-recaptcha[data-size="invisible"], .g-recaptcha[data-size="invisible"]' );
421 + if ( recaptcha.length ) {
422 + recaptchaID = recaptcha.data( 'rid' );
423 + alreadyChecked = grecaptcha.getResponse( recaptchaID );
738 424 if ( alreadyChecked.length === 0 ) {
739 425 return recaptcha;
426 + } else {
427 + return false;
740 428 }
429 + } else {
430 + return false;
741 431 }
742 -
743 - return false;
744 432 }
745 433
746 - /**
747 - * @param {HTMLElement} invisibleRecaptcha
748 - *
749 - * @return {void}
750 - */
751 434 function executeInvisibleRecaptcha( invisibleRecaptcha ) {
752 - const recaptchaID = invisibleRecaptcha.dataset.rid;
435 + var recaptchaID = invisibleRecaptcha.data( 'rid' );
753 436 grecaptcha.reset( recaptchaID );
754 437 grecaptcha.execute( recaptchaID );
755 438 }
756 439
757 440 function validateRecaptcha( form, errors ) {
758 - const formEl = form instanceof jQuery ? form.get( 0 ) : form;
759 - if ( ! formEl ) {
760 - return errors;
761 - }
441 + var recaptchaID, response, fieldContainer, fieldID,
442 + $recaptcha = jQuery( form ).find( '.frm-g-recaptcha' );
443 + if ( $recaptcha.length ) {
444 + recaptchaID = $recaptcha.data( 'rid' );
762 445
763 - const recaptcha = formEl.querySelector( '.frm-g-recaptcha' );
764 - if ( ! recaptcha ) {
765 - return errors;
766 - }
767 -
768 - const recaptchaID = recaptcha.dataset.rid;
769 - let response;
770 -
771 - try {
772 - response = grecaptcha.getResponse( recaptchaID );
773 - } catch ( e ) {
774 - if ( formEl.querySelector( 'input[name="recaptcha_checked"]' ) ) {
775 - return errors;
446 + try {
447 + response = grecaptcha.getResponse( recaptchaID );
448 + } catch ( e ) {
449 + if ( jQuery( form ).find( 'input[name="recaptcha_checked"]' ).length ) {
450 + return errors;
451 + } else {
452 + response = '';
453 + }
776 454 }
777 - response = '';
778 - }
779 455
780 - if ( response.length === 0 ) {
781 - const fieldContainer = recaptcha.closest( '.frm_form_field' );
782 - if ( fieldContainer?.id ) {
783 - const fieldID = fieldContainer.id.replace( 'frm_field_', '' ).replace( '_container', '' );
456 + if ( response.length === 0 ) {
457 + fieldContainer = $recaptcha.closest( '.frm_form_field' );
458 + fieldID = fieldContainer.attr( 'id' ).replace( 'frm_field_', '' ).replace( '_container', '' );
784 459 errors[ fieldID ] = '';
785 460 }
786 461 }
787 -
788 462 return errors;
789 463 }
790 464
791 - /**
792 - * @param {HTMLElement} field
793 - * @param {string} messageType
794 - * @return {string} The error message to display.
795 - */
796 465 function getFieldValidationMessage( field, messageType ) {
797 - let msg = field.getAttribute( messageType );
798 - if ( null === msg ) {
466 + var msg = field.getAttribute( messageType );
467 + if ( msg === null ) {
799 468 msg = '';
800 469 }
801 -
802 - if ( '' !== msg && shouldWrapErrorHtmlAroundMessageType( messageType ) ) {
803 - msg = wrapErrorHtml( msg, field );
804 - }
805 -
806 470 return msg;
807 471 }
808 472
809 - /**
810 - * @param {string} msg
811 - * @param {HTMLElement} field
812 - * @return {string} The error HTML to use.
813 - */
814 - function wrapErrorHtml( msg, field ) {
815 - let errorHtml = field.getAttribute( 'data-error-html' );
816 - if ( null === errorHtml ) {
817 - return msg;
818 - }
819 -
820 - errorHtml = errorHtml.replace( /\+/g, '%20' );
821 - msg = decodeURIComponent( errorHtml ).replace( '[error]', msg );
822 - const fieldId = getFieldId( field, false );
823 - const split = fieldId.split( '-' );
824 - const fieldIdParts = field.id.split( '_' );
825 - fieldIdParts.shift(); // Drop the "field" value from the front.
826 - split[ 0 ] = fieldIdParts.join( '_' );
827 - const errorKey = split.join( '-' );
828 - return msg.replace( '[key]', errorKey );
829 - }
830 -
831 - function shouldWrapErrorHtmlAroundMessageType( type ) {
832 - return 'pattern' !== type;
833 - }
834 -
835 - /**
836 - * Check if JS validation should happen.
837 - *
838 - * @param {HTMLElement|Object} object Form object.
839 - * @return {boolean} True if validation is enabled and we are not saving a draft or going to a previous page.
840 - */
841 473 function shouldJSValidate( object ) {
842 - if ( 'function' === typeof object.get ) {
843 - // Get the HTMLElement from a jQuery object.
844 - object = object.get( 0 );
845 - }
846 - let validate = hasClass( object, 'frm_js_validate' );
474 + var validate = jQuery( object ).hasClass( 'frm_js_validate' );
847 475 if ( validate && typeof frmProForm !== 'undefined' && ( frmProForm.savingDraft( object ) || frmProForm.goingToPreviousPage( object ) ) ) {
848 476 validate = false;
849 477 }
850 478
@@ -850,47 +478,28 @@
850 478
851 479 return validate;
852 480 }
853 481
854 - /**
855 - * @param {HTMLElement} object
856 - * @param {string} action
857 - * @return {void}
858 - */
859 482 function getFormErrors( object, action ) {
860 - const fieldsets = object.querySelectorAll( '.frm_form_field' );
861 - fieldsets.forEach( field => field.classList.add( 'frm_doing_ajax' ) );
483 + var fieldset;
862 484
863 - const data = `${ jQuery( object ).serialize() }&action=frm_entries_${ action }&nonce=${ frm_js.nonce }`; // eslint-disable-line no-jquery/no-serialize
864 - const shouldTriggerEvent = object.classList.contains( 'frm_trigger_event_on_submit' );
485 + if ( typeof action === 'undefined' ) {
486 + jQuery( object ).find( 'input[name="frm_action"]' ).val();
487 + }
865 488
866 - const doRedirect = response => {
867 - jQuery( document ).trigger( 'frmBeforeFormRedirect', [ object, response ] );
489 + fieldset = jQuery( object ).find( '.frm_form_field' );
490 + fieldset.addClass( 'frm_doing_ajax' );
491 + jQuery.ajax({
492 + type: 'POST', url: frm_js.ajax_url,
493 + data: jQuery( object ).serialize() + '&action=frm_entries_' + action + '&nonce=' + frm_js.nonce,
494 + success: function( response ) {
495 + var formID, replaceContent, pageOrder, formReturned, contSubmit, delay,
496 + $fieldCont, key, inCollapsedSection, frmTrigger,
497 + defaultResponse = { 'content': '', 'errors': {}, 'pass': false };
498 + if ( response === null ) {
499 + response = defaultResponse;
500 + }
868 501
869 - if ( ! response.openInNewTab ) {
870 - // We return here because we're redirecting there is no need to update content.
871 - window.location = response.redirect;
872 - return;
873 - }
874 -
875 - // We don't return here because we're opening in a new tab, the old tab will still update.
876 - const newTab = window.open( response.redirect, '_blank' );
877 - if ( ! newTab && response.fallbackMsg && response.content ) {
878 - response.content = response.content.trim().replace( /(<\/div><\/div>)$/, ` ${ response.fallbackMsg }</div></div>` );
879 - }
880 - };
881 -
882 - const success = function( response ) {
883 - const defaultResponse = {
884 - content: '',
885 - errors: {},
886 - pass: false
887 - };
888 -
889 - if ( response === null ) {
890 - response = defaultResponse;
891 - } else {
892 - // Response is a string. Convert it to an object.
893 502 response = response.replace( /^\s+|\s+$/g, '' );
894 503 if ( response.indexOf( '{' ) === 0 ) {
895 504 response = JSON.parse( response );
896 505 } else {
@@ -895,222 +504,151 @@
895 504 response = JSON.parse( response );
896 505 } else {
897 506 response = defaultResponse;
898 507 }
899 - }
900 508
901 - let willRedirect = false;
509 + if ( typeof response.redirect !== 'undefined' ) {
510 + jQuery( document ).trigger( 'frmBeforeFormRedirect', [ object, response ]);
511 + window.location = response.redirect;
512 + } else if ( response.content !== '' ) {
513 + // the form or success message was returned
902 514
903 - if ( response.redirect !== undefined ) {
904 - if ( shouldTriggerEvent ) {
905 - triggerCustomEvent( object, 'frmSubmitEvent' );
906 - return;
907 - }
515 + removeSubmitLoading( jQuery( object ) );
516 + if ( frm_js.offset != -1 ) {
517 + frmFrontForm.scrollMsg( jQuery( object ), false );
518 + }
519 + formID = jQuery( object ).find( 'input[name="form_id"]' ).val();
520 + response.content = response.content.replace( / frm_pro_form /g, ' frm_pro_form frm_no_hide ' );
521 + replaceContent = jQuery( object ).closest( '.frm_forms' );
522 + removeAddedScripts( replaceContent, formID );
523 + delay = maybeSlideOut( replaceContent, response.content );
908 524
909 - if ( response.delay ) {
910 - setTimeout( function() {
911 - doRedirect( response );
912 - }, 1000 * response.delay );
913 - } else {
914 - doRedirect( response );
915 - }
525 + setTimeout(
526 + function() {
527 + var container, input, previousInput;
916 528
917 - willRedirect = true;
918 - }
529 + replaceContent.replaceWith( response.content );
919 530
920 - if ( 'string' === typeof response.content && response.content !== '' ) {
921 - // the form or success message was returned
531 + addUrlParam( response );
922 532
923 - if ( shouldTriggerEvent ) {
924 - triggerCustomEvent( object, 'frmSubmitEvent', { content: response.content } );
925 - return;
926 - }
533 + if ( typeof frmThemeOverride_frmAfterSubmit === 'function' ) { // eslint-disable-line camelcase
534 + pageOrder = jQuery( 'input[name="frm_page_order_' + formID + '"]' ).val();
535 + formReturned = jQuery( response.content ).find( 'input[name="form_id"]' ).val();
536 + frmThemeOverride_frmAfterSubmit( formReturned, pageOrder, response.content, object );
537 + }
927 538
928 - removeSubmitLoading( jQuery( object ) );
929 - if ( frm_js.offset != -1 ) {
930 - frmFrontForm.scrollMsg( jQuery( object ), false );
931 - }
539 + if ( typeof response.recaptcha !== 'undefined' ) {
540 + container = jQuery( '#frm_form_' + formID + '_container' ).find( '.frm_fields_container' );
541 + input = '<input type="hidden" name="recaptcha_checked" value="' + response.recaptcha + '">';
542 + previousInput = container.find( 'input[name="recaptcha_checked"]' );
932 543
933 - const formIdInput = object.querySelector( 'input[name="form_id"]' );
934 - const formID = formIdInput ? formIdInput.value : '';
935 - response.content = response.content.replace( / frm_pro_form /g, ' frm_pro_form frm_no_hide ' );
936 - const replaceContent = jQuery( object ).closest( '.frm_forms' ); // eslint-disable-line no-jquery/no-closest
937 - removeAddedScripts( replaceContent, formID );
938 - const delay = maybeSlideOut( replaceContent, response.content );
544 + if ( previousInput.length ) {
545 + previousInput.replaceWith( input );
546 + } else {
547 + container.append( input );
548 + }
549 + }
939 550
940 - setTimeout(
941 - function() {
942 - afterFormSubmittedBeforeReplace( object, response );
551 + afterFormSubmitted( object, response );
552 + },
553 + delay
554 + );
555 + } else if ( Object.keys( response.errors ).length ) {
556 + // errors were returned
943 557
944 - replaceContent.replaceWith( response.content );
558 + removeSubmitLoading( jQuery( object ), 'enable' );
945 559
946 - addUrlParam( response );
560 + //show errors
561 + contSubmit = true;
562 + removeAllErrors();
947 563
948 - if ( typeof frmThemeOverride_frmAfterSubmit === 'function' ) { // eslint-disable-line camelcase
949 - const pageOrderInput = document.querySelector( `input[name="frm_page_order_${ formID }"]` );
950 - const pageOrder = pageOrderInput ? pageOrderInput.value : '';
951 - const tempDiv = document.createElement( 'div' );
952 - tempDiv.innerHTML = response.content;
953 - const formReturnedInput = tempDiv.querySelector( 'input[name="form_id"]' );
954 - const formReturned = formReturnedInput ? formReturnedInput.value : '';
955 - frmThemeOverride_frmAfterSubmit( formReturned, pageOrder, response.content, object );
956 - }
564 + $fieldCont = null;
957 565
958 - afterFormSubmitted( object, response );
959 - },
960 - delay
961 - );
962 - } else if ( response.errors !== undefined && Object.keys( response.errors ).length ) {
963 - // errors were returned
964 - removeSubmitLoading( jQuery( object ), 'enable' );
566 + for ( key in response.errors ) {
567 + $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
965 568
966 - //show errors
967 - let contSubmit = true;
968 - removeAllErrors();
969 -
970 - let $fieldCont = null;
971 -
972 - for ( const key in response.errors ) {
973 - const fieldContEl = object.querySelector( `#frm_field_${ key }_container` );
974 - $fieldCont = fieldContEl ? jQuery( fieldContEl ) : jQuery();
975 -
976 - if ( $fieldCont.length ) {
977 - if ( ! $fieldCont.is( ':visible' ) ) { // eslint-disable-line no-jquery/no-is
978 - const inCollapsedSection = $fieldCont.closest( '.frm_toggle_container' ); // eslint-disable-line no-jquery/no-closest, formidable/no-jquery-variable-methods
979 - if ( inCollapsedSection.length ) {
980 - let frmTrigger = inCollapsedSection.prev();
981 - if ( ! frmTrigger.hasClass( 'frm_trigger' ) ) { // eslint-disable-line formidable/no-jquery-variable-methods
982 - // If the frmTrigger object is the section description, check to see if the previous element is the trigger
983 - frmTrigger = frmTrigger.prev( '.frm_trigger' );
569 + if ( $fieldCont.length ) {
570 + if ( ! $fieldCont.is( ':visible' ) ) {
571 + inCollapsedSection = $fieldCont.closest( '.frm_toggle_container' );
572 + if ( inCollapsedSection.length ) {
573 + frmTrigger = inCollapsedSection.prev();
574 + if ( ! frmTrigger.hasClass( 'frm_trigger' ) ) {
575 + // If the frmTrigger object is the section description, check to see if the previous element is the trigger
576 + frmTrigger = frmTrigger.prev( '.frm_trigger' );
577 + }
578 + frmTrigger.trigger( 'click' );
984 579 }
985 - frmTrigger.trigger( 'click' );
986 580 }
987 - }
988 581
989 - if ( $fieldCont.is( ':visible' ) ) { // eslint-disable-line no-jquery/no-is
990 - addFieldError( $fieldCont, key, response.errors );
991 - contSubmit = false;
582 + if ( $fieldCont.is( ':visible' ) ) {
583 + addFieldError( $fieldCont, key, response.errors );
584 + contSubmit = false;
585 + }
992 586 }
993 587 }
994 - }
995 588
996 - object.querySelectorAll( '.frm-g-recaptcha, .g-recaptcha, .h-captcha' ).forEach( function( captchaEl ) {
997 - const recaptchaID = captchaEl.dataset.rid;
589 + jQuery( object ).find( '.frm-g-recaptcha, .g-recaptcha' ).each( function() {
590 + var $recaptcha = jQuery( this ),
591 + recaptchaID = $recaptcha.data( 'rid' );
998 592
999 - if ( typeof grecaptcha !== 'undefined' && grecaptcha ) {
1000 - if ( recaptchaID ) {
1001 - grecaptcha.reset( recaptchaID );
1002 - } else {
1003 - grecaptcha.reset();
593 + if ( typeof grecaptcha !== 'undefined' && grecaptcha ) {
594 + if ( recaptchaID ) {
595 + grecaptcha.reset( recaptchaID );
596 + } else {
597 + grecaptcha.reset();
598 + }
1004 599 }
1005 - }
600 + });
1006 601
1007 - if ( typeof hcaptcha !== 'undefined' && hcaptcha ) {
1008 - hcaptcha.reset();
1009 - }
1010 - } );
602 + jQuery( document ).trigger( 'frmFormErrors', [ object, response ]);
1011 603
1012 - if ( window.turnstile ) {
1013 - object.querySelectorAll( '.frm-cf-turnstile' ).forEach(
1014 - turnstileField => turnstileField.dataset.rid && turnstile.reset( turnstileField.dataset.rid )
1015 - );
1016 - }
604 + fieldset.removeClass( 'frm_doing_ajax' );
605 + scrollToFirstField( object );
1017 606
1018 - jQuery( document ).trigger( 'frmFormErrors', [ object, response ] );
607 + if ( contSubmit ) {
608 + object.submit();
609 + } else {
610 + jQuery( object ).prepend( response.error_message );
611 + checkForErrorsAndMaybeSetFocus();
612 + }
613 + } else {
614 + // there may have been a plugin conflict, or the form is not set to submit with ajax
1019 615
1020 - fieldsets.forEach( field => field.classList.remove( 'frm_doing_ajax' ) );
1021 - scrollToFirstField( object );
616 + showFileLoading( object );
1022 617
1023 - if ( contSubmit ) {
1024 618 object.submit();
1025 - } else {
1026 - object.insertAdjacentHTML( 'afterbegin', response.error_message );
1027 - checkForErrorsAndMaybeSetFocus();
1028 619 }
1029 - } else if ( ! willRedirect ) { // Avoid double submission if redirecting to a page.
1030 - // There may have been a plugin conflict, or the form is not set to submit with ajax.
1031 -
1032 - showFileLoading( object );
1033 -
620 + },
621 + error: function() {
622 + jQuery( object ).find( 'input[type="submit"], input[type="button"]' ).prop( 'disabled', false );
1034 623 object.submit();
1035 624 }
1036 - };
1037 -
1038 - const error = function() {
1039 - object.querySelectorAll( 'input[type="submit"], input[type="button"]' ).forEach(
1040 - button => button.disabled = false
1041 - );
1042 - object.submit();
1043 - };
1044 -
1045 - postToAjaxUrl( object, data, success, error );
625 + });
1046 626 }
1047 627
1048 - function postToAjaxUrl( form, data, success, error ) {
1049 - let ajaxUrl = frm_js.ajax_url;
1050 - const action = form.getAttribute( 'action' );
1051 -
1052 - if ( 'string' === typeof action && action.includes( '?action=frm_forms_preview' ) ) {
1053 - ajaxUrl = action.split( '?action=frm_forms_preview' )[ 0 ];
1054 - }
1055 -
1056 - const ajaxParams = {
1057 - type: 'POST',
1058 - url: ajaxUrl,
1059 - data,
1060 - success
1061 - };
1062 -
1063 - if ( 'function' === typeof error ) {
1064 - ajaxParams.error = error;
1065 - }
1066 -
1067 - jQuery.ajax( ajaxParams ); // eslint-disable-line no-jquery/no-ajax
1068 - }
1069 -
1070 628 function afterFormSubmitted( object, response ) {
1071 - const tempDiv = document.createElement( 'div' );
1072 - tempDiv.innerHTML = response.content;
1073 - const formCompleted = tempDiv.querySelector( '.frm_message' );
1074 - if ( formCompleted ) {
1075 - jQuery( document ).trigger( 'frmFormComplete', [ object, response ] );
629 + var formCompleted = jQuery( response.content ).find( '.frm_message' );
630 + if ( formCompleted.length ) {
631 + jQuery( document ).trigger( 'frmFormComplete', [ object, response ]);
1076 632 } else {
1077 - jQuery( document ).trigger( 'frmPageChanged', [ object, response ] );
633 + jQuery( document ).trigger( 'frmPageChanged', [ object, response ]);
1078 634 }
1079 635 }
1080 636
1081 - /**
1082 - * Trigger an event before the form is replaced with a success message.
1083 - *
1084 - * @since 6.9
1085 - *
1086 - * @param {HTMLElement} object The form.
1087 - * @param {Object} response The response from submitting the form with AJAX.
1088 - * @return {void}
1089 - */
1090 - function afterFormSubmittedBeforeReplace( object, response ) {
1091 - const tempDiv = document.createElement( 'div' );
1092 - tempDiv.innerHTML = response.content;
1093 - const formCompleted = tempDiv.querySelector( '.frm_message' );
1094 - if ( formCompleted ) {
1095 - triggerCustomEvent( document, 'frmFormCompleteBeforeReplace', { object, response } );
1096 - }
1097 - }
1098 -
1099 637 function removeAddedScripts( formContainer, formID ) {
1100 - const endReplace = document.querySelectorAll( `.frm_end_ajax_${ formID }` );
638 + var endReplace = jQuery( '.frm_end_ajax_' + formID );
1101 639 if ( endReplace.length ) {
1102 - formContainer.nextUntil( `.frm_end_ajax_${ formID }` ).remove();
1103 - endReplace.forEach( el => el.remove() );
640 + formContainer.nextUntil( '.frm_end_ajax_' + formID ).remove();
641 + endReplace.remove();
1104 642 }
1105 643 }
1106 644
1107 645 function maybeSlideOut( oldContent, newContent ) {
1108 - let c;
1109 - let newClass = 'frm_slideout';
1110 - if ( newContent.includes( ' frm_slide' ) ) {
646 + var c,
647 + newClass = 'frm_slideout';
648 + if ( newContent.indexOf( ' frm_slide' ) !== -1 ) {
1111 649 c = oldContent.children();
1112 - if ( newContent.includes( ' frm_going_back' ) ) {
650 + if ( newContent.indexOf( ' frm_going_back' ) !== -1 ) {
1113 651 newClass += ' frm_going_back';
1114 652 }
1115 653 c.removeClass( 'frm_going_back' );
1116 654 c.addClass( newClass );
@@ -1119,28 +657,30 @@
1119 657 return 0;
1120 658 }
1121 659
1122 660 function addUrlParam( response ) {
1123 - let url;
1124 - if ( history.pushState && response.page !== undefined ) {
661 + var url;
662 + if ( history.pushState && typeof response.page !== 'undefined' ) {
1125 663 url = addQueryVar( 'frm_page', response.page );
1126 - window.history.pushState( { html: response.html }, '', `?${ url }` );
664 + window.history.pushState({ 'html': response.html }, '', '?' + url );
1127 665 }
1128 666 }
1129 667
1130 668 function addQueryVar( key, value ) {
669 + var kvp, i, x;
670 +
1131 671 key = encodeURI( key );
1132 672 value = encodeURI( value );
1133 673
1134 - const kvp = document.location.search.substr( 1 ).split( '&' );
674 + kvp = document.location.search.substr( 1 ).split( '&' );
1135 675
1136 - let i = kvp.length;
676 + i = kvp.length;
1137 677 while ( i-- ) {
1138 - const x = kvp[ i ].split( '=' );
678 + x = kvp[i].split( '=' );
1139 679
1140 - if ( x[ 0 ] == key ) {
1141 - x[ 1 ] = value;
1142 - kvp[ i ] = x.join( '=' );
680 + if ( x[0] == key ) {
681 + x[1] = value;
682 + kvp[i] = x.join( '=' );
1143 683 break;
1144 684 }
1145 685 }
1146 686
@@ -1151,159 +691,60 @@
1151 691 return kvp.join( '&' );
1152 692 }
1153 693
1154 694 function addFieldError( $fieldCont, key, jsErrors ) {
1155 - const container = $fieldCont instanceof jQuery ? $fieldCont.get( 0 ) : $fieldCont;
695 + var input, id, describedBy;
696 + if ( $fieldCont.length && $fieldCont.is( ':visible' ) ) {
697 + $fieldCont.addClass( 'frm_blank_field' );
698 + input = $fieldCont.find( 'input, select, textarea' );
699 + id = 'frm_error_field_' + key;
700 + describedBy = input.attr( 'aria-describedby' );
1156 701
1157 - if ( ! container || container.offsetParent === null ) {
1158 - return;
1159 - }
702 + if ( typeof frmThemeOverride_frmPlaceError === 'function' ) { // eslint-disable-line camelcase
703 + frmThemeOverride_frmPlaceError( key, jsErrors );
704 + } else {
705 + $fieldCont.append( '<div class="frm_error" id="' + id + '">' + jsErrors[key] + '</div>' );
1160 706
1161 - container.classList.add( 'frm_blank_field' );
1162 - const inputs = container.querySelectorAll( 'input, select, textarea' );
1163 - const id = getErrorElementId( key, inputs[ 0 ] );
1164 -
1165 - let describedBy;
1166 -
1167 - if ( typeof frmThemeOverride_frmPlaceError === 'function' ) { // eslint-disable-line camelcase
1168 - frmThemeOverride_frmPlaceError( key, jsErrors );
1169 - } else {
1170 - let errorHtml;
1171 - if ( jsErrors[ key ].includes( '<div' ) ) {
1172 - errorHtml = jsErrors[ key ];
1173 - } else {
1174 - const roleString = frm_js.include_alert_role ? 'role="alert"' : '';
1175 - errorHtml = `<div class="frm_error" ${ roleString } id="${ id }">${ jsErrors[ key ] }</div>`;
1176 - }
1177 - container.insertAdjacentHTML( 'beforeend', errorHtml );
1178 - inputs.forEach( input => {
1179 - describedBy = input.getAttribute( 'aria-describedby' );
1180 - if ( ! describedBy ) {
707 + if ( typeof describedBy === 'undefined' ) {
1181 708 describedBy = id;
1182 - } else if ( ! describedBy.includes( id ) && ! describedBy.includes( 'frm_error_field_' ) ) {
1183 - const { errorFirst } = input.dataset;
1184 - if ( errorFirst === '0' ) {
1185 - describedBy = `${ describedBy } ${ id }`;
1186 - } else {
1187 - describedBy = `${ id } ${ describedBy }`;
1188 - }
709 + } else if ( describedBy.indexOf( id ) === -1 ) {
710 + describedBy = describedBy + ' ' + id;
1189 711 }
1190 - input.setAttribute( 'aria-describedby', describedBy );
1191 - } );
1192 - }
1193 -
1194 - inputs.forEach( input => {
1195 - if ( [ 'radio', 'checkbox' ].includes( input.type ) ) {
1196 - const group = input.closest( '[role="radiogroup"], [role="group"]' );
1197 - if ( group ) {
1198 - group.setAttribute( 'aria-invalid', 'true' );
1199 - }
1200 - } else {
1201 - input.setAttribute( 'aria-invalid', 'true' );
712 + input.attr( 'aria-describedby', describedBy );
1202 713 }
1203 - } );
714 + input.attr( 'aria-invalid', true );
1204 715
1205 - jQuery( document ).trigger( 'frmAddFieldError', [ jQuery( container ), key, jsErrors ] );
1206 - }
1207 -
1208 - /**
1209 - * Get the ID to use for an error element added when submitting with AJAX.
1210 - *
1211 - * @param {string} key
1212 - * @param {HTMLElement} input
1213 - * @return {string} The ID to use for the error element.
1214 - */
1215 - function getErrorElementId( key, input ) {
1216 - if ( isNaN( key ) || ! input || ! input.id ) {
1217 - // If key isn't a number, assume it's already in the right format.
1218 - return `frm_error_field_${ key }`;
716 + jQuery( document ).trigger( 'frmAddFieldError', [ $fieldCont, key, jsErrors ]);
1219 717 }
1220 - return `frm_error_${ input.id }`;
1221 718 }
1222 719
1223 - /**
1224 - * Removes errors before validating with JS.
1225 - * This prevents issues with stale errors that has since been fixed.
1226 - *
1227 - * @param {HTMLElement|jQuery} fieldCont Field container element.
1228 - * @return {void}
1229 - */
1230 - function removeFieldError( fieldCont ) {
1231 - const container = fieldCont instanceof jQuery ? fieldCont.get( 0 ) : fieldCont;
1232 - if ( ! container ) {
1233 - return;
1234 - }
720 + function removeFieldError( $fieldCont ) {
721 + var errorMessage = $fieldCont.find( '.frm_error' ),
722 + errorId = errorMessage.attr( 'id' ),
723 + input = $fieldCont.find( 'input, select, textarea' ),
724 + describedBy = input.attr( 'aria-describedby' );
1235 725
1236 - const errorMessage = container.querySelector( '.frm_error' );
1237 - const input = container.querySelector( 'input, select, textarea' );
726 + $fieldCont.removeClass( 'frm_blank_field has-error' );
727 + errorMessage.remove();
728 + input.attr( 'aria-invalid', false );
1238 729
1239 - container.classList.remove( 'frm_blank_field', 'has-error' );
1240 -
1241 - if ( input ) {
1242 - if ( 'true' === input.getAttribute( 'aria-invalid' ) ) {
1243 - input.setAttribute( 'aria-invalid', 'false' );
1244 - } else if ( [ 'radio', 'checkbox' ].includes( input.type ) ) {
1245 - const group = input.closest( '[role="radiogroup"], [role="group"]' );
1246 - if ( group ) {
1247 - group.setAttribute( 'aria-invalid', 'false' );
1248 - }
1249 - }
730 + if ( typeof describedBy !== 'undefined' ) {
731 + describedBy = describedBy.replace( errorId, '' );
732 + input.attr( 'aria-describedby', describedBy );
1250 733 }
1251 -
1252 - if ( errorMessage ) {
1253 - removeElementFromInputDescribedBy( errorMessage );
1254 - errorMessage.remove();
1255 - }
1256 734 }
1257 735
1258 - /**
1259 - * Updates the aria-describedby attribute, removing the target element ID.
1260 - *
1261 - * @since 6.32
1262 - *
1263 - * @param {HTMLElement} el The target element that is removed from the aria-describedby data.
1264 - * @return {void}
1265 - */
1266 - function removeElementFromInputDescribedBy( el ) {
1267 - document.querySelectorAll( `[aria-describedby*="${ el.id }"]` ).forEach( input => {
1268 - let ariaDescribedBy = input.getAttribute( 'aria-describedby' ).split( ' ' );
1269 - ariaDescribedBy = ariaDescribedBy.filter( value => {
1270 - const trimmedValue = value.trim();
1271 - return trimmedValue && trimmedValue !== el.id;
1272 - } );
1273 -
1274 - if ( ariaDescribedBy.length ) {
1275 - input.setAttribute( 'aria-describedby', ariaDescribedBy.join( ' ' ) );
1276 - return;
1277 - }
1278 - input.removeAttribute( 'aria-describedby' );
1279 - } );
1280 - }
1281 -
1282 736 function removeAllErrors() {
1283 - document.querySelectorAll( '.form-field' ).forEach( field => {
1284 - field.classList.remove( 'frm_blank_field', 'has-error' );
1285 - } );
1286 - document.querySelectorAll( '.form-field .frm_error' ).forEach( el => {
1287 - removeElementFromInputDescribedBy( el );
1288 - el.remove();
1289 - } );
1290 - document.querySelectorAll( '.frm_error_style' ).forEach( error => error.remove() );
737 + jQuery( '.form-field' ).removeClass( 'frm_blank_field has-error' );
738 + jQuery( '.form-field .frm_error' ).replaceWith( '' );
739 + jQuery( '.frm_error_style' ).remove();
1291 740 }
1292 741
1293 - /**
1294 - * @param {HTMLElement|Object} object Form object.
1295 - * @return {void}
1296 - */
1297 742 function scrollToFirstField( object ) {
1298 - if ( 'function' === typeof object.get ) {
1299 - // Get the HTMLElement from a jQuery object.
1300 - object = object.get( 0 );
743 + var field = jQuery( object ).find( '.frm_blank_field' ).first();
744 + if ( field.length ) {
745 + frmFrontForm.scrollMsg( field, object, true );
1301 746 }
1302 - const field = object.querySelector( '.frm_blank_field' );
1303 - if ( field ) {
1304 - frmFrontForm.scrollMsg( jQuery( field ), object, true );
1305 - }
1306 747 }
1307 748
1308 749 function showSubmitLoading( $object ) {
1309 750 showLoadingIndicator( $object );
@@ -1311,9 +752,9 @@
1311 752 disableSaveDraft( $object );
1312 753 }
1313 754
1314 755 function showLoadingIndicator( $object ) {
1315 - if ( ! $object.hasClass( 'frm_loading_form' ) && ! $object.hasClass( 'frm_loading_prev' ) ) { // eslint-disable-line no-jquery/no-class, formidable/no-jquery-variable-methods
756 + if ( ! $object.hasClass( 'frm_loading_form' ) && ! $object.hasClass( 'frm_loading_prev' ) ) {
1316 757 addLoadingClass( $object );
1317 758 $object.trigger( 'frmStartFormLoading' );
1318 759 }
1319 760 }
@@ -1318,889 +759,316 @@
1318 759 }
1319 760 }
1320 761
1321 762 function addLoadingClass( $object ) {
1322 - const loadingClass = isGoingToPrevPage( $object ) ? 'frm_loading_prev' : 'frm_loading_form';
763 + var loadingClass = isGoingToPrevPage( $object ) ? 'frm_loading_prev' : 'frm_loading_form';
1323 764
1324 - $object.addClass( loadingClass ); // eslint-disable-line no-jquery/no-class, formidable/no-jquery-variable-methods
765 + $object.addClass( loadingClass );
1325 766 }
1326 767
1327 768 function isGoingToPrevPage( $object ) {
1328 - return typeof frmProForm !== 'undefined' && frmProForm.goingToPreviousPage( $object );
769 + return ( typeof frmProForm !== 'undefined' && frmProForm.goingToPreviousPage( $object ) );
1329 770 }
1330 771
1331 - function removeSubmitLoading( _, enable, processesRunning ) {
772 + function removeSubmitLoading( $object, enable, processesRunning ) {
773 + var loadingForm;
774 +
1332 775 if ( processesRunning > 0 ) {
1333 776 return;
1334 777 }
1335 778
1336 - document.querySelectorAll( '.frm_loading_form' ).forEach( function( form ) {
1337 - form.classList.remove( 'frm_loading_form', 'frm_loading_prev' );
1338 - jQuery( form ).trigger( 'frmEndFormLoading' );
779 + loadingForm = jQuery( '.frm_loading_form' );
780 + loadingForm.removeClass( 'frm_loading_form' );
781 + loadingForm.removeClass( 'frm_loading_prev' );
1339 782
1340 - if ( enable === 'enable' ) {
1341 - enableSubmitButton( form );
1342 - enableSaveDraft( form );
1343 - }
1344 - } );
783 + loadingForm.trigger( 'frmEndFormLoading' );
784 +
785 + if ( enable === 'enable' ) {
786 + enableSubmitButton( loadingForm );
787 + enableSaveDraft( loadingForm );
788 + }
1345 789 }
1346 790
1347 791 function showFileLoading( object ) {
1348 - const loading = document.getElementById( 'frm_loading' );
1349 - if ( ! loading ) {
1350 - return;
792 + var fileval,
793 + loading = document.getElementById( 'frm_loading' );
794 + if ( loading !== null ) {
795 + fileval = jQuery( object ).find( 'input[type=file]' ).val();
796 + if ( typeof fileval !== 'undefined' && fileval !== '' ) {
797 + setTimeout( function() {
798 + jQuery( loading ).fadeIn( 'slow' );
799 + }, 2000 );
800 + }
1351 801 }
1352 -
1353 - const fileInput = object.querySelector( 'input[type=file]' );
1354 - const fileval = fileInput ? fileInput.value : '';
1355 - if ( fileval !== '' ) {
1356 - setTimeout( function() {
1357 - jQuery( loading ).fadeIn( 'slow' ); // eslint-disable-line no-jquery/no-fade
1358 - }, 2000 );
1359 - }
1360 802 }
1361 803
1362 - /**********************************************
1363 - * General Helpers
1364 - *********************************************/
1365 -
1366 - function confirmClick() {
804 + function clearDefault() {
1367 805 /*jshint validthis:true */
1368 - const message = this.dataset.frmconfirm;
1369 - return confirm( message );
806 + toggleDefault( jQuery( this ), 'clear' );
1370 807 }
1371 808
1372 - /**
1373 - * Check for -webkit-box-shadow css value for input:-webkit-autofill selector.
1374 - * If this is a match, the User is autofilling the input on a Webkit browser.
1375 - * We want to delete the Honeypot field, otherwise it will get triggered as spam on autocomplete.
1376 - */
1377 - function onHoneypotFieldChange() {
809 + function replaceDefault() {
1378 810 /*jshint validthis:true */
1379 - const css = window.getComputedStyle( this ).boxShadow;
1380 - if ( css?.match( /inset/ ) ) {
1381 - this.remove();
1382 - }
811 + toggleDefault( jQuery( this ), 'replace' );
1383 812 }
1384 813
1385 - /**
1386 - * Focus on the first sub field when clicking to the primary label of combo field.
1387 - *
1388 - * @since 4.10.02
1389 - */
1390 - function changeFocusWhenClickComboFieldLabel() {
1391 - let label;
814 + function toggleDefault( $thisField, e ) {
815 + // TODO: Fix this for a default value that is a number or array
816 + var thisVal,
817 + v = $thisField.data( 'frmval' ).replace( /(\n|\r\n)/g, '\r' );
818 + if ( v === '' || typeof v === 'undefined' ) {
819 + return false;
820 + }
821 + thisVal = $thisField.val().replace( /(\n|\r\n)/g, '\r' );
1392 822
1393 - const comboInputsContainer = document.querySelectorAll( '.frm_combo_inputs_container' );
1394 - comboInputsContainer.forEach( function( inputsContainer ) {
1395 - if ( ! inputsContainer.closest( '.frm_form_field' ) ) {
1396 - return;
823 + if ( 'replace' === e ) {
824 + if ( thisVal === '' ) {
825 + $thisField.addClass( 'frm_default' ).val( v );
1397 826 }
1398 -
1399 - label = inputsContainer.closest( '.frm_form_field' ).querySelector( '.frm_primary_label' );
1400 - if ( ! label ) {
1401 - return;
1402 - }
1403 -
1404 - label.addEventListener( 'click', function() {
1405 - inputsContainer.querySelector( '.frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea' ).focus();
1406 - } );
1407 - } );
1408 - }
1409 -
1410 - /**
1411 - * Sets focus on a the first subfield of a combo field that has an error.
1412 - *
1413 - * @since 6.16.3
1414 - *
1415 - * @param {HTMLElement} element
1416 - * @return {boolean} True if the focus was set on a combo field.
1417 - */
1418 - function maybeFocusOnComboSubField( element ) {
1419 - if ( 'FIELDSET' !== element.nodeName ) {
1420 - return false;
827 + } else if ( thisVal == v ) {
828 + $thisField.removeClass( 'frm_default' ).val( '' );
1421 829 }
1422 - if ( ! element.querySelector( '.frm_combo_inputs_container' ) ) {
1423 - return false;
1424 - }
1425 - const comboSubfield = element.querySelector( '[aria-invalid="true"]' );
1426 - if ( comboSubfield ) {
1427 - focusInput( comboSubfield );
1428 - return true;
1429 - }
1430 - return false;
1431 830 }
1432 831
1433 - function checkForErrorsAndMaybeSetFocus() {
1434 - if ( ! frm_js.focus_first_error ) {
1435 - return;
832 + function resendEmail() {
833 + /*jshint validthis:true */
834 + var $link = jQuery( this ),
835 + entryId = this.getAttribute( 'data-eid' ),
836 + formId = this.getAttribute( 'data-fid' ),
837 + label = $link.find( '.frm_link_label' );
838 + if ( label.length < 1 ) {
839 + label = $link;
1436 840 }
841 + label.append( '<span class="frm-wait"></span>' );
1437 842
1438 - const errors = document.querySelectorAll( '.frm_form_field .frm_error' );
1439 - if ( ! errors.length ) {
1440 - return;
1441 - }
1442 -
1443 - let element = errors[ 0 ];
1444 - let timeoutCallback;
1445 - do {
1446 - element = element.previousSibling;
1447 - if ( [ 'input', 'select', 'textarea' ].includes( element.nodeName.toLowerCase() ) ) {
1448 - focusInput( element );
1449 - break;
843 + jQuery.ajax({
844 + type: 'POST',
845 + url: frm_js.ajax_url,
846 + data: {
847 + action: 'frm_entries_send_email',
848 + entry_id: entryId,
849 + form_id: formId,
850 + nonce: frm_js.nonce
851 + },
852 + success: function( msg ) {
853 + var admin = document.getElementById( 'wpbody' );
854 + if ( admin === null ) {
855 + label.html( msg );
856 + } else {
857 + label.html( '' );
858 + $link.after( msg );
859 + }
1450 860 }
861 + });
862 + return false;
863 + }
1451 864
1452 - if ( maybeFocusOnComboSubField( element ) ) {
1453 - break;
1454 - }
865 + /**********************************************
866 + * General Helpers
867 + *********************************************/
1455 868
1456 - if ( element.classList !== undefined ) {
1457 - if ( element.classList.contains( 'html-active' ) ) {
1458 - timeoutCallback = function() {
1459 - const textarea = element.querySelector( 'textarea' );
1460 - if ( null !== textarea ) {
1461 - textarea.focus();
1462 - }
1463 - };
1464 - } else if ( element.classList.contains( 'tmce-active' ) ) {
1465 - timeoutCallback = function() {
1466 - tinyMCE.activeEditor.focus();
1467 - };
1468 - } else if ( element.classList.contains( 'frm_opt_container' ) ) {
1469 - const firstInput = element.querySelector( 'input' );
1470 - if ( firstInput ) {
1471 - focusInput( firstInput );
1472 - break;
1473 - }
1474 - }
1475 -
1476 - if ( 'function' === typeof timeoutCallback ) {
1477 - setTimeout( timeoutCallback, 0 );
1478 - break;
1479 - }
1480 - }
1481 - } while ( element.previousSibling );
869 + function confirmClick() {
870 + /*jshint validthis:true */
871 + var message = jQuery( this ).data( 'frmconfirm' );
872 + return confirm( message );
1482 873 }
1483 874
1484 - /**
1485 - * Focus a visible input, or possibly delay the focus event until the form has faded in.
1486 - *
1487 - * @since 6.16.3
1488 - *
1489 - * @param {HTMLElement} input
1490 - * @return {void}
1491 - */
1492 - function focusInput( input ) {
1493 - if ( input.offsetParent !== null ) {
1494 - input.focus();
875 + function toggleDiv() {
876 + /*jshint validthis:true */
877 + var div = jQuery( this ).data( 'frmtoggle' );
878 + if ( jQuery( div ).is( ':visible' ) ) {
879 + jQuery( div ).slideUp( 'fast' );
1495 880 } else {
1496 - triggerCustomEvent( document, 'frmMaybeDelayFocus', { input } );
881 + jQuery( div ).slideDown( 'fast' );
1497 882 }
883 + return false;
1498 884 }
1499 885
1500 - /**
1501 - * Does the same as jQuery( document ).on( 'event', 'selector', handler ).
1502 - *
1503 - * @since 5.4
1504 - *
1505 - * @param {string} event Event name.
1506 - * @param {string} selector Selector.
1507 - * @param {Function} handler Handler.
1508 - * @param {boolean | Object} options Options to be added to `addEventListener()` method. Default is `false`.
1509 - */
1510 - function documentOn( event, selector, handler, options ) {
1511 - if ( options === undefined ) {
1512 - options = false;
1513 - }
886 + /**********************************************
887 + * Fallback functions
888 + *********************************************/
1514 889
1515 - document.addEventListener( event, function( e ) {
1516 - let target;
890 + function addIndexOfFallbackForIE8() {
891 + var len, from;
1517 892
1518 - // loop parent nodes from the target to the delegation node.
1519 - for ( target = e.target; target && target != this; target = target.parentNode ) {
1520 - if ( target.matches && target.matches( selector ) ) {
1521 - handler.call( target, e );
1522 - break;
893 + if ( ! Array.prototype.indexOf ) {
894 + Array.prototype.indexOf = function( elt /*, from*/ ) {
895 + len = this.length >>> 0;
896 +
897 + from = Number( arguments[1]) || 0;
898 + from = ( from < 0 ) ? Math.ceil( from ) : Math.floor( from );
899 + if ( from < 0 ) {
900 + from += len;
1523 901 }
1524 - }
1525 - }, options );
1526 - }
1527 902
1528 - function initFloatingLabels() {
1529 - const selector = '.frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea';
1530 - const floatClass = 'frm_label_float_top';
1531 -
1532 - const checkFloatLabel = function( input ) {
1533 - const container = input.closest( '.frm_inside_container' );
1534 - if ( ! container ) {
1535 - return;
1536 - }
1537 -
1538 - const shouldFloatTop = input.value || document.activeElement === input;
1539 -
1540 - container.classList.toggle( floatClass, shouldFloatTop );
1541 -
1542 - if ( 'SELECT' === input.tagName ) {
1543 - const firstOpt = input.querySelector( 'option:first-child' );
1544 -
1545 - if ( shouldFloatTop ) {
1546 - if ( firstOpt.hasAttribute( 'data-label' ) ) {
1547 - firstOpt.textContent = firstOpt.getAttribute( 'data-label' );
1548 - firstOpt.removeAttribute( 'data-label' );
903 + for ( ; from < len; from++ ) {
904 + if ( from in this && this[from] === elt ) {
905 + return from;
1549 906 }
1550 - } else if ( firstOpt.textContent ) {
1551 - firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1552 - firstOpt.textContent = '';
1553 907 }
1554 - }
1555 - };
1556 -
1557 - const checkDropdownLabel = function() {
1558 - document.querySelectorAll( `.frm-show-form .frm_inside_container:not(.${ floatClass }) select` ).forEach( function( input ) {
1559 - const firstOpt = input.querySelector( 'option:first-child' );
1560 -
1561 - if ( firstOpt.textContent ) {
1562 - firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1563 - firstOpt.textContent = '';
1564 - }
1565 - } );
1566 - };
1567 -
1568 - [ 'focus', 'blur', 'change' ].forEach( function( eventName ) {
1569 - documentOn(
1570 - eventName,
1571 - selector,
1572 - function( event ) {
1573 - checkFloatLabel( event.target );
1574 - },
1575 - true
1576 - );
1577 - } );
1578 -
1579 - const runOnLoad = function( firstLoad ) {
1580 - if ( firstLoad && document.activeElement && [ 'INPUT', 'SELECT', 'TEXTAREA' ].includes( document.activeElement.tagName ) ) {
1581 - checkFloatLabel( document.activeElement );
1582 - } else if ( firstLoad ) {
1583 - document.querySelectorAll( '.frm_inside_container' ).forEach(
1584 - function( container ) {
1585 - const input = container.querySelector( 'input, select, textarea' );
1586 - if ( input && '' !== input.value ) {
1587 - checkFloatLabel( input );
1588 - }
1589 - }
1590 - );
1591 - }
1592 -
1593 - checkDropdownLabel();
1594 - calcProductsTotal();
1595 - };
1596 -
1597 - runOnLoad( true );
1598 -
1599 - jQuery( document ).on( 'frmPageChanged', function( event ) {
1600 - runOnLoad();
1601 - } );
1602 -
1603 - document.addEventListener( 'frm_after_start_over', function( event ) {
1604 - runOnLoad();
1605 - } );
1606 - }
1607 -
1608 - function shouldUpdateValidityMessage( target ) {
1609 - if ( 'INPUT' !== target.nodeName ) {
1610 - return false;
908 + return -1;
909 + };
1611 910 }
1612 -
1613 - if ( ! target.dataset.invmsg ) {
1614 - return false;
1615 - }
1616 -
1617 - if ( 'text' !== target.getAttribute( 'type' ) ) {
1618 - return false;
1619 - }
1620 -
1621 - if ( target.classList.contains( 'frm_verify' ) ) {
1622 - return false;
1623 - }
1624 -
1625 - return true;
1626 911 }
1627 912
1628 - function maybeClearCustomValidityMessage( event, field ) {
1629 - let key;
1630 - let isInvalid = false;
1631 -
1632 - if ( ! shouldUpdateValidityMessage( field ) ) {
1633 - return;
913 + function addTrimFallbackForIE8() {
914 + if ( typeof String.prototype.trim !== 'function' ) {
915 + String.prototype.trim = function() {
916 + return this.replace( /^\s+|\s+$/g, '' );
917 + };
1634 918 }
1635 -
1636 - for ( key in field.validity ) {
1637 - if ( 'customError' === key ) {
1638 - continue;
1639 - }
1640 - if ( 'valid' !== key && field.validity[ key ] === true ) {
1641 - isInvalid = true;
1642 - break;
1643 - }
1644 - }
1645 -
1646 - if ( ! isInvalid ) {
1647 - field.setCustomValidity( '' );
1648 - }
1649 919 }
1650 920
1651 - function maybeShowNewTabFallbackMessage() {
1652 - if ( ! window.frmShowNewTabFallback ) {
1653 - return;
1654 - }
921 + function addFilterFallbackForIE8() {
922 + var t, len, res, thisp, i, val;
1655 923
1656 - const messageEl = document.querySelector( `#frm_form_${ frmShowNewTabFallback.formId }_container .frm_message` );
1657 - if ( ! messageEl ) {
1658 - return;
1659 - }
924 + if ( ! Array.prototype.filter ) {
1660 925
1661 - messageEl.insertAdjacentHTML( 'beforeend', ` ${ frmShowNewTabFallback.message }` );
1662 - }
926 + Array.prototype.filter = function( fun /*, thisp */ ) {
1663 927
1664 - function setCustomValidityMessage() {
1665 - const forms = document.getElementsByClassName( 'frm-show-form' );
1666 - const { length } = forms;
928 + if ( this === void 0 || this === null ) {
929 + throw new TypeError();
930 + }
1667 931
1668 - for ( let index = 0; index < length; ++index ) {
1669 - forms[ index ].addEventListener(
1670 - 'invalid',
1671 - function( event ) {
1672 - const { target } = event;
932 + t = Object( this );
933 + len = t.length >>> 0;
934 + if ( typeof fun !== 'function' ) {
935 + throw new TypeError();
936 + }
1673 937
1674 - if ( shouldUpdateValidityMessage( target ) ) {
1675 - target.setCustomValidity( target.dataset.invmsg );
938 + res = [];
939 + thisp = arguments[1];
940 + for ( i = 0; i < len; i++ ) {
941 + if ( i in t ) {
942 + val = t[i]; // in case fun mutates this
943 + if ( fun.call( thisp, val, i, t ) ) {
944 + res.push( val );
945 + }
1676 946 }
1677 - },
1678 - true
1679 - );
1680 - }
1681 - }
947 + }
1682 948
1683 - function enableSubmitButtonOnBackButtonPress() {
1684 - window.addEventListener( 'pageshow', function( event ) {
1685 - if ( event.persisted ) {
1686 - document.querySelectorAll( '.frm_loading_form' ).forEach(
1687 - function( form ) {
1688 - enableSubmitButton( form );
1689 - }
1690 - );
1691 - removeSubmitLoading();
1692 - }
1693 - } );
1694 - }
1695 -
1696 - /**
1697 - * Destroys the formidable generated global hcaptcha object since it wouldn't otherwise render.
1698 - */
1699 - function destroyhCaptcha() {
1700 - if ( ! window.hasOwnProperty( 'hcaptcha' ) || ! document.querySelector( '.frm-show-form .h-captcha' ) ) {
1701 - return;
949 + return res;
950 + };
1702 951 }
1703 - window.hcaptcha = null;
1704 952 }
1705 953
1706 - /**
1707 - * @since 6.16.3
1708 - *
1709 - * @return {string} Unique key, used for duplicate checks.
1710 - */
1711 - function getUniqueKey() {
1712 - const uniqueKey = Array.from( window.crypto.getRandomValues( new Uint8Array( 8 ) ) )
1713 - .map( b => b.toString( 16 ).padStart( 2, '0' ) )
1714 - .join( '' );
1715 - const timestamp = Date.now().toString( 16 );
1716 - return `${ uniqueKey }-${ timestamp }`;
1717 - }
954 + function addKeysFallbackForIE8() {
955 + var keys, i;
1718 956
1719 - /**
1720 - * Animates the scroll position of the document.
1721 - *
1722 - * @since 6.20
1723 - *
1724 - * @param {number} start
1725 - * @param {number} end
1726 - * @param {number} duration
1727 - * @return {void}
1728 - */
1729 - function animateScroll( start, end, duration ) {
1730 - if ( ! window.hasOwnProperty( 'performance' ) || ! window.hasOwnProperty( 'requestAnimationFrame' ) ) {
1731 - document.documentElement.scrollTop = end;
1732 - return;
1733 - }
957 + if ( ! Object.keys ) {
958 + Object.keys = function( obj ) {
959 + keys = [];
1734 960
1735 - const startTime = performance.now();
1736 - const step = currentTime => {
1737 - const progress = Math.min( ( currentTime - startTime ) / duration, 1 );
1738 - document.documentElement.scrollTop = start + ( ( end - start ) * progress );
1739 - if ( progress < 1 ) {
1740 - requestAnimationFrame( step );
1741 - }
1742 - };
1743 - requestAnimationFrame( step );
1744 - }
961 + for ( i in obj ) {
962 + if ( obj.hasOwnProperty( i ) ) {
963 + keys.push( i );
964 + }
965 + }
1745 966
1746 - /**
1747 - * Make sure that the captcha label for a reCAPTCHA or Turnstile field matches the response input ID.
1748 - * This is determined dynamically, so we check for the ID after the input is rendered.
1749 - * hCaptcha is handled separately, in the frmCaptcha function as it is not rendered explicitly.
1750 - *
1751 - * @since 6.25.1
1752 - *
1753 - * @param {HTMLElement} captcha
1754 - * @return {void}
1755 - */
1756 - function maybeFixCaptchaLabel( captcha ) {
1757 - const form = captcha.closest( 'form' );
1758 - if ( ! form ) {
1759 - return;
967 + return keys;
968 + };
1760 969 }
1761 -
1762 - const label = form.querySelector( 'label[for="g-recaptcha-response"], label[for="cf-turnstile-response"]' );
1763 - const captchaResponse = form.querySelector( '[name="g-recaptcha-response"], [name="cf-turnstile-response"]' );
1764 -
1765 - if ( label && captchaResponse ) {
1766 - label.htmlFor = captchaResponse.id;
1767 - }
1768 970 }
1769 971
1770 972 /**
1771 - * Check to make sure the quantity field value is within the min and max values.
1772 - *
1773 - * @param {HTMLElement} input
1774 - * @return {number} The quantity value.
973 + * Check for -webkit-box-shadow css value for input:-webkit-autofill selector.
974 + * If this is a match, the User is autofilling the input on a Webkit browser.
975 + * We want to delete the Honeypot field, otherwise it will get triggered as spam on autocomplete.
1775 976 */
1776 - function checkQuantityFieldMinMax( input ) {
1777 - if ( '' === input.value ) {
1778 - // Leave the value if it is empty.
1779 - return 0;
977 + function onHoneypotFieldChange() {
978 + var css = jQuery( this ).css( 'box-shadow' );
979 + if ( css.match( /inset/ ) ) {
980 + this.parentNode.removeChild( this );
1780 981 }
1781 -
1782 - const val = parseFloat( input.value ? input.value.trim() : 0 );
1783 - if ( isNaN( val ) ) {
1784 - return 0;
1785 - }
1786 -
1787 - let max = input.hasAttribute( 'max' ) ? parseFloat( input.getAttribute( 'max' ) ) : 0;
1788 - let min = input.hasAttribute( 'min' ) ? parseFloat( input.getAttribute( 'min' ) ) : 0;
1789 -
1790 - max = isNaN( max ) ? 0 : max;
1791 - min = isNaN( min ) ? 0 : Math.max( 0, min );
1792 -
1793 - if ( val < min ) {
1794 - input.value = min;
1795 - return min;
1796 - }
1797 -
1798 - if ( 0 !== max && val > max ) {
1799 - input.value = max;
1800 - return max;
1801 - }
1802 -
1803 - return val;
1804 982 }
1805 983
1806 - function triggerChange( input, fieldKey ) {
1807 - if ( fieldKey === undefined ) {
1808 - fieldKey = 'dependent';
1809 - }
1810 -
1811 - jQuery( input ).trigger( { type: 'change', selfTriggered: true, frmTriggered: fieldKey } );
1812 - }
1813 -
1814 984 /**
1815 - * Calculates the total price.
985 + * Focus on the first sub field when clicking to the primary label of combo field.
1816 986 *
1817 - * @param {Event|undefined} e The event object.
1818 - * @return {void}
987 + * @since 4.10.02
1819 988 */
1820 - function calcProductsTotal( e ) {
1821 - if ( 'object' === typeof frmProForm ) {
1822 - // Pro is installed, use the Pro JS.
1823 - return;
1824 - }
989 + function changeFocusWhenClickComboFieldLabel() {
990 + let label;
1825 991
1826 - if ( typeof __FRMCURR === 'undefined' ) {
1827 - return;
1828 - }
1829 -
1830 - const totalFields = document.querySelectorAll( '[data-frmtotal]' );
1831 - if ( ! totalFields.length ) {
1832 - return;
1833 - }
1834 -
1835 - const formTotals = [];
1836 -
1837 - totalFields.forEach( totalField => {
1838 - let total = 0;
1839 - const form = totalField.closest( 'form' );
1840 -
1841 - if ( ! form ) {
992 + const comboInputsContainer = document.querySelectorAll( '.frm_combo_inputs_container' );
993 + comboInputsContainer.forEach( function( inputsContainer ) {
994 + if ( ! inputsContainer.closest( '.frm_form_field' ) ) {
1842 995 return;
1843 996 }
1844 997
1845 - const formId = form.querySelector( 'input[name="form_id"]' ).value;
1846 - const currency = getCurrency( formId );
1847 -
1848 - if ( undefined !== formTotals[ formId ] ) {
1849 - total = formTotals[ formId ];
1850 - } else {
1851 - form.querySelectorAll( 'input[data-frmprice],select:has([data-frmprice])' ).forEach( function( input ) {
1852 - let quantity = 0;
1853 - let price = 0;
1854 - const isSingle = 'hidden' === input.type;
1855 -
1856 - if ( input.tagName === 'SELECT' ) {
1857 - if ( input.selectedIndex !== -1 ) {
1858 - price = input.options[ input.selectedIndex ].getAttribute( 'data-frmprice' );
1859 - }
1860 - } else {
1861 - if ( ! isSingle && ! input.matches( ':checked' ) ) {
1862 - return;
1863 - }
1864 - price = input.getAttribute( 'data-frmprice' );
1865 - }
1866 -
1867 - if ( ! price ) {
1868 - price = 0;
1869 - } else {
1870 - price = preparePrice( price, currency );
1871 - quantity = getQuantity( input );
1872 - price = parseFloat( quantity ) * parseFloat( price );
1873 - }
1874 -
1875 - if ( 'true' === input.getAttribute( 'data-frmdiscount' ) ) {
1876 - price = price * -1;
1877 - }
1878 -
1879 - total += price;
1880 - } );
1881 -
1882 - formTotals[ formId ] = total;
1883 - }
1884 -
1885 - total = isNaN( total ) ? 0 : total;
1886 -
1887 - // Set a decimal separator for currency if no default for it
1888 - currency.decimal_separator = currency.decimal_separator.trim(); // first remove unnecessary space(s)
1889 - if ( ! currency.decimal_separator.length ) {
1890 - currency.decimal_separator = '.';
1891 - }
1892 -
1893 - totalField.value = roundTotal( total, currency );
1894 - total = normalizeTotal( total, currency );
1895 -
1896 - // because of e.g. fields that might be using this field for calculations
1897 - triggerChange( totalField );
1898 -
1899 - total = formatCurrency( total, currency );
1900 - const formatted = totalField.previousElementSibling;
1901 - if ( formatted?.matches( '.frm_total_formatted' ) ) {
1902 - // Use innerHTML so that currency symbols like Euros can render and not their encoded string value.
1903 - formatted.innerHTML = total;
998 + label = inputsContainer.closest( '.frm_form_field' ).querySelector( '.frm_primary_label' );
999 + if ( ! label ) {
1904 1000 return;
1905 1001 }
1906 1002
1907 - const formattedEls = totalField.closest( '.frm_form_field' ).querySelectorAll( '.frm_total_formatted' );
1908 - formattedEls.forEach( formattedEl => {
1909 - // Use innerHTML so that currency symbols like Euros can render and not their encoded string value.
1910 - formattedEl.innerHTML = total;
1911 - } );
1912 - } );
1003 + label.addEventListener( 'click', function( e ) {
1004 + inputsContainer.querySelector( '.frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea' ).focus();
1005 + });
1006 + });
1913 1007 }
1914 1008
1915 - /**
1916 - * Round total and maybe add trailing zeros so formatCurrency has a proper format to work with.
1917 - *
1918 - * @param {number} total The total amount to normalize.
1919 - * @param {Object} currency The currency object containing decimal information.
1920 - * @return {string} The normalized total amount.
1921 - */
1922 - function normalizeTotal( total, currency ) {
1923 - const isLargeTotal = total > Number.MAX_SAFE_INTEGER;
1009 + function checkForErrorsAndMaybeSetFocus() {
1010 + var errors, element, timeoutCallback;
1924 1011
1925 - total = roundTotal( total, currency );
1926 -
1927 - return maybeAddTrailingZeroToPrice( total, currency, isLargeTotal );
1928 - }
1929 -
1930 - function roundTotal( total, currency ) {
1931 - const isLargeTotal = total > Number.MAX_SAFE_INTEGER;
1932 -
1933 - if ( ! isLargeTotal ) {
1934 - const { decimals } = currency;
1935 - total = decimals > 0 ? round10( total, decimals ) : Math.ceil( total );
1012 + errors = document.querySelectorAll( '.frm_form_field .frm_error' );
1013 + if ( ! errors.length ) {
1014 + return;
1936 1015 }
1937 1016
1938 - return total;
1939 - }
1940 -
1941 - function round10( value, decimals ) {
1942 - return Number( `${ Math.round( `${ value }e${ decimals }` ) }e-${ decimals }` );
1943 - }
1944 -
1945 - /**
1946 - * Format a numeric value according to the specified currency format settings.
1947 - *
1948 - * @param {string} total The numeric value to format.
1949 - * @param {Object} currency The currency formatting configuration.
1950 - * @return {string} The formatted currency string.
1951 - */
1952 - function formatCurrency( total, currency ) {
1953 - total = maybeAddTrailingZeroToPrice( total, currency );
1954 - if ( total.length && ( total[ total.length - 1 ] === '.' || total[ total.length - 1 ] === currency.decimal_separator ) ) {
1955 - total = total.substr( 0, total.length - 1 );
1956 - }
1957 -
1958 - total = maybeRemoveTrailingZerosFromPrice( total, currency );
1959 - total = addThousands( total, currency );
1960 -
1961 - const leftSymbol = currency.symbol_left ? ( currency.symbol_left + currency.symbol_padding ) : '';
1962 - const rightSymbol = currency.symbol_right ? ( currency.symbol_padding + currency.symbol_right ) : '';
1963 -
1964 - return `${ leftSymbol }${ total }${ rightSymbol }`;
1965 - }
1966 -
1967 - /**
1968 - * Gets currency from form id.
1969 - *
1970 - * @param {number} formId Form ID.
1971 - * @return {Object} Currency object.
1972 - */
1973 - function getCurrency( formId ) {
1974 - if ( undefined !== window.__FRMCURR && undefined !== window.__FRMCURR[ formId ] ) {
1975 - return window.__FRMCURR[ formId ];
1976 - }
1977 -
1978 - return {
1979 - symbol_left: '$',
1980 - symbol_right: '',
1981 - symbol_padding: '',
1982 - thousand_separator: ',',
1983 - decimal_separator: '.',
1984 - decimals: 2,
1985 - };
1986 - }
1987 -
1988 - /**
1989 - * Gets quantity.
1990 - *
1991 - * @param {HTMLElement} field The field element.
1992 - * @return {number} The quantity.
1993 - */
1994 - function getQuantity( field ) {
1995 - const fieldID = frmFrontForm.getFieldId( field, false );
1996 - if ( ! fieldID ) {
1997 - return 0;
1998 - }
1999 -
2000 - const quantityField = getQuantityField( field, fieldID );
2001 - if ( ! quantityField ) {
2002 - // If there is no quantity field, assume 1.
2003 - return 1;
2004 - }
2005 -
2006 - return checkQuantityFieldMinMax( quantityField );
2007 - }
2008 -
2009 - /**
2010 - * Gets quantity field.
2011 - *
2012 - * @param {HTMLElement} element The element.
2013 - * @param {number} fieldID The field ID.
2014 - * @return {HTMLElement|null} The quantity field.
2015 - */
2016 - function getQuantityField( element, fieldID ) {
2017 - const quantityFields = element.closest( 'form' ).querySelectorAll( '[data-frmproduct]' );
2018 - if ( ! quantityFields.length ) {
2019 - return null;
2020 - }
2021 -
2022 - fieldID = fieldID.toString();
2023 -
2024 - return Array.from( quantityFields ).find( element => {
2025 - let ids;
2026 -
2027 - ids = JSON.parse( element.getAttribute( 'data-frmproduct' ).trim() );
2028 - if ( '' === ids ) {
2029 - return false;
1017 + element = errors[0];
1018 + do {
1019 + element = element.previousSibling;
1020 + if ( -1 !== [ 'input', 'select', 'textarea' ].indexOf( element.nodeName.toLowerCase() ) ) {
1021 + element.focus();
1022 + break;
2030 1023 }
2031 1024
2032 - // Convert to array if necessary because of existing fields that are already using single product fields.
2033 - ids = 'string' === typeof ids ? [ ids ] : ids;
2034 - return ids.includes( fieldID );
2035 - } );
2036 - }
2037 -
2038 - /**
2039 - * Prepare a price for calculation.
2040 - *
2041 - * @param {number|string} price The price to prepare.
2042 - * @param {Object} currency The currency object containing decimal information.
2043 - * @return {string} The prepared price.
2044 - */
2045 - function preparePrice( price, currency ) {
2046 - if ( ! price ) {
2047 - return 0;
2048 - }
2049 - price = `${ price }`; // convert to string just to be sure
2050 -
2051 - const regex = getRegexForPrice( currency );
2052 -
2053 - const matches = price.match( regex );
2054 - if ( null === matches ) {
2055 - return 0;
2056 - }
2057 -
2058 - price = matches.length ? matches[ matches.length - 1 ] : 0;
2059 - price = price.trim();
2060 -
2061 - // Fix issues with parsing Fr.15.00. The regex catches .15.00.
2062 - // This checks for the leading decimal and removes it.
2063 - if ( currency.decimal_separator === '.' && 3 === price.split( '.' ).length && price[ 0 ] === '.' ) {
2064 - price = price.substr( 1 );
2065 - }
2066 -
2067 - if ( price ) {
2068 - price = maybeUseDecimal( price, currency );
2069 - price = price.split( currency.thousand_separator ).join( '' ).replace( currency.decimal_separator, '.' );
2070 - }
2071 -
2072 - return price;
2073 - }
2074 -
2075 - /**
2076 - * @param {Object} currency The currency object.
2077 - * @return {RegExp} The regular expression object.
2078 - */
2079 - function getRegexForPrice( currency ) {
2080 - let regexString = '[0-9,.';
2081 -
2082 - if ( currency.thousand_separator !== '.' && currency.thousand_separator !== ',' ) {
2083 - regexString += currency.thousand_separator;
2084 - }
2085 - if ( currency.decimal_separator !== '.' && currency.decimal_separator !== ',' ) {
2086 - regexString += currency.decimal_separator;
2087 - }
2088 -
2089 - regexString += ']*\\.?\\,?[0-9]+';
2090 -
2091 - return new RegExp( regexString, 'g' );
2092 - }
2093 -
2094 - /**
2095 - * Maybe replace the decimal separator with the currency's decimal separator.
2096 - *
2097 - * @param {string} price The price string.
2098 - * @param {Object} currency The currency object.
2099 - * @return {string} The modified price string.
2100 - */
2101 - function maybeUseDecimal( price, currency ) {
2102 - let usedForDecimal;
2103 - let priceParts;
2104 - if ( '.' === currency.thousand_separator ) {
2105 - priceParts = price.split( '.' );
2106 - usedForDecimal = 2 === priceParts.length && 2 === priceParts[ 1 ].length;
2107 - if ( usedForDecimal ) {
2108 - price = price.replace( '.', currency.decimal_separator );
2109 - }
2110 - }
2111 - return price;
2112 - }
2113 -
2114 - /**
2115 - * Add trailing zeros to a price if necessary and replace the decimal separator.
2116 - *
2117 - * @param {number|string} price The price to format.
2118 - * @param {Object} currency The currency object containing the decimal separator.
2119 - * @param {boolean} [force=false] Whether to force processing even if the price is not a number.
2120 - * @return {string} The formatted price string.
2121 - */
2122 - function maybeAddTrailingZeroToPrice( price, currency, force = false ) {
2123 - if ( 'number' !== typeof price && ! force ) {
2124 - return price;
2125 - }
2126 -
2127 - price = String( price ); // first convert to string
2128 - const pos = price.indexOf( '.' );
2129 -
2130 - if ( pos === -1 ) {
2131 - price = `${ price }.`;
2132 -
2133 - for ( let n = 0; n < currency.decimals; ++n ) {
2134 - price += '0';
2135 - }
2136 - } else {
2137 - const decimalsString = price.substring( pos + 1 );
2138 - if ( decimalsString.length < currency.decimals ) {
2139 - if ( decimalsString.length < 2 ) {
2140 - price += '0';
1025 + if ( 'undefined' !== typeof element.classList ) {
1026 + if ( element.classList.contains( 'html-active' ) ) {
1027 + timeoutCallback = function() {
1028 + var textarea = element.querySelector( 'textarea' );
1029 + if ( null !== textarea ) {
1030 + textarea.focus();
1031 + }
1032 + };
1033 + } else if ( element.classList.contains( 'tmce-active' ) ) {
1034 + timeoutCallback = function() {
1035 + tinyMCE.activeEditor.focus();
1036 + };
2141 1037 }
2142 1038
2143 - for ( let n = 2; n < currency.decimals; ++n ) {
2144 - price += '0';
1039 + if ( 'function' === typeof timeoutCallback ) {
1040 + setTimeout( timeoutCallback, 0 );
1041 + break;
2145 1042 }
2146 1043 }
2147 - }
2148 -
2149 - return price.replace( '.', currency.decimal_separator );
1044 + } while ( element.previousSibling );
2150 1045 }
2151 1046
2152 - /**
2153 - * Format a numeric string by adding thousand separators.
2154 - *
2155 - * @param {string|number} price The numeric value to format.
2156 - * @param {Object} options Formatting options.
2157 - * @param {string} options.decimal_separator Character used as decimal separator.
2158 - * @param {string} options.thousand_separator Character used as thousand separator.
2159 - *
2160 - * @return {string} The price string with thousand separators.
2161 - */
2162 - function addThousands( price, options ) {
2163 - const split = options.decimal_separator === ''
2164 - ? [ price.toString() ]
2165 - : price.split( options.decimal_separator );
1047 + return {
1048 + init: function() {
1049 + jQuery( document ).off( 'submit.formidable', '.frm-show-form' );
1050 + jQuery( document ).on( 'submit.formidable', '.frm-show-form', frmFrontForm.submitForm );
2166 1051
2167 - if ( options.thousand_separator ) {
2168 - split[ 0 ] = split[ 0 ].replace( /\B(?=(\d{3})+(?!\d))/g, options.thousand_separator );
2169 - }
1052 + jQuery( '.frm-show-form input[onblur], .frm-show-form textarea[onblur]' ).each( function() {
1053 + if ( jQuery( this ).val() === '' ) {
1054 + jQuery( this ).trigger( 'blur' );
1055 + }
1056 + });
2170 1057
2171 - return split.join( options.decimal_separator );
2172 - }
1058 + jQuery( document ).on( 'focus', '.frm_toggle_default', clearDefault );
1059 + jQuery( document ).on( 'blur', '.frm_toggle_default', replaceDefault );
1060 + jQuery( '.frm_toggle_default' ).trigger( 'blur' );
2173 1061
2174 - /**
2175 - * Maybe remove trailing zeros from a price string.
2176 - *
2177 - * @param {string} price The price string.
2178 - * @param {Object} currency The currency data.
2179 - *
2180 - * @return {string} The price string with trailing zeros removed.
2181 - */
2182 - function maybeRemoveTrailingZerosFromPrice( price, currency ) {
2183 - const split = price.split( currency.decimal_separator );
2184 - if ( 2 !== split.length || split[ 1 ].length <= currency.decimals ) {
2185 - return price;
2186 - }
2187 - if ( 0 === currency.decimals ) {
2188 - return split[ 0 ];
2189 - }
2190 - return `${ split[ 0 ] }${ currency.decimal_separator }${ split[ 1 ].substr( 0, currency.decimals ) }`;
2191 - }
1062 + jQuery( document.getElementById( 'frm_resend_email' ) ).on( 'click', resendEmail );
2192 1063
2193 - return {
2194 - init() {
2195 - jQuery( document ).off( 'submit.formidable', '.frm-show-form' );
2196 - jQuery( document ).on( 'submit.formidable', '.frm-show-form', frmFrontForm.submitForm );
2197 -
2198 1064 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 );
1065 + jQuery( document ).on( 'change keyup', '.frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea', maybeShowLabel );
2199 1066
2200 - jQuery( document ).on( 'change', '.frm_verify[id^=field_]', onHoneypotFieldChange );
1067 + jQuery( document ).on( 'change', '[id^=frm_email_]', onHoneypotFieldChange );
2201 1068
2202 1069 jQuery( document ).on( 'click', 'a[data-frmconfirm]', confirmClick );
1070 + jQuery( 'a[data-frmtoggle]' ).on( 'click', toggleDiv );
2203 1071
2204 1072 checkForErrorsAndMaybeSetFocus();
2205 1073
2206 1074 // Focus on the first sub field when clicking to the primary label of combo field.
@@ -2205,128 +1073,102 @@
2205 1073
2206 1074 // Focus on the first sub field when clicking to the primary label of combo field.
2207 1075 changeFocusWhenClickComboFieldLabel();
2208 1076
2209 - initFloatingLabels();
2210 - maybeShowNewTabFallbackMessage();
1077 + // Add fallbacks for the beloved IE8
1078 + addIndexOfFallbackForIE8();
1079 + addTrimFallbackForIE8();
1080 + addFilterFallbackForIE8();
1081 + addKeysFallbackForIE8();
1082 + },
2211 1083
2212 - jQuery( document ).on( 'frmAfterAddRow', setCustomValidityMessage );
2213 - setCustomValidityMessage();
2214 - jQuery( document ).on( 'frmFieldChanged', maybeClearCustomValidityMessage );
2215 -
2216 - setSelectPlaceholderColor();
2217 -
2218 - // Elementor popup show event. Fix Elementor Popup && FF Captcha field conflicts
2219 - jQuery( document ).on( 'elementor/popup/show', frmRecaptcha );
2220 -
2221 - enableSubmitButtonOnBackButtonPress();
2222 - jQuery( document ).on(
2223 - 'frmPageChanged',
2224 - destroyhCaptcha
2225 - );
2226 -
2227 - jQuery( document ).on( 'frmAfterAddRow frmAfterRemoveRow', calcProductsTotal );
2228 - jQuery( document ).on( 'change', '[type="checkbox"][data-frmprice],[type="radio"][data-frmprice],[type="hidden"][data-frmprice],select:has([data-frmprice])', calcProductsTotal );
2229 - jQuery( document ).on( 'keyup change', '[data-frmproduct],[type="text"][data-frmprice]', calcProductsTotal );
2230 - calcProductsTotal();
1084 + getFieldId: function( field, fullID ) {
1085 + return getFieldId( field, fullID );
2231 1086 },
2232 1087
2233 - getFieldId,
1088 + renderRecaptcha: function( captcha ) {
1089 + var formID, recaptchaID,
1090 + size = captcha.getAttribute( 'data-size' ),
1091 + rendered = captcha.getAttribute( 'data-rid' ) !== null,
1092 + params = {
1093 + 'sitekey': captcha.getAttribute( 'data-sitekey' ),
1094 + 'size': size,
1095 + 'theme': captcha.getAttribute( 'data-theme' )
1096 + };
2234 1097
2235 - /**
2236 - * Render a captcha field.
2237 - *
2238 - * @param {HTMLElement} captcha
2239 - * @param {string} captchaSelector
2240 - * @return {void}
2241 - */
2242 - renderCaptcha( captcha, captchaSelector ) {
2243 - const rendered = captcha.getAttribute( 'data-rid' ) !== null;
2244 1098 if ( rendered ) {
2245 1099 return;
2246 1100 }
2247 1101
2248 - const size = captcha.getAttribute( 'data-size' );
2249 - const params = {
2250 - sitekey: captcha.getAttribute( 'data-sitekey' ),
2251 - size,
2252 - theme: captcha.getAttribute( 'data-theme' )
2253 - };
2254 -
2255 1102 if ( size === 'invisible' ) {
2256 - const formID = captcha.closest( 'form' )?.querySelector( 'input[name="form_id"]' )?.value;
2257 -
2258 - const captchaLabel = captcha.closest( '.frm_form_field' )?.querySelector( '.frm_primary_label' );
2259 - if ( captchaLabel ) {
2260 - captchaLabel.style.display = 'none';
2261 - }
2262 -
1103 + formID = jQuery( captcha ).closest( 'form' ).find( 'input[name="form_id"]' ).val();
1104 + jQuery( captcha ).closest( '.frm_form_field .frm_primary_label' ).hide();
2263 1105 params.callback = function( token ) {
2264 1106 frmFrontForm.afterRecaptcha( token, formID );
2265 1107 };
2266 1108 }
2267 1109
2268 - const activeCaptcha = getSelectedCaptcha( captchaSelector );
2269 - const captchaContainer = typeof turnstile !== 'undefined' && turnstile === activeCaptcha ? `#${ captcha.id }` : captcha.id;
2270 - const captchaID = activeCaptcha.render( captchaContainer, params );
1110 + recaptchaID = grecaptcha.render( captcha.id, params );
2271 1111
2272 - captcha.setAttribute( 'data-rid', captchaID );
2273 -
2274 - maybeFixCaptchaLabel( captcha );
1112 + captcha.setAttribute( 'data-rid', recaptchaID );
2275 1113 },
2276 1114
2277 - afterSingleRecaptcha() {
2278 - const recaptcha = document.querySelector( '.frm-show-form .g-recaptcha' );
2279 - const object = recaptcha ? recaptcha.closest( 'form' ) : null;
1115 + afterSingleRecaptcha: function() {
1116 + var object = jQuery( '.frm-show-form .g-recaptcha' ).closest( 'form' )[0];
2280 1117 frmFrontForm.submitFormNow( object );
2281 1118 },
2282 1119
2283 - afterRecaptcha( _, formID ) {
2284 - const object = document.querySelector( `#frm_form_${ formID }_container form` );
1120 + afterRecaptcha: function( token, formID ) {
1121 + var object = jQuery( '#frm_form_' + formID + '_container form' )[0];
2285 1122 frmFrontForm.submitFormNow( object );
2286 1123 },
2287 1124
2288 - submitForm( e ) {
1125 + submitForm: function( e ) {
2289 1126 frmFrontForm.submitFormManual( e, this );
2290 1127 },
2291 1128
2292 - /**
2293 - * @param {Event} e
2294 - * @param {HTMLElement} object The form object that is being submitted.
2295 - * @return {void}
2296 - */
2297 - submitFormManual( e, object ) {
2298 - if ( document.body.classList.contains( 'wp-admin' ) && ! object.closest( '.frmapi-form' ) ) {
1129 + submitFormManual: function( e, object ) {
1130 + var isPro, errors,
1131 + invisibleRecaptcha = hasInvisibleRecaptcha( object ),
1132 + classList = object.className.trim().split( /\s+/gi );
1133 +
1134 + if ( classList && invisibleRecaptcha.length < 1 ) {
1135 + isPro = classList.indexOf( 'frm_pro_form' ) > -1;
1136 + if ( ! isPro ) {
1137 + return;
1138 + }
1139 + }
1140 +
1141 + if ( jQuery( 'body' ).hasClass( 'wp-admin' ) && jQuery( object ).closest( '.frmapi-form' ).length < 1 ) {
2299 1142 return;
2300 1143 }
2301 1144
2302 1145 e.preventDefault();
2303 1146
2304 - if ( typeof frmProForm !== 'undefined' && typeof frmProForm.submitAllowed === 'function' && ! frmProForm.submitAllowed( object ) ) {
2305 - return;
1147 + if ( typeof frmProForm !== 'undefined' && typeof frmProForm.submitAllowed === 'function' ) {
1148 + if ( ! frmProForm.submitAllowed( object ) ) {
1149 + return;
1150 + }
2306 1151 }
2307 1152
2308 - const errors = frmFrontForm.validateFormSubmit( object );
2309 - if ( Object.keys( errors ).length !== 0 ) {
2310 - return;
2311 - }
2312 -
2313 - const invisibleRecaptcha = hasInvisibleRecaptcha( object );
2314 -
2315 - if ( invisibleRecaptcha ) {
1153 + if ( invisibleRecaptcha.length ) {
2316 1154 showLoadingIndicator( jQuery( object ) );
2317 1155 executeInvisibleRecaptcha( invisibleRecaptcha );
2318 1156 } else {
2319 - showSubmitLoading( jQuery( object ) );
2320 1157
2321 - frmFrontForm.submitFormNow( object );
1158 + errors = frmFrontForm.validateFormSubmit( object );
1159 +
1160 + if ( Object.keys( errors ).length === 0 ) {
1161 + showSubmitLoading( jQuery( object ) );
1162 +
1163 + frmFrontForm.submitFormNow( object, classList );
1164 + }
2322 1165 }
2323 1166 },
2324 1167
2325 - submitFormNow( object ) {
2326 - let hasFileFields;
2327 - let antispamInput;
2328 - const classList = object.className.trim().split( /\s+/gi );
1168 + submitFormNow: function( object ) {
1169 + var hasFileFields, antispamInput,
1170 + classList = object.className.trim().split( /\s+/gi );
2329 1171
2330 1172 if ( object.hasAttribute( 'data-token' ) && null === object.querySelector( '[name="antispam_token"]' ) ) {
2331 1173 // include the antispam token on form submit.
2332 1174 antispamInput = document.createElement( 'input' );
@@ -2332,24 +1174,17 @@
2332 1174 antispamInput = document.createElement( 'input' );
2333 1175 antispamInput.type = 'hidden';
2334 1176 antispamInput.name = 'antispam_token';
2335 1177 antispamInput.value = object.getAttribute( 'data-token' );
2336 - object.append( antispamInput );
1178 + object.appendChild( antispamInput );
2337 1179 }
2338 1180
2339 - // Add a unique ID, used for duplicate checks.
2340 - const uniqueIDInput = document.createElement( 'input' );
2341 - uniqueIDInput.type = 'hidden';
2342 - uniqueIDInput.name = 'unique_id';
2343 - uniqueIDInput.value = getUniqueKey();
2344 - object.append( uniqueIDInput );
2345 -
2346 - if ( classList.includes( 'frm_ajax_submit' ) ) {
2347 - const fileInputs = object.querySelectorAll( 'input[type="file"]' );
2348 - hasFileFields = Array.from( fileInputs ).filter( input => !! input.value ).length;
1181 + if ( classList.indexOf( 'frm_ajax_submit' ) > -1 ) {
1182 + hasFileFields = jQuery( object ).find( 'input[type="file"]' ).filter( function() {
1183 + return !! this.value;
1184 + }).length;
2349 1185 if ( hasFileFields < 1 ) {
2350 - const actionInput = object.querySelector( 'input[name="frm_action"]' );
2351 - const action = actionInput ? actionInput.value : '';
1186 + action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
2352 1187 frmFrontForm.checkFormErrors( object, action );
2353 1188 } else {
2354 1189 object.submit();
2355 1190 }
@@ -2357,16 +1192,10 @@
2357 1192 object.submit();
2358 1193 }
2359 1194 },
2360 1195
2361 - /**
2362 - * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
2363 - *
2364 - * @return {Array} List of errors.
2365 - */
2366 - validateFormSubmit( object ) {
2367 - const form = object instanceof jQuery ? object.get( 0 ) : object;
2368 - if ( typeof tinyMCE !== 'undefined' && form?.querySelector( '.wp-editor-wrap' ) ) {
1196 + validateFormSubmit: function( object ) {
1197 + if ( typeof tinyMCE !== 'undefined' && jQuery( object ).find( '.wp-editor-wrap' ).length ) {
2369 1198 tinyMCE.triggerSave();
2370 1199 }
2371 1200
2372 1201 jsErrors = [];
@@ -2381,23 +1210,16 @@
2381 1210
2382 1211 return jsErrors;
2383 1212 },
2384 1213
2385 - /**
2386 - * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
2387 - * @return {Array} List of errors.
2388 - */
2389 - getAjaxFormErrors( object ) {
2390 - let customErrors;
2391 - let key;
2392 - const form = object instanceof jQuery ? object.get( 0 ) : object;
1214 + getAjaxFormErrors: function( object ) {
1215 + var customErrors, key;
2393 1216
2394 1217 jsErrors = validateForm( object );
2395 1218 if ( typeof frmThemeOverride_jsErrors === 'function' ) { // eslint-disable-line camelcase
2396 - const actionInput = form ? form.querySelector( 'input[name="frm_action"]' ) : null;
2397 - const action = actionInput ? actionInput.value : '';
1219 + action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
2398 1220 customErrors = frmThemeOverride_jsErrors( action, object );
2399 - if ( Object.keys( customErrors ).length ) {
1221 + if ( Object.keys( customErrors ).length ) {
2400 1222 for ( key in customErrors ) {
2401 1223 jsErrors[ key ] = customErrors[ key ];
2402 1224 }
2403 1225 }
@@ -2402,30 +1224,20 @@
2402 1224 }
2403 1225 }
2404 1226 }
2405 1227
2406 - triggerCustomEvent( document, 'frm_get_ajax_form_errors', {
2407 - formEl: object,
2408 - errors: jsErrors
2409 - } );
2410 -
2411 1228 return jsErrors;
2412 1229 },
2413 1230
2414 - /**
2415 - * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
2416 - * @return {void}
2417 - */
2418 - addAjaxFormErrors( object ) {
2419 - let key;
2420 - const form = object instanceof jQuery ? object.get( 0 ) : object;
1231 + addAjaxFormErrors: function( object ) {
1232 + var key, $fieldCont;
2421 1233 removeAllErrors();
2422 1234
2423 1235 for ( key in jsErrors ) {
2424 - const fieldCont = form ? form.querySelector( `#frm_field_${ key }_container` ) : null;
1236 + $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
2425 1237
2426 - if ( fieldCont ) {
2427 - addFieldError( fieldCont, key, jsErrors );
1238 + if ( $fieldCont.length ) {
1239 + addFieldError( $fieldCont, key, jsErrors );
2428 1240 } else {
2429 1241 // we are unable to show the error, so remove it
2430 1242 delete jsErrors[ key ];
2431 1243 }
@@ -2434,33 +1246,39 @@
2434 1246 scrollToFirstField( object );
2435 1247 checkForErrorsAndMaybeSetFocus();
2436 1248 },
2437 1249
2438 - checkFormErrors: getFormErrors,
2439 - checkRequiredField,
2440 - showSubmitLoading,
2441 - removeSubmitLoading,
1250 + checkFormErrors: function( object, action ) {
1251 + getFormErrors( object, action );
1252 + },
2442 1253
2443 - scrollToID( id ) {
2444 - const object = jQuery( document.getElementById( id ) );
2445 - frmFrontForm.scrollMsg( object, false );
1254 + checkRequiredField: function( field, errors ) {
1255 + return checkRequiredField( field, errors );
2446 1256 },
2447 1257
2448 - scrollMsg( id, object, animate ) {
2449 - let newPos;
2450 - let screenTop;
2451 - let screenBottom;
2452 - let scrollObj = '';
1258 + showSubmitLoading: function( $object ) {
1259 + showSubmitLoading( $object );
1260 + },
2453 1261
2454 - if ( object === undefined ) {
2455 - scrollObj = jQuery( document.getElementById( `frm_form_${ id }_container` ) );
1262 + removeSubmitLoading: function( $object, enable, processesRunning ) {
1263 + removeSubmitLoading( $object, enable, processesRunning );
1264 + },
1265 +
1266 + scrollToID: function( id ) {
1267 + var object = jQuery( document.getElementById( id ) );
1268 + frmFrontForm.scrollMsg( object, false );
1269 + },
1270 +
1271 + scrollMsg: function( id, object, animate ) {
1272 + var newPos, m, b, screenTop, screenBottom,
1273 + scrollObj = '';
1274 + if ( typeof object === 'undefined' ) {
1275 + scrollObj = jQuery( document.getElementById( 'frm_form_' + id + '_container' ) );
2456 1276 if ( scrollObj.length < 1 ) {
2457 1277 return;
2458 1278 }
2459 1279 } else if ( typeof id === 'string' ) {
2460 - const formEl = object instanceof jQuery ? object.get( 0 ) : object;
2461 - const fieldEl = formEl ? formEl.querySelector( `#frm_field_${ id }_container` ) : null;
2462 - scrollObj = fieldEl ? jQuery( fieldEl ) : jQuery();
1280 + scrollObj = jQuery( object ).find( '#frm_field_' + id + '_container' );
2463 1281 } else {
2464 1282 scrollObj = id;
2465 1283 }
2466 1284
@@ -2470,12 +1288,12 @@
2470 1288 return;
2471 1289 }
2472 1290 newPos = newPos - frm_js.offset;
2473 1291
2474 - const docMarginTop = getComputedStyle( document.documentElement ).marginTop;
2475 - const bodyMarginTop = getComputedStyle( document.body ).marginTop;
2476 - if ( docMarginTop || bodyMarginTop ) {
2477 - newPos = newPos - parseInt( docMarginTop ) - parseInt( bodyMarginTop );
1292 + m = jQuery( 'html' ).css( 'margin-top' );
1293 + b = jQuery( 'body' ).css( 'margin-top' );
1294 + if ( m || b ) {
1295 + newPos = newPos - parseInt( m ) - parseInt( b );
2478 1296 }
2479 1297
2480 1298 if ( newPos && window.innerHeight ) {
2481 1299 screenTop = document.documentElement.scrollTop || document.body.scrollTop;
@@ -2482,12 +1300,12 @@
2482 1300 screenBottom = screenTop + window.innerHeight;
2483 1301
2484 1302 if ( newPos > screenBottom || newPos < screenTop ) {
2485 1303 // Not in view
2486 - if ( animate === undefined ) {
2487 - document.documentElement.scrollTop = newPos;
1304 + if ( typeof animate === 'undefined' ) {
1305 + jQuery( window ).scrollTop( newPos );
2488 1306 } else {
2489 - animateScroll( screenTop, newPos, 500 );
1307 + jQuery( 'html,body' ).animate({ scrollTop: newPos }, 500 );
2490 1308 }
2491 1309 return false;
2492 1310 }
2493 1311 }
@@ -2492,13 +1310,13 @@
2492 1310 }
2493 1311 }
2494 1312 },
2495 1313
2496 - fieldValueChanged( e ) {
1314 + fieldValueChanged: function( e ) {
2497 1315 /*jshint validthis:true */
2498 1316
2499 - const fieldId = frmFrontForm.getFieldId( this, false );
2500 - if ( ! fieldId ) {
1317 + var fieldId = frmFrontForm.getFieldId( this, false );
1318 + if ( ! fieldId || typeof fieldId === 'undefined' ) {
2501 1319 return;
2502 1320 }
2503 1321
2504 1322 if ( e.frmTriggered && e.frmTriggered == fieldId ) {
@@ -2504,17 +1322,63 @@
2504 1322 if ( e.frmTriggered && e.frmTriggered == fieldId ) {
2505 1323 return;
2506 1324 }
2507 1325
2508 - jQuery( document ).trigger( 'frmFieldChanged', [ this, fieldId, e ] );
1326 + jQuery( document ).trigger( 'frmFieldChanged', [ this, fieldId, e ]);
2509 1327
2510 1328 if ( e.selfTriggered !== true ) {
2511 - maybeValidateChange( this );
1329 + maybeValidateChange( fieldId, this );
2512 1330 }
2513 1331 },
2514 1332
2515 - escapeHtml( text ) {
2516 - console.warn( 'DEPRECATED: function frmFrontForm.escapeHtml in v6.17' );
1333 + savingDraft: function( object ) {
1334 + console.warn( 'DEPRECATED: function frmFrontForm.savingDraft in v3.0 use frmProForm.savingDraft' );
1335 + if ( typeof frmProForm !== 'undefined' ) {
1336 + return frmProForm.savingDraft( object );
1337 + }
1338 + },
1339 +
1340 + goingToPreviousPage: function( object ) {
1341 + console.warn( 'DEPRECATED: function frmFrontForm.goingToPreviousPage in v3.0 use frmProForm.goingToPreviousPage' );
1342 + if ( typeof frmProForm !== 'undefined' ) {
1343 + return frmProForm.goingToPreviousPage( object );
1344 + }
1345 + },
1346 +
1347 + hideOrShowFields: function() {
1348 + console.warn( 'DEPRECATED: function frmFrontForm.hideOrShowFields in v3.0 use frmProForm.hideOrShowFields' );
1349 + if ( typeof frmProForm !== 'undefined' ) {
1350 + frmProForm.hideOrShowFields();
1351 + }
1352 + },
1353 +
1354 + hidePreviouslyHiddenFields: function() {
1355 + console.warn( 'DEPRECATED: function frmFrontForm.hidePreviouslyHiddenFields in v3.0 use frmProForm.hidePreviouslyHiddenFields' );
1356 + if ( typeof frmProForm !== 'undefined' ) {
1357 + frmProForm.hidePreviouslyHiddenFields();
1358 + }
1359 + },
1360 +
1361 + checkDependentDynamicFields: function( ids ) {
1362 + console.warn( 'DEPRECATED: function frmFrontForm.checkDependentDynamicFields in v3.0 use frmProForm.checkDependentDynamicFields' );
1363 + if ( typeof frmProForm !== 'undefined' ) {
1364 + frmProForm.checkDependentDynamicFields( ids );
1365 + }
1366 + },
1367 +
1368 + checkDependentLookupFields: function( ids ) {
1369 + console.warn( 'DEPRECATED: function frmFrontForm.checkDependentLookupFields in v3.0 use frmProForm.checkDependentLookupFields' );
1370 + if ( typeof frmProForm !== 'undefined' ) {
1371 + frmProForm.checkDependentLookupFields( ids );
1372 + }
1373 + },
1374 +
1375 + loadGoogle: function() {
1376 + console.warn( 'DEPRECATED: function frmFrontForm.loadGoogle in v3.0 use frmProForm.loadGoogle' );
1377 + frmProForm.loadGoogle();
1378 + },
1379 +
1380 + escapeHtml: function( text ) {
2517 1381 return text
2518 1382 .replace( /&/g, '&amp;' )
2519 1383 .replace( /</g, '&lt;' )
2520 1384 .replace( />/g, '&gt;' )
@@ -2521,82 +1385,97 @@
2521 1385 .replace( /"/g, '&quot;' )
2522 1386 .replace( /'/g, '&#039;' );
2523 1387 },
2524 1388
2525 - triggerCustomEvent,
2526 - documentOn
1389 + invisible: function( classes ) {
1390 + jQuery( classes ).css( 'visibility', 'hidden' );
1391 + },
1392 +
1393 + visible: function( classes ) {
1394 + jQuery( classes ).css( 'visibility', 'visible' );
1395 + }
2527 1396 };
2528 1397 }
1398 +frmFrontForm = frmFrontFormJS();
2529 1399
2530 -window.frmFrontForm = frmFrontFormJS();
2531 -
2532 1400 jQuery( document ).ready( function() {
2533 1401 frmFrontForm.init();
2534 -} );
1402 +});
2535 1403
2536 1404 function frmRecaptcha() {
2537 - frmCaptcha( '.frm-g-recaptcha' );
1405 + var c, cl,
1406 + captchas = jQuery( '.frm-g-recaptcha' );
1407 + for ( c = 0, cl = captchas.length; c < cl; c++ ) {
1408 + frmFrontForm.renderRecaptcha( captchas[c]);
1409 + }
2538 1410 }
2539 1411
2540 -function frmHcaptcha() {
2541 - frmCaptcha( '.h-captcha' );
1412 +function frmAfterRecaptcha( token ) {
1413 + frmFrontForm.afterSingleRecaptcha( token );
2542 1414 }
2543 1415
2544 -function frmTurnstile() {
2545 - frmCaptcha( '.frm-cf-turnstile' );
1416 +function frmUpdateField( entryId, fieldId, value, message, num ) {
1417 + jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).html( '<span class="frm-loading-img"></span>' );
1418 + jQuery.ajax({
1419 + type: 'POST',
1420 + url: frm_js.ajax_url,
1421 + data: {
1422 + action: 'frm_entries_update_field_ajax',
1423 + entry_id: entryId,
1424 + field_id: fieldId,
1425 + value: value,
1426 + nonce: frm_js.nonce
1427 + },
1428 + success: function() {
1429 + if ( message.replace( /^\s+|\s+$/g, '' ) === '' ) {
1430 + jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).fadeOut( 'slow' );
1431 + } else {
1432 + jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).replaceWith( message );
1433 + }
1434 + }
1435 + });
2546 1436 }
2547 1437
2548 -function frmCaptcha( captchaSelector ) {
2549 - if ( '.h-captcha' === captchaSelector ) {
2550 - // hCaptcha is still rendered implicitly, so we only want to handle the label and exit early.
2551 - // Match the hcaptcha labels to the hcaptcha response fields.
2552 - const captchaLabels = document.querySelectorAll( 'label[for="h-captcha-response"]' );
2553 - if ( captchaLabels.length ) {
2554 - captchaLabels.forEach( label => {
2555 - const captchaResponse = label.closest( 'form' )?.querySelector( '[name="h-captcha-response"]' );
2556 - if ( captchaResponse ) {
2557 - label.htmlFor = captchaResponse.id;
2558 - }
2559 - } );
1438 +function frmDeleteEntry( entryId, prefix ) {
1439 + console.warn( 'DEPRECATED: function frmDeleteEntry in v2.0.13 use frmFrontForm.deleteEntry' );
1440 + jQuery( document.getElementById( 'frm_delete_' + entryId ) ).replaceWith( '<span class="frm-loading-img" id="frm_delete_' + entryId + '"></span>' );
1441 + jQuery.ajax({
1442 + type: 'POST',
1443 + url: frm_js.ajax_url,
1444 + data: {
1445 + action: 'frm_entries_destroy',
1446 + entry: entryId,
1447 + nonce: frm_js.nonce
1448 + },
1449 + success: function( html ) {
1450 + if ( html.replace( /^\s+|\s+$/g, '' ) === 'success' ) {
1451 + jQuery( document.getElementById( prefix + entryId ) ).fadeOut( 'slow' );
1452 + } else {
1453 + jQuery( document.getElementById( 'frm_delete_' + entryId ) ).replaceWith( html );
1454 + }
2560 1455 }
2561 - return;
2562 - }
2563 -
2564 - let c;
2565 - const captchas = document.querySelectorAll( captchaSelector );
2566 - const cl = captchas.length;
2567 - for ( c = 0; c < cl; c++ ) {
2568 - const closestForm = captchas[ c ].closest( 'form' );
2569 - const formIsVisible = closestForm && closestForm.offsetParent !== null;
2570 - const captcha = captchas[ c ];
2571 - if ( ! formIsVisible ) {
2572 - // If the form is not visible, try again later in 400ms.
2573 - // This fixes issues where the form fades visible on page load.
2574 - // Or when the form is inside of a modal.
2575 - const interval = setInterval(
2576 - function() {
2577 - if ( closestForm && closestForm.offsetParent !== null ) {
2578 - frmFrontForm.renderCaptcha( captcha, captchaSelector );
2579 - clearInterval( interval );
2580 - }
2581 - },
2582 - 400
2583 - );
2584 - continue;
2585 - }
2586 - frmFrontForm.renderCaptcha( captcha, captchaSelector );
2587 - }
1456 + });
2588 1457 }
2589 1458
2590 -function getSelectedCaptcha( captchaSelector ) {
2591 - if ( captchaSelector === '.frm-g-recaptcha' ) {
2592 - return grecaptcha;
2593 - }
2594 - if ( document.querySelector( '.frm-cf-turnstile' ) ) {
2595 - return turnstile;
2596 - }
2597 - return hcaptcha;
1459 +function frmOnSubmit( e ) {
1460 + console.warn( 'DEPRECATED: function frmOnSubmit in v2.0 use frmFrontForm.submitForm' );
1461 + frmFrontForm.submitForm( e, this );
2598 1462 }
2599 1463
2600 -function frmAfterRecaptcha( token ) {
2601 - frmFrontForm.afterSingleRecaptcha( token );
1464 +function frm_resend_email( entryId, formId ) { // eslint-disable-line camelcase
1465 + var $link = jQuery( document.getElementById( 'frm_resend_email' ) );
1466 + console.warn( 'DEPRECATED: function frm_resend_email in v2.0' );
1467 + $link.append( '<span class="spinner" style="display:inline"></span>' );
1468 + jQuery.ajax({
1469 + type: 'POST',
1470 + url: frm_js.ajax_url,
1471 + data: {
1472 + action: 'frm_entries_send_email',
1473 + entry_id: entryId,
1474 + form_id: formId,
1475 + nonce: frm_js.nonce
1476 + },
1477 + success: function( msg ) {
1478 + $link.replaceWith( msg );
1479 + }
1480 + });
2602 1481 }