1/* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2 SERVICE RATE MENU (W-080) â measurement-only progressive enhancement. 3 4 The rate card is fully functional without this file: the categorized 5 dotted-leader list, every price, the "what's included" native <details> 6 disclosure, and the Service/Offer JSON-LD are ALL server-rendered 7 (rate-menu.php). This script adds exactly ONE additive layer â the fleet's 8 highest-value untracked action, per-row CTA conversion tracking (§13): 9 10 · a `call` CTA (tel:) tap â dataLayer `phone_call` 11 · a book / quote / link CTA tap â dataLayer `select_content` 12 13 It mirrors the DOM anchors directly (the widget emits no internal hzo:* event 14 to mirror â cf. nap.js's identical tel: adapter) and is a strict no-op when 15 window.dataLayer is never created. There is NO filter/scroll-spy behavior in 16 this pass (the widget is Category-B / no-JS complete); the reserved 17 filter=chips option can land later behind a data-attr without touching this. 18 19 The listener is delegated + bound once (idempotent via a namespace flag), so a 20 second script load or an AJAX-injected menu needs no re-init. Reduced-motion, 21 focus, and print are all CSS/native concerns â this file only measures. 22 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 23(function () { 24 'use strict'; 25 26 window.hzo = window.hzo || {}; 27 28 // ââ Measurement: per-row CTA tap â dataLayer âââââââââââââââââââââââââââââââ 29 // Delegated + idempotent (bound once). A `call` desktop tel: no-ops silently, 30 // so the number is ALSO the anchor's selectable link text (server-rendered) â 31 // this only measures the intent, it is not the desktop fallback. 32 function bindMeasurement() { 33 if (window.hzo.__rateMenuMeasureBound) { return; } 34 window.hzo.__rateMenuMeasureBound = true; 35 36 document.addEventListener('click', function (e) { 37 var a = (e.target && e.target.closest) 38 ? e.target.closest('[data-hzo-rate-menu] a[data-hzo-rate-cta]') 39 : null; 40 if (!a) { return; } 41 42 // Respect modifier / middle clicks â never swallow "open in new tab" 43 // and never mis-attribute a non-navigating gesture (§11.4). 44 if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) { 45 return; 46 } 47 48 var kind = a.getAttribute('data-hzo-rate-cta') || ''; 49 var name = ctaServiceName(a); 50 window.dataLayer = window.dataLayer || []; 51 52 if (kind === 'call') { 53 window.dataLayer.push({ 54 event: 'phone_call', 55 phone_number: (a.getAttribute('href') || '').replace(/^tel:/, ''), 56 service_name: name, 57 source: 'rate-menu' 58 }); 59 } else { 60 // book | quote | link â the destination form owns generate_lead. 61 window.dataLayer.push({ 62 event: 'select_content', 63 content_type: 'rate_menu_cta', 64 cta_kind: kind, 65 service_name: name, 66 source: 'rate-menu' 67 }); 68 } 69 }, false); 70 } 71 72 // Best-effort service label for the pushed event: the row's name cell. 73 function ctaServiceName(a) { 74 var row = a.closest ? a.closest('.hzo-rate-row') : null; 75 if (!row) { return ''; } 76 var nameEl = row.querySelector('.hzo-rate-row__name'); 77 return nameEl ? (nameEl.textContent || '').replace(/\s+/g, ' ').trim() : ''; 78 } 79 80 // ââ Init (idempotent) ââââââââââââââââââââââââââââââââââââââââââââââââââââââ
81 function init() { 82 bindMeasurement(); 83 } 84 85 // Expose on the shared namespace (mirrors the widget convention) so an 86 // AJAX-injected menu can re-assert the binding; both calls are no-ops after 87 // the first because the delegated listener lives on document. 88 window.hzo.rateMenu = { init: init }; 89 90 if (document.readyState === 'loading') { 91 document.addEventListener('DOMContentLoaded', init); 92 } else { 93 init(); 94 } 95})();
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.