|
1 | | -((d, w)=>{ |
2 | | - const bm = 'biscuitman' |
3 | | - const ui = d.createElement('div') |
4 | | - let dialog |
5 | | - |
| 1 | +((d, w, bm)=>{ |
6 | 2 | const defaults = { |
7 | 3 | storageKey: 'myconsent', |
8 | 4 | global: 'Consent', |
|
44 | 40 | // uncategorizedTitle: 'Uncategorized', |
45 | 41 | // uncategorizedMessage: 'Uncategorized cookies are those currently under analysis and have not yet been assigned to a specific category', |
46 | 42 | } |
| 43 | + const o = {...defaults, ...w.biscuitman} |
47 | 44 |
|
48 | | - // read user options from 'biscuitman' global object |
49 | | - const o = w.biscuitman ? {...defaults, ...w.biscuitman} : defaults |
| 45 | + /* UI & Events */ |
| 46 | + |
| 47 | + const ui = d.createElement('div') |
| 48 | + let dialog |
50 | 49 |
|
51 | | - // Apply UI and bind events |
52 | 50 | function render() { |
53 | 51 | ui.classList.add(bm) |
54 | | - ui.style = 'position:fixed;background:#fff;bottom:0' // critical css |
| 52 | + ui.style = 'position:fixed;background:#fff;bottom:0' // critical CSS |
55 | 53 | ui.innerHTML = ` |
56 | 54 | <article> |
57 | 55 | <b>${o.title}</b> |
|
118 | 116 | d.body.appendChild(ui) |
119 | 117 | } |
120 | 118 |
|
| 119 | + const displayUI = (show) => ui.classList[show ? 'remove' : 'add']('bm-hide') |
| 120 | + |
121 | 121 | function buttonHandler(e) { |
122 | | - id = e.target.dataset.id |
| 122 | + let id = e.target.dataset.id |
| 123 | + dispatch('button', {id}) |
123 | 124 | switch (id) { |
124 | 125 | case 'accept': saveConsent(true); break; |
125 | 126 | case 'close': dialog.close(); break; |
126 | 127 | case 'settings': openModal(); break; |
127 | 128 | case 'save': saveConsent(); break; |
128 | 129 | case 'reject': saveConsent(false); break; |
129 | | - default: break |
130 | 130 | } |
131 | | - dispatch('button', {id}) |
132 | 131 | } |
133 | 132 |
|
134 | 133 | function closeModalHandler() { |
|
140 | 139 | } |
141 | 140 |
|
142 | 141 | function openModal() { |
143 | | - dialog.showModal() |
144 | 142 | dispatch('open') |
| 143 | + dialog.showModal() |
145 | 144 | } |
146 | 145 |
|
147 | | - const displayUI = (show) => d.documentElement.classList[show ? 'remove' : 'add']('js-bm-hidden') |
| 146 | + function dispatch(eventName, data) { |
| 147 | + const name = `${bm}:${eventName}` |
| 148 | + const payload = { |
| 149 | + ...(data !== undefined && data), |
| 150 | + time: +new Date() |
| 151 | + } |
| 152 | + d.dispatchEvent(new CustomEvent(name, payload)) |
| 153 | + console.debug(name, payload); |
| 154 | + } |
| 155 | + |
| 156 | + /* Consents & Injection */ |
148 | 157 |
|
149 | 158 | function readConsent() { |
150 | 159 | try { |
151 | 160 | return JSON.parse(localStorage.getItem(o.storageKey)) |
152 | 161 | } catch (err) { |
153 | | - console.error(bm, err) |
154 | | - localStorage.removeItem(o.storageKey) // If there's an error in localstorage, wipe consent data |
| 162 | + console.error(err) |
| 163 | + localStorage.removeItem(o.storageKey) |
155 | 164 | return {} |
156 | 165 | } |
157 | 166 | } |
158 | 167 |
|
159 | | - // Update global and localstorage with consent options |
160 | 168 | function saveConsent(value) { |
161 | 169 | const willReadValues = value === undefined |
162 | 170 | w[o.global].consentTime = +new Date() |
|
169 | 177 | if (!willReadValues) sectionElement.checked = value |
170 | 178 | }) |
171 | 179 | localStorage.setItem(o.storageKey, JSON.stringify(w[o.global])) |
172 | | - insertScripts() |
173 | 180 | dispatch('save', {data: w[o.global]}) |
| 181 | + insertScripts() |
174 | 182 | dialog.close() |
175 | 183 | displayUI(false) |
176 | 184 | } |
177 | 185 |
|
178 | | - // Custom events in case you want to trigger behaviour |
179 | | - function dispatch(eventName, data) { |
180 | | - const name = `${bm}:${eventName}` |
181 | | - const payload = { |
182 | | - ...(data !== undefined && data), |
183 | | - time: +new Date() |
184 | | - } |
185 | | - d.dispatchEvent(new CustomEvent(name, payload)) |
186 | | - console.debug(name, payload); |
187 | | - } |
188 | | - |
189 | | - // replaces neutralized script tags with active script tags |
190 | 186 | function insertScripts() { |
191 | | - const scripts = d.querySelectorAll('script[data-consent]') |
| 187 | + const scripts = ui.querySelectorAll('script[data-consent]') |
192 | 188 | scripts.forEach(script => { |
193 | | - // Check consent |
194 | 189 | if (!w[o.global][script.dataset.consent]) return false |
195 | | - // Create new tag |
| 190 | + |
196 | 191 | const newScript = d.createElement('script') |
197 | | - // Copy attributes, ignoring data- and type attributes |
198 | 192 | for (let { name, value } of script.attributes) { |
199 | 193 | if (name.startsWith('data-') || name === 'type') continue |
200 | 194 | newScript.setAttribute(name, value) |
201 | 195 | } |
202 | | - // set type property |
203 | 196 | newScript.setAttribute('type', script.dataset.type || 'text/javascript') |
204 | | - // copy inline scripts if they do not have a src attribute |
205 | 197 | if (!script.src) newScript.textContent = script.textContent |
206 | | - // Insert script to DOM |
207 | 198 | script.parentNode.replaceChild(newScript, script) |
208 | 199 | dispatch('inject', {el: script}) |
209 | 200 |
|
210 | | - // If tag has src and inline script, insert dependent inline script as new tag on load |
| 201 | + // If tag has src AND tag content, inject new tag adjacent to parent after load |
211 | 202 | if (script.src && script.textContent.trim() !== '') newScript.addEventListener('load', () => { |
212 | 203 | let depScript = d.createElement('script') |
213 | 204 | depScript.textContent = script.textContent |
|
217 | 208 | }); |
218 | 209 | } |
219 | 210 |
|
220 | | - // Load consent |
| 211 | + /* Start */ |
| 212 | + |
221 | 213 | w[o.global] = readConsent() || {} |
222 | 214 |
|
223 | | - // Optionally auto-accept consent if timezone is not EU |
| 215 | + // Optional Non-EU auto-consent |
224 | 216 | const tz = Intl.DateTimeFormat().resolvedOptions().timeZone |
225 | 217 | const isEuropeTimezone = /^(GMT|UTC)$/.test(tz) || /(Europe|BST|CEST|CET|EET|IST|WEST|WET|GMT-1|GMT-2|UTC+1|UTC+2|UTC+3)/.test(tz) |
226 | 218 | if (o.acceptNonEU && !isEuropeTimezone) { |
227 | 219 | saveConsent(true) |
228 | 220 | displayUI(false) |
229 | 221 | } |
230 | 222 |
|
231 | | - // Initiate UI |
| 223 | + // Render UI |
232 | 224 | render() |
233 | 225 |
|
234 | 226 | // Consent logic |
235 | 227 | if (w[o.global].consentTime) { |
236 | 228 | displayUI(false) |
237 | | - // Consent exists, initiate scripts: |
238 | 229 | insertScripts() |
239 | | - } else if (o.force) { |
240 | | - // Force consent choice |
241 | | - openModal() |
242 | | - } |
| 230 | + } else if (o.force) openModal() |
243 | 231 |
|
244 | | - // Helper global methods |
245 | | - // <a onclick="bmUpdateConsent()" href="javascript:void(0)">Update Consent Preferences</a> |
246 | | - w.bmInvalidateConsent = () => { |
| 232 | + // Helper methods |
| 233 | + // <a onclick="bmInvalidate()" href="javascript:void(0)">Delete Consent Preferences</a> |
| 234 | + w.bmInvalidate = () => { |
247 | 235 | dispatch('invalidate', {data: readConsent()}) |
248 | | - saveConsent(false) // resets UI |
| 236 | + saveConsent(false) |
249 | 237 | localStorage.removeItem(o.storageKey) |
250 | 238 | displayUI(true) |
251 | 239 | } |
252 | | - w.bmUpdateConsent = () => { |
| 240 | + // <a onclick="bmUpdate()" href="javascript:void(0)">Update Consent Preferences</a> |
| 241 | + w.bmUpdate = () => { |
253 | 242 | dispatch('update', {data: readConsent()}) |
254 | 243 | openModal() |
255 | 244 | } |
256 | | -})(document, window) |
| 245 | +})(document, window, 'biscuitman') |
0 commit comments