Forstå udfordringen

Single Page Applications (SPA'er), der er bygget med React, Vue, Angular eller andre rammer, udgør unikke udfordringer for TermsFeed Cookie Consent, fordi:

  1. Ingen sideopdatering. Siden genindlæses ikke som klassiske websider.
  2. Dynamisk indhold. Indholdet indlæses dynamisk uden at udløse DOMContentLoaded igen.
  3. Ruteændringer. Navigationen sker via JavaScript, ikke via traditionelle sideindlæsninger.
  4. Statusstyring. Samtykkestatus skal bevares på tværs af ruteændringer.

Løsning: Genindlæs ved navigation

Problem

"Jeg har en React SPA, og cookie-banneret vises kun ved den første sideindlæsning. Når brugerne navigerer til andre sider, vises banneret ikke igen, hvis de ikke har truffet et valg. Desuden indlæses scripts ikke på efterfølgende sider, hvis de accepterer cookies på én side, før der foretages en fuld opdatering."

Løsning

Når du implementerer TermsFeed Cookie Consent i en SPA, skal du:

  1. Initialisere samtykke ved første indlæsning.
  2. Registrere ruteændringer.
  3. Genindlæse banneret, hvis det er nødvendigt.
  4. Kontrollere samtykkestatus igen.

React-implementering

Trin 1: Opret en Cookie Consent-komponent

// CookieConsent.js
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

const CookieConsent = () => {
    const location = useLocation();

    useEffect(() => {
        // Load TermsFeed script if not already loaded
        if (!window.cookieconsent) {
            const script = document.createElement('script');
            script.src = '//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js';
            script.charset = 'UTF-8';
            script.onload = () => initializeCookieConsent();
            document.head.appendChild(script);
        } else {
            // Script already loaded, just check if banner needs to be shown
            checkBannerState();
        }
    }, []); // Run once on mount

    useEffect(() => {
        // Check banner state on route change
        if (window.cookieconsent) {
            checkBannerState();
        }
    }, [location]); // Run on every route change

    const initializeCookieConsent = () => {
        if (window.cookieconsent && typeof window.cookieconsent.run === 'function') {
            window.cookieconsent.run({
                "notice_banner_type": "headline",
                "consent_type": "express",
                "palette": "light",
                "language": "en",
                "website_name": "My SPA",
                "website_privacy_policy_url": "https://mysite.com/privacy",
                "open_preferences_center_selector": "#changePreferences",
                "page_load_consent_levels": ["strictly-necessary"],
                "callbacks": {
                    "scripts_specific_loaded": (level) => {
                        console.log("Scripts loaded for: " + level);
                        // Re-initialize any components that need tracking
                        if (level === 'tracking') {
                            reinitializeAnalytics();
                        }
                    }
                }
            });
        }
    };

    const checkBannerState = () => {
        // Check if user has already made a consent choice
        const consentCookie = getCookie('cookie_consent_level');
        if (!consentCookie) {
            // No consent yet, banner should be visible
            // No action needed - banner will show automatically
        }
    };

    const getCookie = (name) => {
        const value = ; ${document.cookie};
        const parts = value.split(; ${name}=);
        if (parts.length === 2) return parts.pop().split(';').shift();
    };

    const reinitializeAnalytics = () => {
        // Re-run any analytics initialization needed
        if (window.gtag) {
            window.gtag('config', 'G-XXXXXXXXXX', {
                'page_path': location.pathname
            });
        }
    };

    return null; // This component doesn't render anything
};

export default CookieConsent;

Trin 2: Føj til App-komponenten

// App.js
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import CookieConsent from './components/CookieConsent';
import Home from './pages/Home';
import About from './pages/About';

function App() {
    return (
        <Router>
            <CookieConsent />
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/about" element={<About />} />
            </Routes>

            {/ Preferences button /}
            <button id="changePreferences" style={{
                position: 'fixed',
                bottom: '20px',
                right: '20px',
                zIndex: 9999
            }}>
                Cookie Settings
            </button>

            <noscript>
                Free cookie consent management tool by <a href="https://www.termsfeed.com/">TermsFeed</a>
            </noscript>
        </Router>
    );
}

export default App;

Vue.js-implementering

Trin 1: Opret plugin til cookie-samtykke

// plugins/cookieConsent.js
export default {
    install(app, options) {
        // Load script
        const loadCookieConsent = () => {
            if (window.cookieconsent) return Promise.resolve();

            return new Promise((resolve, reject) => {
                const script = document.createElement('script');
                script.src = '//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js';
                script.charset = 'UTF-8';
                script.onload = () => resolve();
                script.onerror = () => reject();
                document.head.appendChild(script);
            });
        };

        // Initialize
        const initCookieConsent = () => {
            if (window.cookieconsent && typeof window.cookieconsent.run === 'function') {
                window.cookieconsent.run({
                    "notice_banner_type": "headline",
                    "consent_type": "express",
                    "palette": "light",
                    "language": "en",
                    "website_name": options.websiteName || "My Vue SPA",
                    "website_privacy_policy_url": options.privacyUrl || "#",
                    "open_preferences_center_selector": "#changePreferences",
                    "page_load_consent_levels": ["strictly-necessary"]
                });
            }
        };

        // Make available globally
        app.config.globalProperties.$cookieConsent = {
            load: loadCookieConsent,
            init: initCookieConsent
        };

        // Auto-initialize on mount
        if (typeof window !== 'undefined') {
            loadCookieConsent().then(() => {
                document.addEventListener('DOMContentLoaded', initCookieConsent);
            });
        }
    }
};

Trin 2: Brug i hovedappen

// main.js
import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';
import cookieConsentPlugin from './plugins/cookieConsent';

const router = createRouter({
    history: createWebHistory(),
    routes: [
        { path: '/', component: () => import('./views/Home.vue') },
        { path: '/about', component: () => import('./views/About.vue') }
    ]
});

const app = createApp(App);

app.use(router);
app.use(cookieConsentPlugin, {
    websiteName: 'My Vue App',
    privacyUrl: 'https://mysite.com/privacy'
});

app.mount('#app');

Trin 3: Opret app-komponenten

<!-- App.vue -->
<template>
    <div id="app">
        <router-view />

        <button id="changePreferences" class="cookie-settings-btn">
            Cookie Settings
        </button>

        <noscript>
            Free cookie consent management tool by
            <a href="https://www.termsfeed.com/">TermsFeed</a>
        </noscript>
    </div>
</template>

<script>
export default {
    name: 'App',
    mounted() {
        // Watch for route changes
        this.$router.afterEach((to, from) => {
            // Check if consent banner needs to be re-shown
            this.checkConsentState();
        });
    },
    methods: {
        checkConsentState() {
            const consentCookie = document.cookie
                .split('; ')
                .find(row => row.startsWith('cookie_consent_level='));

            if (!consentCookie) {
                console.log('No consent given yet');
            }
        }
    }
};
</script>

<style>
.cookie-settings-btn {
    position: fixed;
    bottom: 20px;
    right: 20px;
    z-index: 9999;
    padding: 10px 20px;
    background: #007bff;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}
</style>

Angular-implementering

Trin 1: Opret Cookie Consent-tjenesten

// services/cookie-consent.service.ts
import { Injectable } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';

declare global {
    interface Window {
        cookieconsent: any;
    }
}

@Injectable({
    providedIn: 'root'
})
export class CookieConsentService {
    private scriptLoaded = false;

    constructor(private router: Router) {}

    initialize(): Promise<void> {
        return this.loadScript().then(() => {
            this.initializeConsent();
            this.watchRouteChanges();
        });
    }

    private loadScript(): Promise<void> {
        if (this.scriptLoaded || window.cookieconsent) {
            return Promise.resolve();
        }

        return new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.src = '//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js';
            script.charset = 'UTF-8';
            script.onload = () => {
                this.scriptLoaded = true;
                resolve();
            };
            script.onerror = () => reject();
            document.head.appendChild(script);
        });
    }

    private initializeConsent(): void {
        if (window.cookieconsent && typeof window.cookieconsent.run === 'function') {
            window.cookieconsent.run({
                notice_banner_type: 'headline',
                consent_type: 'express',
                palette: 'light',
                language: 'en',
                website_name: 'My Angular App',
                website_privacy_policy_url: 'https://mysite.com/privacy',
                open_preferences_center_selector: '#changePreferences',
                page_load_consent_levels: ['strictly-necessary'],
                callbacks: {
                    scripts_specific_loaded: (level: string) => {
                        console.log('Scripts loaded for:', level);
                        this.handleConsentUpdate(level);
                    }
                }
            });
        }
    }

    private watchRouteChanges(): void {
        this.router.events.pipe(
            filter(event => event instanceof NavigationEnd)
        ).subscribe(() => {
            // Check consent state on route change
            this.checkConsentState();
        });
    }

    private checkConsentState(): void {
        const consentCookie = this.getCookie('cookie_consent_level');
        if (!consentCookie) {
            console.log('No consent given yet');
        }
    }

    private handleConsentUpdate(level: string): void {
        // Handle consent updates
        if (level === 'tracking') {
            this.initializeAnalytics();
        }
    }

    private initializeAnalytics(): void {
        // Re-initialize analytics on consent
        if ((window as any).gtag) {
            (window as any).gtag('config', 'G-XXXXXXXXXX', {
                page_path: this.router.url
            });
        }
    }

    private getCookie(name: string): string | null {
        const value = ; ${document.cookie};
        const parts = value.split(; ${name}=);
        if (parts.length === 2) {
            return parts.pop()?.split(';').shift() || null;
        }
        return null;
    }

    openPreferences(): void {
        if (window.cookieconsent && typeof window.cookieconsent.openPreferencesCenter === 'function') {
            window.cookieconsent.openPreferencesCenter();
        }
    }
}

