orestbida / cookieconsent

:cookie: Simple cross-browser cookie-consent plugin written in vanilla js

Home Page:https://playground.cookieconsent.orestbida.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Bug]: Uncaught (in promise) TypeError: Cannot read properties of null (reading 'appendChild')

mrleblanc101 opened this issue · comments

Expected Behavior

I'm trying to implement CookieConsent with GTM Consent Mode.
Here is my config:

<script>
    window.dataLayer = window.dataLayer || [];
    function gtag() { window.dataLayer.push(arguments); }

    gtag('consent', 'default', {
        ad_storage: 'denied',
        ad_user_data: 'denied',
        ad_personalization: 'denied',
        analytics_storage: 'denied',
        functionality_storage: 'denied',
        personalization_storage: 'denied',
        security_storage: 'denied',
    });

    gtag('consent', 'update', {
        functionality_storage: CookieConsent.acceptedCategory('functionality') ? 'granted' : 'denied',
        ad_storage: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
        ad_user_data: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
        ad_personalization: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
        analytics_storage: CookieConsent.acceptedCategory('analytics') ? 'granted' : 'denied',
        personalization_storage: CookieConsent.acceptedCategory('personalization') ? 'granted' : 'denied',
        security_storage: CookieConsent.acceptedCategory('security') ? 'granted' : 'denied',
    });
    CookieConsent.run({
        guiOptions: {
            consentModal: {
                layout: 'box wide',
                position: 'bottom right',
                equalWeightButtons: true,
                flipButtons: true,
            },
            preferencesModal: {
                layout: 'box',
                position: 'right',
                equalWeightButtons: true,
                flipButtons: false,
            },
        },
        categories: {
            necessary: {
                readOnly: true,
            },
            marketing: {},
            analytics: {},
            functionality: {},
            personalization: {},
            security: {},
        },
        language: { ... }
        onConsent: (...args) => {
            manageConsent(args);
            window.dataLayer.push({ event: 'consent_ready' });
        },
        onChange: (...args) => {
            manageConsent(args);
            window.dataLayer.push({ event: 'consent_update' });
        },
    });

    function manageConsent() {
        gtag('consent', 'update', {
            functionality_storage: CookieConsent.acceptedCategory('functionality') ? 'granted' : 'denied',
            ad_storage: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
            ad_user_data: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
            ad_personalization: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
            analytics_storage: CookieConsent.acceptedCategory('analytics') ? 'granted' : 'denied',
            personalization_storage: CookieConsent.acceptedCategory('personalization') ? 'granted' : 'denied',
            security_storage: CookieConsent.acceptedCategory('security') ? 'granted' : 'denied',
        });
    }
</script>
<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','GTM-XXXXXXXX');</script>

Current Behavior

I understand that the errors is because the plugin try to insert the banner into the DOM, while the page has not finished loading (DOMContentLoaded).

My issue is that if I put CookieConsent.run(...) inside a window.addEventListener('load', () => { ... }), the Container Loaded event will be fired before GTM receive the consent value from gtag() inside onConsent, which mean Facebook Pixel is blocked even if the user clicked Accept All.

Screenshot 2024-04-23 at 9 37 48 AM

Screenshot 2024-04-23 at 9 37 57 AM

Steps to reproduce

.

Proposed fix or additional info.

Would it be possible to execute the "cc.run()" command as soon as possible, and only delay the insertion of the banner into the DOM once the page is loaded ? Currently, I need to wrap the whole cc.run() inside the window.addEventListener('load', () => { ... }) which delay the plugin initialization which does not require access to DOM and could be executed earlier.

Version

3.0.1

On which browser do you see the issue?

Chrome

Also, if I run CookieConsent.acceptedCategory('functionality') ? 'granted' : 'denied' before CookieConsent.run(...), it will always return denied even if the cookie is set !

Should I move all my tags from All pages trigger to DOM Ready or Window Loaded instead ?
This seems odd as All pages is the default for GA4 when using to Google Template.

Would it be possible to execute the "cc.run()" command as soon as possible

By default the plugin is loaded asynchronously, but you can change this behaviour via the lazyHtmlGeneration option.

Also, if I run CookieConsent.acceptedCategory('functionality') ? 'granted' : 'denied' before CookieConsent.run(...), it will always return denied even if the cookie is set !

