JavaScript code to show an EU cookie policy banner.

A banner to comply with EU Cookie Law.

I recently received an email from Google in regards to my Google AdSense account…

They want me to obtain the end user’s consent to the storing and accessing of cookies etc. Which, I interpret as a request to add one of those EU cookie consent banners, that are appearing absolutely everywhere at the moment.

So, I’d best add one to my website… But, rather than just add one of the millions of Word Press plugins already available, I thought it might be fun to roll my own one in JavaScript.

While creating the banner in PHP, would probably be the best approach for a Word Press based website, I decided to implement my version 100% in JavaScript – because of reasons…

For more information on EU cookie law, and how it may effect you, or your website, you could do far worse than take a look at this website: http://eucookielaw.org.uk/how-it-effects-me.

OK, so best speed, to my JavaScript implementation!

To get the following code to work in Word Press, I added the following code to the HTML head in option: Appearance -> Editor -> Header (header.php):


		



Be careful with the paths for cookies.css and cookies.js in the HTML above. They must obviously be changed to match the correct location of the files on your web server.

The code below does not require Word Press to work, it will also work with the example HTML file I’ve added below.


Files:

Here are the files needed to add my banner to your site. I’ll explain how to use it below, plus talk a bit about how it was coded.

I uploaded them to /scripts/cookies/* on my site. So, as ever, if you need them to be placed elsewhere, please check/change the paths in the code/HTML below.

cookies.css – CSS file to format the banner.
cookies.html – An example HTML file that shows how to add the banner to a web page.
cookies.js – The banner JavaScript code itself.
policy.txt – An example policy text file – see eucookielaw.org.uk for more examples.

Above files are all that is needed to add the banner.

However, if you are interested in how it works, or how to change it. Then please keep reading!


How it works:

On load the banner code will request a cookie from the user’s web browser. If this cookie doesn’t exist, then we know that the user has not accepted our cookie policy yet, so the banner is drawn.

If the cookie does already exist on the user’s machine, then the value of this cookie is compared to the current minimum policy version number our web site will accept.

If the current minimum policy version number is greater than the value stored in the cookie, then the banner is drawn with an ‘our policy has changed’ type message.

(i.e.: Maybe they accepted our cookie policy in the past, but are now visiting us again six months or so later, and during that time our policy has changed).

Otherwise, no banner is drawn at all, as the user previously accepted our cookie policy, so we need not display an annoying banner again until the cookie stored on their computer expires.


The HTML required to bootstrap the banner JavaScript code.



        
		
        
        
        


As you can see, I use a font from Google (set in the css file below), and then load in two files. The first is cookies.css, the second the JavaScript banner code itself in cookies.js.

I then add an event listener to create the banner when all DOM content has been loaded, by calling function EUconsetForCookies in cookies.js.

The sample HTML file cookies.html assumes all files are in the same directory. When deployed to production, you will need to make sure the path to cookies.css and cookies.js in your HTML point to the correct location. For me, this is /scriptes/cookies/*.


Definitions used by cookies.js

    // DEFINITIONS:

    var COOKIE_NAME = "EUconsentForCookies-Accepted-Cookies-Policy";

The banner works by storing a cookie on the clients web browser. And this is the name it will use.

    var ACCEPTANCE_DURATION_IN_DAYS = 365;

The number of days before the banner is redrawn. In this case one whole year. (Unless we change our policies terms and conditions – see below).

    var MINIMUM_ACCEPTANCE_VERSION_TO_REMOVE_BANNER = 1;

The minimum version number of our ‘policy terms and conditions’ that our website will accept before re-showing the banner again.

For example: We release terms and conditions for our website under policy version number 1. Three months later we add a cool game that requires us to change our terms and conditions. So we update our policy details to version 2, and set MINIMUM_ACCEPTANCE_VERSION_TO_REMOVE_BANNER to 2.

Now the banner will be redrawn for anyone who re-visits our site that has previously only agreed to policy version 1.

    var COOKIE_DIV_ID = "EUconsentForCookies-cookie-banner";
    var ACCEPT_BUTTON_ID = "EUconsentForCookies-accept-button";

ID Names for HTML elements the banner code will create.

    var POLICY_MESSAGE = "This website uses cookies";

The message that will appear on the banner for the first time the user visits our website.

    var POLICY_MESSAGE_NEW = "Our cookie policy has changed";

The message that will appear on the banner when our cookies policy agreement changes.

    var POLICY_DETAILS = "View our cookie policy";

The text inside the button our user can click to view the cookies policy details.

    var POLICY_ACCEPTANCE = "Okay, understood";

The text inside the button the user can click to accept our cookies policy.

    var OUR_DETAILS_POLICY_URL = "http://frutbunn.tk/scripts/cookies/policy.txt";

URL of the web page that shows our cookie policy.

    var DO_NOT_FADE_IN_EVERY_TIME = 1;

If set to 1, do not re-fade in again for this session. So the banner doesn’t keep fading in and out every time we navigate to another page on our site.

Change this to 0 if you want the banner to annoyingly fade in every time.

    var DO_NOT_FADE_IN_EVERY_TIME_COOKIE_NAME = 'EUconsentForCookies-No-Fade';

Cookie name for ‘do not fade in every time’ option above. Is only stored for the current session. Will be deleted when the user closes their web browser.


Tools to get and set cookie values from the user’s web browser.

    var tools = {
        cookie: {
            set: function(name, value, path, days) {
                days = typeof days === 'undefined' ? ""
                 : "expires=" + new Date(+new Date() + 24*60*60*days*1000 ).toUTCString() + "; ";
                path = typeof path === 'undefined' ? "" : "path=" + path + "; ";

                document.cookie = name + "=" + JSON.stringify(value) + "; " + days + path;
            },

            get: function(name) {
                var c = String(document.cookie);
                var a = c == "" ? null : c.split(';');

                for(var i in a) {
                    var p = String(a[i]).trim().match(/^(.*?)=(.*)/, '$1, $2');
                    if (! p) continue;

                    var key = p[1];
                    if (name == key) {
                        var value = p[2];
                        try{ value = JSON.parse(value); } catch(e) {}

                        return value;
                    }
                }

                return null;
            }
        }

    }

These two functions allow us to ‘get’ a cookie from the user’s web browser, or ‘set’ a cookie on it.

If we ‘set’ a cookie value, then the set function will first convert the payload object into a string using JSON.stringify. So we can use this code to store any valid JavaScript object as a cookie.

When we ‘get’ a cookie value, the get function will also try and convert the string received back into a JavaScript object using JSON.parse. If this conversion fails, then the string value is returned instead.

The continue isn’t really needed, but as a rule of thumb, I like to treat any data I receive from a source I have no control over (i.e.: user’s web browser) as suspicious.


All the code is abstracted away in an enclosure.

var EUconsentForCookies = function() {

...

    return addBanner;
}();

All code to display the banner is wrapped in an enclosure. As the variable EUconsentForCookies is a function that returns another function (from the enclosure’s scope) all we need do is treat EUconsentForCookies as a function. That’s why we call EUconsentForCookies in the HTML example code above instead of the addBanner() function.

If you do not supply any arguments (as the example HTML above), then the banner will appear before all HTML on the web page. If you supply an ID, then the banner will appear after that element instead.

You can supply arguments to the event listener like this:

document.addEventListener('DOMContentLoaded', function(){ EUconsentForCookies("My argument"); }, false);


State structure (an object in JavaScript parlance) where the object instance’s state variables are stored.

    var state = {
        intervalTimer: {
            fadeIn: null,
            fadeOut: null
        },
        opacity: 0,
        headerNode: null,
        policyVersion: null
    }

I like to keep an object instance’s variables all in one place. Usually for a simple object, this will be one structure (object in JavaScript parlance) called state. I think this is better than lots of loose variables at the objects scope level.

Our banner object only needs a few variables:

state.intervalTimer.fadeIn: The ‘fade in’ Timer event created with window.setInterval.
state.intervalTimer.fadeOut: The ‘fade out’ Timer event created with window.setInterval.
state.opacity: The current opacity of the banner whilst fading in or out.
headerNode: The element in the web page HTML we are to draw the banner after – if any.
policyVersion: The current policy version the user has accepted – if any.


The function that adds the banner web page.

    function addBanner(after) {
        state.policyVersion = tools.cookie.get(COOKIE_NAME);

        if (state.policyVersion < MINIMUM_ACCEPTANCE_VERSION_TO_REMOVE_BANNER)
            prependBanner(after);
    }

This function is called by the DOMContentLoaded event triggered in our web page HTML.

It will try to retrieve the current policy number cookie from the user's web browser. If it cannot, it assumes the user is visiting our site for the first time, and we draw the banner.

If the user has visited us before (i.e.: tools.cookie.get returns a number instead of null), then the policy version number they agreed to, is compared to the current minimum policy version number we can accept.

If they have not previously agreed to a policy version number high enough, we draw the banner with an 'our policy has changed' message.

    function prependBanner(after) {
        state.headerNode = (typeof after === 'string') ? document.getElementById(after) : document.body;

        // Create banner HTML:
        var banner = document.createElement("div");
        banner.id = COOKIE_DIV_ID;
        banner.className += " " + "eu-cookies-policy";

        banner.innerHTML=
'' + (state.policyVersion ? POLICY_MESSAGE_NEW : POLICY_MESSAGE)
 + ' ' + POLICY_ACCEPTANCE + ''
 + ' ' + POLICY_DETAILS
 + '';

        // Prepend banner HTML:
        state.headerNode.insertBefore(banner, state.headerNode.firstChild);

        document.getElementById(ACCEPT_BUTTON_ID).addEventListener('click', acceptCookiePolicy, false);

        // Do not fade in banner if session cookie exists.
        if ( tools.cookie.get(DO_NOT_FADE_IN_EVERY_TIME_COOKIE_NAME) )
            state.opacity = 1;
		
        // Add interval timer to fade banner in:
        state.intervalTimer.fadeIn = window.setInterval(fadeIn, 20);	
		
        // Add session cookie if no fade in enabled. 
        if (DO_NOT_FADE_IN_EVERY_TIME) 
            tools.cookie.set(DO_NOT_FADE_IN_EVERY_TIME_COOKIE_NAME, 1, '/');
    }

This function creates and prepends the banner HTML onto the web page.


Timer to 'fade in' the banner.

    function fadeIn() {
        state.opacity += 0.01;
        if (state.opacity > .7) {
            window.clearInterval(state.intervalTimer.fadeIn);
            state.opacity = .7;
        }

        document.getElementById(COOKIE_DIV_ID).style.opacity = state.opacity + "";
    }

The timer event function called while the banner is faded in.


Function called when the user clicks the 'accept policy' button.

    function acceptCookiePolicy() {
        document.getElementById(ACCEPT_BUTTON_ID).removeEventListener('click', acceptCookiePolicy);

        state.intervalTimer.fadeOut = window.setInterval(fadeOut, 20);
        window.clearInterval(state.intervalTimer.fadeIn);
        tools.cookie.set(COOKIE_NAME, ACCEPTANCE_VERSION, '/', ACCEPTANCE_DURATION_IN_DAYS);
    }

This function is called when the user clicks the 'Okay, Understood' button - thus accepting our cookies policy.

It will save a new cookie to their web browser, so we know they have accepted our terms and conditions next time they visit us.

Then, clear the 'fade in' timer, and start the 'fade out' timer function to slowly fade out the banner from the screen.


Timer to 'fade out' the banner.

    function fadeOut() {
        state.opacity -= 0.01;
        if (state.opacity < 0) {
            window.clearInterval(state.intervalTimer.fadeOut);
            state.opacity = 0

            removeBanner();
        } else {
            document.getElementById(COOKIE_DIV_ID).style.opacity = state.opacity + "";
        }
    }

The timer event function called while the banner is faded out.


Function to remove the banner from the web page.

    function removeBanner() {
        document.getElementById(COOKIE_DIV_ID).parentNode.removeChild(document.getElementById(COOKIE_DIV_ID));
    }

All HTML that comprises the banner is contained within one HTML DIV element, and by deleting that DIV and everything inside it, we remove the banner.


OK, that's it!

OK, that is all! If you would like to use/abuse this code, then please feel free to do so etc...


All the code listed for easy copy & pasting:

An example HTML file to show the banner:






    
        

        
        
        
        
        



        
		
        
        
        



    
    

    

TEST PAGE...

cookies.js:

'use strict'

/*
A small library to ask the user for consent to store cookies in order to
comply with EU law.

Made By David Eggleston & James Dawson 2015 - frutbunn@gmail.com

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

var EUconsentForCookies = function() {

    // DEFINITIONS:

    var COOKIE_NAME = "EUconsentForCookies-Accepted-Cookies-Policy";
    var ACCEPTANCE_DURATION_IN_DAYS = 365;

    var MINIMUM_ACCEPTANCE_VERSION_TO_REMOVE_BANNER = 1;
    var ACCEPTANCE_VERSION = MINIMUM_ACCEPTANCE_VERSION_TO_REMOVE_BANNER;

    var COOKIE_DIV_ID = "EUconsentForCookies-cookie-banner";
    var ACCEPT_BUTTON_ID = "EUconsentForCookies-accept-button";

    var POLICY_MESSAGE = "This website uses cookies";
    var POLICY_MESSAGE_NEW = "Our cookie policy has changed";
    var POLICY_DETAILS = "View our cookie policy";
    var POLICY_ACCEPTANCE = "Okay, understood";
    var OUR_DETAILS_POLICY_URL = "http://frutbunn.tk/scripts/cookies/policy.txt";

    var DO_NOT_FADE_IN_EVERY_TIME = 1;
    var DO_NOT_FADE_IN_EVERY_TIME_COOKIE_NAME = 'EUconsentForCookies-No-Fade';

    // PRIVATE METHODS AND PROPERTIES:

    var tools = {
        cookie: {
            set: function(name, value, path, days) {
                days = typeof days === 'undefined' ? ""
                 : "expires=" + new Date(+new Date() + 24*60*60*days*1000 ).toUTCString() + "; ";
                path = typeof path === 'undefined' ? "" : "path=" + path + "; ";

                document.cookie = name + "=" + JSON.stringify(value) + "; " + days + path;
            },

            get: function(name) {
                var c = String(document.cookie);
                var a = c == "" ? null : c.split(';');

                for(var i in a) {
                    var p = String(a[i]).trim().match(/^(.*?)=(.*)/, '$1, $2');
                    if (! p) continue;

                    var key = p[1];
                    if (name == key) {
                        var value = p[2];
                        try{ value = JSON.parse(value); } catch(e) {}

                        return value;
                    }
                }

                return null;
            }
        }

    }

    var state = {
        intervalTimer: {
            fadeIn: null,
            fadeOut: null
        },
        opacity: 0,
        headerNode: null,
        policyVersion: null
    }

    function addBanner(after) {
        state.policyVersion = tools.cookie.get(COOKIE_NAME);

        if (state.policyVersion < MINIMUM_ACCEPTANCE_VERSION_TO_REMOVE_BANNER)
            prependBanner(after);
    }

    function fadeIn() {
        state.opacity += 0.01;
        if (state.opacity > .7) {
            window.clearInterval(state.intervalTimer.fadeIn);
            state.opacity = .7;
        }

        document.getElementById(COOKIE_DIV_ID).style.opacity = state.opacity + "";
    }

    function fadeOut() {
        state.opacity -= 0.01;
        if (state.opacity < 0) {
            window.clearInterval(state.intervalTimer.fadeOut);
            state.opacity = 0

            removeBanner();
        } else {
            document.getElementById(COOKIE_DIV_ID).style.opacity = state.opacity + "";
        }
    }

    function acceptCookiePolicy() {
        document.getElementById(ACCEPT_BUTTON_ID).removeEventListener('click', acceptCookiePolicy);

        state.intervalTimer.fadeOut = window.setInterval(fadeOut, 20);
        window.clearInterval(state.intervalTimer.fadeIn);
        tools.cookie.set(COOKIE_NAME, ACCEPTANCE_VERSION, '/', ACCEPTANCE_DURATION_IN_DAYS);
    }

    function prependBanner(after) {
        state.headerNode = (typeof after === 'string') ? document.getElementById(after) : document.body;

        // Create banner HTML:
        var banner = document.createElement("div");
        banner.id = COOKIE_DIV_ID;
        banner.className += " " + "eu-cookies-policy";

        banner.innerHTML=
'' + (state.policyVersion ? POLICY_MESSAGE_NEW : POLICY_MESSAGE)
 + ' ' + POLICY_ACCEPTANCE + ''
 + ' ' + POLICY_DETAILS
 + '';

        // Prepend banner HTML:
        state.headerNode.insertBefore(banner, state.headerNode.firstChild);

        document.getElementById(ACCEPT_BUTTON_ID).addEventListener('click', acceptCookiePolicy, false);

        // Do not fade in banner if session cookie exists.
        if ( tools.cookie.get(DO_NOT_FADE_IN_EVERY_TIME_COOKIE_NAME) )
            state.opacity = 1;
		
        // Add interval timer to fade banner in:
        state.intervalTimer.fadeIn = window.setInterval(fadeIn, 20);	
		
        // Add session cookie if no fade in enabled. 
        if (DO_NOT_FADE_IN_EVERY_TIME) 
            tools.cookie.set(DO_NOT_FADE_IN_EVERY_TIME_COOKIE_NAME, 1, '/');
    }

    function removeBanner() {
        document.getElementById(COOKIE_DIV_ID).parentNode.removeChild(document.getElementById(COOKIE_DIV_ID));
    }

    // USER API:

    return addBanner;
}();

cookies.css

/*
A small library to ask the user for consent to store cookies in order to
comply with EU law.

Made By David Eggleston & James Dawson 2015 - frutbunn@gmail.com

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

body{
  margin:0px;
  background:#fff;
}

.eu-cookies-policy a{
  color: #fff;
  font-family: 'Open Sans', sans-serif;
  z-index: 10;
}

.eu-cookies-policy {
  /* #222222 */
  opacity: 0;
  text-align: center;
  width:100%;
  position:fixed;
  top:0px;
  background:#552222;
  color: #fff;
  border:6px solid #552222;
  font-family: 'Open Sans', sans-serif;
  z-index: 10;
}

.eu-cookies-policy_btn {
  font-family: Arial;
  color: #ffffff;
  font-size: 15px;
  background: #787822;
  padding: 1px 20px 1px 20px;
  border-radius: 6px;
  text-decoration: none;
}

.eu-cookies-policy_btn:hover {
  background: #3cb0fd;
  background-image: -webkit-linear-gradient(top, #3cb0fd, #3498db);
  background-image: -moz-linear-gradient(top, #3cb0fd, #3498db);
  background-image: -ms-linear-gradient(top, #3cb0fd, #3498db);
  background-image: -o-linear-gradient(top, #3cb0fd, #3498db);
  background-image: linear-gradient(to bottom, #3cb0fd, #3498db);
  text-decoration: none;
}

Leave a Reply

Your email address will not be published. Required fields are marked *