Trin 2: Initialiser i app-komponenten

// app.component.ts
import { Component, OnInit } from '@angular/core';
import { CookieConsentService } from './services/cookie-consent.service';

@Component({
    selector: 'app-root',
    template: 
        <router-outlet></router-outlet>

        <button id="changePreferences" class="cookie-settings-btn">
            Cookie Settings
        </button>

        <noscript>
            Free cookie consent management tool by
            <a href="https://www.termsfeed.com/">TermsFeed</a>
        </noscript>
    ,
    styles: [
        .cookie-settings-btn {
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 9999;
            padding: 10px 20px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
    ]
})
export class AppComponent implements OnInit {
    constructor(private cookieConsentService: CookieConsentService) {}

    ngOnInit(): void {
        this.cookieConsentService.initialize().catch(error => {
            console.error('Failed to initialize cookie consent:', error);
        });
    }
}

Problem 1: Banneren vises ikke igen ved navigation

Problem

Brugeren navigerer uden at træffe et valg, og banneret forsvinder.

Løsning

Kontroller, om der blev givet samtykke ved skift af rute.

// Check if consent was given on route change
router.afterEach(() => {
    const hasConsent = getCookie('cookie_consent_level');
    if (!hasConsent) {
        // Banner should be visible - it will show automatically
        // No manual action needed if properly configured
    }
});

Problem 2: Scripts indlæses ikke efter samtykke på nye ruter

Problem

"Brugeren accepterer cookies, navigerer til en anden side i min React-app, men Google Analytics sporer ikke den nye sidevisning."

Løsning

Brug callbacks til at spore sidevisninger ved hvert ruteændring.

// In your callback
"callbacks": {
    "scripts_specific_loaded": (level) => {
        if (level === 'tracking' && window.gtag) {
            // Track page view on every route change
            router.afterEach((to) => {
                gtag('config', 'G-XXXXXXXXXX', {
                    'page_path': to.path
                });
            });
        }
    }
}

Problem 3: Der vises flere bannerinstanser

Problem

Banneret vises flere gange, når man navigerer hurtigt.

Løsning

Brug en guard til at forhindre flere initialiseringer

// Use a guard to prevent multiple initializations
let isInitialized = false;

const initCookieConsent = () => {
    if (isInitialized) return;
    isInitialized = true;

    cookieconsent.run({ / config / });
};

Åbn Indstillingscenter programmatisk

For SPA'er kan du åbne indstillinger uden en statisk knap:

// Method 1: Using the openPreferencesCenter() method
const openPreferences = () => {
    if (window.cookieconsent && typeof window.cookieconsent.openPreferencesCenter === 'function') {
        window.cookieconsent.openPreferencesCenter();
    } else {
        console.error('Cookie consent not initialized');
    }
};

// In React
<button onClick={openPreferences}>Change Cookie Settings</button>

// In Vue
<button @click="openPreferences">Change Cookie Settings</button>

// In Angular
<button (click)="openPreferences()">Change Cookie Settings</button>