Yes, that's the expected behaviour; the only method that can be run before the plugin's own initialization is getCookie. You can access the acceptedCategories before the plugin's initialization like this:

const acceptedCategories = CookieConsent.getCookie('categories', 'cc_cookie') || [];
// acceptedCategories.includes('analytics')

CookieConsent.run({...});

@orestbida

By default the plugin is loaded asynchronously, but you can change this behaviour via the lazyHtmlGeneration option.

Maybe I'm not understanding correctly, lazyHtmlGeneration seems to default to true in the documentation, yet it I still have the error. I also tried explicitely:

CookieConsent.run({
    autoShow: false,
    lazyHtmlGeneration: true,
    ...
});

Yes, that's the expected behaviour; the only method that can be run before the plugin's own initialization is getCookie. You can access the acceptedCategories before the plugin's initialization like this:

Thanks, I guess that will do, but I think it would be nice to decouple initialization and injection into the DOM.
I thought about reading the cookie manually using a cookie library like js-cookie, but I thought it was overkill.
Nice to know I can use CookieConsent.getCookie before init.

Let me know If you want me to close this or you'd like to keep it open for doc/exemple update.
Here is my final results if it can help someone else:

<script src="https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@3.0.1/dist/cookieconsent.umd.js"></script>
<script>
    window.dataLayer = window.dataLayer || [];
    function gtag() { window.dataLayer.push(arguments); }

    gtag('consent', 'default', {
        ad_storage: 'denied',
        ad_user_data: 'denied',
        ad_personalization: 'denied',
        analytics_storage: 'denied',
        functionality_storage: 'denied',
        personalization_storage: 'denied',
        security_storage: 'denied',
    });

    if(CookieConsent.getCookie('categories', 'cc_cookie')) {
        const acceptedCategories = CookieConsent.getCookie('categories', 'cc_cookie') || [];
        gtag('consent', 'update', {
            functionality_storage: acceptedCategories.includes('functionality') ? 'granted' : 'denied',
            ad_storage: acceptedCategories.includes('marketing') ? 'granted' : 'denied',
            ad_user_data: acceptedCategories.includes('marketing') ? 'granted' : 'denied',
            ad_personalization: acceptedCategories.includes('marketing') ? 'granted' : 'denied',
            analytics_storage: acceptedCategories.includes('analytics') ? 'granted' : 'denied',
            personalization_storage: acceptedCategories.includes('personalization') ? 'granted' : 'denied',
            security_storage: acceptedCategories.includes('security') ? 'granted' : 'denied',
        });
    }

    window.addEventListener('load', function () {
        CookieConsent.run({
            guiOptions: {
                consentModal: {
                    layout: 'box wide',
                    position: 'bottom right',
                    equalWeightButtons: true,
                    flipButtons: true,
                },
                preferencesModal: {
                    layout: 'box',
                    position: 'right',
                    equalWeightButtons: true,
                    flipButtons: false,
                },
            },
            categories: {
                necessary: {
                    readOnly: true,
                },
                marketing: {},
                analytics: {},
                functionality: {},
                personalization: {},
                security: {},
            },
            language: { ... },
            onFirstConsent: (...args) => {
                manageConsent(args);
                window.dataLayer.push({ event: 'consent_ready' });
            },
            onChange: (...args) => {
                manageConsent(args);
                window.dataLayer.push({ event: 'consent_update' });
            },
        });
    });

    function manageConsent() {
        gtag('consent', 'update', {
            functionality_storage: CookieConsent.acceptedCategory('functionality') ? 'granted' : 'denied',
            ad_storage: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
            ad_user_data: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
            ad_personalization: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
            analytics_storage: CookieConsent.acceptedCategory('analytics') ? 'granted' : 'denied',
            personalization_storage: CookieConsent.acceptedCategory('personalization') ? 'granted' : 'denied',
            security_storage: CookieConsent.acceptedCategory('security') ? 'granted' : 'denied',
        });
    }
</script>
<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','GTM-XXXXXXXX');</script>

Perhaps I wasn't clear, you should set lazyHtmlGeneration to false to run the plugin synchronously.

We can leave this open, until there is a docs. section on how to set up GTM.

@orestbida I tried false too and i had the exact same issue, but anyway I posted my solution above.

Thanks again for your help