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