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

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

1,402 lines 38.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* exported frmRecaptcha, frmAfterRecaptcha, frmUpdateField, frmDeleteEntry, frmOnSubmit, frm_resend_email */
2
3 var frmFrontForm;
4
5 function frmFrontFormJS() {
6 'use strict';
7
8 /*global jQuery:false, frm_js, grecaptcha, frmProForm, tinyMCE */
9 /*global frmThemeOverride_jsErrors, frmThemeOverride_frmPlaceError, frmThemeOverride_frmAfterSubmit */
10
11 var action = '';
12 var jsErrors = [];
13
14 function maybeShowLabel() {
15 /*jshint validthis:true */
16 var $field = jQuery( this ),
17 $label = $field.closest( '.frm_inside_container' ).find( '.frm_primary_label' ),
18 val = $field.val();
19
20 if ( val !== null && val.length > 0 ) {
21 $label.addClass( 'frm_visible' );
22 } else {
23 $label.removeClass( 'frm_visible' );
24 }
25 }
26
27 /* Get the ID of the field that changed*/
28 function getFieldId( field, fullID ) {
29 var nameParts, fieldId,
30 isRepeating = false,
31 fieldName = '';
32 if ( field instanceof jQuery ) {
33 fieldName = field.attr( 'name' );
34 } else {
35 fieldName = field.name;
36 }
37
38 if ( typeof fieldName === 'undefined' ) {
39 fieldName = '';
40 }
41
42 if ( fieldName === '' ) {
43 if ( field instanceof jQuery ) {
44 fieldName = field.data( 'name' );
45 } else {
46 fieldName = field.getAttribute( 'data-name' );
47 }
48
49 if ( typeof fieldName === 'undefined' ) {
50 fieldName = '';
51 }
52
53 if ( fieldName !== '' && fieldName ) {
54 return fieldName;
55 }
56 return 0;
57 }
58
59 nameParts = fieldName.replace( 'item_meta[', '' ).replace( '[]', '' ).split( ']' );
60 //TODO: Fix this for checkboxes and address fields
61 if ( nameParts.length < 1 ) {
62 return 0;
63 }
64 nameParts = nameParts.filter( function( n ) {
65 return n !== '';
66 });
67
68 fieldId = nameParts[0];
69
70 if ( nameParts.length === 1 ) {
71 return fieldId;
72 }
73
74 if ( nameParts[1] === '[form' || nameParts[1] === '[row_ids' ) {
75 return 0;
76 }
77
78 // Check if 'this' is in a repeating section
79 if ( jQuery( 'input[name="item_meta[' + fieldId + '][form]"]' ).length ) {
80
81 // this is a repeatable section with name: item_meta[repeating-section-id][row-id][field-id]
82 fieldId = nameParts[2].replace( '[', '' );
83 isRepeating = true;
84 }
85
86 // Check if 'this' is an other text field and get field ID for it
87 if ( 'other' === fieldId ) {
88 if ( isRepeating ) {
89 // name for other fields: item_meta[370][0][other][414]
90 fieldId = nameParts[3].replace( '[', '' );
91 } else {
92 // Other field name: item_meta[other][370]
93 fieldId = nameParts[1].replace( '[', '' );
94 }
95 }
96
97 if ( fullID === true ) {
98 // For use in the container div id
99 if ( fieldId === nameParts[0]) {
100 fieldId = fieldId + '-' + nameParts[1].replace( '[', '' );
101 } else {
102 fieldId = fieldId + '-' + nameParts[0] + '-' + nameParts[1].replace( '[', '' );
103 }
104 }
105
106 return fieldId;
107 }
108
109 /**
110 * Disable the submit button for a given jQuery form object
111 *
112 * @since 2.03.02
113 *
114 * @param {object} $form
115 */
116 function disableSubmitButton( $form ) {
117 $form.find( 'input[type="submit"], input[type="button"], button[type="submit"]' ).attr( 'disabled', 'disabled' );
118 }
119
120 /**
121 * Enable the submit button for a given jQuery form object
122 *
123 * @since 2.03.02
124 *
125 * @param {object} $form
126 */
127 function enableSubmitButton( $form ) {
128 $form.find( 'input[type="submit"], input[type="button"], button[type="submit"]' ).prop( 'disabled', false );
129 }
130
131 /**
132 * Disable the save draft link for a given jQuery form object
133 *
134 * @since 4.04.03
135 *
136 * @param {object} $form
137 */
138 function disableSaveDraft( $form ) {
139 $form.find( 'a.frm_save_draft' ).css( 'pointer-events', 'none' );
140 }
141
142 /**
143 * Enable the save draft link for a given jQuery form object
144 *
145 * @since 4.04.03
146 *
147 * @param {object} $form
148 */
149 function enableSaveDraft( $form ) {
150 $form.find( 'a.frm_save_draft' ).css( 'pointer-events', '' );
151 }
152
153 function validateForm( object ) {
154 var r, rl, n, nl, fields, field, value, requiredFields,
155 errors = [];
156
157 // Make sure required text field is filled in
158 requiredFields = jQuery( object ).find(
159 '.frm_required_field:visible input, .frm_required_field:visible select, .frm_required_field:visible textarea'
160 ).filter( ':not(.frm_optional)' );
161 if ( requiredFields.length ) {
162 for ( r = 0, rl = requiredFields.length; r < rl; r++ ) {
163 errors = checkRequiredField( requiredFields[r], errors );
164 }
165 }
166
167 fields = jQuery( object ).find( 'input,select,textarea' );
168 if ( fields.length ) {
169 for ( n = 0, nl = fields.length; n < nl; n++ ) {
170 field = fields[n];
171 value = field.value;
172 if ( value !== '' ) {
173 if ( field.type === 'hidden' ) {
174 // don't validate
175 } else if ( field.type === 'number' ) {
176 errors = checkNumberField( field, errors );
177 } else if ( field.type === 'email' ) {
178 errors = checkEmailField( field, errors );
179 } else if ( field.type === 'password' ) {
180 errors = checkPasswordField( field, errors );
181 } else if ( field.type === 'url' ) {
182 errors = checkUrlField( field, errors );
183 } else if ( field.pattern !== null ) {
184 errors = checkPatternField( field, errors );
185 }
186 }
187 }
188 }
189
190 errors = validateRecaptcha( object, errors );
191
192 return errors;
193 }
194
195 function maybeValidateChange( fieldId, field ) {
196 if ( field.type === 'url' ) {
197 maybeAddHttpToUrl( field );
198 }
199 if ( jQuery( field ).closest( 'form' ).hasClass( 'frm_js_validate' ) ) {
200 validateField( fieldId, field );
201 }
202 }
203
204 function maybeAddHttpToUrl( field ) {
205 var url = field.value;
206 var matches = url.match( /^(https?|ftps?|mailto|news|feed|telnet):/ );
207 if ( field.value !== '' && matches === null ) {
208 field.value = 'http://' + url;
209 }
210 }
211
212 function validateField( fieldId, field ) {
213 var key,
214 errors = [];
215
216 var $fieldCont = jQuery( field ).closest( '.frm_form_field' );
217 if ( $fieldCont.hasClass( 'frm_required_field' ) && ! jQuery( field ).hasClass( 'frm_optional' ) ) {
218 errors = checkRequiredField( field, errors );
219 }
220
221 if ( errors.length < 1 ) {
222 if ( field.type === 'email' ) {
223 errors = checkEmailField( field, errors );
224 } else if ( field.type === 'password' ) {
225 errors = checkPasswordField( field, errors );
226 } else if ( field.type === 'number' ) {
227 errors = checkNumberField( field, errors );
228 } else if ( field.type === 'url' ) {
229 errors = checkUrlField( field, errors );
230 } else if ( field.pattern !== null ) {
231 errors = checkPatternField( field, errors );
232 }
233 }
234
235 removeFieldError( $fieldCont );
236 if ( Object.keys( errors ).length > 0 ) {
237 for ( key in errors ) {
238 addFieldError( $fieldCont, key, errors );
239 }
240 }
241 }
242
243 function checkRequiredField( field, errors ) {
244 var checkGroup, fieldClasses, tempVal, i, placeholder,
245 val = '',
246 fieldID = '',
247 fileID = field.getAttribute( 'data-frmfile' );
248
249 if ( field.type === 'hidden' && fileID === null ) {
250 return errors;
251 }
252
253 if ( field.type === 'checkbox' || field.type === 'radio' ) {
254 checkGroup = jQuery( 'input[name="' + field.name + '"]' ).closest( '.frm_required_field' ).find( 'input:checked' );
255 jQuery( checkGroup ).each( function() {
256 val = this.value;
257 });
258 } else if ( field.type === 'file' || fileID ) {
259 if ( typeof fileID === 'undefined' ) {
260 fileID = getFieldId( field, true );
261 fileID = fileID.replace( 'file', '' );
262 }
263
264 if ( typeof errors[ fileID ] === 'undefined' ) {
265 val = getFileVals( fileID );
266 }
267 fieldID = fileID;
268 } else {
269 fieldClasses = field.className;
270 if ( fieldClasses.indexOf( 'frm_pos_none' ) !== -1 ) {
271 // skip hidden other fields
272 return errors;
273 }
274
275 val = jQuery( field ).val();
276 if ( val === null ) {
277 val = '';
278 } else if ( typeof val !== 'string' ) {
279 tempVal = val;
280 val = '';
281 for ( i = 0; i < tempVal.length; i++ ) {
282 if ( tempVal[i] !== '' ) {
283 val = tempVal[i];
284 }
285 }
286 }
287
288 if ( fieldClasses.indexOf( 'frm_other_input' ) === -1 ) {
289 fieldID = getFieldId( field, true );
290 } else {
291 fieldID = getFieldId( field, false );
292 }
293
294 if ( fieldClasses.indexOf( 'frm_time_select' ) !== -1 ) {
295 // set id for time field
296 fieldID = fieldID.replace( '-H', '' ).replace( '-m', '' );
297 }
298
299 placeholder = field.getAttribute( 'data-frmplaceholder' );
300 if ( placeholder !== null && val === placeholder ) {
301 val = '';
302 }
303 }
304
305 if ( val === '' ) {
306 if ( fieldID === '' ) {
307 fieldID = getFieldId( field, true );
308 }
309 if ( ! ( fieldID in errors ) ) {
310 errors[ fieldID ] = getFieldValidationMessage( field, 'data-reqmsg' );
311 }
312 }
313
314 return errors;
315 }
316
317 function getFileVals( fileID ) {
318 var val = '',
319 fileFields = jQuery( 'input[name="file' + fileID + '"], input[name="file' + fileID + '[]"], input[name^="item_meta[' + fileID + ']"]' );
320
321 fileFields.each( function() {
322 if ( val === '' ) {
323 val = this.value;
324 }
325 });
326 return val;
327 }
328
329 function checkUrlField( field, errors ) {
330 var fieldID,
331 url = field.value;
332
333 if ( url !== '' && ! /^http(s)?:\/\/(?:localhost|(?:[\da-z\.-]+\.[\da-z\.-]+))/i.test( url ) ) {
334 fieldID = getFieldId( field, true );
335 if ( ! ( fieldID in errors ) ) {
336 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
337 }
338 }
339 return errors;
340 }
341
342 function checkEmailField( field, errors ) {
343 var fieldID = getFieldId( field, true ),
344 pattern = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i;
345
346 // validate the current field we're editing first
347 if ( '' !== field.value && pattern.test( field.value ) === false ) {
348 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
349 }
350
351 confirmField( field, errors );
352 return errors;
353 }
354
355 function checkPasswordField( field, errors ) {
356 confirmField( field, errors );
357 return errors;
358 }
359
360 function confirmField( field, errors ) {
361 var value, confirmValue, firstField,
362 fieldID = getFieldId( field, true ),
363 strippedId = field.id.replace( 'conf_', '' ),
364 strippedFieldID = fieldID.replace( 'conf_', '' ),
365 confirmField = document.getElementById( strippedId.replace( 'field_', 'field_conf_' ) );
366
367 if ( confirmField === null || typeof errors[ 'conf_' + strippedFieldID ] !== 'undefined' ) {
368 return;
369 }
370
371 if ( fieldID !== strippedFieldID ) {
372 firstField = document.getElementById( strippedId );
373 value = firstField.value;
374 confirmValue = confirmField.value;
375 if ( '' !== value && '' !== confirmValue && value !== confirmValue ) {
376 errors[ 'conf_' + strippedFieldID ] = getFieldValidationMessage( confirmField, 'data-confmsg' );
377 }
378 } else {
379 validateField( 'conf_' + strippedFieldID, confirmField );
380 }
381 }
382
383 function checkNumberField( field, errors ) {
384 var fieldID,
385 number = field.value;
386
387 if ( number !== '' && isNaN( number / 1 ) !== false ) {
388 fieldID = getFieldId( field, true );
389 if ( ! ( fieldID in errors ) ) {
390 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
391 }
392 }
393 return errors;
394 }
395
396 function checkPatternField( field, errors ) {
397 var fieldID,
398 text = field.value,
399 format = getFieldValidationMessage( field, 'pattern' );
400
401 if ( format !== '' && text !== '' ) {
402 fieldID = getFieldId( field, true );
403 if ( ! ( fieldID in errors ) ) {
404 format = new RegExp( '^' + format + '$', 'i' );
405 if ( format.test( text ) === false ) {
406 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
407 }
408 }
409 }
410 return errors;
411 }
412
413 function hasInvisibleRecaptcha( object ) {
414 var recaptcha, recaptchaID, alreadyChecked;
415
416 if ( isGoingToPrevPage( object ) ) {
417 return false;
418 }
419
420 recaptcha = jQuery( object ).find( '.frm-g-recaptcha[data-size="invisible"], .g-recaptcha[data-size="invisible"]' );
421 if ( recaptcha.length ) {
422 recaptchaID = recaptcha.data( 'rid' );
423 alreadyChecked = grecaptcha.getResponse( recaptchaID );
424 if ( alreadyChecked.length === 0 ) {
425 return recaptcha;
426 } else {
427 return false;
428 }
429 } else {
430 return false;
431 }
432 }
433
434 function executeInvisibleRecaptcha( invisibleRecaptcha ) {
435 var recaptchaID = invisibleRecaptcha.data( 'rid' );
436 grecaptcha.reset( recaptchaID );
437 grecaptcha.execute( recaptchaID );
438 }
439
440 function validateRecaptcha( form, errors ) {
441 var recaptchaID, response, fieldContainer, fieldID,
442 $recaptcha = jQuery( form ).find( '.frm-g-recaptcha' );
443 if ( $recaptcha.length ) {
444 recaptchaID = $recaptcha.data( 'rid' );
445
446 try {
447 response = grecaptcha.getResponse( recaptchaID );
448 } catch ( e ) {
449 if ( jQuery( form ).find( 'input[name="recaptcha_checked"]' ).length ) {
450 return errors;
451 } else {
452 response = '';
453 }
454 }
455
456 if ( response.length === 0 ) {
457 fieldContainer = $recaptcha.closest( '.frm_form_field' );
458 fieldID = fieldContainer.attr( 'id' ).replace( 'frm_field_', '' ).replace( '_container', '' );
459 errors[ fieldID ] = '';
460 }
461 }
462 return errors;
463 }
464
465 function getFieldValidationMessage( field, messageType ) {
466 var msg = field.getAttribute( messageType );
467 if ( msg === null ) {
468 msg = '';
469 }
470 return msg;
471 }
472
473 function shouldJSValidate( object ) {
474 var validate = jQuery( object ).hasClass( 'frm_js_validate' );
475 if ( validate && typeof frmProForm !== 'undefined' && ( frmProForm.savingDraft( object ) || frmProForm.goingToPreviousPage( object ) ) ) {
476 validate = false;
477 }
478
479 return validate;
480 }
481
482 function getFormErrors( object, action ) {
483 var fieldset;
484
485 if ( typeof action === 'undefined' ) {
486 jQuery( object ).find( 'input[name="frm_action"]' ).val();
487 }
488
489 fieldset = jQuery( object ).find( '.frm_form_field' );
490 fieldset.addClass( 'frm_doing_ajax' );
491 jQuery.ajax({
492 type: 'POST', url: frm_js.ajax_url,
493 data: jQuery( object ).serialize() + '&action=frm_entries_' + action + '&nonce=' + frm_js.nonce,
494 success: function( response ) {
495 var formID, replaceContent, pageOrder, formReturned, contSubmit, delay,
496 $fieldCont, key, inCollapsedSection, frmTrigger,
497 defaultResponse = { 'content': '', 'errors': {}, 'pass': false };
498 if ( response === null ) {
499 response = defaultResponse;
500 }
501
502 response = response.replace( /^\s+|\s+$/g, '' );
503 if ( response.indexOf( '{' ) === 0 ) {
504 response = JSON.parse( response );
505 } else {
506 response = defaultResponse;
507 }
508
509 if ( typeof response.redirect !== 'undefined' ) {
510 jQuery( document ).trigger( 'frmBeforeFormRedirect', [ object, response ]);
511 window.location = response.redirect;
512 } else if ( response.content !== '' ) {
513 // the form or success message was returned
514
515 removeSubmitLoading( jQuery( object ) );
516 if ( frm_js.offset != -1 ) {
517 frmFrontForm.scrollMsg( jQuery( object ), false );
518 }
519 formID = jQuery( object ).find( 'input[name="form_id"]' ).val();
520 response.content = response.content.replace( / frm_pro_form /g, ' frm_pro_form frm_no_hide ' );
521 replaceContent = jQuery( object ).closest( '.frm_forms' );
522 removeAddedScripts( replaceContent, formID );
523 delay = maybeSlideOut( replaceContent, response.content );
524
525 setTimeout(
526 function() {
527 var container, input, previousInput;
528
529 replaceContent.replaceWith( response.content );
530
531 addUrlParam( response );
532
533 if ( typeof frmThemeOverride_frmAfterSubmit === 'function' ) { // eslint-disable-line camelcase
534 pageOrder = jQuery( 'input[name="frm_page_order_' + formID + '"]' ).val();
535 formReturned = jQuery( response.content ).find( 'input[name="form_id"]' ).val();
536 frmThemeOverride_frmAfterSubmit( formReturned, pageOrder, response.content, object );
537 }
538
539 if ( typeof response.recaptcha !== 'undefined' ) {
540 container = jQuery( '#frm_form_' + formID + '_container' ).find( '.frm_fields_container' );
541 input = '<input type="hidden" name="recaptcha_checked" value="' + response.recaptcha + '">';
542 previousInput = container.find( 'input[name="recaptcha_checked"]' );
543
544 if ( previousInput.length ) {
545 previousInput.replaceWith( input );
546 } else {
547 container.append( input );
548 }
549 }
550
551 afterFormSubmitted( object, response );
552 },
553 delay
554 );
555 } else if ( Object.keys( response.errors ).length ) {
556 // errors were returned
557
558 removeSubmitLoading( jQuery( object ), 'enable' );
559
560 //show errors
561 contSubmit = true;
562 removeAllErrors();
563
564 $fieldCont = null;
565
566 for ( key in response.errors ) {
567 $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
568
569 if ( $fieldCont.length ) {
570 if ( ! $fieldCont.is( ':visible' ) ) {
571 inCollapsedSection = $fieldCont.closest( '.frm_toggle_container' );
572 if ( inCollapsedSection.length ) {
573 frmTrigger = inCollapsedSection.prev();
574 if ( ! frmTrigger.hasClass( 'frm_trigger' ) ) {
575 // If the frmTrigger object is the section description, check to see if the previous element is the trigger
576 frmTrigger = frmTrigger.prev( '.frm_trigger' );
577 }
578 frmTrigger.trigger( 'click' );
579 }
580 }
581
582 if ( $fieldCont.is( ':visible' ) ) {
583 addFieldError( $fieldCont, key, response.errors );
584 contSubmit = false;
585 }
586 }
587 }
588
589 jQuery( object ).find( '.frm-g-recaptcha, .g-recaptcha' ).each( function() {
590 var $recaptcha = jQuery( this ),
591 recaptchaID = $recaptcha.data( 'rid' );
592
593 if ( typeof grecaptcha !== 'undefined' && grecaptcha ) {
594 if ( recaptchaID ) {
595 grecaptcha.reset( recaptchaID );
596 } else {
597 grecaptcha.reset();
598 }
599 }
600 });
601
602 jQuery( document ).trigger( 'frmFormErrors', [ object, response ]);
603
604 fieldset.removeClass( 'frm_doing_ajax' );
605 scrollToFirstField( object );
606
607 if ( contSubmit ) {
608 object.submit();
609 } else {
610 jQuery( object ).prepend( response.error_message );
611 }
612 } else {
613 // there may have been a plugin conflict, or the form is not set to submit with ajax
614
615 showFileLoading( object );
616
617 object.submit();
618 }
619 },
620 error: function() {
621 jQuery( object ).find( 'input[type="submit"], input[type="button"]' ).prop( 'disabled', false );
622 object.submit();
623 }
624 });
625 }
626
627 function afterFormSubmitted( object, response ) {
628 var formCompleted = jQuery( response.content ).find( '.frm_message' );
629 if ( formCompleted.length ) {
630 jQuery( document ).trigger( 'frmFormComplete', [ object, response ]);
631 } else {
632 jQuery( document ).trigger( 'frmPageChanged', [ object, response ]);
633 }
634 }
635
636 function removeAddedScripts( formContainer, formID ) {
637 var endReplace = jQuery( '.frm_end_ajax_' + formID );
638 if ( endReplace.length ) {
639 formContainer.nextUntil( '.frm_end_ajax_' + formID ).remove();
640 endReplace.remove();
641 }
642 }
643
644 function maybeSlideOut( oldContent, newContent ) {
645 var c,
646 newClass = 'frm_slideout';
647 if ( newContent.indexOf( ' frm_slide' ) !== -1 ) {
648 c = oldContent.children();
649 if ( newContent.indexOf( ' frm_going_back' ) !== -1 ) {
650 newClass += ' frm_going_back';
651 }
652 c.removeClass( 'frm_going_back' );
653 c.addClass( newClass );
654 return 300;
655 }
656 return 0;
657 }
658
659 function addUrlParam( response ) {
660 var url;
661 if ( history.pushState && typeof response.page !== 'undefined' ) {
662 url = addQueryVar( 'frm_page', response.page );
663 window.history.pushState({ 'html': response.html }, '', '?' + url );
664 }
665 }
666
667 function addQueryVar( key, value ) {
668 var kvp, i, x;
669
670 key = encodeURI( key );
671 value = encodeURI( value );
672
673 kvp = document.location.search.substr( 1 ).split( '&' );
674
675 i = kvp.length;
676 while ( i-- ) {
677 x = kvp[i].split( '=' );
678
679 if ( x[0] == key ) {
680 x[1] = value;
681 kvp[i] = x.join( '=' );
682 break;
683 }
684 }
685
686 if ( i < 0 ) {
687 kvp[ kvp.length ] = [ key, value ].join( '=' );
688 }
689
690 return kvp.join( '&' );
691 }
692
693 function addFieldError( $fieldCont, key, jsErrors ) {
694 var input, id, describedBy;
695 if ( $fieldCont.length && $fieldCont.is( ':visible' ) ) {
696 $fieldCont.addClass( 'frm_blank_field' );
697 input = $fieldCont.find( 'input, select, textarea' );
698 id = 'frm_error_field_' + key;
699 describedBy = input.attr( 'aria-describedby' );
700
701 if ( typeof frmThemeOverride_frmPlaceError === 'function' ) { // eslint-disable-line camelcase
702 frmThemeOverride_frmPlaceError( key, jsErrors );
703 } else {
704 $fieldCont.append( '<div class="frm_error" id="' + id + '">' + jsErrors[key] + '</div>' );
705
706 if ( typeof describedBy === 'undefined' ) {
707 describedBy = id;
708 } else if ( describedBy.indexOf( id ) === -1 ) {
709 describedBy = describedBy + ' ' + id;
710 }
711 input.attr( 'aria-describedby', describedBy );
712 }
713 input.attr( 'aria-invalid', true );
714
715 jQuery( document ).trigger( 'frmAddFieldError', [ $fieldCont, key, jsErrors ]);
716 }
717 }
718
719 function removeFieldError( $fieldCont ) {
720 var errorMessage = $fieldCont.find( '.frm_error' ),
721 errorId = errorMessage.attr( 'id' ),
722 input = $fieldCont.find( 'input, select, textarea' ),
723 describedBy = input.attr( 'aria-describedby' );
724
725 $fieldCont.removeClass( 'frm_blank_field has-error' );
726 errorMessage.remove();
727 input.attr( 'aria-invalid', false );
728
729 if ( typeof describedBy !== 'undefined' ) {
730 describedBy = describedBy.replace( errorId, '' );
731 input.attr( 'aria-describedby', describedBy );
732 }
733 }
734
735 function removeAllErrors() {
736 jQuery( '.form-field' ).removeClass( 'frm_blank_field has-error' );
737 jQuery( '.form-field .frm_error' ).replaceWith( '' );
738 jQuery( '.frm_error_style' ).remove();
739 }
740
741 function scrollToFirstField( object ) {
742 var field = jQuery( object ).find( '.frm_blank_field' ).first();
743 if ( field.length ) {
744 frmFrontForm.scrollMsg( field, object, true );
745 }
746 }
747
748 function showSubmitLoading( $object ) {
749 showLoadingIndicator( $object );
750 disableSubmitButton( $object );
751 disableSaveDraft( $object );
752 }
753
754 function showLoadingIndicator( $object ) {
755 if ( ! $object.hasClass( 'frm_loading_form' ) && ! $object.hasClass( 'frm_loading_prev' ) ) {
756 addLoadingClass( $object );
757 $object.trigger( 'frmStartFormLoading' );
758 }
759 }
760
761 function addLoadingClass( $object ) {
762 var loadingClass = isGoingToPrevPage( $object ) ? 'frm_loading_prev' : 'frm_loading_form';
763
764 $object.addClass( loadingClass );
765 }
766
767 function isGoingToPrevPage( $object ) {
768 return ( typeof frmProForm !== 'undefined' && frmProForm.goingToPreviousPage( $object ) );
769 }
770
771 function removeSubmitLoading( $object, enable, processesRunning ) {
772 var loadingForm;
773
774 if ( processesRunning > 0 ) {
775 return;
776 }
777
778 loadingForm = jQuery( '.frm_loading_form' );
779 loadingForm.removeClass( 'frm_loading_form' );
780 loadingForm.removeClass( 'frm_loading_prev' );
781
782 loadingForm.trigger( 'frmEndFormLoading' );
783
784 if ( enable === 'enable' ) {
785 enableSubmitButton( loadingForm );
786 enableSaveDraft( loadingForm );
787 }
788 }
789
790 function showFileLoading( object ) {
791 var fileval,
792 loading = document.getElementById( 'frm_loading' );
793 if ( loading !== null ) {
794 fileval = jQuery( object ).find( 'input[type=file]' ).val();
795 if ( typeof fileval !== 'undefined' && fileval !== '' ) {
796 setTimeout( function() {
797 jQuery( loading ).fadeIn( 'slow' );
798 }, 2000 );
799 }
800 }
801 }
802
803 function clearDefault() {
804 /*jshint validthis:true */
805 toggleDefault( jQuery( this ), 'clear' );
806 }
807
808 function replaceDefault() {
809 /*jshint validthis:true */
810 toggleDefault( jQuery( this ), 'replace' );
811 }
812
813 function toggleDefault( $thisField, e ) {
814 // TODO: Fix this for a default value that is a number or array
815 var thisVal,
816 v = $thisField.data( 'frmval' ).replace( /(\n|\r\n)/g, '\r' );
817 if ( v === '' || typeof v === 'undefined' ) {
818 return false;
819 }
820 thisVal = $thisField.val().replace( /(\n|\r\n)/g, '\r' );
821
822 if ( 'replace' === e ) {
823 if ( thisVal === '' ) {
824 $thisField.addClass( 'frm_default' ).val( v );
825 }
826 } else if ( thisVal == v ) {
827 $thisField.removeClass( 'frm_default' ).val( '' );
828 }
829 }
830
831 function resendEmail() {
832 /*jshint validthis:true */
833 var $link = jQuery( this ),
834 entryId = this.getAttribute( 'data-eid' ),
835 formId = this.getAttribute( 'data-fid' ),
836 label = $link.find( '.frm_link_label' );
837 if ( label.length < 1 ) {
838 label = $link;
839 }
840 label.append( '<span class="frm-wait"></span>' );
841
842 jQuery.ajax({
843 type: 'POST',
844 url: frm_js.ajax_url,
845 data: {
846 action: 'frm_entries_send_email',
847 entry_id: entryId,
848 form_id: formId,
849 nonce: frm_js.nonce
850 },
851 success: function( msg ) {
852 var admin = document.getElementById( 'wpbody' );
853 if ( admin === null ) {
854 label.html( msg );
855 } else {
856 label.html( '' );
857 $link.after( msg );
858 }
859 }
860 });
861 return false;
862 }
863
864 /**********************************************
865 * General Helpers
866 *********************************************/
867
868 function confirmClick() {
869 /*jshint validthis:true */
870 var message = jQuery( this ).data( 'frmconfirm' );
871 return confirm( message );
872 }
873
874 function toggleDiv() {
875 /*jshint validthis:true */
876 var div = jQuery( this ).data( 'frmtoggle' );
877 if ( jQuery( div ).is( ':visible' ) ) {
878 jQuery( div ).slideUp( 'fast' );
879 } else {
880 jQuery( div ).slideDown( 'fast' );
881 }
882 return false;
883 }
884
885 /**********************************************
886 * Fallback functions
887 *********************************************/
888
889 function addIndexOfFallbackForIE8() {
890 var len, from;
891
892 if ( ! Array.prototype.indexOf ) {
893 Array.prototype.indexOf = function( elt /*, from*/ ) {
894 len = this.length >>> 0;
895
896 from = Number( arguments[1]) || 0;
897 from = ( from < 0 ) ? Math.ceil( from ) : Math.floor( from );
898 if ( from < 0 ) {
899 from += len;
900 }
901
902 for ( ; from < len; from++ ) {
903 if ( from in this && this[from] === elt ) {
904 return from;
905 }
906 }
907 return -1;
908 };
909 }
910 }
911
912 function addTrimFallbackForIE8() {
913 if ( typeof String.prototype.trim !== 'function' ) {
914 String.prototype.trim = function() {
915 return this.replace( /^\s+|\s+$/g, '' );
916 };
917 }
918 }
919
920 function addFilterFallbackForIE8() {
921 var t, len, res, thisp, i, val;
922
923 if ( ! Array.prototype.filter ) {
924
925 Array.prototype.filter = function( fun /*, thisp */ ) {
926
927 if ( this === void 0 || this === null ) {
928 throw new TypeError();
929 }
930
931 t = Object( this );
932 len = t.length >>> 0;
933 if ( typeof fun !== 'function' ) {
934 throw new TypeError();
935 }
936
937 res = [];
938 thisp = arguments[1];
939 for ( i = 0; i < len; i++ ) {
940 if ( i in t ) {
941 val = t[i]; // in case fun mutates this
942 if ( fun.call( thisp, val, i, t ) ) {
943 res.push( val );
944 }
945 }
946 }
947
948 return res;
949 };
950 }
951 }
952
953 function addKeysFallbackForIE8() {
954 var keys, i;
955
956 if ( ! Object.keys ) {
957 Object.keys = function( obj ) {
958 keys = [];
959
960 for ( i in obj ) {
961 if ( obj.hasOwnProperty( i ) ) {
962 keys.push( i );
963 }
964 }
965
966 return keys;
967 };
968 }
969 }
970
971 /**
972 * Check for -webkit-box-shadow css value for input:-webkit-autofill selector.
973 * If this is a match, the User is autofilling the input on a Webkit browser.
974 * We want to delete the Honeypot field, otherwise it will get triggered as spam on autocomplete.
975 */
976 function onHoneypotFieldChange() {
977 var css = jQuery( this ).css( 'box-shadow' );
978 if ( css.match( /inset/ ) ) {
979 this.parentNode.removeChild( this );
980 }
981 }
982
983 return {
984 init: function() {
985 jQuery( document ).off( 'submit.formidable', '.frm-show-form' );
986 jQuery( document ).on( 'submit.formidable', '.frm-show-form', frmFrontForm.submitForm );
987
988 jQuery( '.frm-show-form input[onblur], .frm-show-form textarea[onblur]' ).each( function() {
989 if ( jQuery( this ).val() === '' ) {
990 jQuery( this ).trigger( 'blur' );
991 }
992 });
993
994 jQuery( document ).on( 'focus', '.frm_toggle_default', clearDefault );
995 jQuery( document ).on( 'blur', '.frm_toggle_default', replaceDefault );
996 jQuery( '.frm_toggle_default' ).trigger( 'blur' );
997
998 jQuery( document.getElementById( 'frm_resend_email' ) ).on( 'click', resendEmail );
999
1000 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 );
1001 jQuery( document ).on( 'change keyup', '.frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea', maybeShowLabel );
1002
1003 jQuery( document ).on( 'change', '[id^=frm_email_]', onHoneypotFieldChange );
1004
1005 jQuery( document ).on( 'click', 'a[data-frmconfirm]', confirmClick );
1006 jQuery( 'a[data-frmtoggle]' ).on( 'click', toggleDiv );
1007
1008 // Add fallbacks for the beloved IE8
1009 addIndexOfFallbackForIE8();
1010 addTrimFallbackForIE8();
1011 addFilterFallbackForIE8();
1012 addKeysFallbackForIE8();
1013 },
1014
1015 getFieldId: function( field, fullID ) {
1016 return getFieldId( field, fullID );
1017 },
1018
1019 renderRecaptcha: function( captcha ) {
1020 var formID, recaptchaID,
1021 size = captcha.getAttribute( 'data-size' ),
1022 rendered = captcha.getAttribute( 'data-rid' ) !== null,
1023 params = {
1024 'sitekey': captcha.getAttribute( 'data-sitekey' ),
1025 'size': size,
1026 'theme': captcha.getAttribute( 'data-theme' )
1027 };
1028
1029 if ( rendered ) {
1030 return;
1031 }
1032
1033 if ( size === 'invisible' ) {
1034 formID = jQuery( captcha ).closest( 'form' ).find( 'input[name="form_id"]' ).val();
1035 jQuery( captcha ).closest( '.frm_form_field .frm_primary_label' ).hide();
1036 params.callback = function( token ) {
1037 frmFrontForm.afterRecaptcha( token, formID );
1038 };
1039 }
1040
1041 recaptchaID = grecaptcha.render( captcha.id, params );
1042
1043 captcha.setAttribute( 'data-rid', recaptchaID );
1044 },
1045
1046 afterSingleRecaptcha: function() {
1047 var object = jQuery( '.frm-show-form .g-recaptcha' ).closest( 'form' )[0];
1048 frmFrontForm.submitFormNow( object );
1049 },
1050
1051 afterRecaptcha: function( token, formID ) {
1052 var object = jQuery( '#frm_form_' + formID + '_container form' )[0];
1053 frmFrontForm.submitFormNow( object );
1054 },
1055
1056 submitForm: function( e ) {
1057 frmFrontForm.submitFormManual( e, this );
1058 },
1059
1060 submitFormManual: function( e, object ) {
1061 var isPro, errors,
1062 invisibleRecaptcha = hasInvisibleRecaptcha( object ),
1063 classList = object.className.trim().split( /\s+/gi );
1064
1065 if ( classList && invisibleRecaptcha.length < 1 ) {
1066 isPro = classList.indexOf( 'frm_pro_form' ) > -1;
1067 if ( ! isPro ) {
1068 return;
1069 }
1070 }
1071
1072 if ( jQuery( 'body' ).hasClass( 'wp-admin' ) && jQuery( object ).closest( '.frmapi-form' ).length < 1 ) {
1073 return;
1074 }
1075
1076 e.preventDefault();
1077
1078 if ( typeof frmProForm !== 'undefined' && typeof frmProForm.submitAllowed === 'function' ) {
1079 if ( ! frmProForm.submitAllowed( object ) ) {
1080 return;
1081 }
1082 }
1083
1084 if ( invisibleRecaptcha.length ) {
1085 showLoadingIndicator( jQuery( object ) );
1086 executeInvisibleRecaptcha( invisibleRecaptcha );
1087 } else {
1088
1089 errors = frmFrontForm.validateFormSubmit( object );
1090
1091 if ( Object.keys( errors ).length === 0 ) {
1092 showSubmitLoading( jQuery( object ) );
1093
1094 frmFrontForm.submitFormNow( object, classList );
1095 }
1096 }
1097 },
1098
1099 submitFormNow: function( object ) {
1100 var hasFileFields,
1101 classList = object.className.trim().split( /\s+/gi );
1102 if ( classList.indexOf( 'frm_ajax_submit' ) > -1 ) {
1103 hasFileFields = jQuery( object ).find( 'input[type="file"]' ).filter( function() {
1104 return !! this.value;
1105 }).length;
1106 if ( hasFileFields < 1 ) {
1107 action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
1108 frmFrontForm.checkFormErrors( object, action );
1109 } else {
1110 object.submit();
1111 }
1112 } else {
1113 object.submit();
1114 }
1115 },
1116
1117 validateFormSubmit: function( object ) {
1118 if ( typeof tinyMCE !== 'undefined' && jQuery( object ).find( '.wp-editor-wrap' ).length ) {
1119 tinyMCE.triggerSave();
1120 }
1121
1122 jsErrors = [];
1123
1124 if ( shouldJSValidate( object ) ) {
1125 frmFrontForm.getAjaxFormErrors( object );
1126
1127 if ( Object.keys( jsErrors ).length ) {
1128 frmFrontForm.addAjaxFormErrors( object );
1129 }
1130 }
1131
1132 return jsErrors;
1133 },
1134
1135 getAjaxFormErrors: function( object ) {
1136 var customErrors, key;
1137
1138 jsErrors = validateForm( object );
1139 if ( typeof frmThemeOverride_jsErrors === 'function' ) { // eslint-disable-line camelcase
1140 action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
1141 customErrors = frmThemeOverride_jsErrors( action, object );
1142 if ( Object.keys( customErrors ).length ) {
1143 for ( key in customErrors ) {
1144 jsErrors[ key ] = customErrors[ key ];
1145 }
1146 }
1147 }
1148
1149 return jsErrors;
1150 },
1151
1152 addAjaxFormErrors: function( object ) {
1153 var key, $fieldCont;
1154 removeAllErrors();
1155
1156 for ( key in jsErrors ) {
1157 $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
1158
1159 if ( $fieldCont.length ) {
1160 addFieldError( $fieldCont, key, jsErrors );
1161 } else {
1162 // we are unable to show the error, so remove it
1163 delete jsErrors[ key ];
1164 }
1165 }
1166
1167 scrollToFirstField( object );
1168 },
1169
1170 checkFormErrors: function( object, action ) {
1171 getFormErrors( object, action );
1172 },
1173
1174 checkRequiredField: function( field, errors ) {
1175 return checkRequiredField( field, errors );
1176 },
1177
1178 showSubmitLoading: function( $object ) {
1179 showSubmitLoading( $object );
1180 },
1181
1182 removeSubmitLoading: function( $object, enable, processesRunning ) {
1183 removeSubmitLoading( $object, enable, processesRunning );
1184 },
1185
1186 scrollToID: function( id ) {
1187 var object = jQuery( document.getElementById( id ) );
1188 frmFrontForm.scrollMsg( object, false );
1189 },
1190
1191 scrollMsg: function( id, object, animate ) {
1192 var newPos, m, b, screenTop, screenBottom,
1193 scrollObj = '';
1194 if ( typeof object === 'undefined' ) {
1195 scrollObj = jQuery( document.getElementById( 'frm_form_' + id + '_container' ) );
1196 if ( scrollObj.length < 1 ) {
1197 return;
1198 }
1199 } else if ( typeof id === 'string' ) {
1200 scrollObj = jQuery( object ).find( '#frm_field_' + id + '_container' );
1201 } else {
1202 scrollObj = id;
1203 }
1204
1205 jQuery( scrollObj ).trigger( 'focus' );
1206 newPos = scrollObj.offset().top;
1207 if ( ! newPos || frm_js.offset === '-1' ) {
1208 return;
1209 }
1210 newPos = newPos - frm_js.offset;
1211
1212 m = jQuery( 'html' ).css( 'margin-top' );
1213 b = jQuery( 'body' ).css( 'margin-top' );
1214 if ( m || b ) {
1215 newPos = newPos - parseInt( m ) - parseInt( b );
1216 }
1217
1218 if ( newPos && window.innerHeight ) {
1219 screenTop = document.documentElement.scrollTop || document.body.scrollTop;
1220 screenBottom = screenTop + window.innerHeight;
1221
1222 if ( newPos > screenBottom || newPos < screenTop ) {
1223 // Not in view
1224 if ( typeof animate === 'undefined' ) {
1225 jQuery( window ).scrollTop( newPos );
1226 } else {
1227 jQuery( 'html,body' ).animate({ scrollTop: newPos }, 500 );
1228 }
1229 return false;
1230 }
1231 }
1232 },
1233
1234 fieldValueChanged: function( e ) {
1235 /*jshint validthis:true */
1236
1237 var fieldId = frmFrontForm.getFieldId( this, false );
1238 if ( ! fieldId || typeof fieldId === 'undefined' ) {
1239 return;
1240 }
1241
1242 if ( e.frmTriggered && e.frmTriggered == fieldId ) {
1243 return;
1244 }
1245
1246 jQuery( document ).trigger( 'frmFieldChanged', [ this, fieldId, e ]);
1247
1248 if ( e.selfTriggered !== true ) {
1249 maybeValidateChange( fieldId, this );
1250 }
1251 },
1252
1253 savingDraft: function( object ) {
1254 console.warn( 'DEPRECATED: function frmFrontForm.savingDraft in v3.0 use frmProForm.savingDraft' );
1255 if ( typeof frmProForm !== 'undefined' ) {
1256 return frmProForm.savingDraft( object );
1257 }
1258 },
1259
1260 goingToPreviousPage: function( object ) {
1261 console.warn( 'DEPRECATED: function frmFrontForm.goingToPreviousPage in v3.0 use frmProForm.goingToPreviousPage' );
1262 if ( typeof frmProForm !== 'undefined' ) {
1263 return frmProForm.goingToPreviousPage( object );
1264 }
1265 },
1266
1267 hideOrShowFields: function() {
1268 console.warn( 'DEPRECATED: function frmFrontForm.hideOrShowFields in v3.0 use frmProForm.hideOrShowFields' );
1269 if ( typeof frmProForm !== 'undefined' ) {
1270 frmProForm.hideOrShowFields();
1271 }
1272 },
1273
1274 hidePreviouslyHiddenFields: function() {
1275 console.warn( 'DEPRECATED: function frmFrontForm.hidePreviouslyHiddenFields in v3.0 use frmProForm.hidePreviouslyHiddenFields' );
1276 if ( typeof frmProForm !== 'undefined' ) {
1277 frmProForm.hidePreviouslyHiddenFields();
1278 }
1279 },
1280
1281 checkDependentDynamicFields: function( ids ) {
1282 console.warn( 'DEPRECATED: function frmFrontForm.checkDependentDynamicFields in v3.0 use frmProForm.checkDependentDynamicFields' );
1283 if ( typeof frmProForm !== 'undefined' ) {
1284 frmProForm.checkDependentDynamicFields( ids );
1285 }
1286 },
1287
1288 checkDependentLookupFields: function( ids ) {
1289 console.warn( 'DEPRECATED: function frmFrontForm.checkDependentLookupFields in v3.0 use frmProForm.checkDependentLookupFields' );
1290 if ( typeof frmProForm !== 'undefined' ) {
1291 frmProForm.checkDependentLookupFields( ids );
1292 }
1293 },
1294
1295 loadGoogle: function() {
1296 console.warn( 'DEPRECATED: function frmFrontForm.loadGoogle in v3.0 use frmProForm.loadGoogle' );
1297 frmProForm.loadGoogle();
1298 },
1299
1300 escapeHtml: function( text ) {
1301 return text
1302 .replace( /&/g, '&amp;' )
1303 .replace( /</g, '&lt;' )
1304 .replace( />/g, '&gt;' )
1305 .replace( /"/g, '&quot;' )
1306 .replace( /'/g, '&#039;' );
1307 },
1308
1309 invisible: function( classes ) {
1310 jQuery( classes ).css( 'visibility', 'hidden' );
1311 },
1312
1313 visible: function( classes ) {
1314 jQuery( classes ).css( 'visibility', 'visible' );
1315 }
1316 };
1317 }
1318 frmFrontForm = frmFrontFormJS();
1319
1320 jQuery( document ).ready( function() {
1321 frmFrontForm.init();
1322 });
1323
1324 function frmRecaptcha() {
1325 var c, cl,
1326 captchas = jQuery( '.frm-g-recaptcha' );
1327 for ( c = 0, cl = captchas.length; c < cl; c++ ) {
1328 frmFrontForm.renderRecaptcha( captchas[c]);
1329 }
1330 }
1331
1332 function frmAfterRecaptcha( token ) {
1333 frmFrontForm.afterSingleRecaptcha( token );
1334 }
1335
1336 function frmUpdateField( entryId, fieldId, value, message, num ) {
1337 jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).html( '<span class="frm-loading-img"></span>' );
1338 jQuery.ajax({
1339 type: 'POST',
1340 url: frm_js.ajax_url,
1341 data: {
1342 action: 'frm_entries_update_field_ajax',
1343 entry_id: entryId,
1344 field_id: fieldId,
1345 value: value,
1346 nonce: frm_js.nonce
1347 },
1348 success: function() {
1349 if ( message.replace( /^\s+|\s+$/g, '' ) === '' ) {
1350 jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).fadeOut( 'slow' );
1351 } else {
1352 jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).replaceWith( message );
1353 }
1354 }
1355 });
1356 }
1357
1358 function frmDeleteEntry( entryId, prefix ) {
1359 console.warn( 'DEPRECATED: function frmDeleteEntry in v2.0.13 use frmFrontForm.deleteEntry' );
1360 jQuery( document.getElementById( 'frm_delete_' + entryId ) ).replaceWith( '<span class="frm-loading-img" id="frm_delete_' + entryId + '"></span>' );
1361 jQuery.ajax({
1362 type: 'POST',
1363 url: frm_js.ajax_url,
1364 data: {
1365 action: 'frm_entries_destroy',
1366 entry: entryId,
1367 nonce: frm_js.nonce
1368 },
1369 success: function( html ) {
1370 if ( html.replace( /^\s+|\s+$/g, '' ) === 'success' ) {
1371 jQuery( document.getElementById( prefix + entryId ) ).fadeOut( 'slow' );
1372 } else {
1373 jQuery( document.getElementById( 'frm_delete_' + entryId ) ).replaceWith( html );
1374 }
1375 }
1376 });
1377 }
1378
1379 function frmOnSubmit( e ) {
1380 console.warn( 'DEPRECATED: function frmOnSubmit in v2.0 use frmFrontForm.submitForm' );
1381 frmFrontForm.submitForm( e, this );
1382 }
1383
1384 function frm_resend_email( entryId, formId ) { // eslint-disable-line camelcase
1385 var $link = jQuery( document.getElementById( 'frm_resend_email' ) );
1386 console.warn( 'DEPRECATED: function frm_resend_email in v2.0' );
1387 $link.append( '<span class="spinner" style="display:inline"></span>' );
1388 jQuery.ajax({
1389 type: 'POST',
1390 url: frm_js.ajax_url,
1391 data: {
1392 action: 'frm_entries_send_email',
1393 entry_id: entryId,
1394 form_id: formId,
1395 nonce: frm_js.nonce
1396 },
1397 success: function( msg ) {
1398 $link.replaceWith( msg );
1399 }
1400 });
1401 }
1402