1/** 2 * sd_events.js - Comprehensive Client-Side Event Tracker for Shift Digital Analytics 3 * 4 * This script handles all client-side event tracking requirements outlined in the SD Analytics guide. 5 * It uses a single, efficient event listener and relies on data-attributes in the HTML for configuration. 6 * 7 * @version 2.3 8 * @author AAN 9 */ 10document.addEventListener('DOMContentLoaded', () => { 11 12 /** 13 * Centralized helper function to send an SD Analytics event. 14 * @param {object} data - The data layer object for the event. 15 */ 16 function sendSDEvent(data) { 17 if (typeof sd !== 'function') { 18 console.error('Shift Digital Analytics function "sd" not found.'); 19 return; 20 } 21 22 // If vehicle data exists on the page, intelligently attach it to the event. 23 if (window.sdVehicleData) { 24 const eventName = data.events; 25 // Attach to any event that is not a generic page-level event like formShown 26 if (eventName && !eventName.endsWith('Shown')) { 27 data.vehicleDetails = window.sdVehicleData; 28 if (eventName.startsWith('form')) { 29 data.formVehicle = window.sdVehicleData; 30 } 31 } 32 } 33 34 sd('dataLayer', data); 35 sd('send'); 36 console.log('SD Event Sent:', data); 37 } 38 39 // Expose for inline handlers (e.g., onclick="sendSDEvent(...)") 40 window.sendSDEvent = sendSDEvent; 41 42 /** 43 * Retrieves vehicle data if it's embedded in the page. 44 */ 45 function loadVehicleData() { 46 const vehicleDataElement = document.getElementById('sd-vehicle-data'); 47 if (vehicleDataElement) { 48 try { 49 window.sdVehicleData = JSON.parse(vehicleDataElement.textContent); 50 } catch (e) { 51 console.error('Error parsing SD vehicle data JSON.', e); 52 } 53 } 54 } 55 56 loadVehicleData(); 57 58 /** 59 * =================================================================== 60 * Consolidated Click Event Listener 61 * =================================================================== 62 */ 63 document.body.addEventListener('click', (event) => { 64 const trackableElement = event.target.closest('[data-sd-event]'); 65 if (!trackableElement) return; 66 67 const eventType = trackableElement.dataset.sdEvent; 68 69 // FIX: Ignore events that are handled by other listeners (e.g., form submit) 70 if (eventType === 'typedSearch') { 71 return; 72 } 73 74 // Prevent default link navigation to see the console log 75 event.preventDefault(); 76 77 let eventData = { events: eventType }; 78 79 switch (eventType) { 80 case 'clickToCall': 81 case 'clickToText': 82 eventData.clickToCallDepartment = trackableElement.dataset.sdDepartment || 'Other'; 83 break; 84 case 'getDirections': 85 case 'scheduleServiceClick': 86 // No extra data needed 87 break; 88 case 'brochureDownload': 89 eventData.brochureName = trackableElement.href ? trackableElement.href.split('/').pop() : 'unknown'; 90 break;
91 case 'linkClick': 92 eventData.linkType = trackableElement.dataset.sdLinkType || 'Other'; 93 break; 94 case 'carouselClick': 95 eventData.carouselClick = { 96 assetName: trackableElement.dataset.sdAssetName || 'Unnamed', 97 assetPosition: parseInt(trackableElement.dataset.sdAssetPosition, 10) || 0 98 }; 99 break; 100 case 'vehicleQuickView': 101 try { 102 eventData.vehicleDetails = JSON.parse(trackableElement.dataset.sdVehicleJson || '{}'); 103 } catch (e) { console.error('Invalid JSON for vehicleQuickView', e); } 104 break; 105 case 'offerClick': 106 case 'serviceOfferClick': 107 try { 108 const offerData = JSON.parse(trackableElement.dataset.sdOfferJson || '{}'); 109 eventData[eventType] = offerData; // Use dynamic key for correct object name 110 } catch (e) { console.error('Invalid JSON for offerClick', e); } 111 break; 112 default: 113 console.warn(`Unknown data-sd-event type: ${eventType}`); 114 return; 115 } 116 sendSDEvent(eventData); 117 }); 118 119 /** 120 * =================================================================== 121 * Form Interaction & Submission Tracking 122 * =================================================================== 123 */ 124 // FIX: Broaden the selector to include the typed search form. 125 const forms = document.querySelectorAll('form[data-sd-form-type], form[data-sd-event="typedSearch"]'); 126 forms.forEach(form => { 127 // Fallback for typedSearch forms that don't have a form-type 128 const formType = form.dataset.sdFormType || 'Typed Search'; 129 const displayType = form.dataset.sdDisplayType || 'in-page'; 130 let hasBeenInitiated = false; 131 132 const observer = new IntersectionObserver((entries) => { 133 entries.forEach(entry => { 134 if (entry.isIntersecting) { 135 sendSDEvent({ events: 'formShown', formType, displayType }); 136 observer.unobserve(form); 137 } 138 }); 139 }, { threshold: 0.5 }); 140 observer.observe(form); 141 142 form.addEventListener('focusin', () => { 143 if (!hasBeenInitiated) { 144 hasBeenInitiated = true; 145 sendSDEvent({ events: 'formInitiation', formType, displayType }); 146 } 147 }, { once: true, capture: true }); 148 149 form.addEventListener('input', (event) => { 150 if (event.target.matches('input, textarea, select')) { 151 const fieldName = event.target.name || 'unknown'; 152 clearTimeout(event.target.sdDebounce); 153 event.target.sdDebounce = setTimeout(() => { 154 sendSDEvent({ events: 'formFieldInteraction', formType, formFieldName: fieldName, displayType }); 155 }, 500); 156 } 157 }); 158 159 form.addEventListener('submit', (event) => { 160 event.preventDefault(); // Stop actual form submission for testing 161 if (form.dataset.sdEvent === 'typedSearch') { 162 const input = form.querySelector('input[type="search"], input[type="text"]'); 163 if (input) { 164 sendSDEvent({ events: 'typedSearch', typedSearchContent: input.value }); 165 } 166 } else { 167 const leadId = form.dataset.sdLeadId || `client-${Date.now()}`; 168 sendSDEvent({ 169 events: 'formSubmission', 170 formType, 171 displayType, 172 leadId, 173 leadType: form.dataset.sdLeadType || 'lead' 174 }); 175 } 176 }); 177 }); 178 179 /** 180 * Global function to trigger a 'formSubmissionError' event. 181 */ 182 window.triggerSDFormError = function(formElement, errorMessage) { 183 if (formElement && formElement.dataset.sdFormType) { 184 const formType = formElement.dataset.sdFormType; 185 const displayType = formElement.dataset.sdDisplayType || 'in-page'; 186 sendSDEvent({ events: 'formSubmissionError', formType, displayType, formError: errorMessage }); 187 } 188 } 189}); 190
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.