Skills › Marketing Skills › conversion-tracking › phone-click.js
phone-click.js
69 lines · skills/conversion-tracking/assets/snippets/phone-click.js at 88c79f6
1/*! Phone call click detection (MIT)
2 * Detects: click on a tel: link. Pushes dataLayer event: phone_click
3 * From the conversion-tracking skill in Marketing Skills */
4(function () {
5 if (window.__ctDetectPhoneClick) return;
6 window.__ctDetectPhoneClick = true;
7 window.dataLayer = window.dataLayer || [];
9 // A click is a call INTENT, not a completed call. On desktop especially,
10 // many tel: clicks never become a conversation. Count it as a lead signal,
11 // and prefer a call tracking number if you need answered-call accuracy.
13 // Ignore a repeat click on the same number inside this window. Impatient
14 // double-taps on mobile are common and would otherwise double count.
15 var DEDUPE_MS = 2000;
16 var lastNumber = null;
17 var lastAt = 0;
19 function normalise(href) {
20 // tel:+61 (2) 1234-5678 -> +61212345678 . Keeps a leading +, drops the
21 // punctuation humans put in phone numbers so dedupe compares like for like.
22 var raw = String(href || '').replace(/^tel:/i, '');
23 try { raw = decodeURIComponent(raw); } catch (e) { /* leave as-is */ }
24 var plus = raw.trim().charAt(0) === '+' ? '+' : '';
25 return plus + raw.replace(/[^0-9]/g, '');
26 }
28 function handleClick(event) {
29 try {
30 // Left click only, and not a modified click that opens elsewhere.
31 if (event.button !== undefined && event.button !== 0) return;
32 if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
34 var target = event.target;
35 if (!target || !target.closest) return;
37 // closest() because the click usually lands on an icon or span INSIDE
38 // the anchor, not the anchor itself.
39 var link = target.closest('a[href^="tel:"], a[href^="TEL:"]');
40 if (!link) return;
42 var number = normalise(link.getAttribute('href'));
43 if (!number) return;
45 var now = new Date().getTime();
46 if (number === lastNumber && (now - lastAt) < DEDUPE_MS) return;
47 lastNumber = number;
48 lastAt = now;
50 // PII: if the site shows visitor-specific numbers (an account page, a
51 // CRM-driven "your rep" number), drop phone_number and link_text here.
52 window.dataLayer.push({
53 event: 'phone_click',
54 phone_number: number,
55 // The visible text, useful when a site shows different numbers per
56 // location or campaign. Trimmed and capped so a whole button's worth
57 // of markup text never lands in the dataLayer.
58 link_text: (link.textContent || '').trim().slice(0, 100),
59 page_path: window.location.pathname
60 });
61 } catch (e) {
62 // Never throw back into the page's own click handling.
63 }
64 }
66 // Bubble phase: a tel: click has no validation to lose a race with, and
67 // bubbling means anything that legitimately cancels the click wins first.
68 document.addEventListener('click', handleClick, false);
69})();