Skip to Content

eventTracking()

Track generic user events in Google Analytics.

JavaScript July 2, 2019

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/nebula.js on line 760.

Note: This function contains 2 to-do comments.

JavaScript
nebula.eventTracking = function(){
    if ( nebula.isDoNotTrack() ){
        return false;
    }

    nebula.cacheSelectors(); //If event tracking is initialized by the async GA callback, selectors won't be cached yet

    if ( typeof window.ga === 'function' ){
        window.ga(function(tracker){
            nebula.dom.document.trigger('nebula_ga_tracker', tracker);
            nebula.user.cid = tracker.get('clientId');
        });
    }

    nebula.dom.document.trigger('nebula_event_tracking');

    nebula.once(function(){
        window.dataLayer = window.dataLayer || []; //Prevent overwriting an existing GTM Data Layer array

        //Btn Clicks
        nebula.dom.document.on('mousedown', "button, .btn, [role='button'], a.wp-block-button__link, .hs-button", function(e){
            var thisEvent = {
                event: e,
                category: 'Button',
                action: 'Click', //GA4 Name: "button_click"?
                intent: ( e.which >= 2 )? 'Intent' : 'Explicit',
                text: jQuery(this).val() || jQuery(this).text() || '(Unknown)',
                link: jQuery(this).attr('href')
            };

            ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', 'Button Click', thisEvent.text.trim(), thisEvent.link);
            window.dataLayer.push({'event': 'nebula-button-click', 'nebula-event': thisEvent});
        });

        //Bootstrap "Collapse" Accordions
        nebula.dom.document.on('shown.bs.collapse', function(e){
            var thisEvent = {
                event: e,
                category: 'Accordion',
                action: 'Shown', //GA4 Name: "accordion_toggle"?
                label: jQuery('[data-target="#' + e.target.id + '"]').text().trim() || e.target.id,
            };

            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.label);
            window.dataLayer.push({'event': 'nebula-accordion-shown', 'nebula-event': thisEvent});
        });
        nebula.dom.document.on('hidden.bs.collapse', function(e){
            var thisEvent = {
                event: e,
                category: 'Accordion',
                action: 'Hidden', //GA4 Name: "accordion_toggle"?
                label: jQuery('[data-target="#' + e.target.id + '"]').text().trim() || e.target.id,
            };

            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.label);
            window.dataLayer.push({'event': 'nebula-accordion-hidden', 'nebula-event': thisEvent});
        });

        //Bootstrap Modals
        nebula.dom.document.on('shown.bs.modal', function(e){
            var thisEvent = {
                event: e,
                category: 'Modal',
                action: 'Shown', //GA4 Name: "modal_toggle"?
                label: jQuery('#' + e.target.id + ' .modal-title').text().trim() || e.target.id,
            };

            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.label);
            window.dataLayer.push({'event': 'nebula-modal-shown', 'nebula-event': thisEvent});
        });
        nebula.dom.document.on('hidden.bs.modal', function(e){
            var thisEvent = {
                event: e,
                category: 'Modal',
                action: 'Hidden', //GA4 Name: "modal_toggle"?
                label: jQuery('#' + e.target.id + ' .modal-title').text().trim() || e.target.id,
            };

            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.label);
            window.dataLayer.push({'event': 'nebula-modal-hidden', 'nebula-event': thisEvent});
        });

        //Bootstrap Carousels (Sliders)
        nebula.dom.document.on('slide.bs.carousel', function(e){
            if ( window.event ){ //Only if sliding manually
                var thisEvent = {
                    event: e,
                    category: 'Carousel',
                    action: e.target.id || e.target.title || e.target.className.replace(/\s/g, '.'), //GA4 Name: "carousel_slide"?
                    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.label = 'Slide to ' + thisEvent.to + ' (' + thisEvent.activeSlideName + ') from ' + thisEvent.from + ' (' + thisEvent.prevSlideName + ')';

                nebula.dom.document.trigger('nebula_event', thisEvent);
                ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.label);
                window.dataLayer.push({'event': 'nebula-carousel-slide', 'nebula-event': thisEvent});
            }
        });

        //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){
            var thisEvent = {
                event: e,
                category: 'Generic Form',
                action: 'Submit', //GA4 Name: "form_submit"? How to differentiate it from conversions?
                formID: e.target.id || 'form.' + e.target.className.replace(/\s/g, '.'),
            };

            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.formID);
            if ( typeof clarity === 'function' ){clarity('set', thisEvent.category, thisEvent.action);}
            window.dataLayer.push({'event': 'nebula-generic-form', 'nebula-event': thisEvent});
        });

        //Notable File Downloads
        jQuery.each(['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'csv', 'zip', 'zipx', 'rar', 'gz', 'tar', 'txt', 'rtf', 'ics', 'vcard'], function(index, extension){
            nebula.dom.document.on('mousedown', "a[href$='." + extension + "'], a[href$='." + extension.toUpperCase() + "']", function(e){ //Make this a case insensitive attribute selector after IE11 support ends: a[href$='.pdf' i]
                var thisEvent = {
                    event: e,
                    category: 'Download',
                    action: extension, //GA4 Name: "file_download" Note: This is a default GA4 event and is not needed to be tracked in Nebula. Consider deleting entirely.
                    intent: ( e.which >= 2 )? 'Intent' : 'Explicit',
                    extension: extension,
                    fileName: jQuery(this).attr('href').substr(jQuery(this).attr('href').lastIndexOf("/")+1),
                };

                ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
                nebula.dom.document.trigger('nebula_event', thisEvent);
                ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.fileName);
                window.dataLayer.push({'event': 'nebula-download', 'nebula-event': thisEvent});
                if ( typeof fbq === 'function' ){fbq('track', 'ViewContent', {content_name: thisEvent.fileName});}
                if ( typeof clarity === 'function' ){clarity('set', thisEvent.category, thisEvent.fileName);}
                nebula.crm('event', 'File Download');
            });
        });

        //Notable Downloads
        nebula.dom.document.on('mousedown', ".notable a, a.notable", function(e){
            var thisEvent = {
                event: e,
                category: 'Download',
                action: 'Notable', //GA4 Name: "file_download"
                intent: ( e.which >= 2 )? 'Intent' : 'Explicit',
                filePath: jQuery(this).attr('href').trim(),
                linkText: jQuery(this).text()
            };

            if ( thisEvent.filePath.length && thisEvent.filePath !== '#' ){
                thisEvent.fileName = filePath.substr(filePath.lastIndexOf("/")+1);
                ga('set', nebula.analytics.metrics.notableDownloads, 1);
                nebula.dom.document.trigger('nebula_event', thisEvent);

                ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.fileName);
                window.dataLayer.push({'event': 'nebula-download', 'nebula-event': thisEvent});
                if ( typeof fbq === 'function' ){fbq('track', 'ViewContent', {content_name: thisEvent.fileName});}
                if ( typeof clarity === 'function' ){clarity('set', thisEvent.category, thisEvent.fileName);}
                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
        nebula.dom.document.on('submit', '#s, input.search', function(){
            var thisEvent = {
                event: e,
                category: 'Internal Search',
                action: 'Submit', //GA4 Name: "search"
                intent: ( e.which >= 2 )? 'Intent' : 'Explicit',
                query: jQuery(this).find('input[name="s"]').val().toLowerCase().trim()
            };

            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.query);
            window.dataLayer.push({'event': 'nebula-internal-search', 'nebula-event': thisEvent});
            if ( typeof fbq === 'function' ){fbq('track', 'Search', {search_string: thisEvent.query});}
            if ( typeof clarity === 'function' ){clarity('set', thisEvent.category, thisEvent.query);}
            nebula.crm('identify', {internal_search: thisEvent.query});
        });

        //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.which === 187 || e.which === 107) ){ //187 is plus (and equal), 107 is plus on the numpad
                modifiedZoomLevel++; //Increment the zoom level iterator

                var thisEvent = {
                    event: e,
                    category: 'Keyboard Shortcut',
                    action: 'Zoom In (Ctrl+)', //GA4 Name: "zoom_change"?
                    modifiedZoomLevel: modifiedZoomLevel
                };

                nebula.dom.document.trigger('nebula_event', thisEvent);
                ga('send', 'event', thisEvent.category, thisEvent.action, 'Modified Zoom Level: ' + thisEvent.modifiedZoomLevel, {'nonInteraction': true});
                window.dataLayer.push({'event': 'nebula-keyboard-shortcut', 'nebula-event': thisEvent});
            }

            //Ctrl- (Zoom Out)
            if ( (e.ctrlKey || e.metaKey) && (e.which === 189 || e.which === 109) ){ //189 is minus, 109 is minus on the numpad
                modifiedZoomLevel--; //Decrement the zoom level iterator

                var thisEvent = {
                    event: e,
                    category: 'Keyboard Shortcut',
                    action: 'Zoom Out (Ctrl-)', //GA4 Name: "zoom_change"?
                    modifiedZoomLevel: modifiedZoomLevel
                };

                nebula.dom.document.trigger('nebula_event', thisEvent);
                ga('send', 'event', thisEvent.category, thisEvent.action, 'Modified Zoom Level: ' + thisEvent.modifiedZoomLevel, {'nonInteraction': true});
                window.dataLayer.push({'event': 'nebula-keyboard-shortcut', 'nebula-event': thisEvent});
            }

            //Ctrl+0 (Reset Zoom)
            if ( (e.ctrlKey || e.metaKey) && (e.which === 48 || e.which === 0 || e.which === 96) ){ //48 is 0 (Mac), 0 is Windows 0, and 96 is Windows numpad
                modifiedZoomLevel = 0; //Reset the zoom level iterator

                var thisEvent = {
                    event: e,
                    category: 'Keyboard Shortcut',
                    action: 'Reset Zoom (Ctrl+0)', //GA4 Name: "zoom_change"?
                    modifiedZoomLevel: modifiedZoomLevel
                };

                nebula.dom.document.trigger('nebula_event', thisEvent);
                ga('send', 'event', thisEvent.category, thisEvent.action, 'Modified Zoom Level: ' + thisEvent.modifiedZoomLevel, {'nonInteraction': true});
                window.dataLayer.push({'event': 'nebula-keyboard-shortcut', 'nebula-event': thisEvent});
            }

            //Ctrl+F or Cmd+F (Find)
            if ( (e.ctrlKey || e.metaKey) && e.which === 70 ){
                var thisEvent = {
                    event: e,
                    category: 'Keyboard Shortcut',
                    action: 'Find on Page (Ctrl+F)', //GA4 Name: "search" but we will not have a "search_term" parameter. Make sure we do have something to note that this is a Find On Page
                    highlightedText: window.getSelection().toString().trim() || '(No highlighted text when initiating find)'
                };

                nebula.dom.document.trigger('nebula_event', thisEvent);
                ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.highlightedText, {'nonInteraction': true});
                window.dataLayer.push({'event': 'nebula-keyboard-shortcut', 'nebula-event': thisEvent});
            }

            //Ctrl+D or Cmd+D (Bookmark)
            if ( (e.ctrlKey || e.metaKey) && e.which === 68 ){ //Ctrl+D
                var thisEvent = {
                    event: e,
                    category: 'Keyboard Shortcut',
                    action: 'Bookmark (Ctrl+D)', //GA4 Name: "bookmark"?
                    label: 'User bookmarked the page (with keyboard shortcut)'
                };

                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);
                ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.label, {'nonInteraction': true});
                window.dataLayer.push({'event': 'nebula-keyboard-shortcut', 'nebula-event': thisEvent});
            }
        });

        //Mailto link tracking
        nebula.dom.document.on('mousedown', 'a[href^="mailto"]', function(e){
            var thisEvent = {
                event: e,
                category: 'Contact',
                action: 'Mailto', //GA4 Name: "mailto"?
                intent: ( e.which >= 2 )? 'Intent' : 'Explicit',
                emailAddress: jQuery(this).attr('href').replace('mailto:', '')
            };

            ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
            ga('set', nebula.analytics.dimensions.contactMethod, thisEvent.action);
            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.emailAddress);
            window.dataLayer.push({'event': 'nebula-mailto', 'nebula-event': thisEvent});
            if ( typeof fbq === 'function' ){fbq('track', 'Lead', {content_name: thisEvent.action});}
            if ( typeof clarity === 'function' ){clarity('set', thisEvent.category, thisEvent.action);}
            nebula.crm('event', thisEvent.action);
            nebula.crm('identify', {mailto_contacted: thisEvent.emailAddress});
        });

        //Telephone link tracking
        nebula.dom.document.on('mousedown', 'a[href^="tel"]', function(e){
            var thisEvent = {
                event: e,
                category: 'Contact',
                action: 'Click-to-Call', //GA4 Name: "click_to_call"?
                intent: ( e.which >= 2 )? 'Intent' : 'Explicit',
                phoneNumber: jQuery(this).attr('href').replace('tel:', '')
            };

            ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
            ga('set', nebula.analytics.dimensions.contactMethod, thisEvent.action);
            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.phoneNumber);
            window.dataLayer.push({'event': 'nebula-click-to-call', 'nebula-event': thisEvent});
            if ( typeof fbq === 'function' ){fbq('track', 'Lead', {content_name: thisEvent.action});}
            if ( typeof clarity === 'function' ){clarity('set', thisEvent.category, thisEvent.action);}
            nebula.crm('event', thisEvent.action);
            nebula.crm('identify', {phone_contacted: thisEvent.phoneNumber});
        });

        //SMS link tracking
        nebula.dom.document.on('mousedown', 'a[href^="sms"]', function(e){
            var thisEvent = {
                event: e,
                category: 'Contact',
                action: 'SMS', //GA4 Name: "sms"?
                intent: ( e.which >= 2 )? 'Intent' : 'Explicit',
                phoneNumber: jQuery(this).attr('href').replace('tel:', '')
            };

            ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
            ga('set', nebula.analytics.dimensions.contactMethod, thisEvent.action);
            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.phoneNumber);
            window.dataLayer.push({'event': 'nebula-sms', 'nebula-event': thisEvent});
            if ( typeof fbq === 'function' ){fbq('track', 'Lead', {content_name: thisEvent.action});}
            if ( typeof clarity === 'function' ){clarity('set', thisEvent.category, thisEvent.action);}
            nebula.crm('event', thisEvent.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){
            var thisEvent = {
                event: e,
                category: 'Navigation Menu',
                action: 'Utility Menu', //GA4 Name: "menu_click"?
                intent: ( e.which >= 2 )? 'Intent' : 'Explicit',
                linkText: jQuery(this).text().trim()
            };

            ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.linkText);
            window.dataLayer.push({'event': 'nebula-navigation-menu-click', 'nebula-event': thisEvent});
        });

        //Primary Navigation Menu
        nebula.dom.document.on('mousedown', '#primary-nav ul.menu a', function(e){
            var thisEvent = {
                event: e,
                category: 'Navigation Menu',
                action: 'Primary Menu', //GA4 Name: "menu_click"?
                intent: ( e.which >= 2 )? 'Intent' : 'Explicit',
                linkText: jQuery(this).text().trim()
            };

            ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.linkText);
            window.dataLayer.push({'event': 'nebula-navigation-menu-click', 'nebula-event': thisEvent});
        });

        //Mobile Navigation Menu
        nebula.dom.document.on('mousedown', '#mobilenav ul.menu a.mm-listitem__text', function(e){
            var thisEvent = {
                event: e,
                category: 'Navigation Menu',
                action: 'Mobile Menu', //GA4 Name: "menu_click"?
                intent: ( e.which >= 2 )? 'Intent' : 'Explicit',
                linkText: jQuery(this).text().trim()
            };

            ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.linkText);
            window.dataLayer.push({'event': 'nebula-navigation-menu-click', 'nebula-event': thisEvent});
        });

        //Breadcrumb Navigation
        nebula.dom.document.on('mousedown', 'ol.nebula-breadcrumbs a', function(e){
            var thisEvent = {
                event: e,
                category: 'Navigation Menu',
                action: 'Breadcrumbs', //GA4 Name: "menu_click"?
                intent: ( e.which >= 2 )? 'Intent' : 'Explicit',
                linkText: jQuery(this).text().trim()
            };

            ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.linkText);
            window.dataLayer.push({'event': 'nebula-navigation-menu-click', 'nebula-event': thisEvent});
        });

        //Sidebar Navigation Menu
        nebula.dom.document.on('mousedown', '#sidebar-section ul.menu a', function(e){
            var thisEvent = {
                event: e,
                category: 'Navigation Menu',
                action: 'Sidebar Menu', //GA4 Name: "menu_click"?
                intent: ( e.which >= 2 )? 'Intent' : 'Explicit',
                linkText: jQuery(this).text().trim()
            };

            ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.linkText);
            window.dataLayer.push({'event': 'nebula-navigation-menu-click', 'nebula-event': thisEvent});
        });

        //Footer Navigation Menu
        nebula.dom.document.on('mousedown', '#powerfooter ul.menu a', function(e){
            var thisEvent = {
                event: e,
                category: 'Navigation Menu',
                action: 'Footer Menu', //GA4 Name: "menu_click"?
                intent: ( e.which >= 2 )? 'Intent' : 'Explicit',
                linkText: jQuery(this).text().trim()
            };

            ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.linkText);
            window.dataLayer.push({'event': 'nebula-navigation-menu-click', 'nebula-event': thisEvent});
        });

        //Nebula Cookie Notification link clicks
        nebula.dom.document.on('mousedown', '#nebula-cookie-notification a', function(e){
            var thisEvent = {
                event: e,
                category: 'Cookie Notification',
                action: 'Click', //GA4 Name: "cookie_notification"?
                intent: ( e.which >= 2 )? 'Intent' : 'Explicit',
                text: jQuery(this).text(),
                link: jQuery(this).attr('href')
            };

            ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.text.trim(), thisEvent.link, {'nonInteraction': true}); //Non-interaction because the user is not interacting with any content yet so this should not influence the bounce rate
            window.dataLayer.push({'event': 'nebula-cookie-notification-click', 'nebula-event': thisEvent});
        });

        //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!
                var thisEvent = {
                    event: e,
                    category: 'History Popstate',
                    action: document.title,
                    location: document.location,
                    state: JSON.stringify(e.state)
                };

                ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.location);
            });
        }

        //Dead Clicks (Non-Linked Click Attempts)
        nebula.dom.document.on('click', 'img', function(e){
            if ( !jQuery(this).parents('a, button').length ){
                var thisEvent = {
                    event: e,
                    category: 'Dead Click',
                    action: 'Image', //GA4 Name: "dead_click"?
                    element: 'Image',
                    src: jQuery(this).attr('src')
                };

                nebula.dom.document.trigger('nebula_event', thisEvent);
                ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.src, {'nonInteraction': true}); //Non-interaction because if the user leaves due to this it should be considered a bounce
                window.dataLayer.push({'event': 'nebula-dead-click', 'nebula-event': thisEvent});
                nebula.crm('event', thisEvent.category);
            }
        });

        //Detect "Rage Clicks"
        var 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 and remove left of them.
            if ( clickEvents.length > 5 ){
                clickEvents.splice(0, clickEvents.length - 5);
            }

            //Detect 3 clicks in 5 seconds
            if ( clickEvents.length >= 5 ){
                var numberOfClicks = 5; //Number of clicks to detect within the period
                var period = 3; //The period to listen for the number of clicks

                var last = clickEvents.length - 1;
                var timeDiff = (clickEvents[last].time.getTime() - clickEvents[last - numberOfClicks + 1].time.getTime()) / 1000;

                //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
                var max_distance = 0;
                for ( var i = last - numberOfClicks+1; i < last; i++ ){
                    for ( var j = i+1; j <= last; j++ ){
                        var 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 > max_distance ){
                            max_distance = distance;
                        }

                        //Ignore if distance is outside 100px radius
                        if ( distance > 100 ){
                            return null; //Return null because false will prevent regular clicks!
                        }
                    }
                }

                var thisEvent = {
                    event: e,
                    category: 'Rage Clicks',
                    action: 'Detected', //GA4 Name: "rage_clicks"?
                    clicks: numberOfClicks,
                    period: timeDiff,
                    selector: nebula.domTreeToString(e.target),
                };

                thisEvent.description = numberOfClicks + ' clicks in ' + timeDiff + ' seconds detected within ' + max_distance + 'px of ' + thisEvent.selector;

                nebula.dom.document.trigger('nebula_event', thisEvent);
                ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.description, {'nonInteraction': true}); //Non-interaction because if the user exits due to this it should be considered a bounce
                window.dataLayer.push({'event': 'nebula-rage-click', 'nebula-event': thisEvent});

                clickEvents.splice(clickEvents.length-5, 5); //Remove unused click points
            }
        });

        //Skip to Content and other screen reader links Focus/Clicks
        nebula.dom.document.on('focus', '.sr-only', function(e){
            var thisEvent = {
                event: e,
                category: 'Accessibility Links',
                action: 'Focus', //GA4 Name: "accessibility_links"?
                linkText: jQuery(this).text().trim()
            };

            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.linkText);
            if ( typeof clarity === 'function' ){clarity('set', thisEvent.category, thisEvent.action);}
            window.dataLayer.push({'event': 'nebula-accessibility-link', 'nebula-event': thisEvent});
        });

        //Screenreader Links
        nebula.dom.document.on('click', '.sr-only', function(e){
            var thisEvent = {
                event: e,
                category: 'Accessibility Links',
                action: 'Click', //GA4 Name: "accessibility_links"?
                intent: ( e.which >= 2 )? 'Intent' : 'Explicit',
                linkText: jQuery(this).text().trim()
            };

            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.linkText);
            if ( typeof clarity === 'function' ){clarity('set', thisEvent.category, thisEvent.action);}
            window.dataLayer.push({'event': 'nebula-accessibility-link', 'nebula-event': thisEvent});
        });

        //Video Enter Picture-in-Picture //https://caniuse.com/#feat=picture-in-picture
        nebula.dom.document.on('enterpictureinpicture', 'video', function(e){
            var thisEvent = {
                event: e,
                category: 'Videos',
                action: 'Enter Picture-in-Picture',  //GA4 Name: "video_pip"?
                videoID: e.target.id
            };

            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.videoID, {'nonInteraction': true}); //Non-interaction because this may not be triggered by the user.
        });

        //Video Leave Picture-in-Picture
        nebula.dom.document.on('leavepictureinpicture', 'video', function(e){
            var thisEvent = {
                event: e,
                category: 'Videos',
                action: 'Leave Picture-in-Picture', //GA4 Name: "video_pip"?
                videoID: e.target.id
            };

            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.videoID, {'nonInteraction': true}); //Non-interaction because this may not be triggered by the user.
        });

        //Word copy tracking
        var copyCount = 0;
        nebula.dom.document.on('cut copy', function(){
            var selection = window.getSelection().toString();
            var words = selection.split(' ');
            var wordsLength = words.length;

            //Track Email or Phone copies as contact intent.
            var emailPhoneAddress = words.join(' ').trim();
            if ( nebula.regex.email.test(emailPhoneAddress) ){
                var thisEvent = {
                    category: 'Contact',
                    action: 'Email (Copy)', //GA4 Name: "mailto"?
                    intent: 'Intent',
                    emailAddress: emailPhoneAddress,
                    selection: selection,
                    words: words,
                    wordcount: wordsLength
                };

                ga('set', nebula.analytics.dimensions.contactMethod, 'Mailto');
                ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
                nebula.dom.document.trigger('nebula_event', thisEvent);
                ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.emailAddress);
                nebula.crm('event', 'Email Address Copied');
                nebula.crm('identify', {mailto_contacted: thisEvent.emailAddress});
            } else if ( nebula.regex.address.test(emailPhoneAddress) ){
                var thisEvent = {
                    category: 'Contact',
                    action: 'Street Address (Copy)',
                    intent: 'Intent',
                    address: emailPhoneAddress,
                    selection: selection,
                    words: words,
                    wordcount: wordsLength
                };

                ga('set', nebula.analytics.dimensions.contactMethod, 'Street Address');
                ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
                nebula.dom.document.trigger('nebula_event', thisEvent);
                ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.address);
                nebula.crm('event', 'Street Address Copied');
            } else {
                var alphanumPhone = emailPhoneAddress.replace(/\W/g, ''); //Keep only alphanumeric characters
                var 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(emailPhoneAddress) ){
                    var thisEvent = {
                        category: 'Contact',
                        action: 'Phone (Copy)', //GA4 Name: "click_to_call"?
                        intent: 'Intent',
                        phoneNumber: emailPhoneAddress,
                        selection: selection,
                        words: words,
                        wordcount: wordsLength
                    };

                    ga('set', nebula.analytics.dimensions.contactMethod, 'Click-to-Call');
                    ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
                    nebula.dom.document.trigger('nebula_event', thisEvent);
                    ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.phoneNumber);
                    nebula.crm('event', 'Phone Number Copied');
                    nebula.crm('identify', {phone_contacted: thisEvent.phoneNumber});
                }
            }

            var thisEvent = {
                category: 'Copied Text',
                action: 'Copy', //This is not used for the below events //GA4 Name: "copy_text"?
                intent: 'Intent',
                phoneNumber: emailPhoneAddress,
                selection: selection,
                words: words,
                wordcount: wordsLength
            };

            nebula.dom.document.trigger('nebula_event', thisEvent);

            if ( copyCount < 5 ){
                if ( words.length > 8 ){
                    words = words.slice(0, 8).join(' ');
                    ga('send', 'event', thisEvent.category, words.length + ' words', words + '... [' + wordsLength + ' words]'); //GA4: This will need to change significantly. Event Name: "copy_text"?
                } else {
                    if ( selection.trim() === '' ){
                        ga('send', 'event', thisEvent.category, '[0 words]'); //GA4: This will need to change significantly. Event Name: "copy_text"?
                    } else {
                        ga('send', 'event', thisEvent.category, words.length + ' words', selection, words.length); //GA4: This will need to change significantly. Event Name: "copy_text"?
                    }
                }

                ga('send', 'event', thisEvent.category, words.length + ' words', words + '... [' + wordsLength + ' words]'); //GA4: This will need to change significantly. Event Name: "copy_text"?
                window.dataLayer.push({'event': 'nebula-copied-text', 'nebula-event': thisEvent});
                nebula.crm('event', 'Text Copied');
            }

            copyCount++;
        });

        //AJAX Errors
        nebula.dom.document.ajaxError(function(e, jqXHR, settings, thrownError){
            var 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';
            }

            ga('send', 'exception', {'exDescription': '(JS) AJAX Error (' + jqXHR.status + '): ' + errorMessage + ' on ' + settings.url, 'exFatal': true});
            window.dataLayer.push({'event': 'nebula-ajax-error', 'nebula-event': errorMessage});
            nebula.crm('event', 'AJAX Error');
        });

        //Window Errors
        window.addEventListener('error', function(error){
            var errorMessage = error.message + ' at ' + error.lineno + ' of ' + error.filename;
            if ( error.message.toLowerCase().indexOf('script error') > -1 ){ //If it is a script error
                errorMessage = 'Script error (An error occurred in a script hosted on a different domain)'; //No additional information is available because of the browser's same-origin policy. Use CORS when possible to get additional information.
            }

            ga('send', 'exception', {'exDescription': '(JS) ' + errorMessage, 'exFatal': false}); //Is there a better way to detect fatal vs non-fatal errors?
            window.dataLayer.push({'event': 'nebula-window-error', 'nebula-event': errorMessage});
            nebula.crm('event', 'JavaScript Error');
            nebula.usage(error);
        });

        //Reporting Observer deprecations and interventions
        //@todo Nebula 0: This may be causing "aw snap" errors in Chrome. Disabling for now until the feature is more stable.
        //https://caniuse.com/#feat=mdn-api_reportingobserver
    /*
        if ( typeof window.ReportingObserver !== 'undefined' ){ //Chrome 68+
            var nebulaReportingObserver = new ReportingObserver(function(reports, observer){
                for ( report of reports ){
                    if ( report.body.sourceFile.indexOf('extension') < 0 ){ //Ignore browser extensions
                        ga('send', 'exception', {'exDescription': '(JS) Reporting Observer [' + report.type + ']: ' + report.body.message + ' in ' + report.body.sourceFile + ' on line ' + report.body.lineNumber, 'exFatal': false});
                    }
                }
            }, {buffered: true});
            nebulaReportingObserver.observe();
        }
    */

        //Capture Print Intent
        //Note: This sends 2 events per print (beforeprint and afterprint). If one occurs more than the other we can remove one.
        if ( 'matchMedia' in window ){ //IE10+
            var mediaQueryList = window.matchMedia('print');
            mediaQueryList.addListener(function(mql){
                if ( mql.matches ){
                    sendPrintEvent('Before Print', 'mql.matches');
                } else {
                    sendPrintEvent('After Print', '!mql.matches');
                }
            });
        } else {
            window.onbeforeprint = sendPrintEvent('Before Print', 'onbeforeprint');
            window.onafterprint = sendPrintEvent('After Print', 'onafterprint');
        }
        function sendPrintEvent(action, trigger){
            var thisEvent = {
                category: 'Print',
                action: action, //GA4 Name: "print"?
                label: 'User triggered print via ' + trigger,
                intent: 'Intent'
            };

            ga('set', nebula.analytics.dimensions.eventIntent, thisEvent.intent);
            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.label);
            if ( typeof clarity === 'function' ){clarity('set', thisEvent.category, thisEvent.action);}
            window.dataLayer.push({'event': 'nebula-print', 'nebula-event': thisEvent});
            nebula.crm('event', thisEvent.category);
        }

        //Detect Adblock
        if ( nebula.user.client.bot === false && nebula.site.options.adblock_detect ){
            window.performance.mark('(Nebula) Detect AdBlock [Start]');
            jQuery.ajax({ //Eventually update this to fetch with ES6
                type: 'GET',
                url: nebula.site.directory.template.uri + '/assets/js/vendor/show_ads.js',
                dataType: 'script',
                cache: true,
                timeout: 5000
            }).done(function(){
                nebula.session.flags.adblock = false;
            }).fail(function(){
                nebula.dom.html.addClass('ad-blocker');
                ga('set', nebula.analytics.dimensions.blocker, 'Ad Blocker');
                if ( nebula.session.flags.adblock != true ){
                    ga('send', 'event', 'Ad Blocker', 'Blocked', 'This user is using ad blocking software.', {'nonInteraction': true}); //Uses an event because it is asynchronous!
                    window.dataLayer.push({'event': 'nebula-adblock-detected', 'nebula-event': thisEvent});
                    nebula.session.flags.adblock = true;
                }
            }).always(function(){
                window.performance.mark('(Nebula) Detect AdBlock [End]');
                window.performance.measure('(Nebula) Detect AdBlock', '(Nebula) Detect AdBlock [Start]', 'Detect AdBlock [End]');
            });
        }

        //DataTables Filter
        nebula.dom.document.on('keyup', '.dataTables_filter input', function(e){
            var oThis = jQuery(this);
            var thisEvent = {
                event: e,
                category: 'DataTables',
                action: 'Search Filter', //GA4 Name: "search"?
                query: oThis.val().toLowerCase().trim()
            };

            nebula.debounce(function(){
                nebula.dom.document.trigger('nebula_event', thisEvent);
                ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.query);
                window.dataLayer.push({'event': 'nebula-datatables', 'nebula-event': thisEvent});
            }, 1000, 'datatables_search_filter');
        });

        //DataTables Sorting
        nebula.dom.document.on('click', 'th.sorting', function(e){
            var thisEvent = {
                event: e,
                category: 'DataTables',
                action: 'Sort', //GA4 Name: "datatables_sort"?
                heading: jQuery(this).text()
            };

            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.heading);
            window.dataLayer.push({'event': 'nebula-datables', 'nebula-event': thisEvent});
        });

        //DataTables Pagination
        nebula.dom.document.on('click', 'a.paginate_button ', function(e){
            var thisEvent = {
                event: e,
                category: 'DataTables',
                action: 'Paginate', //GA4 Name: "datatables_paginate"?
                page: jQuery(this).text()
            };

            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.page);
            window.dataLayer.push({'event': 'nebula-datatables', 'nebula-event': thisEvent});
        });

        //DataTables Show Entries
        nebula.dom.document.on('change', '.dataTables_length select', function(e){
            var thisEvent = {
                event: e,
                category: 'DataTables',
                action: 'Shown Entries Change', //Number of visible rows select dropdown
                selected: jQuery(this).val()
            };

            nebula.dom.document.trigger('nebula_event', thisEvent);
            ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.selected);
            window.dataLayer.push({'event': 'nebula-datatables', 'nebula-event': thisEvent});
        });

        nebula.scrollDepth();
        nebula.ecommerceTracking();
    }, 'nebula event tracking');
};

Override

To override or disable this JavaScript function, simply redeclare it with the exact same function name.

JavaScript
nebula.eventTracking = function(){
    //Write your own code here, leave it blank, or return false.
}