Load Scripts after consent with JavaScript
Load Google Maps after click on consent button
The best way to learn something is to try and build things! So let's build a real-world example by loading and initialize a google map when the user clicks on a button.
$ mkdir loady loady/assets
$ cd loady
$ touch index.html assets/main.js
<!-- index.html -->
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Load scripts after consent</title>
<style>
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#map {
width: 100%;
height: 100%;
display: grid;
place-content: center;
}
#load {
border: 1px solid #eee;
background: transparent;
padding: 6px 10px;
font-size: 16px;
cursor: pointer;
border-radius: 3px;
}
</style>
</head>
<body>
<div id="map">
<button id="load">Click me to load js</button>
</div>
<script src="./assets/main.js"></script>
</body>
</html>
Cool, that's all we need to get started. At the moment, nothing happens, so let's fill the main.js file!
// main.js
const loady = (trigger, storageItem, script, attributes) => {}
loady
is our main function that handles the loading of the Google Maps
JavaScript API. It takes four arguments, which do the following: trigger
is
the clicked button, storageItem
is the name of the item in localStorage to look
for consent, script
is the URL to the API script, and attributes
are added
to the script element, like defer or async.
// main.js
const triggerButton = document.querySelector('#load')
const storageItem = 'mapsConsent'
const script = 'https://maps.googleapis.com/maps/api/js?key=YOUR-API-KEY&callback=initMap'
const attributes = {}
const loady = (trigger, storageItem, script, attributes) => {}
loady(triggerButton, storageItem, script, attributes)
At the moment nothing happens, but we fed our loady
function with all information
it needs! Now let's move on and build some logic into it.
Check trigger and localStorage
First, we need to make sure that the trigger is a DOM element, and the browser supports localStorage.
// main.js
// ...
const loady = (trigger, storageItem, script, attributes) => {
if (trigger instanceof HTMLElement === false) {
throw new Error('Trigger is not a DOM Element.')
}
if (typeof localStorage === undefined) {
throw new Error('localStorage is not available in this browser.')
}
}
// ...
Add eventListener to trigger
Ok, now that we're sure that the trigger is a DOM element and localStorage works, let's add an eventListener to the trigger and the function that runs on click.
// main.js
// ...
const loady = (trigger, storageItem, script, attributes) => {
// ...
const run = () => {
localStorage.setItem(storageItem, true) // set storageItem in localStorage to 'true'
const scriptElement = createScriptElement() // function that will create our script element
appendScriptElement(scriptElement) // function that will append our script to the DOM
}
trigger.addEventListener('click', () => run())
}
// ...
Create script element and append it to the DOM
Like you saw in our run function, we need to more functions: one that creates our script element and on that appends it to the head (or before the closing body if you wish).
// main.js
// ...
const loady = (trigger, storageItem, script, attributes) => {
// ...
const createScriptElement = () => {
const el = document.createElement('script')
el.src = script
for (let [key, value] of Object.entries(attributes)) {
el.setAttribute(key, value)
}
return el
}
const appendScriptElement = (el) => {
document.getElementsByTagName('head')[0].appendChild(el) // appends the script to the head
}
// ...
}
// ...
Write a basic google maps initialization
Before we can test loady
, we need to write the function that initializes google
maps.
// main.js
// ...
window.initMap = () => {
let map = new google.maps.Map(document.getElementById('map'), {
center: { lat: 52.28155, lng: 8.04235 },
zoom: 15,
})
}
const loady = (trigger, storageItem, script, attributes) => {
// ...
}
// ...
Now spin up a small server and let's test loady
!
$ php -S localhost:8181
If you visit our test site and click on the button you should see... a map!
Nice! But loady
has two small problems we need to tackle.
Check if consent is already given
At the moment, every time you reload the site you'll need to click the button again, even
if you gave the consent before. To avoid this, we check the localStorage
at the
beginning, and trigger the run function if our storageItem
is already set to true.
// main.js
// ...
const loady = (trigger, storageItem, script, attributes) => {
// ...
let consent = localStorage.getItem(storageItem)
// ...
const run = () => {
localStorage.setItem(storageItem, true)
consent = localStorage.getItem(storageItem) // override localStorage item with new value
const scriptElement = createScriptElement()
appendScriptElement(scriptElement)
}
trigger.addEventListener('click', () => run())
if (consent === 'true') trigger.click() // Trigger run function
}
// ...
Now our script gets loaded if the consent was given before, even on reload.
Check if the script is already appended
One last thing we need to check is if the script is already appended. Otherwise,
it could happen that the script will be appended twice. Add the following
isAlreadyAppended
function and adjust our appendScriptElement
to check the
status.
// main.js
// ...
const loady = (trigger, storageItem, script, attributes) => {
// ...
const isAlreadyAppended = () => {
// check if script with src is already in DOM
const scripts = Array.from(document.getElementsByTagName('script'))
let result = false
scripts.forEach((s) => (s.src == script ? (result = true) : null))
return result
}
// ...
const appendScriptElement = (el) => {
const isAppended = isAlreadyAppended()
if (!isAppended) {
// append script if it's not already in the DOM
document.getElementsByTagName('head')[0].appendChild(el)
}
}
// ...
}
// ...
Done! Our small little loady
function successfully loads scripts after a
consent click! You could expand loady
to, for example, run a callback after
adding the script, or make it work with async/await, etc.
The complete script
// Set our variables
const triggerButton = document.querySelector('#load') // our trigger button
const storageItem = 'mapsConsent' // key of the item to check if consent is given
const script = 'https://maps.googleapis.com/maps/api/js?key=AIzaSyCTpR8Iqp8AFDWbP64uPK9wKTJThDfD4os&callback=initMap' // script to load
const attributes = {} // could contain something like { 'defer': true }
// Basic google maps init function
window.initMap = () => {
let map = new google.maps.Map(document.getElementById('map'), {
center: { lat: 52.28155, lng: 8.04235 },
zoom: 15,
})
}
// Our main function to handle script loading
const loady = (trigger, storageItem, script, attributes) => {
// Check if trigger parameter is a DOM element
if (trigger instanceof HTMLElement === false) {
throw new Error('Trigger is not a DOM Element.')
}
// Check if browser supports localStorage
if (typeof localStorage === undefined) {
throw new Error('localStorage is not available in this browser.')
}
// Read the localStorage item, to check if consent is already given
let consent = localStorage.getItem(storageItem)
// Function to check if script is already appended to the DOM
const isAlreadyAppended = () => {
const scripts = Array.from(document.getElementsByTagName('script'))
let result = false
scripts.forEach((s) => (s.src == script ? (result = true) : null))
return result
}
// Function that creates our script element. Also adds attributes if that
// parameter is filled with an object
const createScriptElement = () => {
const el = document.createElement('script')
el.src = script
for (let [key, value] of Object.entries(attributes)) {
el.setAttribute(key, value)
}
return el
}
// Function that appends script to the Head. Could also be adjusted to prepend
// it before the closing </body> if you want to.
const appendScriptElement = (el) => {
const isAppended = isAlreadyAppended()
if (!isAppended) {
document.getElementsByTagName('head')[0].appendChild(el)
}
}
// Our run function that will be trigger if the user a) clicks on our trigger
// element or b) if consent is already given and found in localStorage.
const run = () => {
localStorage.setItem(storageItem, true)
consent = localStorage.getItem(storageItem)
const scriptElement = createScriptElement()
appendScriptElement(scriptElement)
}
// Add eventListener to trigger element and check if consent is already given
trigger.addEventListener('click', () => run())
if (consent === 'true') trigger.click()
}
// Run loady
loady(triggerButton, storageItem, script, attributes)