1(function (Drupal, drupalSettings) { 2 3 'use strict'; 4 5 /** 6 * Initialize Klaro! with custom settings. 7 */ 8 Drupal.behaviors.klaro = { 9 config: false, 10 manager: false, 11 observing: false, 12 needCallBehaviors: false, 13 ready: false, 14 attach: function (context, settings) { 15 if (!settings.klaro) { 16 return; 17 } 18 if (typeof klaro === 'undefined') { 19 document.querySelector('#klaro-js').addEventListener("load", () => { 20 Drupal.behaviors.klaro.proceed(context, settings); 21 }); 22 } 23 else { 24 Drupal.behaviors.klaro.proceed(context, settings); 25 } 26 }, 27 proceed: function(context, settings) { 28 // Bootstrap config once. 29 if (!Drupal.behaviors.klaro.config) { 30 settings.klaro.config.services.forEach(function (service, a_index) { 31 // Set callback for each service. 32 settings.klaro.config.services[a_index].callback = Drupal.behaviors.klaro.serviceCallback; 33 // Wrap onInit/onAccept/onDecline string code as error-safe functions. 34 ['onInit', 'onAccept', 'onDecline'].forEach(function (hook) { 35 const code = service[hook]; 36 if (code && code.length > 0) { 37 settings.klaro.config.services[a_index][hook] = function (opts) { 38 try { 39 Function('opts', code)(opts); 40 } 41 catch (error) { 42 console.error('Error executing ' + hook + ' code for service "' + service.name + '":', error); 43 } 44 }; 45 } 46 }); 47 // Create regular expressions from config. 48 service.cookies.forEach(function (cookie, c_index) { 49 settings.klaro.config.services[a_index]['cookies'][c_index] = [new RegExp(cookie[0], 'i'), cookie[1] || '/', cookie[2] || location.host]; 50 }); 51 }); 52 53 Drupal.behaviors.klaro.config = settings.klaro.config; 54 } 55 56 // Decorate wrapperIdentifiers in context. 57 Drupal.behaviors.klaro.klaroDecorateWrapper(context, settings); 58 59 // Remove unused data attributes. 60 context.querySelectorAll('a[data-name]')?.forEach((item) => { 61 item.removeAttribute('data-href'); 62 }); 63 64 // Store reference to manager once. 65 if (!Drupal.behaviors.klaro.manager) { 66 Drupal.behaviors.klaro.manager = klaro.getManager(Drupal.behaviors.klaro.config); 67 } 68 69 // Loading klaro less intrusive. 70 if (settings.klaro.dialog_mode === 'silent') { 71 Drupal.behaviors.klaro.manager.confirmed = true; 72 } 73 74 // Setup klaro on each attach (support ajax insert commands). 75 klaro.setup(Drupal.behaviors.klaro.config); 76 77 // Observe klaro-element to add aria-features. 78 Drupal.behaviors.klaro.klaroElementObserver(); 79 80 // Add accessibility features to the learn more link. 81 let learn_more_link = document.querySelector('a.cm-link.cn-learn-more'); 82 if (learn_more_link) { 83 // Add title to learn more link. 84 let label_open_consent_dialog = Drupal.t("Open consent dialog", {},{context: 'klaro'}); 85 learn_more_link.setAttribute('title', label_open_consent_dialog); 86 // Add the "button" role to the learn more link. 87 learn_more_link.setAttribute('role', 'button'); 88 // Add the attribute aria-haspopup="dialog". 89 learn_more_link.setAttribute('aria-haspopup', 'dialog'); 90 } 91 92 // Store reference to manager once. 93 if (!Drupal.behaviors.klaro.manager) { 94 Drupal.behaviors.klaro.manager = klaro.getManager(Drupal.behaviors.klaro.config); 95 } 96 97 if(context === document) { 98 var elements = once('klaro', 'body', context); 99 Array.prototype.forEach.call(elements, function () { 100 // Add toggle dialog button. 101 if (settings.klaro.show_toggle_button && !document.getElementById('klaro_toggle_dialog')) { 102 var button_label = Drupal.t("Manage consents", {},{context: 'klaro'}); 103 var button_html = '<button id="klaro_toggle_dialog" aria-label="' + button_label + '" aria-haspopup="dialog" title="'+ button_label + '" type="button" class="klaro_toggle_dialog klaro_toggle_dialog_override" rel="open-consent-manager"></button>'; 104 document.body.insertAdjacentHTML('afterbegin', button_html); 105 } 106 }); 107 } 108 else { 109 for (var app in Drupal.behaviors.klaro.manager.executedOnce) { 110 if (Drupal.behaviors.klaro.manager.executedOnce[app]) {
111 // (Re-)apply consents on each attach (ajax add_js support) 112 // only if there are elements with data-src left. 113 if (document.querySelectorAll('[data-name="' + app + '"][data-src]:not([src])').length > 0) { 114 Drupal.behaviors.klaro.manager.applyConsents(); 115 } 116 } 117 } 118 } 119 120 // Set preview image for contextual consent. 121 var elements = once('klaro-thumbnail', '.klaro.cm-as-context-notice', context); 122 Array.prototype.forEach.call(elements, function (el) { 123 let klaro_elem = (el.closest('.field'))?.querySelector('[data-modified-by-klaro]'); 124 let thumbnail = klaro_elem?.getAttribute('data-thumbnail'); 125 let title = klaro_elem?.getAttribute('title'); 126 127 if (thumbnail) { 128 el.parentElement.style.backgroundImage = 'url(' + thumbnail + ')'; 129 el.parentElement.style.backgroundSize = 'cover'; 130 el.parentElement.style.backgroundPosition = 'center'; 131 el.firstChild.style.backgroundColor = 'rgba(250, 250, 250, 0.75)'; 132 } 133 134 if (title) { 135 let title_elem = document.createElement('p'); 136 title_elem.textContent = title; 137 el.firstChild.prepend(title_elem); 138 } 139 }); 140 141 // Iterate services and replace placeholder texts. 142 Drupal.behaviors.klaro.config.services.forEach(function(service, a_index) { 143 if (service.contextualConsentText) { 144 var elements = once('klaro-text', '[data-type="placeholder"][data-name="' + service.name + '"] div.context-notice > p:first-child', context); 145 if (elements.length > 0) { 146 let title_elem = document.createElement('p'); 147 title_elem.innerHTML = service.contextualConsentText; 148 Array.prototype.forEach.call(elements, function (el) { 149 el.parentNode.replaceChild(title_elem.cloneNode(true), el); 150 }); 151 } 152 } 153 }); 154 155 // Add link to consent manager for contextual consents. 156 var elements = once('klaro-consent-link', '[data-type="placeholder"] div.context-notice'); 157 var title = Drupal.t("Open the Consent Management Dialog", {}, {context: 'klaro'}); 158 var linktext = Drupal.t("Manage privacy settings", {}, {context: 'klaro'}); 159 Array.prototype.forEach.call(elements, function (el) { 160 el.insertAdjacentHTML('beforeend', `<p class="cm-dialog-link"><a href="#" title="${title}" rel="open-consent-manager">${linktext}</a></p>`); 161 }); 162 163 // Call Behaviors if needed. 164 if (Drupal.behaviors.klaro.needCallBehaviors && context === document) { 165 Drupal.behaviors.klaro.needCallBehaviors = false; 166 Drupal.attachBehaviors(document, drupalSettings); 167 } 168 169 // Set ready. 170 Drupal.behaviors.klaro.ready = true; 171 }, 172 173 /** 174 * A callback for each service that will run at the beginning and on consent change. 175 */ 176 serviceCallback: function (consent, service) { 177 178 // If onlyOnce, abort if already executed. 179 if (Drupal.behaviors.klaro.manager && service.onlyOnce && Drupal.behaviors.klaro.manager.executedOnce[service.name]) { 180 return; 181 } 182 183 // Execute callbackCode from config form. 184 if (service.callbackCode.length > 0) { 185 try { 186 Function('consent', 'service', service.callbackCode)(consent, service); 187 } 188 catch (error) { 189 console.error('Error executing callback code for service "' + service.name + '":', error); 190 } 191 } 192 193 // Do the behaviors only for consent. 194 if (!consent) { 195 return; 196 } 197 198 // Rewrite data-id to id. 199 var elements = document.querySelectorAll('[data-name="' + service.name + '"][data-id]'); 200 if (elements.length > 0) { 201 Array.prototype.forEach.call(elements, function(element) { 202 element.setAttribute('id', element.getAttribute('data-id')); 203 }); 204 if (Drupal.behaviors.klaro.ready) { 205 Drupal.attachBehaviors(document, drupalSettings); 206 } 207 else { 208 Drupal.behaviors.klaro.needCallBehaviors = true; 209 } 210 } 211 212 let knownBehaviors = Object.keys(Drupal.behaviors); 213 var elements = document.querySelectorAll('[data-name="' + service.name + '"][src]'); 214 if (!elements) { 215 return; 216 } 217 218 // #3459261 Fix HtmlMediaElements not loading correctly. 219 var source_tags = document.querySelectorAll('source[data-name="' + service.name + '"]'); 220 if (source_tags.length > 0) { 221 var loading_state; 222 for (var i = 0; i < source_tags.length; i++) { 223 loading_state = source_tags[i].parentElement.readyState; 224 if (loading_state == 0 || loading_state == 1) { 225 source_tags[i].parentElement.load(); 226 } 227 } 228 } 229 230 function processBehaviors() { 231 for (let name in Drupal.behaviors) { 232 if (knownBehaviors.indexOf(name) === -1) { 233 // Execute newly added behaviors. This is somewhat guesswork, as 234 // the order of execution is undetermined based on file load order. 235 Drupal.behaviors[name].attach(document, drupalSettings); 236 knownBehaviors.push(name); 237 } 238 } 239 } 240 241 Array.prototype.forEach.call(elements, function(element) { 242 if (element.complete) { 243 processBehaviors(); 244 } 245 else { 246 element.addEventListener('load', processBehaviors); 247 } 248 }); 249 }, 250 251 /** 252 * Decorate wrapperIdentifiers in context. 253 */ 254 klaroDecorateWrapper: function(context, settings) { 255 // Iterate services and wrapperIdentifier.
256 settings.klaro.config.services.forEach(function(service, a_index) { 257 service.wrapperIdentifier.forEach(function(wrapperIdentifier) { 258 var elements = once('klaro-wrap', wrapperIdentifier, context); 259 Array.prototype.forEach.call(elements, function(el) { 260 if (el) { 261 var wrapper = document.createElement('div'); 262 wrapper.setAttribute('data-name', service.name); 263 wrapper.setAttribute('data-original-display', getComputedStyle(el).display); 264 el.parentNode.insertBefore(wrapper, el); 265 wrapper.appendChild(el); 266 } 267 }); 268 }); 269 }); 270 }, 271 272 /** 273 * Watch for DOMchanges on klaro-element. 274 */ 275 klaroElementObserver: function() { 276 var targetNode = document.getElementById("klaro"); 277 if (Drupal.behaviors.klaro.observing || !targetNode) { 278 return; 279 } 280 Drupal.behaviors.klaro.observing = true; 281 282 const config = { childList: true, subtree: true }; 283 const callback = (mutationList, observer) => { 284 for (const mutation of mutationList) { 285 if (mutation.type === "childList") { 286 Drupal.behaviors.klaro.klaroElementMutator(); 287 } 288 } 289 }; 290 // Create an observer instance linked to the callback function 291 const observer = new MutationObserver(callback); 292 observer.observe(targetNode, config); 293 Drupal.behaviors.klaro.klaroElementMutator(); 294 }, 295 296 /** 297 * Observer klaro element to adapt customizations. 298 */ 299 klaroElementMutator: function() { 300 301 // Add minimal accessibility features to the customize consent dialog. 302 var labels = document.querySelectorAll('#klaro label') 303 if (labels.length > 0){ 304 for (var i = 0; i < labels.length; i++) { 305 labels[i].setAttribute('onkeydown', 'Drupal.behaviors.klaro.KlaroToggleService(event)'); 306 labels[i].setAttribute('aria-labelledby', labels[i].getAttribute('for') + '-title'); 307 labels[i].setAttribute('aria-describedby', labels[i].getAttribute('for') + '-description'); 308 if (labels[i].previousElementSibling && labels[i].previousElementSibling.tagName == 'INPUT') { 309 labels[i].previousElementSibling.setAttribute('tabindex', '0'); 310 labels[i].removeAttribute('tabindex'); 311 } 312 } 313 // Set focus to first possible label. 314 document.querySelector('#klaro input:not(:disabled)+label')?.focus(); 315 } 316 // Add accessibility features to the preferences dialog : role dialog, aria-modal and aria-labelledby. 317 if (document.querySelector('.cm-modal.cm-klaro')) { 318 var elem = document.querySelector('.cm-modal.cm-klaro'); 319 elem.setAttribute('role', 'dialog'); 320 elem.setAttribute('aria-modal', 'true'); 321 elem.querySelector('.cm-header .title').setAttribute('id', 'cm-modal-title'); 322 elem.setAttribute('aria-labelledby', 'cm-modal-title'); 323 } 324 325 // Handle close button X. 326 if (drupalSettings.klaro.show_close_button) { 327 if (document.querySelector('#klaro-cookie-notice')) { 328 var elem = document.querySelector('#klaro-cookie-notice'); 329 } 330 else if (document.querySelector('.cm-modal.cm-klaro')) { 331 var elem = document.querySelector('.cm-modal.cm-klaro'); 332 if ((Drupal.behaviors.klaro.manager.confirmed) && (!Drupal.behaviors.klaro.config.mustConsent)) { 333 elem = false; 334 } 335 } 336 337 if (elem && !document.querySelector('.klaro-close')) { 338 var close_label = Drupal.t("Close dialog and decline all", {},{context: 'klaro'}); 339 var close_html = '<button title="' + close_label + '" type="button" class="cn-decline klaro-close" tabindex="0" aria-label="' + close_label + '"></button>'; 340 elem.insertAdjacentHTML('beforeend', close_html);; 341 document.querySelector('.klaro-close')?.addEventListener('click', (e) => { 342 Drupal.behaviors.klaro.manager.changeAll(false); 343 Drupal.behaviors.klaro.manager.saveAndApplyConsents(); 344 }, false); 345 document.querySelector('.klaro').classList.add('klaro-close-enabled'); 346 document.querySelector('.klaro .cookie-modal .cm-modal .hide')?.remove(); 347 } 348 } 349 350 }, 351 KlaroToggleService: function(event) { 352 if (event.key === "Enter") { 353 event.target.click();
354 event.target.focus(); 355 } 356 } 357 }; 358 359 /** 360 * Open the Klaro! consent notice from links & buttons. 361 */ 362 Drupal.behaviors.klaroLink = { 363 attach: function (context, settings) { 364 var elements = once('klaroLink', '[rel*="open-consent-manager"], [href*="#klaro"], .open-consent-manager', context); 365 Array.prototype.forEach.call(elements, function(element) { 366 element.addEventListener('click', function (event) { 367 klaro.show(Drupal.behaviors.klaro.config); 368 event.preventDefault(); 369 return false; 370 }); 371 }); 372 } 373 }; 374})(Drupal, drupalSettings);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.