Usage
This function runs automatically, so it is not called manually. Is this incorrect?
Additional Notes
This function handles the timing of individual form fields as well as overall forms.
It also handles the Google Analytics event tracking for form submission funnels including starting forms, submit attempt, invalid, spam, mail failure, and successful submissions.
Source File
Located in /assets/js/modules/forms.js on line 3.
No Hooks
This function does not have any filters or actions available. Request one?nebula.cf7Functions = async function(){ if ( !jQuery('.wpcf7-form').length ){ return false; } //Indicate a form page on all CF7 forms jQuery('.wpcf7').each(async function(){ //await nebula.yield(); let formID = jQuery(this).attr('id'); nebula.updateFormFlow(formID, '[Form Page View]'); }); jQuery('.wpcf7-form p:empty').remove(); //Remove empty <p> tags within CF7 forms let formStarted = {}; //Replace submit input with a button so a spinner icon can be used instead of the CF7 spin gif (unless it has the class "no-button") jQuery('.wpcf7-form input[type=submit]').each(async function(){ //await nebula.yield(); if ( !jQuery(this).hasClass('no-button') ){ jQuery(this).replaceWith('<button id="submit" type="submit" class="' + nebula.sanitize(jQuery(this).attr('class')) + '">' + nebula.sanitize(jQuery(this).val()) + '</button>'); //Sanitized to prevent XSS } }); //Observe CF7 Forms when they scroll into the viewport try { //Observe the entries that are identified and added later (below) let cf7Observer = new IntersectionObserver(function(entries){ entries.forEach(async function(entry){ //await nebula.yield(); if ( entry.intersectionRatio > 0 ){ let formID = jQuery(entry.target).closest('.wpcf7').attr('id') || jQuery(entry.target).attr('id'); let thisEvent = { event_name: 'cf7_form_impression', event_category: 'CF7 Form', event_action: 'Impression', event_label: jQuery(entry.target).closest('.wpcf7').attr('id') || jQuery(entry.target).attr('id'), form_id: formID, non_interaction: true }; thisEvent.form_flow = nebula.updateFormFlow(formID, '[Impression]'); nebula.dom.document.trigger('nebula_event', thisEvent); if ( typeof gaEventObject === 'function' ){ //If the page is loaded pre-scrolled this may not be available for the very first intersection gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); } window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula_cf7_impression'})); cf7Observer.unobserve(entry.target); //Stop observing the element } }); }, { rootMargin: '0px', threshold: 0.10 }); //Create the entries and add them to the observer jQuery('.wpcf7-form').each(function(){ cf7Observer.observe(jQuery(this)[0]); //Observe the element }); } catch(error){ nebula.help('CF7 Impression Observer: ' + error.message, '/functions/cf7Functions/', true); } //Re-init forms inside Bootstrap modals (to enable AJAX submission) when needed nebula.dom.document.on('shown.bs.modal', function(e){ if ( typeof wpcf7.initForm === 'function' && jQuery(e.target).find('.wpcf7-form').length && !jQuery(e.target).find('.ajax-loader').length ){ //If initForm function exists, and a form is inside the modal, and if it has not yet been initialized (The initForm function adds the ".ajax-loader" span) wpcf7.initForm(jQuery(e.target).find('.wpcf7-form')); } }); //Form starts and field focuses nebula.dom.document.on('focus', '.wpcf7-form input, .wpcf7-form select, .wpcf7-form button, .wpcf7-form textarea', function(e){ let formID = jQuery(this).closest('div.wpcf7').attr('id'); //This wraps the form element let thisField = e.target.name || jQuery(this).closest('.form-group').find('label').text() || e.target.id || 'Unknown'; let fieldInfo = ''; if ( jQuery(this).attr('type') === 'checkbox' || jQuery(this).attr('type') === 'radio' ){ fieldInfo = jQuery(this).attr('value'); } if ( !jQuery(this).hasClass('.ignore-form') && !jQuery(this).find('.ignore-form').length && !jQuery(this).parents('.ignore-form').length ){ let thisEvent = { event: e, event_category: 'CF7 Form', event_action: 'Started Form (Focus)', event_label: formID, form_id: formID, //Actual ID (not Unit Tag) form_field: thisField, form_field_info: fieldInfo }; thisEvent.form_flow = nebula.updateFormFlow(thisEvent.form_id, thisEvent.form_field, thisEvent.form_field_info); //Form starts if ( typeof formStarted[formID] === 'undefined' || !formStarted[formID] ){ thisEvent.event_name = 'form_start'; thisEvent.event_label = 'Began filling out form ID: ' + thisEvent.form_id + ' (' + thisEvent.form_field + ')'; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); nebula.crm('identify', {'form_contacted': 'CF7 (' + thisEvent.form_id + ') Started'}, false); nebula.crm('event', 'Contact Form (' + thisEvent.form_id + ') Started (' + thisEvent.form_field + ')'); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula_form_started'})); //This is the event name to use in the GTM custom event trigger formStarted[formID] = true; } //Track each individual field focuses if ( !jQuery(this).is('button') ){ thisEvent.event_name = 'form_field_focus'; thisEvent.event_action = 'Individual Field Focused'; thisEvent.event_label = 'Focus into ' + thisEvent.form_field + ' (Form ID: ' + thisEvent.form_id + ')'; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); } } nebula.timer(formID, 'start', thisField); //Individual form field timings if ( nebula.timings && typeof nebula.timings[formID] !== 'undefined' && typeof nebula.timings[formID].lap[nebula.timings[formID].laps-1] !== 'undefined' ){ let labelText = ''; if ( jQuery(this).parent('.label') ){ labelText = jQuery(this).parent('.label').text(); } else if ( jQuery('label[for="' + jQuery(this).attr('id') + '"]').length ){ labelText = jQuery('label[for="' + jQuery(this).attr('id') + '"]').text(); } else if ( jQuery(this).attr('placeholder').length ){ labelText = ' "' + jQuery(this).attr('placeholder') + '"'; } gtag('event', 'timing_complete', { name: nebula.timings[formID].lap[nebula.timings[formID].laps-1].name + labelText + ' (Form ID: ' + formID + ')', value: Math.round(nebula.timings[formID].lap[nebula.timings[formID].laps-1].duration), event_category: 'CF7 Form', event_label: 'Amount of time on this input field (until next focus or submit).', }); } }); //CF7 Submit "Attempts" (submissions of any CF7 form on the HTML-side: before REST API) //This metric should always match the "Submit (Processing)" metric or else something is wrong! nebula.dom.document.on('wpcf7beforesubmit', function(e){ try { jQuery(e.target).find('button#submit').addClass('active'); let thisEvent = { event: e, event_name: 'cf7_form_submit_attempt', event_category: 'CF7 Form', event_action: 'Submit (Attempt)', event_label: e.detail.unitTag, form_id: e.detail.contactFormId, //CF7 Form ID post_id: e.detail.containerPostId, //Post/Page ID unit_tag: e.detail.unitTag, //CF7 Unit Tag }; //If timing data exists if ( nebula.timings && typeof nebula.timings[e.detail.unitTag] !== 'undefined' ){ thisEvent.form_time = nebula.timer(e.detail.unitTag, 'lap', 'wpcf7-submit-attempt'); thisEvent.inputs = nebula.timings[e.detail.unitTag].laps + ' inputs'; } thisEvent.form_timing = nebula.millisecondsToString(thisEvent.form_time) + 'ms (' + thisEvent.inputs + ')'; thisEvent.event_label = 'HTML submission attempt for form ID: ' + thisEvent.unit_tag; gtag('set', 'user_properties', { contact_method: 'CF7 Form (Attempt)' }); nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); //This event is required for the notable form metric! window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula_form_submit_attempt'})); //This is the event name to use in the GTM custom event trigger //nebula.fbq('track', 'Lead', {content_name: 'Form Submit (Attempt)'}); nebula.clarity('set', thisEvent.event_category, thisEvent.event_action); } catch { gtag('event', 'exception', { message: '(JS) CF7 Catch (cf7 HTML form submit): ' + error, fatal: false }); nebula.usage('CF7 (HTML) Catch: ' + error); } }); //CF7 Submit "Processing" (CF7 AJAX response after any submit attempt). This triggers after the other submit triggers. //This metric should always match the "Submit (Attempt)" metric or else something is wrong! nebula.dom.document.on('wpcf7submit', function(e){ try { let thisEvent = { event: e, event_name: 'cf7_form_submit_processing', event_category: 'CF7 Form', event_action: 'Submit (Processing)', event_label: e.detail.unitTag, form_id: e.detail.contactFormId, //CF7 Form ID post_id: e.detail.containerPostId, //Post/Page ID unit_tag: e.detail.unitTag, //CF7 Unit Tag }; thisEvent.event_label = 'Submission processing for form ID: ' + thisEvent.unit_tag; thisEvent.form_timing = nebula.millisecondsToString(thisEvent.formTime) + 'ms (' + thisEvent.inputs + ')'; //This is a backup for the HTML form listener nebula.crmForm(thisEvent.unit_tag); //nebula.crmForm() here because it triggers after all others. No nebula.crm() here so it doesn't overwrite the other (more valuable) data. gtag('set', 'user_properties', { contact_method: 'CF7 Form (Processing)' }); nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); //This event is required for the notable form metric! window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula_form_submit_processing'})); //This is the event name to use in the GTM custom event trigger //nebula.fbq('track', 'Lead', {content_name: 'Form Submit (Processing)'}); nebula.clarity('track', 'Lead', {content_name: 'Form Submit (Processing)'}); jQuery('#' + e.detail.unitTag).find('button#submit').removeClass('active'); jQuery('.invalid-feedback').addClass('hidden'); //Reset all of the "live" feedback to let CF7 handle its feedback jQuery('#cf7-privacy-acceptance').trigger('change'); //Until CF7 has a native invalid indicator for the privacy acceptance checkbox, force the Nebula validator here } catch(error){ console.error('Error in wpcf7submit event listener:', error.message, error.stack); //Don't fail silently– output to the console. gtag('event', 'exception', { message: '(JS) CF7 Catch (wpcf7submit): ' + error, fatal: false }); nebula.usage('CF7 Catch: ' + error); } }); //CF7 Invalid (CF7 AJAX response after invalid form) nebula.dom.document.on('wpcf7invalid', function(e){ try { let thisEvent = { event: e, event_name: 'cf7_form_submit_invalid', event_category: 'CF7 Form', event_action: 'Submit (CF7 Invalid)', event_label: e.detail.unitTag, description: '(JS) Invalid form submission for form ID ' + e.detail.unitTag, form_id: e.detail.contactFormId, //CF7 Form ID post_id: e.detail.containerPostId, //Post/Page ID unit_tag: e.detail.unitTag, //CF7 Unit Tag fatal: false }; thisEvent.form_flow = nebula.updateFormFlow(thisEvent.unit_tag, '[Invalid]'); //If timing data exists if ( nebula.timings && typeof nebula.timings[e.detail.unitTag] !== 'undefined' ){ thisEvent.formTime = nebula.timer(e.detail.unitTag, 'lap', 'wpcf7-submit-invalid'); thisEvent.inputs = nebula.timings[e.detail.unitTag].laps + ' inputs'; } thisEvent.event_label = 'Form validation errors occurred on form ID: ' + thisEvent.unit_tag; thisEvent.form_timing = nebula.millisecondsToString(thisEvent.formTime) + 'ms (' + thisEvent.inputs + ')'; //Apply Bootstrap validation classes to invalid fields jQuery('.wpcf7-not-valid').each(function(){ jQuery(this).addClass('is-invalid'); }); gtag('set', 'user_properties', { contact_method: 'CF7 Form (Invalid)' }); nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); //gtag('event', 'exception', nebula.gaEventObject(thisEvent)); //This breaks because thisEvent gets modified by gaEventObject() window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula_form_invalid'})); //This is the event name to use in the GTM custom event trigger nebula.scrollTo(jQuery('.wpcf7-not-valid').first(), 35); //Scroll to the first invalid input nebula.crm('identify', {'form_contacted': 'CF7 (' + thisEvent.unit_tag + ') Invalid'}, false); nebula.crm('event', 'Contact Form (' + thisEvent.unit_tag + ') Invalid'); } catch(error){ console.error('Error in wpcf7invalid event listener:', error.message, error.stack); //Don't fail silently– output to the console. gtag('event', 'exception', { message: '(JS) CF7 Catch (wpcf7invalid): ' + error, fatal: false }); nebula.usage('CF7 Catch: ' + error); } }); //General HTML5 validation errors jQuery('.wpcf7-form input').on('invalid', function(e){ //Would it be more useful to capture all inputs (rather than just CF7)? How would we categorize this in GA? nebula.debounce(function(){ let thisEvent = { event: e, event_name: 'cf7_form_submit_invalid', event_category: 'CF7 Form', event_action: 'Submit (HTML5 Invalid)', event_label: 'General HTML5 validation error', description: 'General HTML5 validation error', }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula_form_invalid'})); //This is the event name to use in the GTM custom event trigger nebula.crm('identify', {'form_contacted': 'CF7 HTML5 Validation Error'}); }, 50, 'invalid form'); }); //CF7 Spam (CF7 AJAX response after spam detection) nebula.dom.document.on('wpcf7spam', function(e){ try { let formInputs = 'Unknown'; if ( nebula.timings[e.detail.unitTag] && nebula.timings[e.detail.unitTag].laps ){ formInputs = nebula.timings[e.detail.unitTag].laps + ' inputs'; } let thisEvent = { event: e, event_name: 'cf7_form_submit_spam', event_category: 'CF7 Form', event_action: 'Submit (Spam)', event_label: e.detail.unitTag, form_id: e.detail.contactFormId, //CF7 Form ID post_id: e.detail.containerPostId, //Post/Page ID unit_tag: e.detail.unitTag, //CF7 Unit Tag description: '(JS) Spam form submission for form ID ' + e.detail.unitTag, form_time: nebula.timer(e.detail.unitTag, 'end'), form_inputs: formInputs, fatal: true //Fatal because the user was unable to submit }; thisEvent.form_flow = nebula.updateFormFlow(thisEvent.unit_tag, '[Spam]'); thisEvent.event_label = 'Form submission failed spam tests on form ID: ' + thisEvent.unit_tag; thisEvent.form_timing = nebula.millisecondsToString(thisEvent.formTime) + 'ms (' + thisEvent.inputs + ')'; gtag('set', 'user_properties', { contact_method: 'CF7 Form (Spam)' }); nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); gtag('event', 'exception', nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula_form_spam'})); //This is the event name to use in the GTM custom event trigger nebula.crm('identify', {'form_contacted': 'CF7 (' + thisEvent.unit_tag + ') Submit Spam'}, false); nebula.crm('event', 'Contact Form (' + thisEvent.unit_tag + ') Spam'); } catch(error){ console.error('Error in wpcf7spam event listener:', error.message, error.stack); //Don't fail silently– output to the console. gtag('event', 'exception', { message: '(JS) CF7 Catch (wpcf7spam): ' + error, fatal: false }); nebula.usage('CF7 Catch: ' + error); } }); //CF7 Mail Send Failure (CF7 AJAX response after mail failure) nebula.dom.document.on('wpcf7mailfailed', function(e){ try { let formInputs = 'Unknown'; if ( nebula.timings[e.detail.unitTag] && nebula.timings[e.detail.unitTag].laps ){ formInputs = nebula.timings[e.detail.unitTag].laps + ' inputs'; } let thisEvent = { event: e, event_name: 'cf7_form_submit_failed', event_category: 'CF7 Form', event_action: 'Submit (Mail Failed)', event_label: e.detail.unitTag, form_id: e.detail.contactFormId, //CF7 Form ID post_id: e.detail.containerPostId, //Post/Page ID unit_tag: e.detail.unitTag, //CF7 Unit Tag description: '(JS) Mail failed to send for form ID ' + e.detail.unitTag, form_time: nebula.timer(e.detail.unitTag, 'end'), form_inputs: formInputs, fatal: true //Fatal because the user was unable to submit }; thisEvent.form_flow = nebula.updateFormFlow(thisEvent.unit_tag, '[Failed]'); thisEvent.event_label = 'Form submission email send failed for form ID: ' + thisEvent.unit_tag; thisEvent.form_timing = nebula.millisecondsToString(thisEvent.formTime) + 'ms (' + thisEvent.inputs + ')'; gtag('set', 'user_properties', { contact_method: 'CF7 Form (Failed)' }); nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); gtag('event', 'exception', nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula_form_failed'})); //This is the event name to use in the GTM custom event trigger nebula.crm('identify', {'form_contacted': 'CF7 (' + thisEvent.unit_tag + ') Submit Failed'}, false); nebula.crm('event', 'Contact Form (' + thisEvent.unit_tag + ') Failed'); } catch(error){ console.error('Error in wpcf7mailfailed event listener:', error.message, error.stack); //Don't fail silently– output to the console. gtag('event', 'exception', { message: '(JS) CF7 Catch (wpcf7mailfailed): ' + error, fatal: false }); nebula.usage('CF7 Catch: ' + error); } }); //CF7 Mail Sent Success (CF7 AJAX response after submit success) nebula.dom.document.on('wpcf7mailsent', function(e){ try { formStarted[e.detail.unitTag] = false; //Reset abandonment tracker for this form. let formInputs = 'Unknown'; if ( nebula.timings[e.detail.unitTag] && nebula.timings[e.detail.unitTag].laps ){ formInputs = nebula.timings[e.detail.unitTag].laps + ' inputs'; } let thisEvent = { event: e, event_name: 'cf7_form_submit_success', event_category: 'CF7 Form', event_action: 'Submit (Success)', event_label: e.detail.unitTag, form_id: e.detail.contactFormId, //CF7 Form ID post_id: e.detail.containerPostId, //Post/Page ID unit_tag: e.detail.unitTag, //CF7 Unit Tag ("f" is CF7 form ID, "p" is WP post ID, and "o" is the count if there are multiple per page) form_time: nebula.timer(e.detail.unitTag, 'end'), form_inputs: formInputs, }; thisEvent.form_flow = nebula.updateFormFlow(thisEvent.unit_tag, '[Success]'); thisEvent.form_timing = nebula.millisecondsToString(thisEvent.form_time) + 'ms (' + thisEvent.form_inputs + ')'; thisEvent.event_label = 'Form ID: ' + thisEvent.unit_tag; gtag('set', 'user_properties', { contact_method: 'CF7 Form' }); nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); //Note that this event is often received by GA before attempt/processing events gtag('event', 'timing_complete', { name: 'Form Completion (ID: ' + thisEvent.unit_tag + ')', value: Math.round(thisEvent.formTime), event_category: thisEvent.event_category, event_label: 'Initial form focus until valid submit', }); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula_form_submit_success'})); //This is the event name to use in the GTM custom event trigger nebula.fbq('track', 'Lead', {content_name: 'Form Submit (Success)'}); nebula.clarity('set', thisEvent.event_category, thisEvent.event_action); nebula.crm('identify', {'form_contacted': 'CF7 (' + thisEvent.unit_tag + ') Submit Success'}, false); nebula.crm('event', 'Contact Form (' + thisEvent.unit_tag + ') Submit Success'); //Clear localstorage on submit success on non-persistent forms if ( !jQuery('#' + e.detail.unitTag).hasClass('nebula-persistent') && !jQuery('#' + e.detail.unitTag).parents('.nebula-persistent').length ){ jQuery('#' + e.detail.unitTag + ' .wpcf7-textarea, #' + e.detail.unitTag + ' .wpcf7-text').each(function(){ jQuery(this).trigger('keyup'); //Clear validation localStorage.removeItem('cf7_' + jQuery(this).attr('name')); }); } jQuery('#' + e.detail.unitTag).find('button#submit').removeClass('active'); jQuery('#' + e.detail.unitTag).find('.is-valid, .is-invalid').removeClass('is-valid is-invalid'); //Clear all validation classes } catch(error){ console.error('Error in wpcf7mailsent event listener:', error.message, error.stack); //Don't fail silently– output to the console. gtag('event', 'exception', { message: '(JS) CF7 Catch (wpcf7mailsent): ' + error, fatal: false }); nebula.usage('CF7 Catch: ' + error); } }); };
Override
To override or disable this JavaScript function, simply redeclare it with the exact same function name. Remember: Some functionality is conditionally loaded via dynamic imports, so if your function is not overriding properly, try listening for a DOM event (described below).
For non-module import functions:
nebula.cf7Functions = function(){ //Write your own code here, leave it blank, or return false. }
For dynamically imported module function overrides:
jQuery(window).on('load', function(){ nebula.cf7Functions = function(){ //Write your own code here, leave it blank, or return false. } });
Custom Nebula DOM events do also exist, so you could also try the following if the Window Load listener does not work:
jQuery(document).on('nebula_module_loaded', function(module){ //Note that the module variable is also available to know which module specifically was imported if ( typeof nebula.cf7Functions === 'function' ){ nebula.cf7Functions = function(){ //Write your own code here, leave it blank, or return false. } } });