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