Usage
This function runs automatically, so it is not called manually. Is this incorrect?
Additional Notes
This function should be supplemented by adding a project-specific set of event tracking in a child theme JavaScript file.
If the Event Intent custom dimension (in Nebula Options) is used, each click event is associated with an “explicit” or “intent” dimension (for right-clicking or middle-clicking).
For a list of all events tracked by Nebula, see [page coming soon].
Source File
Located in /assets/js/modules/analytics.js on line 114.
4 Hooks
Find these filters and actions in the source code below to hook into them. Use wp.hooks.doAction() and wp.hooks.addFilter() in your JavaScript file.
Filters
"nebulaButtonSelectors""nebulaNotableFiles"
"nebulaInternalSearchInputs"
"excludeDomains"
Need a new filter hook? Request one here.
Actions
This function has no action hooks available. Request one?Note: This function contains 2 to-do comments.
nebula.eventTracking = async function(){ if ( nebula.isDoNotTrack() ){ return false; } nebula.cacheSelectors(); //Just to be safe (this is no longer called from anywhere besides nebula.js so this should never be needed) //Check for Topics API support @todo "Nebula" 0: when it is better supported update this further if ( 'browsingTopics' in document && document.featurePolicy.allowsFeature('browsing-topics') ){ //console.log('Topics API is available on this page', document.browsingTopics()); gtag('event', 'browser_navigation', { event_category: 'Topics API', event_action: 'Available', non_interaction: true }); } nebula.once(function(){ window.dataLayer = window.dataLayer || []; //Prevent overwriting an existing GTM Data Layer array nebula.dom.document.trigger('nebula_event_tracking'); if ( nebula?.user?.cid ){ window.dataLayer.push(Object.assign({'client-id': nebula.user.cid})); } else if ( nebula?.analytics?.measurementID && typeof window.gtag === 'function' ){ gtag('get', nebula.analytics.measurementID, 'client_id', function(clientId){ nebula.user.id = clientId; window.dataLayer.push(Object.assign({'client-id': clientId})); }); } //When the page is restored from BFCache (which means it is not fully reloaded) window.addEventListener('pageshow', function(event){ if ( event.persisted === true ){ gtag('event', 'page_view'); //Send another pageview if the page is restored from bfcache } }); //Back/Forward if ( performance.navigation.type === 2 ){ //If the user arrived at this page via the back/forward button let previousPage = '(Unknown)'; let quickBack = false; if ( 'localStorage' in window ){ let prev = JSON.parse(localStorage.getItem('prev')); //Get the previous page from localstorage previousPage = prev.path; //Get the previous page from localstorage quickBack = prev.quick || false; } gtag('event', 'browser_navigation', { event_category: 'Browser Navigation', event_action: 'Back/Forward', event_label: 'From: ' + previousPage, previous_page: previousPage, non_interaction: true }); if ( quickBack && previousPage !== document.location.pathname ){ //If the previous page was viewed for a very short time and is different than the current page gtag('event', 'quick_back', { //Technically this could be a "quick forward" too, but less likely event_category: 'Quick Back', event_action: 'Quickly left from: ' + previousPage, event_label: 'Back to: ' + document.location.pathname, back_from: previousPage, back_to: document.location.pathname, non_interaction: true }); } } //Reloads if ( performance.navigation.type === 1 ){ //If the user reloaded the page gtag('event', 'browser_navigation', { event_category: 'Browser Navigation', event_action: 'Reload', event_label: document.location.pathname, non_interaction: true }); } //Prep page info and detect quick unloads if ( 'localStorage' in window ){ let prev = { 'path': document.location.pathname, //Prep the "previous page" to this page for future use. 'quick': true //Set this to true initially until it is not longer considered a quick back }; localStorage.setItem('prev', JSON.stringify(prev)); //Store them in localstorage //After 4 seconds change quick to false so it is no longer considered a quick back setTimeout(function(){ prev.quick = false; localStorage.setItem('prev', JSON.stringify(prev)); }, 4000); } //When the page becomes frozen/unfrozen by the browser Lifecycle API document.addEventListener('freeze', function(event){ gtag('event', 'page_lifecycle_frozen', { //Note that "frozen" does not indicate an error. The browser has preserved its state as inactive. event_category: 'Page Lifecycle', event_action: 'Frozen', non_interaction: true }); }); document.addEventListener('resume', function(event){ gtag('event', 'page_lifecycle_resumed', { //This may happen when it is unfrozen from a frozen state, or from BFCache. event_category: 'Page Lifecycle', event_action: 'Resumed', non_interaction: true }); }); //Button Clicks let nebulaButtonSelector = wp.hooks.applyFilters('nebulaButtonSelectors', 'button, .button, .btn, [role="button"], a.wp-block-button__link, .hs-button'); //Allow child theme or plugins to add button selectors without needing to override/duplicate this function nebula.dom.document.on('mousedown', nebulaButtonSelector, function(e){ let thisEvent = { event: e, event_name: 'button_click', event_category: 'Button', event_action: 'Click', intent: ( e.which >= 2 )? 'Intent' : 'Explicit', text: jQuery(this).val() || jQuery(this).attr('value') || jQuery(this).text() || jQuery(this).attr('title') || '(Unknown)', link: jQuery(this).attr('href') || jQuery(this).attr('title') || '(Unknown)' }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-button-click'})); }); //Linked Image Clicks nebula.dom.document.on('click', 'a img', function(e){ let thisEvent = { event: e, event_name: 'image_click', event_category: 'Image Click', event_action: jQuery(this).attr('alt') || jQuery(this).attr('src'), event_label: jQuery(this).parents('a').attr('href') }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-image-click'})); }); //Bootstrap "Collapse" Accordions nebula.dom.document.on('shown.bs.collapse', function(e){ let thisEvent = { event: e, event_name: 'accordion_toggle', event_category: 'Accordion', event_action: 'Shown', event_label: jQuery('[data-bs-target="#' + e.target.id + '"]').text().trim() || e.target.id, }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-accordion-shown'})); }); nebula.dom.document.on('hidden.bs.collapse', function(e){ let thisEvent = { event: e, event_name: 'accordion_toggle', event_category: 'Accordion', event_action: 'Hidden', event_label: jQuery('[data-bs-target="#' + e.target.id + '"]').text().trim() || e.target.id, }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-accordion-hidden'})); }); //Bootstrap Modals nebula.dom.document.on('shown.bs.modal', function(e){ let thisEvent = { event: e, event_name: 'modal_toggle', event_category: 'Modal', event_action: 'Shown', event_label: jQuery('#' + e.target.id + ' .modal-title').text().trim() || e.target.id, }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-modal-shown'})); }); nebula.dom.document.on('hidden.bs.modal', function(e){ let thisEvent = { event: e, event_name: 'modal_toggle', event_category: 'Modal', event_action: 'Hidden', event_label: jQuery('#' + e.target.id + ' .modal-title').text().trim() || e.target.id, }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-modal-hidden'})); }); //Bootstrap Carousels (Sliders) nebula.dom.document.on('slide.bs.carousel', function(e){ if ( window.event ){ //Only if sliding manually let thisEvent = { event: e, event_name: 'carousel_slide', event_category: 'Carousel', event_action: e.target.id || e.target.title || e.target.className.replaceAll(/\s/g, '.'), from: e.from, to: e.to, }; thisEvent.activeSlide = jQuery(e.target).find('.carousel-item').eq(e.to); thisEvent.activeSlideName = thisEvent.activeSlide.attr('id') || thisEvent.activeSlide.attr('title') || 'Unnamed'; thisEvent.prevSlide = jQuery(e.target).find('.carousel-item').eq(e.from); thisEvent.prevSlideName = thisEvent.prevSlide.attr('id') || thisEvent.prevSlide.attr('title') || 'Unnamed'; thisEvent.event_label = 'Slide to ' + thisEvent.to + ' (' + thisEvent.activeSlideName + ') from ' + thisEvent.from + ' (' + thisEvent.prevSlideName + ')'; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-carousel-slide'})); } }); //Generic Form Submissions //This event will be a duplicate if proper event tracking is setup on each form, but serves as a safety net. //It is not recommended to use this event for goal tracking unless absolutely necessary (this event does not check for submission success)! nebula.dom.document.on('submit', 'form', function(e){ let thisEvent = { event: e, event_name: 'form_submit', //How to differentiate this from conversions? event_category: 'Generic Form', event_action: 'Submit', formID: e.target.id || 'form.' + e.target.className.replaceAll(/\s/g, '.'), }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); if ( typeof clarity === 'function' ){clarity('set', thisEvent.event_category, thisEvent.event_action);} window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-generic-form'})); }); //Notable File Downloads let notableFileExtensions = wp.hooks.applyFilters('nebulaNotableFiles', ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'csv', 'zip', 'zipx', 'rar', 'gz', 'tar', 'txt', 'rtf', 'ics', 'vcard']); jQuery.each(notableFileExtensions, function(index, extension){ jQuery("a[href$='." + extension + "' i]").on('mousedown', function(e){ //Cannot defer case insensitive attribute selectors in jQuery (or else you will get an "unrecognized expression" error) let thisEvent = { event: e, event_name: 'file_download', //Note: This is a default GA4 event and is not needed to be tracked in Nebula. Consider deleting entirely. event_category: 'Download', event_action: extension, intent: ( e.which >= 2 )? 'Intent' : 'Explicit', file_extension: extension, file_name: jQuery(this).attr('href').substr(jQuery(this).attr('href').lastIndexOf('/')+1), }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-download'})); nebula.fbq('track', 'ViewContent', {content_name: thisEvent.file_name}); nebula.clarity('set', thisEvent.event_category, thisEvent.file_name); nebula.crm('event', 'File Download'); }); }); //Notable Downloads nebula.dom.document.on('mousedown', '.notable a, a.notable', function(e){ let thisEvent = { event: e, event_name: 'file_download', event_category: 'Download', event_action: 'Notable', intent: ( e.which >= 2 )? 'Intent' : 'Explicit', file_path: jQuery(this).attr('href').trim(), link_text: jQuery(this).text() }; if ( thisEvent.file_path.length && thisEvent.file_path !== '#' ){ thisEvent.file_name = file_path.substr(file_path.lastIndexOf('/')+1); nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-download'})); nebula.fbq('track', 'ViewContent', {content_name: thisEvent.file_name}); nebula.clarity('set', thisEvent.event_category, thisEvent.file_name); nebula.crm('event', 'Notable File Download'); } }); //Generic Internal Search Tracking //This event will need to correspond to the GA4 event name "search" and use "search_term" as a parameter: https://support.google.com/analytics/answer/9267735 let internalSearchInputSelector = wp.hooks.applyFilters('nebulaInternalSearchInputs', '#s, input.search'); nebula.dom.document.on('submit', internalSearchInputSelector, function(){ let thisEvent = { event: e, event_name: 'search', event_category: 'Internal Search', event_action: 'Submit', intent: ( e.which >= 2 )? 'Intent' : 'Explicit', query: jQuery(this).find('input[name="s"]').val().toLowerCase().trim() }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-internal-search'})); nebula.fbq('track', 'Search', {search_string: thisEvent.query}); nebula.clarity('set', thisEvent.event_category, thisEvent.query); nebula.crm('identify', {internal_search: thisEvent.query}); }); //Suggested pages on 404 results nebula.dom.document.on('mousedown', 'a.internal-suggestion', function(e){ let thisEvent = { event: e, event_name: 'select_content', event_category: 'Page Suggestion', event_action: 'Internal', intent: ( e.which >= 2 )? 'Intent' : 'Explicit', suggestion: jQuery(this).text(), }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); nebula.crm('event', 'Page Suggestion Click'); }); //Keyboard Shortcut (Non-interaction because they are not taking explicit action with the webpage) nebula.dom.document.on('keydown', function(e){ window.modifiedZoomLevel = window.modifiedZoomLevel || 0; //Scope to window so it is not reset every event. Note: This is just how it was modified and not the actual zoom level! Zoom level is saved between pageloads so it may have started at non-zero! //Ctrl+ (Zoom In) if ( (e.ctrlKey || e.metaKey) && (e.keyCode === 187 || e.keyCode === 107) ){ //187 is plus (and equal), 107 is plus on the numpad modifiedZoomLevel++; //Increment the zoom level iterator let thisEvent = { event: e, event_name: 'zoom_change', event_category: 'Keyboard Shortcut', event_action: 'Zoom In (Ctrl+)', modified_zoom_level: modifiedZoomLevel, non_interaction: true }; thisEvent.event_label = 'Modified Zoom Level: ' + thisEvent.modified_zoom_level; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-keyboard-shortcut'})); } //Ctrl- (Zoom Out) if ( (e.ctrlKey || e.metaKey) && (e.keyCode === 189 || e.keyCode === 109) ){ //189 is minus, 109 is minus on the numpad modifiedZoomLevel--; //Decrement the zoom level iterator let thisEvent = { event: e, event_name: 'zoom_change', event_category: 'Keyboard Shortcut', event_action: 'Zoom Out (Ctrl-)', modified_zoom_level: modifiedZoomLevel, non_interaction: true }; thisEvent.event_label = 'Modified Zoom Level: ' + thisEvent.modified_zoom_level; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-keyboard-shortcut'})); } //Ctrl+0 (Reset Zoom) if ( (e.ctrlKey || e.metaKey) && (e.keyCode === 48 || e.keyCode === 0 || e.keyCode === 96) ){ //48 is 0 (Mac), 0 is Windows 0, and 96 is Windows numpad modifiedZoomLevel = 0; //Reset the zoom level iterator let thisEvent = { event: e, event_name: 'zoom_change', event_category: 'Keyboard Shortcut', event_action: 'Reset Zoom (Ctrl+0)', modified_zoom_level: modifiedZoomLevel, non_interaction: true }; thisEvent.event_label = 'Modified Zoom Level: ' + thisEvent.modified_zoom_level; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-keyboard-shortcut'})); } //Ctrl+F or Cmd+F (Find) if ( (e.ctrlKey || e.metaKey) && e.keyCode === 70 ){ let thisEvent = { event: e, event_name: 'search', //We will not have a "search_term" parameter. Make sure we do have something to note that this is a Find On Page event_category: 'Keyboard Shortcut', event_action: 'Find on Page (Ctrl+F)', highlighted_ext: window.getSelection().toString().trim() || '(No highlighted text when initiating find)', non_interaction: true }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-keyboard-shortcut'})); } //Ctrl+D or Cmd+D (Bookmark) if ( (e.ctrlKey || e.metaKey) && e.keyCode === 68 ){ //Ctrl+D let thisEvent = { event: e, event_name: 'bookmark', event_category: 'Keyboard Shortcut', event_action: 'Bookmark (Ctrl+D)', event_label: 'User bookmarked the page (with keyboard shortcut)', non_interaction: true }; nebula.removeQueryParameter(['utm_campaign', 'utm_medium', 'utm_source', 'utm_content', 'utm_term'], window.location.href); //Remove existing UTM parameters history.replaceState(null, document.title, window.location.href + '?utm_source=bookmark'); nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-keyboard-shortcut'})); } }); //Mailto link tracking nebula.dom.document.on('mousedown', 'a[href^="mailto"]', function(e){ let emailAddress = jQuery(this).attr('href').replace('mailto:', ''); let emailDomain = emailAddress.split('@')[1]; //Get everything after the @ let anonymizedEmail = nebula.anonymizeEmail(emailAddress); //Mask the email with asterisks let thisEvent = { event: e, event_name: 'mailto', event_category: 'Contact', event_action: 'Mailto', event_label: ( emailAddress.toLowerCase().includes(window.location.hostname) )? emailAddress : anonymizedEmail, //If the email matches the website use it, otherwise use the anonymized email intent: ( e.which >= 2 )? 'Intent' : 'Explicit', email_address: emailAddress, email_domain: emailDomain, anonymized_email: anonymizedEmail }; gtag('set', 'user_properties', { contact_method : 'Email' }); nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-mailto'})); nebula.fbq('track', 'Lead', {content_name: thisEvent.event_action}); nebula.clarity('set', thisEvent.event_category, thisEvent.event_action); nebula.crm('event', thisEvent.event_action); nebula.crm('identify', {mailto_contacted: thisEvent.emailAddress}); }); //Telephone link tracking nebula.dom.document.on('mousedown', 'a[href^="tel"]', function(e){ let thisEvent = { event: e, event_name: 'click_to_call', event_category: 'Contact', event_action: 'Click-to-Call', intent: ( e.which >= 2 )? 'Intent' : 'Explicit', phone_umber: jQuery(this).attr('href').replace('tel:', '') }; gtag('set', 'user_properties', { contact_method : 'Phone' }); nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-click-to-call'})); nebula.fbq('track', 'Lead', {content_name: thisEvent.event_action}); nebula.clarity('set', thisEvent.event_category, thisEvent.event_action); nebula.crm('event', thisEvent.event_action); nebula.crm('identify', {phone_contacted: thisEvent.phoneNumber}); }); //SMS link tracking nebula.dom.document.on('mousedown', 'a[href^="sms"]', function(e){ let thisEvent = { event: e, event_name: 'sms', event_category: 'Contact', event_action: 'SMS', intent: ( e.which >= 2 )? 'Intent' : 'Explicit', phone_number: jQuery(this).attr('href').replace('tel:', '') }; gtag('set', 'user_properties', { contact_method : 'SMS' }); nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-sms'})); nebula.fbq('track', 'Lead', {content_name: thisEvent.event_action}); nebula.clarity('set', thisEvent.event_category, thisEvent.event_action); nebula.crm('event', thisEvent.event_action); nebula.crm('identify', {phone_contacted: thisEvent.phoneNumber}); }); //Street Address click //@todo "Nebula" 0: How to detect when a user clicks an address that is not linked, but mobile opens the map anyway? What about when it *is* linked? //Utility Navigation Menu nebula.dom.document.on('mousedown', '#utility-nav ul.menu a', function(e){ let thisEvent = { event: e, event_name: 'menu_click', event_category: 'Navigation Menu', event_action: 'Utility Menu', intent: ( e.which >= 2 )? 'Intent' : 'Explicit', link_text: jQuery(this).text().trim() }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-navigation-menu-click'})); }); //Primary Navigation Menu nebula.dom.document.on('mousedown', '#primary-nav ul.menu a', function(e){ let thisEvent = { event: e, event_name: 'menu_click', event_category: 'Navigation Menu', event_action: 'Primary Menu', intent: ( e.which >= 2 )? 'Intent' : 'Explicit', link_text: jQuery(this).text().trim() }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-navigation-menu-click'})); }); //Offcanvas Menu Open nebula.dom.document.on('show.bs.offcanvas', function(e){ let thisEvent = { event: e, event_name: 'menu_toggle', event_category: 'Navigation Menu', event_action: 'Offcanvas Menu (' + e.target.id + ')', event_label: 'Opened', }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-offcanvas-shown'})); nebula.timer('(Nebula) Offcanvas Menu', 'start'); }); //Offcanvas Menu Close nebula.dom.document.on('hide.bs.offcanvas', function(e){ let thisEvent = { event: e, event_name: 'menu_toggle', event_category: 'Navigation Menu', event_action: 'Offcanvas Menu (' + e.target.id + ')', event_label: 'Closed (without Navigation)', }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-offcanvas-closed'})); }); //Offcanvas Navigation Link nebula.dom.document.on('mousedown', '.offcanvas-body a', function(e){ let thisEvent = { event: e, event_name: 'menu_click', event_category: 'Navigation Menu', event_action: 'Offcanvas Menu (' + e.target.id + ')', intent: ( e.which >= 2 )? 'Intent' : 'Explicit', link_text: jQuery(this).text().trim() }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-navigation-menu-click'})); gtag('event', 'timing_complete', { name: 'Navigated', value: Math.round(nebula.timer('(Nebula) Offcanvas Menu', 'lap')), event_category: 'Offcanvas Menu', event_label: 'From opening offcanvas menu until navigation', }); }); //Breadcrumb Navigation nebula.dom.document.on('mousedown', 'ol.nebula-breadcrumbs a', function(e){ let thisEvent = { event: e, event_name: 'menu_click', event_category: 'Navigation Menu', event_action: 'Breadcrumbs', intent: ( e.which >= 2 )? 'Intent' : 'Explicit', link_text: jQuery(this).text().trim() }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-navigation-menu-click'})); }); //Sidebar Navigation Menu nebula.dom.document.on('mousedown', '#sidebar-section ul.menu a', function(e){ let thisEvent = { event: e, event_name: 'menu_click', event_category: 'Navigation Menu', event_action: 'Sidebar Menu', intent: ( e.which >= 2 )? 'Intent' : 'Explicit', link_text: jQuery(this).text().trim() }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-navigation-menu-click'})); }); //Footer Navigation Menu nebula.dom.document.on('mousedown', '#powerfooter a', function(e){ let thisEvent = { event: e, event_name: 'menu_click', event_category: 'Navigation Menu', event_action: 'Footer Menu', intent: ( e.which >= 2 )? 'Intent' : 'Explicit', link_text: jQuery(this).text().trim() }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-navigation-menu-click'})); }); //Outbound links (do not use jQuery click listener here) document.body.addEventListener('click', function(e){ let oThis = jQuery(e.target); //Convert the JS event to a jQuery object let linkElement = false; //Assume the element is not a link first if ( oThis.is('a') ){ //If this element is an <a> tag, use it linkElement = oThis; } else if ( oThis.parents('a').length ){ //If the clicked element is not an <a> tag, check parent elements to an <a> tag linkElement = oThis.parents('a'); //Use the parent <a> as the target element } if ( linkElement ){ //If we ended up with a link after all let href = linkElement.attr('href'); if ( href ){ //href may be undefined in special circumstances so we can ignore those let domain = nebula?.site?.domain || window.location.hostname; let excludedDomain = false; let excludeDomains = wp.hooks.applyFilters('excludeDomains', []); //Don't log these domains/subdomains as outbound links jQuery.each(excludeDomains, function(index, excludeDomain){ if ( href.includes(excludeDomain) ){ excludedDomain = true; } }); if ( href.includes('http') ){ //If the link contains "http" if ( !href.includes(domain) || href.includes('.' + domain) ){ //If the link does not contain "example.com" -or- if the link does contain a subdomain like "something.example.com" if ( !excludedDomain && !href.includes('//www.' + domain) ){ //Exclude the "www" subdomain and other defined excluded domains (above) let thisEvent = { event: e, event_name: 'outbound_link', event_category: 'Outbound Link', event_action: 'Click', outbound: true, subdomain: href.includes('.' + domain), //Boolean if this is a subdomain of the primary domain link_text: linkElement.text().trim(), intent: ( e.which >= 2 )? 'Intent' : 'Explicit', href: href }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-outbound-link-click'})); } } } } } }, false); //Nebula Cookie Notification link clicks nebula.dom.document.on('mousedown', '#nebula-cookie-notification a', function(e){ let thisEvent = { event: e, event_name: 'cookie_notification', event_category: 'Cookie Notification', event_action: 'Click', intent: ( e.which >= 2 )? 'Intent' : 'Explicit', text: jQuery(this).text().trim(), link: jQuery(this).attr('href'), non_interaction: true //Non-interaction because the user is not interacting with any content yet so this should not influence the bounce rate }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-cookie-notification-click'})); }); //History Popstate (dynamic URL changes via the History API when "states" are pushed into the browser history) if ( typeof history.pushState === 'function' ){ nebula.dom.window.on('popstate', function(e){ //When a state that was previously pushed is used, or "popped". This *only* triggers when a pushed state is popped! let thisEvent = { event: e, event_name: 'history_popstate', event_category: 'History Popstate', event_action: document.title, location: document.location, state: JSON.stringify(e.state) }; gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); }); } //High Redirect Counts if ( window.performance && performance.navigation.redirectCount >= 3 ){ //If the browser redirected 3+ times let previousPage = nebula.session.referrer || document.referrer || '(Unknown Previous Page)'; let thisEvent = { event_name: 'high_redirect_count', event_category: 'High Redirect Count', event_action: performance.navigation.redirectCount + ' Redirects', event_label: 'Previous Page: ' + previousPage, redirect_count: performance.navigation.redirectCount, non_interaction: true //Non-interaction because this happens on load }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-high-redirect-count'})); nebula.crm('event', thisEvent.event_category); } //Dead Clicks (Non-Linked Click Attempts) nebula.dom.document.on('click', 'img', function(e){ //Clicks on images if ( !jQuery(this).parents('a, button').length ){ let thisEvent = { event: e, event_name: 'dead_click', event_category: 'Dead Click', event_action: 'Image', element: 'Image', src: jQuery(this).attr('src'), non_interaction: true //Non-interaction because if the user leaves due to this it should be considered a bounce }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-dead-click'})); nebula.crm('event', thisEvent.event_category); } }); nebula.dom.document.on('click', function(e){ //Check for clicks on unlinked underlined text if ( jQuery(e.target).css('text-decoration').includes('underline') || jQuery(e.target).is('u') ){ //Do not use jQuery(this) to avoid issues with the document (depending on what was actually clicked) if ( !jQuery(e.target).is('a, button') && !jQuery(e.target).parents('a, button').length && !jQuery(e.target).find('a, button').length ){ //Check if this element is an <a> tag or if parents or children are let thisEvent = { event: e, event_name: 'dead_click', event_category: 'Dead Click', event_action: 'Underlined Text', element: 'Text', text: jQuery(e.target).text().trim(), non_interaction: true //Non-interaction because if the user leaves due to this it should be considered a bounce }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-dead-click'})); nebula.crm('event', thisEvent.event_category); } } }); //Detect "Rage Clicks" let clickEvents = []; nebula.dom.document.on('click', 'body', function(e){ //Ignore clicks on certain elements that typically incur many clicks if ( jQuery(this).is('input[type="number"]') ){ return null; } clickEvents.push({ event: e, time: new Date() }); //Keep only required number of click events if ( clickEvents.length > 5 ){ //If there are more than 5 click events clickEvents.splice(0, clickEvents.length - 5); //Remove everything except the latest 5 } //Detect 3 clicks in 5 seconds if ( clickEvents.length >= 5 ){ const numberOfClicks = 5; //Number of clicks to detect within the period const period = 3; //The period to listen for the number of clicks let last = clickEvents.length - 1; //The latest click event let timeDiff = (clickEvents[last].time.getTime() - clickEvents[last - numberOfClicks + 1].time.getTime()) / 1000; //Time between the last click and previous click //Ignore event periods longer than desired if ( timeDiff > period ){ return null; //Return null because false will prevent regular clicks! } //Loop through the last number of click events to check the distance between them let maxDistance = 0; for ( let i = last - numberOfClicks+1; i < last; i++ ){ //Consider for... of loop here? for ( let j = i+1; j <= last; j++ ){ //Consider for... of loop here? let distance = Math.round(Math.sqrt(Math.pow(clickEvents[i].event.clientX - clickEvents[j].event.clientX, 2) + Math.pow(clickEvents[i].event.clientY - clickEvents[j].event.clientY, 2))); if ( distance > maxDistance ){ maxDistance = distance; } //Ignore if distance is outside 100px radius if ( distance > 100 ){ return null; //Return null because false will prevent regular clicks! } } } //If we have not returned null by now, we have a set of rage clicks let thisEvent = { event: e, event_name: 'rage_clicks', event_category: 'Rage Clicks', event_action: numberOfClicks + ' clicks in ' + timeDiff + ' seconds detected within ' + maxDistance + 'px', clicks: numberOfClicks, period: timeDiff, selector: nebula.domTreeToString(e.target), non_interaction: true //Non-interaction because if the user exits due to this it should be considered a bounce }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-rage-clicks'})); clickEvents.splice(clickEvents.length-5, 5); //Remove unused click points } }); //Focus on Skip to Content and other screen reader links (which indicate screenreader software is being used in this session) nebula.dom.document.on('focus', '#skip-to-content-link, .visually-hidden, .visually-hidden-focusable', function(e){ let thisEvent = { event: e, event_name: 'accessibility_links', event_category: 'Accessibility Links', event_action: 'Focus', link_text: jQuery(this).text().trim(), non_interaction: true //Non-interaction because they are not actually taking action and these links do not indicate engagement }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); if ( typeof clarity === 'function' ){clarity('set', thisEvent.event_category, thisEvent.event_action);} window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-accessibility-link'})); }); //Clicks on Skip to Content and other screen reader links (which indicate screenreader software is being used in this session) nebula.dom.document.on('click', '#skip-to-content-link, .visually-hidden, .visually-hidden-focusable', function(e){ let thisEvent = { event: e, event_name: 'accessibility_links', event_category: 'Accessibility Links', event_action: 'Click', intent: ( e.which >= 2 )? 'Intent' : 'Explicit', link_text: jQuery(this).text().trim(), non_interaction: true //Non-interaction because these links do not indicate engagement }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); if ( typeof clarity === 'function' ){clarity('set', thisEvent.event_category, thisEvent.event_action);} window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-accessibility-link'})); }); //Video Enter Picture-in-Picture //https://caniuse.com/#feat=picture-in-picture nebula.dom.document.on('enterpictureinpicture', 'video', function(e){ let thisEvent = { event: e, event_name: 'video_pip', event_category: 'Videos', event_action: 'Enter Picture-in-Picture', videoID: e.target.id, non_interaction: true //Non-interaction because this may not be triggered by the user }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); }); //Video Leave Picture-in-Picture nebula.dom.document.on('leavepictureinpicture', 'video', function(e){ let thisEvent = { event: e, event_name: 'video_pip', event_category: 'Videos', event_action: 'Leave Picture-in-Picture', videoID: e.target.id, non_interaction: true //Non-interaction because this may not be triggered by the user }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); }); //Page Visibility nebula.dom.document.on('visibilitychange', function(e){ let thisEvent = { event: e, event_name: 'visibility_change', event_category: 'Visibility Change', event_action: document.visibilityState, //Hidden, Visible, Prerender, or Unloaded event_label: 'The state of the visibility of this page has changed.', non_interaction: true //Non-interaction because these are not interactions with the website itself }; gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); }); //Word copy tracking let copyCount = 0; nebula.dom.document.on('cut copy', function(e){ //Ignore clipboard events that occur within form inputs or on Woocommerce checkout/confirmation pages if ( jQuery(e.target).is('input, textarea') || jQuery(e.target).parents('form').length || jQuery('body.woocommerce-checkout').length || jQuery('body.woocommerce-order-received').length ){ return false; } let selection = window.getSelection().toString().trim(); if ( selection ){ let words = selection.split(' '); let wordsLength = words.length; //Track Email or Phone copies as contact intent. if ( nebula.regex.email.test(selection) ){ let thisEvent = { event_name: 'mailto', event_category: 'Contact', event_action: 'Email (Copy)', intent: 'Intent', email_address: nebula.anonymizeEmail(selection), //Mask the email with asterisks, words: words, word_count: wordsLength }; gtag('set', 'user_properties', { contact_method : 'Email' }); nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-copied-email'})); nebula.crm('event', 'Email Address Copied'); nebula.crm('identify', {mailto_contacted: thisEvent.emailAddress}); } else if ( nebula.regex.address.test(selection) ){ let thisEvent = { event_name: 'address_copy', //Probably could be a better name event_category: 'Contact', event_action: 'Street Address (Copy)', intent: 'Intent', address: selection, words: words, word_count: wordsLength }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-copied-address'})); nebula.crm('event', 'Street Address Copied'); } else { let alphanumPhone = selection.replaceAll(/\W/g, ''); //Keep only alphanumeric characters let firstFourNumbers = parseInt(alphanumPhone.substring(0, 4)); //Store the first four numbers as an integer //If the first three/four chars are numbers and the full string is either 10 or 11 characters (to capture numbers with words) -or- if it matches the phone RegEx pattern if ( (!isNaN(firstFourNumbers) && firstFourNumbers.toString().length >= 3 && (alphanumPhone.length === 10 || alphanumPhone.length === 11)) || nebula.regex.phone.test(selection) ){ let thisEvent = { event_name: 'click_to_call', event_category: 'Contact', event_action: 'Phone (Copy)', intent: 'Intent', phone_number: selection, words: words, word_count: wordsLength }; gtag('set', 'user_properties', { contact_method : 'Phone' }); nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-copied-phone'})); nebula.crm('event', 'Phone Number Copied'); nebula.crm('identify', {phone_contacted: thisEvent.phoneNumber}); } } //Send the regular copied text event since it does not contain contact information let thisEvent = { event_name: 'copy_text', event_category: 'Copied Text', event_action: 'Copy', intent: 'Intent', selection: selection, words: words, word_count: wordsLength }; if ( selection.length > 150 ){ thisEvent.selection = thisEvent.selection.substring(0, 150) + '...'; //Max character length for GA event is 256 } else if ( thisEvent.word_count >= 10 ){ thisEvent.words = thisEvent.words.slice(0, 10).join(' ') + '... [' + thisEvent.word_count + ' Words]'; } else if ( selection.trim() === '' ){ thisEvent.words = '[0 words]'; } if ( thisEvent.words.length > 150 ){ thisEvent.words = thisEvent.words.substring(0, 150) + '...'; //Max character length for GA event is 256 } nebula.dom.document.trigger('nebula_event', thisEvent); if ( copyCount < 5 ){ //If fewer than 5 copies have happened in this page view gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-copied-text'})); nebula.crm('event', 'Text Copied'); } copyCount++; } }); //AJAX Errors nebula.dom.document.ajaxError(function(e, jqXHR, settings, thrownError){ let errorMessage = thrownError; if ( jqXHR.status === 0 ){ //A status of 0 means the error is unknown. Possible network connection issue (like a blocked request). errorMessage = 'Unknown error'; } gtag('event', 'exception', { xhr_status: jqXHR.status, error_message: errorMessage, url: settings.url, description: '(JS) AJAX Error (' + jqXHR.status + '): ' + errorMessage + ' on ' + settings.url, fatal: true }); window.dataLayer.push({'event': 'nebula-ajax-error', 'error': errorMessage}); nebula.crm('event', 'AJAX Error'); }); //Note: Window errors are detected in usage.js for better visibility //Reporting Observer deprecations and interventions try { if ( 'ReportingObserver' in window ){ //Chrome 68+ let nebulaReportingObserver = new ReportingObserver(function(reports, observer){ for ( let report of reports ){ if ( report?.body?.sourceFile && !['extension', 'about:blank'].some((item) => report.body.sourceFile.includes(item)) ){ //Ignore certain files gtag('event', 'exception', { report_type: report.type, report_message: report.body.message, source_file: report.body.sourceFile, line_number: report.body.lineNumber, description: '(JS) Reporting Observer [' + report.type + ']: ' + report.body.message + ' in ' + report.body.sourceFile + ' on line ' + report.body.lineNumber, fatal: false }); } } }, {buffered: true}); //Buffer to capture reports that happened prior to the observer being created nebulaReportingObserver.observe(); } } catch { //Ignore errors } //Capture Print Intent function sendPrintEvent(action, trigger){ let thisEvent = { event_name: 'print', event_category: 'Print', event_action: action, event_label: 'User triggered print via ' + trigger, intent: 'Intent' }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-print'})); if ( typeof clarity === 'function' ){clarity('set', thisEvent.event_category, thisEvent.event_action);} nebula.crm('event', thisEvent.event_category); } //Note: This sends 2 events per print (beforeprint and afterprint). If one occurs more than the other we can remove one. window.matchMedia('print').addListener(function(mql){ if ( mql.matches ){ sendPrintEvent('Before Print', 'mql.matches'); } else { sendPrintEvent('After Print', '!mql.matches'); } }); //DataTables Filter nebula.dom.document.on('keyup', '.dataTables_filter input', function(e){ let oThis = jQuery(this); let thisEvent = { event: e, event_name: 'search', event_category: 'DataTables', event_action: 'Search Filter', query: oThis.val().toLowerCase().trim() }; nebula.debounce(function(){ nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-datatables'})); }, 1000, 'datatables_search_filter'); }); //DataTables Sorting nebula.dom.document.on('click', 'th.sorting', function(e){ let thisEvent = { event: e, event_name: 'datatables_sort', event_category: 'DataTables', event_action: 'Sort', heading: jQuery(this).text() }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-datatables'})); }); //DataTables Pagination nebula.dom.document.on('click', 'a.paginate_button ', function(e){ let thisEvent = { event: e, event_name: 'datatables_paginate', event_category: 'DataTables', event_action: 'Paginate', page: jQuery(this).text() }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-datatables'})); }); //DataTables Show Entries nebula.dom.document.on('change', '.dataTables_length select', function(e){ let thisEvent = { event: e, event_name: 'datatables_length', event_category: 'DataTables', event_action: 'Shown Entries Change', //Number of visible rows select dropdown selected: jQuery(this).val() }; nebula.dom.document.trigger('nebula_event', thisEvent); gtag('event', thisEvent.event_name, nebula.gaEventObject(thisEvent)); window.dataLayer.push(Object.assign(thisEvent, {'event': 'nebula-datatables'})); }); nebula.scrollDepth(); nebula.ecommerceTracking(); }, 'nebula event tracking'); };
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.eventTracking = function(){ //Write your own code here, leave it blank, or return false. }
For dynamically imported module function overrides:
jQuery(window).on('load', function(){ nebula.eventTracking = 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.eventTracking === 'function' ){ nebula.eventTracking = function(){ //Write your own code here, leave it blank, or return false. } } });