/** * Tracking Controller * * Handles toggle switches, input changes, and code preview generation * for the Settings > Tracking page. * * Note: Toggle slider click behavior is handled by ControlsController. * This controller listens for 'toggle' events to update card states and preview. */ export class TrackingController { constructor() { this.setupToggleListeners(); this.setupInputListeners(); this.setupCopyButton(); this.initializeCardStates(); this.updatePreview(); } /** * Listen for toggle events from ControlsController */ private setupToggleListeners(): void { document.querySelectorAll('.tracking-card swp-toggle-slider').forEach(slider => { slider.addEventListener('toggle', (e) => { const detail = (e as CustomEvent).detail; const card = slider.closest('.tracking-card'); if (card) { if (detail.value === 'no') { card.classList.add('disabled'); } else { card.classList.remove('disabled'); } } this.updatePreview(); }); }); } /** * Setup input listeners for contenteditable fields and textareas */ private setupInputListeners(): void { // Contenteditable fields const editableIds = [ 'metaPixelId', 'ga4Id', 'gtmId', 'plausibleDomain', 'fathomSiteId', 'matomoUrl', 'matomoSiteId' ]; editableIds.forEach(id => { const element = document.getElementById(id); if (element) { element.addEventListener('input', () => this.updatePreview()); } }); // Textareas const textareaIds = ['customHeadScripts', 'customBodyScripts']; textareaIds.forEach(id => { const element = document.getElementById(id) as HTMLTextAreaElement; if (element) { element.addEventListener('input', () => this.updatePreview()); } }); } /** * Setup copy button functionality */ private setupCopyButton(): void { const copyBtn = document.getElementById('copyTrackingCode'); if (!copyBtn) return; copyBtn.addEventListener('click', () => this.copyCode()); } /** * Initialize card states based on toggle values */ private initializeCardStates(): void { document.querySelectorAll('.tracking-card[data-tracker]').forEach(card => { const toggle = card.querySelector('swp-toggle-slider'); if (toggle && toggle.dataset.value === 'no') { card.classList.add('disabled'); } }); } /** * Check if a tracker is enabled */ private isTrackerEnabled(trackerName: string): boolean { const card = document.querySelector(`[data-tracker="${trackerName}"]`); if (!card) return false; const toggle = card.querySelector('swp-toggle-slider'); return toggle?.dataset.value === 'yes'; } /** * Get text content from an element by ID */ private getValue(id: string): string { const element = document.getElementById(id); return element?.textContent?.trim() || ''; } /** * Get value from textarea by ID */ private getTextareaValue(id: string): string { const element = document.getElementById(id) as HTMLTextAreaElement; return element?.value?.trim() || ''; } /** * Escape HTML entities */ private escapeHtml(text: string): string { return text .replace(/&/g, '&') .replace(//g, '>'); } /** * Update the code preview based on active trackers */ private updatePreview(): void { const preview = document.getElementById('trackingCodePreview'); if (!preview) return; let code = ''; // Meta Pixel if (this.isTrackerEnabled('meta')) { const pixelId = this.getValue('metaPixelId'); if (pixelId) { code += `<!-- Meta Pixel --> <script> !function(f,b,e,v,n,t,s) {if(f.fbq)return;n=f.fbq=function(){n.callMethod? n.callMethod.apply(n,arguments):n.queue.push(arguments)}; if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0'; n.queue=[];t=b.createElement(e);t.async=!0; t.src=v;s=b.getElementsByTagName(e)[0]; s.parentNode.insertBefore(t,s)}(window, document,'script', 'https://connect.facebook.net/en_US/fbevents.js'); fbq('init', '${pixelId}'); fbq('track', 'PageView'); </script> `; } } // Google Analytics if (this.isTrackerEnabled('ga4')) { const measurementId = this.getValue('ga4Id'); if (measurementId) { code += `<!-- Google Analytics (GA4) --> <script async src="https://www.googletagmanager.com/gtag/js?id=${measurementId}"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', '${measurementId}'); </script> `; } } // Google Tag Manager if (this.isTrackerEnabled('gtm')) { const containerId = this.getValue('gtmId'); if (containerId) { code += `<!-- Google Tag Manager --> <script> (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','${containerId}'); </script> `; } } // Plausible if (this.isTrackerEnabled('plausible')) { const domain = this.getValue('plausibleDomain'); if (domain) { code += `<!-- Plausible Analytics --> <script defer data-domain="${domain}" src="https://plausible.io/js/script.js"></script> `; } } // Fathom if (this.isTrackerEnabled('fathom')) { const siteId = this.getValue('fathomSiteId'); if (siteId) { code += `<!-- Fathom Analytics --> <script src="https://cdn.usefathom.com/script.js" data-site="${siteId}" defer></script> `; } } // Matomo if (this.isTrackerEnabled('matomo')) { const serverUrl = this.getValue('matomoUrl'); const siteId = this.getValue('matomoSiteId'); if (serverUrl && siteId) { code += `<!-- Matomo Analytics --> <script> var _paq = window._paq = window._paq || []; _paq.push(['trackPageView']); _paq.push(['enableLinkTracking']); (function() { var u="${serverUrl}/"; _paq.push(['setTrackerUrl', u+'matomo.php']); _paq.push(['setSiteId', '${siteId}']); var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); })(); </script> `; } } // Custom Head Scripts const customHead = this.getTextareaValue('customHeadScripts'); if (customHead) { code += `<!-- Custom Scripts --> ${this.escapeHtml(customHead)} `; } // Update preview const codeElement = preview.querySelector('code'); if (codeElement) { if (code) { codeElement.innerHTML = code.trim(); } else { codeElement.innerHTML = '<!-- Ingen aktive tracking-koder -->'; } } } /** * Copy generated code to clipboard */ private copyCode(): void { const preview = document.getElementById('trackingCodePreview'); if (!preview) return; const codeElement = preview.querySelector('code'); if (!codeElement) return; // Get plain text version (strip HTML tags and decode entities) const code = codeElement.innerHTML .replace(/]*>/g, '') .replace(/<\/span>/g, '') .replace(/</g, '<') .replace(/>/g, '>') .replace(/&/g, '&'); navigator.clipboard.writeText(code).then(() => { const btn = document.getElementById('copyTrackingCode'); if (!btn) return; const originalHtml = btn.innerHTML; btn.innerHTML = ' Kopieret!'; btn.style.color = 'var(--color-green)'; setTimeout(() => { btn.innerHTML = originalHtml; btn.style.color = ''; }, 2000); }); } }