1/** 2 * UMBC Interaction Tracking â generic button / CTA click coverage. 3 * 4 * Fires a single `ui_click` dataLayer event for high-signal interactions: 5 * ⢠every <button> and [role="button"] (real controls â toggles, modals, tabsâ¦) 6 * ⢠anything explicitly tagged with data-track="Label" (editor opt-in) 7 * ⢠CTA-styled links (class contains btn / button / cta) 8 * 9 * Deliberately NOT every <a>: plain navigation is already visible via pageviews, 10 * and outbound/download links should come from GA4 Enhanced Measurement (a toggle, 11 * no code). Tracking every click would duplicate data and risk the Stape request 12 * cap â see umbc-sgtm-migration-plan.md §11.6 / §12.5. Add data-track to any link 13 * you want captured; add data-no-track to anything you want suppressed. 14 * 15 * This is a high-volume micro-interaction stream: per §12.5, route `ui_click` to 16 * GA4 directly (not through Stape). Conversions live in umbc-conversion-tracking.js. 17 */ 18(function () { 19 'use strict'; 20 21 var MAX_LABEL = 100; 22 23 function classStr(el) { 24 return (typeof el.className === 'string' ? el.className : '').toLowerCase(); 25 } 26 27 function looksLikeCta(el) { 28 return /(^|[\s_-])(btn|button|cta)([\s_-]|$)/.test(classStr(el)); 29 } 30 31 function textLabel(el) { 32 var t = (el.textContent || '').replace(/\s+/g, ' ').trim(); 33 return t.length > MAX_LABEL ? t.slice(0, MAX_LABEL) : t; 34 } 35 36 function resolveLabel(el) { 37 return el.getAttribute('data-track') 38 || el.getAttribute('aria-label') 39 || textLabel(el) 40 || el.getAttribute('title') 41 || el.id 42 || ''; 43 } 44 45 function nearestSection(el) { 46 var s = el.closest('[data-section], section[id], [role="region"][aria-label]'); 47 if (!s) return ''; 48 return s.getAttribute('data-section') || s.id || s.getAttribute('aria-label') || ''; 49 } 50 51 // Capture phase so we still record clicks on controls that stopPropagation. 52 document.addEventListener('click', function (e) { 53 var el = e.target.closest ? e.target.closest('button, [role="button"], [data-track], a') : null; 54 if (!el) return; 55 56 // Owned by other trackers / intentionally excluded. 57 if (el.closest('[data-umbc-register-cta]')) return; // event_register_click 58 if (el.closest('[data-no-track]')) return; // explicit opt-out 59 if (el.closest('#umbc-cookie-banner')) return; // consent UI, own semantics 60 if (el.closest('.qs-listbox')) return; // quick-search, already tracked 61 62 var tag = el.tagName.toLowerCase(); 63 var isButton = tag === 'button' || el.getAttribute('role') === 'button'; 64 var isTagged = el.hasAttribute('data-track'); 65 var isLink = tag === 'a'; 66 67 // Plain (non-CTA, non-tagged) links: skip â navigation shows up in pageviews. 68 if (isLink && !isButton && !isTagged && !looksLikeCta(el)) return; 69 70 var label = resolveLabel(el); 71 if (!label) return; // nothing meaningful to report 72 73 var payload = { 74 event: 'ui_click', 75 click_text: label, 76 click_type: isTagged ? 'tagged' : (isButton ? 'button' : 'link'), 77 click_id: el.id || '', 78 click_classes: classStr(el), 79 click_section: nearestSection(el) 80 }; 81 if (isLink) { 82 var href = el.getAttribute('href'); 83 if (href) payload.click_url = href; 84 } 85 86 window.dataLayer = window.dataLayer || []; 87 window.dataLayer.push(payload); 88 }, true); 89})();
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.