1/*! 2 * CookieConsent v2.9.1 3 * https://www.github.com/orestbida/cookieconsent 4 * Author Orest Bida 5 * Released under the MIT License 6 */ 7(function(){ 8 'use strict'; 9 /** 10 * @param {HTMLElement} [root] - [optional] element where the cookieconsent will be appended 11 * @returns {Object} cookieconsent object with API 12 */ 13 var CookieConsent = function(root){ 14 15 /** 16 * CHANGE THIS FLAG FALSE TO DISABLE console.log() 17 */ 18 var ENABLE_LOGS = false; 19 20 var _config = { 21 'mode': 'opt-in', // 'opt-in', 'opt-out' 22 'current_lang': 'en', 23 'auto_language': null, 24 'autorun': true, // run as soon as loaded 25 'page_scripts': true, 26 'hide_from_bots': true, 27 'cookie_name': 'cc_cookie', 28 'cookie_expiration': 182, // default: 6 months (in days) 29 'cookie_domain': location.hostname, // default: current domain 30 'cookie_path': '/', 31 'cookie_same_site': 'Lax', 32 'use_rfc_cookie': false, 33 'autoclear_cookies': true, 34 'revision': 0, 35 'script_selector': 'data-cookiecategory' 36 }; 37 38 var 39 /** 40 * Object which holds the main methods/API (.show, .run, ...) 41 */ 42 _cookieconsent = {}, 43 44 /** 45 * Global user configuration object 46 */ 47 user_config, 48 49 /** 50 * Internal state variables 51 */ 52 saved_cookie_content = {}, 53 cookie_data = null, 54 55 /** 56 * @type {Date} 57 */ 58 consent_date, 59 60 /** 61 * @type {Date} 62 */ 63 last_consent_update, 64 65 /** 66 * @type {string} 67 */ 68 consent_uuid, 69 70 /** 71 * @type {boolean} 72 */ 73 invalid_consent = true, 74 75 consent_modal_exists = false, 76 consent_modal_visible = false, 77 78 settings_modal_visible = false, 79 80 /** 81 * @type {HTMLElement[]} 82 */ 83 focusable_edges, 84 85 /** 86 * @type {HTMLDivElement} 87 */ 88 focused_modal, 89 90 /** 91 * @type {HTMLSpanElement} 92 */ 93 cmFocusSpan, 94 95 /** 96 * @type {HTMLSpanElement} 97 */ 98 smFocusSpan, 99 100 all_table_headers, 101 102 /** 103 * @type {{}[]} 104 */ 105 all_blocks, 106 107 // Helper callback functions 108 // (avoid calling "user_config['onAccept']" all the time) 109 onAccept, 110 onChange, 111 onFirstAction, 112 113 revision_enabled = false, 114 valid_revision = true, 115 revision_message = '', 116 117 // State variables for the autoclearCookies function 118 changed_settings = [], 119 reload_page = false; 120 121 /** 122 * Accept type: 123 * - "all" 124 * - "necessary" 125 * - "custom" 126 * @type {string} 127 */ 128 var accept_type; 129 130 /** 131 * Contains all accepted categories 132 * @type {string[]} 133 */ 134 var accepted_categories = []; 135 136 /** 137 * Contains all non-accepted (rejected) categories 138 * @type {string[]} 139 */ 140 var rejected_categories = []; 141 142 /** 143 * Contains all categories enabled by default 144 * @type {string[]} 145 */ 146 var default_enabled_categories = []; 147 148 // Don't run plugin (to avoid indexing its text content) if bot detected 149 var is_bot = false; 150 151 /** 152 * Save reference to the last focused element on the page 153 * (used later to restore focus when both modals are closed) 154 */ 155 var last_elem_before_modal; 156 var last_consent_modal_btn_focus; 157 158 /** 159 * Both of the arrays below have the same structure: 160 * [0] => holds reference to the FIRST focusable element inside modal 161 * [1] => holds reference to the LAST focusable element inside modal 162 */ 163 var consent_modal_focusable = []; 164 var settings_modal_focusable = []; 165 166 /** 167 * Keep track of enabled/disabled categories 168 * @type {boolean[]} 169 */ 170 var toggle_states = []; 171 172 /** 173 * Stores all available categories 174 * @type {string[]} 175 */ 176 var all_categories = []; 177 178 /** 179 * Keep track of readonly toggles 180 * @type {string[]} 181 */ 182 var readonly_categories = []; 183 184 /** 185 * Pointers to main dom elements (to avoid retrieving them later using document.getElementById) 186 */ 187 var 188 /** @type {HTMLElement} */ html_dom = document.documentElement, 189 /** @type {HTMLElement} */ main_container, 190 /** @type {HTMLElement} */ all_modals_container, 191 192 /** @type {HTMLElement} */ consent_modal, 193 /** @type {HTMLElement} */ consent_modal_title, 194 /** @type {HTMLElement} */ consent_modal_description, 195 /** @type {HTMLElement} */ consent_primary_btn, 196 /** @type {HTMLElement} */ consent_secondary_btn, 197 /** @type {HTMLElement} */ consent_buttons, 198 /** @type {HTMLElement} */ consent_modal_inner, 199 200 /** @type {HTMLElement} */ settings_container, 201 /** @type {HTMLElement} */ settings_inner, 202 /** @type {HTMLElement} */ settings_title, 203 /** @type {HTMLElement} */ settings_close_btn, 204 /** @type {HTMLElement} */ settings_blocks, 205 /** @type {HTMLElement} */ new_settings_blocks, 206 /** @type {HTMLElement} */ settings_buttons, 207 /** @type {HTMLElement} */ settings_save_btn, 208 /** @type {HTMLElement} */ settings_accept_all_btn, 209 /** @type {HTMLElement} */ settings_reject_all_btn; 210 211 /** 212 * Update config settings 213 * @param {Object} user_config 214 */ 215 var _setConfig = function(_user_config){ 216 217 /** 218 * Make user configuration globally available 219 */ 220 user_config = _user_config; 221 222 _log("CookieConsent [CONFIG]: received_config_settings ", user_config); 223 224 if(typeof user_config['cookie_expiration'] === "number") 225 _config.cookie_expiration = user_config['cookie_expiration']; 226 227 if(typeof user_config['cookie_necessary_only_expiration'] === "number") 228 _config.cookie_necessary_only_expiration = user_config['cookie_necessary_only_expiration']; 229 230 if(typeof user_config['autorun'] === "boolean") 231 _config.autorun = user_config['autorun']; 232 233 if(typeof user_config['cookie_domain'] === "string") 234 _config.cookie_domain = user_config['cookie_domain']; 235 236 if(typeof user_config['cookie_same_site'] === "string") 237 _config.cookie_same_site = user_config['cookie_same_site']; 238 239 if(typeof user_config['cookie_path'] === "string") 240 _config.cookie_path = user_config['cookie_path']; 241 242 if(typeof user_config['cookie_name'] === "string") 243 _config.cookie_name = user_config['cookie_name']; 244 245 if(typeof user_config['onAccept'] === "function") 246 onAccept = user_config['onAccept']; 247 248 if(typeof user_config['onFirstAction'] === "function") 249 onFirstAction = user_config['onFirstAction']; 250 251 if(typeof user_config['onChange'] === "function") 252 onChange = user_config['onChange']; 253 254 if(user_config['mode'] === 'opt-out') 255 _config.mode = 'opt-out'; 256
257 if(typeof user_config['revision'] === "number"){ 258 user_config['revision'] > -1 && (_config.revision = user_config['revision']); 259 revision_enabled = true; 260 } 261 262 if(typeof user_config['autoclear_cookies'] === "boolean") 263 _config.autoclear_cookies = user_config['autoclear_cookies']; 264 265 if(user_config['use_rfc_cookie'] === true) 266 _config.use_rfc_cookie = true; 267 268 if(typeof user_config['hide_from_bots'] === "boolean"){ 269 _config.hide_from_bots = user_config['hide_from_bots']; 270 } 271 272 if(_config.hide_from_bots){ 273 is_bot = navigator && 274 ((navigator.userAgent && /bot|crawl|spider|slurp|teoma/i.test(navigator.userAgent)) || navigator.webdriver); 275 } 276 277 _config.page_scripts = user_config['page_scripts'] === true; 278 279 if (user_config['auto_language'] === 'browser' || user_config['auto_language'] === true) { 280 _config.auto_language = 'browser'; 281 } else if (user_config['auto_language'] === 'document') { 282 _config.auto_language = 'document'; 283 } 284 285 _log("CookieConsent [LANG]: auto_language strategy is '" + _config.auto_language + "'"); 286 287 _config.current_lang = _resolveCurrentLang(user_config.languages, user_config['current_lang']); 288 } 289 290 /** 291 * Add an onClick listeners to all html elements with data-cc attribute 292 */ 293 var _addDataButtonListeners = function(elem){ 294 295 var _a = 'accept-'; 296 297 var show_settings = _getElements('c-settings'); 298 var accept_all = _getElements(_a + 'all'); 299 var accept_necessary = _getElements(_a + 'necessary'); 300 var accept_custom_selection = _getElements(_a + 'custom'); 301 302 for(var i=0; i<show_settings.length; i++){ 303 show_settings[i].setAttribute('aria-haspopup', 'dialog'); 304 _addEvent(show_settings[i], 'click', function(event){ 305 event.preventDefault(); 306 _cookieconsent.showSettings(0); 307 }); 308 } 309 310 for(i=0; i<accept_all.length; i++){ 311 _addEvent(accept_all[i], 'click', function(event){ 312 _acceptAction(event, 'all'); 313 }); 314 } 315 316 for(i=0; i<accept_custom_selection.length; i++){ 317 _addEvent(accept_custom_selection[i], 'click', function(event){ 318 _acceptAction(event); 319 }); 320 } 321 322 for(i=0; i<accept_necessary.length; i++){ 323 _addEvent(accept_necessary[i], 'click', function(event){ 324 _acceptAction(event, []); 325 }); 326 } 327 328 /** 329 * Return all elements with given data-cc role 330 * @param {string} data_role 331 * @returns {NodeListOf<Element>} 332 */ 333 function _getElements(data_role){ 334 return (elem || document).querySelectorAll('[data-cc="' + data_role + '"]'); 335 } 336 337 /** 338 * Helper function: accept and then hide modals 339 * @param {PointerEvent} e source event 340 * @param {string} [accept_type] 341 */ 342 function _acceptAction(e, accept_type){ 343 e.preventDefault(); 344 _cookieconsent.accept(accept_type); 345 _cookieconsent.hideSettings(); 346 _cookieconsent.hide(); 347 } 348 } 349 350 /** 351 * Get a valid language (at least 1 must be defined) 352 * @param {string} lang - desired language 353 * @param {{}} all_languages - all defined languages 354 * @returns {string} validated language 355 */ 356 var _getValidatedLanguage = function(lang, all_languages){ 357 if(all_languages.hasOwnProperty(lang)){ 358 return lang; 359 }else if(_getKeys(all_languages).length > 0){ 360 if(all_languages.hasOwnProperty(_config.current_lang)){ 361 return _config.current_lang ; 362 }else{ 363 return _getKeys(all_languages)[0]; 364 } 365 } 366 } 367 368 /** 369 * Save reference to first and last focusable elements inside each modal 370 * to prevent losing focus while navigating with TAB 371 */ 372 var _getModalFocusableData = function(){ 373 374 /** 375 * Note: any of the below focusable elements, which has the attribute tabindex="-1" AND is either 376 * the first or last element of the modal, won't receive focus during "open/close" modal 377 */ 378 var allowed_focusable_types = ['[href]', 'button', 'input', 'details', '[tabindex="0"]']; 379 380 function _getAllFocusableElements(modal, _array){ 381 var focus_later=false, focus_first=false; 382 383 // ie might throw exception due to complex unsupported selector => a:not([tabindex="-1"]) 384 try{ 385 var focusable_elems = modal.querySelectorAll(allowed_focusable_types.join(':not([tabindex="-1"]), ')); 386 var attr, len=focusable_elems.length, i=0; 387 388 while(i < len){ 389 390 attr = focusable_elems[i].getAttribute('data-focus'); 391 392 if(!focus_first && attr === "1"){ 393 focus_first = focusable_elems[i]; 394 395 }else if(attr === "0"){ 396 focus_later = focusable_elems[i]; 397 if(!focus_first && focusable_elems[i+1].getAttribute('data-focus') !== "0"){ 398 focus_first = focusable_elems[i+1]; 399 } 400 } 401 402 i++; 403 } 404 405 }catch(e){ 406 return modal.querySelectorAll(allowed_focusable_types.join(', ')); 407 } 408 409 /** 410 * Save first and last elements (used to lock/trap focus inside modal) 411 */
412 _array[0] = focusable_elems[0]; 413 _array[1] = focusable_elems[focusable_elems.length - 1]; 414 _array[2] = focus_later; 415 _array[3] = focus_first; 416 } 417 418 /** 419 * Get settings modal'S all focusable elements 420 * Save first and last elements (used to lock/trap focus inside modal) 421 */ 422 _getAllFocusableElements(settings_inner, settings_modal_focusable); 423 424 /** 425 * If consent modal exists, do the same 426 */ 427 if(consent_modal_exists){ 428 _getAllFocusableElements(consent_modal, consent_modal_focusable); 429 } 430 } 431 432 var _createConsentModal = function(lang){ 433 434 if(user_config['force_consent'] === true) 435 _addClass(html_dom, 'force--consent'); 436 437 // Create modal if it doesn't exist 438 if(!consent_modal){ 439 440 consent_modal = _createNode('div'); 441 var consent_modal_inner_inner = _createNode('div'); 442 var overlay = _createNode('div'); 443 444 consent_modal.id = 'cm'; 445 consent_modal_inner_inner.id = 'c-inr-i'; 446 overlay.id = 'cm-ov'; 447 448 consent_modal.tabIndex = -1; 449 consent_modal.setAttribute('role', 'dialog'); 450 consent_modal.setAttribute('aria-modal', 'true'); 451 consent_modal.setAttribute('aria-hidden', 'false'); 452 consent_modal.setAttribute('aria-labelledby', 'c-ttl'); 453 consent_modal.setAttribute('aria-describedby', 'c-txt'); 454 455 // Append consent modal to main container 456 all_modals_container.appendChild(consent_modal); 457 all_modals_container.appendChild(overlay); 458 459 /** 460 * Make modal by default hidden to prevent weird page jumps/flashes (shown only once css is loaded) 461 */ 462 consent_modal.style.visibility = overlay.style.visibility = "hidden"; 463 overlay.style.opacity = 0; 464 } 465 466 // Use insertAdjacentHTML instead of innerHTML 467 var consent_modal_title_value = user_config.languages[lang]['consent_modal']['title']; 468 469 // Add title (if valid) 470 if(consent_modal_title_value){ 471 472 if(!consent_modal_title){ 473 consent_modal_title = _createNode('div'); 474 consent_modal_title.id = 'c-ttl'; 475 consent_modal_title.setAttribute('role', 'heading'); 476 consent_modal_title.setAttribute('aria-level', '2'); 477 consent_modal_inner_inner.appendChild(consent_modal_title); 478 } 479 480 consent_modal_title.innerHTML = consent_modal_title_value; 481 } 482 483 var description = user_config.languages[lang]['consent_modal']['description']; 484 485 if(revision_enabled){ 486 if(!valid_revision){ 487 description = description.replace("{{revision_message}}", revision_message || user_config.languages[lang]['consent_modal']['revision_message'] || ""); 488 }else{ 489 description = description.replace("{{revision_message}}", ""); 490 } 491 } 492 493 if(!consent_modal_description){ 494 consent_modal_description = _createNode('div'); 495 consent_modal_description.id = 'c-txt'; 496 consent_modal_inner_inner.appendChild(consent_modal_description); 497 } 498 499 // Set description content 500 consent_modal_description.innerHTML = description; 501 502 var primary_btn_data = user_config.languages[lang]['consent_modal']['primary_btn'], // accept current selection 503 secondary_btn_data = user_config.languages[lang]['consent_modal']['secondary_btn']; 504 505 // Add primary button if not falsy 506 if(primary_btn_data){ 507 508 if(!consent_primary_btn){ 509 consent_primary_btn = _createNode('button'); 510 consent_primary_btn.id = 'c-p-bn'; 511 consent_primary_btn.className = "c-bn"; 512 consent_primary_btn.appendChild(generateFocusSpan(1)) 513 514 var _accept_type; 515 516 if(primary_btn_data['role'] === 'accept_all') 517 _accept_type = 'all' 518 519 _addEvent(consent_primary_btn, "click", function(){ 520 _cookieconsent.hide(); 521 _log("CookieConsent [ACCEPT]: cookie_consent was accepted!"); 522 _cookieconsent.accept(_accept_type); 523 }); 524 } 525 526 consent_primary_btn.firstElementChild.innerHTML = user_config.languages[lang]['consent_modal']['primary_btn']['text']; 527 } 528 529 // Add secondary button if not falsy 530 if(secondary_btn_data){ 531 532 if(!consent_secondary_btn){ 533 consent_secondary_btn = _createNode('button'); 534 consent_secondary_btn.id = 'c-s-bn'; 535 consent_secondary_btn.className = "c-bn c_link"; 536 consent_secondary_btn.appendChild(generateFocusSpan(1)) 537 538 if(secondary_btn_data['role'] === 'accept_necessary'){ 539 _addEvent(consent_secondary_btn, 'click', function(){ 540 _cookieconsent.hide(); 541 _cookieconsent.accept([]); // accept necessary only 542 }); 543 }else{ 544 _addEvent(consent_secondary_btn, 'click', function(){ 545 _cookieconsent.showSettings(0); 546 }); 547 } 548 } 549 550 consent_secondary_btn.firstElementChild.innerHTML = user_config.languages[lang]['consent_modal']['secondary_btn']['text']; 551 } 552 553 // Swap buttons 554 var gui_options_data = user_config['gui_options']; 555 556 if(!consent_modal_inner){ 557 consent_modal_inner = _createNode('div'); 558 consent_modal_inner.id = 'c-inr'; 559 560 consent_modal_inner.appendChild(consent_modal_inner_inner); 561 } 562 563 if(!consent_buttons){ 564 consent_buttons = _createNode('div'); 565 consent_buttons.id = "c-bns"; 566 567 if(gui_options_data && gui_options_data['consent_modal'] && gui_options_data['consent_modal']['swap_buttons'] === true){ 568 secondary_btn_data && consent_buttons.appendChild(consent_secondary_btn); 569 primary_btn_data && consent_buttons.appendChild(consent_primary_btn); 570 consent_buttons.className = 'swap'; 571 }else{ 572 primary_btn_data && consent_buttons.appendChild(consent_primary_btn); 573 secondary_btn_data && consent_buttons.appendChild(consent_secondary_btn); 574 } 575 576 (primary_btn_data || secondary_btn_data ) && consent_modal_inner.appendChild(consent_buttons); 577 consent_modal.appendChild(consent_modal_inner); 578 } 579 580 consent_modal_exists = true; 581 582 _addDataButtonListeners(consent_modal_inner); 583 } 584 585 var _createSettingsModal = function(lang){ 586 587 /** 588 * Create all consent_modal elements 589 */ 590 if(!settings_container){ 591 settings_container = _createNode('div'); 592 settings_container.tabIndex = -1; 593 var settings_container_valign = _createNode('div'); 594 var settings = _createNode('div'); 595 var settings_container_inner = _createNode('div'); 596 settings_inner = _createNode('div'); 597 settings_title = _createNode('div'); 598 var settings_header = _createNode('div'); 599 settings_close_btn = _createNode('button'); 600 settings_close_btn.appendChild(generateFocusSpan(2)); 601 var settings_close_btn_container = _createNode('div'); 602 settings_blocks = _createNode('div');
603 var overlay = _createNode('div'); 604 605 _addEvent(settings_container_inner, 'click', function(e){ 606 /** 607 * If click is on the foreground overlay (and not inside settings_modal), 608 * hide settings modal 609 */ 610 if(settings_modal_visible && !settings_inner.contains(e.target)){ 611 _cookieconsent.hideSettings(); 612 } 613 614 }, true); 615 616 /** 617 * Set ids 618 */ 619 settings_container.id = 's-cnt'; 620 settings_container_valign.id = "c-vln"; 621 settings_container_inner.id = "c-s-in"; 622 settings.id = "cs"; 623 settings_title.id = 's-ttl'; 624 settings_inner.id = 's-inr'; 625 settings_header.id = "s-hdr"; 626 settings_blocks.id = 's-bl'; 627 settings_close_btn.id = 's-c-bn'; 628 overlay.id = 'cs-ov'; 629 settings_close_btn_container.id = 's-c-bnc'; 630 settings_close_btn.className = 'c-bn'; 631 632 settings_container.setAttribute('role', 'dialog'); 633 settings_container.setAttribute('aria-modal', 'true'); 634 settings_container.setAttribute('aria-hidden', 'true'); 635 settings_container.setAttribute('aria-labelledby', 's-ttl'); 636 settings_title.setAttribute('role', 'heading'); 637 settings_container.style.visibility = overlay.style.visibility = "hidden"; 638 overlay.style.opacity = 0; 639 640 settings_close_btn_container.appendChild(settings_close_btn); 641 642 // If 'esc' key is pressed inside settings_container div => hide settings 643 _addEvent(document, 'keydown', function(evt){ 644 if (evt.keyCode === 27 && settings_modal_visible) { 645 _cookieconsent.hideSettings(); 646 } 647 }, true); 648 649 _addEvent(settings_close_btn, 'click', function(){ 650 _cookieconsent.hideSettings(); 651 }); 652 }else{ 653 new_settings_blocks = _createNode('div'); 654 new_settings_blocks.id = 's-bl'; 655 } 656 657 var settings_modal_config = user_config.languages[lang]['settings_modal']; 658 659 // Add label to close button 660 settings_close_btn.setAttribute('aria-label', settings_modal_config['close_btn_label'] || 'Close'); 661 662 all_blocks = settings_modal_config['blocks']; 663 all_table_headers = settings_modal_config['cookie_table_headers']; 664 var table_caption = settings_modal_config['cookie_table_caption']; 665 666 var n_blocks = all_blocks.length; 667 668 // Set settings modal title 669 settings_title.innerHTML = settings_modal_config['title']; 670 671 // Create settings modal content (blocks) 672 for(var i=0; i<n_blocks; ++i){ 673 674 var title_data = all_blocks[i]['title'], 675 description_data = all_blocks[i]['description'], 676 toggle_data = all_blocks[i]['toggle'], 677 cookie_table_data = all_blocks[i]['cookie_table'], 678 remove_cookie_tables = user_config['remove_cookie_tables'] === true, 679 isExpandable = !!description_data || (!remove_cookie_tables && !!cookie_table_data); 680 681 // Create title 682 var block_section = _createNode('div'); 683 var block_table_container = _createNode('div'); 684 685 // Create description 686 if(description_data){ 687 var block_desc = _createNode('div'); 688 block_desc.className = 'p'; 689 block_desc.insertAdjacentHTML('beforeend', description_data); 690 } 691 692 var block_title_container = _createNode('div'); 693 block_title_container.className = 'title'; 694 695 block_section.className = 'c-bl';
696 block_table_container.className = 'desc'; 697 698 // Create toggle if specified (opt in/out) 699 if(typeof toggle_data !== 'undefined'){ 700 701 var accordion_id = "c-ac-"+i; 702 703 // Create button (to collapse/expand block description) 704 var block_title_btn = isExpandable ? _createNode('button') : _createNode('div'); 705 var block_switch_label = _createNode('label'); 706 var block_switch = _createNode('input'); 707 var block_switch_span = _createNode('span'); 708 var label_text_span = _createNode('span'); 709 710 // These 2 spans will contain each 2 pseudo-elements to generate 'tick' and 'x' icons 711 var block_switch_span_on_icon = _createNode('span'); 712 var block_switch_span_off_icon = _createNode('span'); 713 714 block_title_btn.className = isExpandable ? 'b-tl exp' : 'b-tl'; 715 block_switch_label.className = 'b-tg'; 716 block_switch.className = 'c-tgl'; 717 block_switch_span_on_icon.className = 'on-i'; 718 block_switch_span_off_icon.className = 'off-i'; 719 block_switch_span.className = 'c-tg'; 720 label_text_span.className = "t-lb"; 721 722 if(isExpandable){ 723 block_title_btn.setAttribute('aria-expanded', 'false'); 724 block_title_btn.setAttribute('aria-controls', accordion_id); 725 } 726 727 block_switch.type = 'checkbox'; 728 block_switch_span.setAttribute('aria-hidden', 'true'); 729 730 var cookie_category = toggle_data.value; 731 block_switch.value = cookie_category; 732 733 label_text_span.textContent = title_data; 734 block_title_btn.insertAdjacentHTML('beforeend', title_data); 735 736 block_title_container.appendChild(block_title_btn); 737 block_switch_span.appendChild(block_switch_span_on_icon); 738 block_switch_span.appendChild(block_switch_span_off_icon); 739 740 /** 741 * If consent is valid => retrieve category states from cookie 742 * Otherwise use states defined in the user_config. object 743 */ 744 if(!invalid_consent){ 745 if(_inArray(saved_cookie_content['categories'], cookie_category) > -1){ 746 block_switch.checked = true; 747 !new_settings_blocks && toggle_states.push(true); 748 }else{ 749 !new_settings_blocks && toggle_states.push(false); 750 } 751 }else if(toggle_data['enabled']){ 752 block_switch.checked = true; 753 !new_settings_blocks && toggle_states.push(true); 754 755 /** 756 * Keep track of categories enabled by default (useful when mode=='opt-out') 757 */ 758 if(toggle_data['enabled']) 759 !new_settings_blocks && default_enabled_categories.push(cookie_category); 760 761 }else{ 762 !new_settings_blocks && toggle_states.push(false); 763 } 764 765 !new_settings_blocks && all_categories.push(cookie_category); 766 767 /** 768 * Set toggle as readonly if true (disable checkbox) 769 */ 770 if(toggle_data['readonly']){ 771 block_switch.disabled = true; 772 _addClass(block_switch_span, 'c-ro'); 773 !new_settings_blocks && readonly_categories.push(cookie_category); 774 } 775 776 _addClass(block_table_container, 'b-acc'); 777 _addClass(block_title_container, 'b-bn'); 778 _addClass(block_section, 'b-ex'); 779
780 block_table_container.id = accordion_id; 781 block_table_container.setAttribute('aria-hidden', 'true'); 782 783 block_switch_label.appendChild(block_switch); 784 block_switch_label.appendChild(block_switch_span); 785 block_switch_label.appendChild(label_text_span); 786 block_title_container.appendChild(block_switch_label); 787 788 /** 789 * On button click handle the following :=> aria-expanded, aria-hidden and act class for current block 790 */ 791 isExpandable && (function(accordion, block_section, btn){ 792 _addEvent(block_title_btn, 'click', function(){ 793 if(!_hasClass(block_section, 'act')){ 794 _addClass(block_section, 'act'); 795 btn.setAttribute('aria-expanded', 'true'); 796 accordion.setAttribute('aria-hidden', 'false'); 797 }else{ 798 _removeClass(block_section, 'act'); 799 btn.setAttribute('aria-expanded', 'false'); 800 accordion.setAttribute('aria-hidden', 'true'); 801 } 802 }, false); 803 })(block_table_container, block_section, block_title_btn); 804 805 }else{ 806 /** 807 * If block is not a button (no toggle defined), 808 * create a simple div instead 809 */ 810 if(title_data){ 811 var block_title = _createNode('div'); 812 block_title.className = 'b-tl'; 813 block_title.setAttribute('role', 'heading'); 814 block_title.setAttribute('aria-level', '3'); 815 block_title.insertAdjacentHTML('beforeend', title_data); 816 block_title_container.appendChild(block_title); 817 } 818 } 819 820 title_data && block_section.appendChild(block_title_container); 821 description_data && block_table_container.appendChild(block_desc); 822 823 // if cookie table found, generate table for this block 824 if(!remove_cookie_tables && typeof cookie_table_data !== 'undefined'){ 825 var tr_tmp_fragment = document.createDocumentFragment(); 826 827 /** 828 * Use custom table headers 829 */ 830 for(var p=0; p<all_table_headers.length; ++p){ 831 // create new header 832 var th1 = _createNode('th'); 833 var obj = all_table_headers[p]; 834 th1.setAttribute('scope', 'col'); 835 836 // get custom header content 837 if(obj){ 838 var new_column_key = obj && _getKeys(obj)[0]; 839 th1.textContent = all_table_headers[p][new_column_key]; 840 tr_tmp_fragment.appendChild(th1); 841 } 842 } 843 844 var tr_tmp = _createNode('tr'); 845 tr_tmp.appendChild(tr_tmp_fragment); 846 847 // create table header & append fragment 848 var thead = _createNode('thead'); 849 thead.appendChild(tr_tmp); 850 851 var block_table = _createNode('table'); 852 853 if(table_caption) { 854 var caption = _createNode('caption'); 855 caption.innerHTML = table_caption; 856 block_table.appendChild(caption); 857 } 858 859 // append header to table 860 block_table.appendChild(thead); 861 862 var tbody_fragment = document.createDocumentFragment(); 863 864 // create table content 865 for(var n=0; n<cookie_table_data.length; n++){ 866 var tr = _createNode('tr'); 867 868 for(var g=0; g<all_table_headers.length; ++g){ 869 // get custom header content 870 obj = all_table_headers[g]; 871 if(obj){ 872 new_column_key = _getKeys(obj)[0]; 873 874 var td_tmp = _createNode('td'); 875 876 // Allow html inside table cells 877 td_tmp.insertAdjacentHTML('beforeend', cookie_table_data[n][new_column_key]); 878 td_tmp.setAttribute('data-column', obj[new_column_key]); 879 880 tr.appendChild(td_tmp); 881 } 882 } 883 884 tbody_fragment.appendChild(tr); 885 } 886 887 // append tbody_fragment to tbody & append the latter into the table 888 var tbody = _createNode('tbody'); 889 tbody.appendChild(tbody_fragment); 890 block_table.appendChild(tbody); 891 892 block_table_container.appendChild(block_table); 893 } 894 895 /** 896 * Append only if is either: 897 * - togglable div with title 898 * - a simple div with at least a title or description 899 */ 900 if(toggle_data && title_data || (!toggle_data && (title_data || description_data))){ 901 block_section.appendChild(block_table_container); 902 903 if(new_settings_blocks) 904 new_settings_blocks.appendChild(block_section); 905 else 906 settings_blocks.appendChild(block_section); 907 } 908 } 909 910 // Create settings buttons 911 if(!settings_buttons){ 912 settings_buttons = _createNode('div'); 913 settings_buttons.id = 's-bns'; 914 } 915 916 if(!settings_accept_all_btn){ 917 settings_accept_all_btn = _createNode('button'); 918 settings_accept_all_btn.id = 's-all-bn'; 919 settings_accept_all_btn.className ='c-bn'; 920 settings_buttons.appendChild(settings_accept_all_btn); 921 922 _addEvent(settings_accept_all_btn, 'click', function(){ 923 _cookieconsent.accept('all'); 924 _cookieconsent.hideSettings(); 925 _cookieconsent.hide(); 926 }); 927 } 928 929 settings_accept_all_btn.innerHTML = settings_modal_config['accept_all_btn']; 930 931 var reject_all_btn_text = settings_modal_config['reject_all_btn']; 932 933 // Add third [optional] reject all button if provided 934 if(reject_all_btn_text){ 935 936 if(!settings_reject_all_btn){ 937 settings_reject_all_btn = _createNode('button'); 938 settings_reject_all_btn.id = 's-rall-bn'; 939 settings_reject_all_btn.className = 'c-bn'; 940 941 _addEvent(settings_reject_all_btn, 'click', function(){ 942 _cookieconsent.accept([]); 943 _cookieconsent.hideSettings(); 944 _cookieconsent.hide(); 945 }); 946 947 settings_inner.className = "bns-t"; 948 settings_buttons.appendChild(settings_reject_all_btn); 949 } 950 951 settings_reject_all_btn.innerHTML = reject_all_btn_text; 952 } 953 954 955 if(!settings_save_btn){ 956 settings_save_btn = _createNode('button'); 957 settings_save_btn.id = 's-sv-bn'; 958 settings_save_btn.className ='c-bn'; 959 settings_buttons.appendChild(settings_save_btn); 960 961 // Add save preferences button onClick event 962 // Hide both settings modal and consent modal 963 _addEvent(settings_save_btn, 'click', function(){ 964 _cookieconsent.accept(); 965 _cookieconsent.hideSettings(); 966 _cookieconsent.hide(); 967 }); 968 } 969 970 settings_save_btn.innerHTML = settings_modal_config['save_settings_btn']; 971 972 973 if(new_settings_blocks) { 974 // replace entire existing cookie category blocks with the new cookie categories new blocks (in a different language) 975 settings_inner.replaceChild(new_settings_blocks, settings_blocks); 976 settings_blocks = new_settings_blocks; 977 return; 978 }; 979 980 settings_header.appendChild(settings_title); 981 settings_header.appendChild(settings_close_btn_container); 982 settings_inner.appendChild(settings_header); 983 settings_inner.appendChild(settings_blocks); 984 settings_inner.appendChild(settings_buttons); 985 settings_container_inner.appendChild(settings_inner); 986 987 settings.appendChild(settings_container_inner); 988 settings_container_valign.appendChild(settings); 989 settings_container.appendChild(settings_container_valign); 990 991 all_modals_container.appendChild(settings_container); 992 all_modals_container.appendChild(overlay); 993 } 994 995 /** 996 * Generate cookie consent html markup 997 */ 998 var _createCookieConsentHTML = function(){ 999 1000 // Create main container which holds both consent modal & settings modal 1001 main_container = _createNode('div'); 1002 main_container.id = 'cc--main'; 1003 1004 // Fix layout flash 1005 main_container.style.position = "fixed"; 1006 main_container.innerHTML = '<div id="cc_div" class="cc_div"></div>' 1007 all_modals_container = main_container.children[0]; 1008 1009 // Get current language 1010 var lang = _config.current_lang; 1011 1012 // Create consent modal 1013 if(consent_modal_exists) 1014 _createConsentModal(lang); 1015 1016 // Always create settings modal 1017 _createSettingsModal(lang); 1018 1019 // Finally append everything (main_container holds both modals) 1020 (root || document.body).appendChild(main_container); 1021 } 1022 1023 /** 1024 * Update/change modals language 1025 * @param {String} lang new language 1026 * @param {Boolean} [force] update language fields forcefully 1027 * @returns {Boolean} 1028 */ 1029 _cookieconsent.updateLanguage = function(lang, force){ 1030 1031 if(typeof lang !== 'string') return; 1032 1033 /** 1034 * Validate language to avoid errors 1035 */ 1036 var new_validated_lang = _getValidatedLanguage(lang, user_config.languages); 1037 1038 /** 1039 * Set language only if it differs from current 1040 */ 1041 if(new_validated_lang !== _config.current_lang || force === true){ 1042 _config.current_lang = new_validated_lang; 1043 1044 if(consent_modal_exists){ 1045 _createConsentModal(new_validated_lang); 1046 } 1047 1048 _createSettingsModal(new_validated_lang); 1049 1050 _log("CookieConsent [LANGUAGE]: curr_lang: '" + new_validated_lang + "'"); 1051 1052 return true; 1053 } 1054 1055 return false;
1056 } 1057 1058 /** 1059 * Delete all cookies which are unused (based on selected preferences) 1060 * 1061 * @param {boolean} [clearOnFirstAction] 1062 */ 1063 var _autoclearCookies = function(clearOnFirstAction){ 1064 1065 // Get number of blocks 1066 var len = all_blocks.length; 1067 var count = -1; 1068 1069 // reset reload state 1070 reload_page = false; 1071 1072 // Retrieve all cookies 1073 var all_cookies_array = _getCookie('', 'all'); 1074 1075 // delete cookies on 'www.domain.com' and '.www.domain.com' (can also be without www) 1076 var domains = [_config.cookie_domain, '.'+_config.cookie_domain]; 1077 1078 // if domain has www, delete cookies also for 'domain.com' and '.domain.com' 1079 if(_config.cookie_domain.slice(0, 4) === 'www.'){ 1080 var non_www_domain = _config.cookie_domain.substr(4); // remove first 4 chars (www.) 1081 domains.push(non_www_domain); 1082 domains.push('.' + non_www_domain); 1083 } 1084 1085 // For each block 1086 for(var i=0; i<len; i++){ 1087 1088 // Save current block (local scope & less accesses -> ~faster value retrieval) 1089 var curr_block = all_blocks[i]; 1090 1091 // If current block has a toggle for opt in/out 1092 if(curr_block.hasOwnProperty('toggle')){ 1093 1094 // if current block has a cookie table, an off toggle, 1095 // and its preferences were just changed => delete cookies 1096 var category_just_disabled = _inArray(changed_settings, curr_block['toggle']['value']) > -1; 1097 if( 1098 !toggle_states[++count] && 1099 curr_block.hasOwnProperty('cookie_table') && 1100 (clearOnFirstAction || category_just_disabled) 1101 ){ 1102 var curr_cookie_table = curr_block['cookie_table']; 1103 1104 // Get first property name 1105 var ckey = _getKeys(all_table_headers[0])[0]; 1106 1107 // Get number of cookies defined in cookie_table 1108 var clen = curr_cookie_table.length; 1109 1110 // set "reload_page" to true if reload=on_disable 1111 if(curr_block['toggle']['reload'] === 'on_disable') 1112 category_just_disabled && (reload_page = true); 1113 1114 // for each row defined in the cookie table 1115 for(var j=0; j<clen; j++){ 1116 var curr_domains = domains; 1117 1118 // Get current row of table (corresponds to all cookie params) 1119 var curr_row = curr_cookie_table[j], found_cookies = []; 1120 var curr_cookie_name = curr_row[ckey]; 1121 var is_regex = curr_row['is_regex'] || false; 1122 var curr_cookie_domain = curr_row['domain'] || null; 1123 var curr_cookie_path = curr_row['path'] || false; 1124 1125 // set domain to the specified domain 1126 curr_cookie_domain && ( curr_domains = [curr_cookie_domain, '.'+curr_cookie_domain]); 1127 1128 // If regex provided => filter cookie array 1129 if(is_regex){ 1130 for(var n=0; n<all_cookies_array.length; n++){ 1131 if(all_cookies_array[n].match(curr_cookie_name)){ 1132 found_cookies.push(all_cookies_array[n]); 1133 } 1134 } 1135 }else{ 1136 var found_index = _inArray(all_cookies_array, curr_cookie_name); 1137 if(found_index > -1) found_cookies.push(all_cookies_array[found_index]); 1138 } 1139 1140 _log("CookieConsent [AUTOCLEAR]: search cookie: '" + curr_cookie_name + "', found:", found_cookies); 1141 1142 // If cookie exists -> delete it 1143 if(found_cookies.length > 0){ 1144 _eraseCookies(found_cookies, curr_cookie_path, curr_domains); 1145 curr_block['toggle']['reload'] === 'on_clear' && (reload_page = true); 1146 } 1147 } 1148 } 1149 } 1150 } 1151 } 1152 1153 /** 1154 * Set toggles/checkboxes based on accepted categories and save cookie 1155 * @param {string[]} accepted_categories - Array of categories to accept 1156 */ 1157 var _saveCookiePreferences = function(accepted_categories){ 1158 1159 changed_settings = []; 1160 1161 // Retrieve all toggle/checkbox values 1162 var category_toggles = settings_container.querySelectorAll('.c-tgl') || []; 1163 1164 // If there are opt in/out toggles ... 1165 if(category_toggles.length > 0){ 1166 1167 for(var i=0; i<category_toggles.length; i++){ 1168 if(_inArray(accepted_categories, all_categories[i]) !== -1){ 1169 category_toggles[i].checked = true; 1170 if(!toggle_states[i]){ 1171 changed_settings.push(all_categories[i]); 1172 toggle_states[i] = true; 1173 } 1174 }else{ 1175 category_toggles[i].checked = false;
1176 if(toggle_states[i]){ 1177 changed_settings.push(all_categories[i]); 1178 toggle_states[i] = false; 1179 } 1180 } 1181 } 1182 } 1183 1184 /** 1185 * Clear cookies when settings/preferences change 1186 */ 1187 if(!invalid_consent && _config.autoclear_cookies && changed_settings.length > 0) 1188 _autoclearCookies(); 1189 1190 if(!consent_date) consent_date = new Date(); 1191 if(!consent_uuid) consent_uuid = _uuidv4(); 1192 1193 saved_cookie_content = { 1194 "categories": accepted_categories, 1195 "level": accepted_categories, // Copy of the `categories` property for compatibility purposes with version v2.8.0 and below. 1196 "revision": _config.revision, 1197 "data": cookie_data, 1198 "rfc_cookie": _config.use_rfc_cookie, 1199 "consent_date": consent_date.toISOString(), 1200 "consent_uuid": consent_uuid 1201 } 1202 1203 // save cookie with preferences 'categories' (only if never accepted or settings were updated) 1204 if(invalid_consent || changed_settings.length > 0){ 1205 valid_revision = true; 1206 1207 /** 1208 * Update "last_consent_update" only if it is invalid (after t) 1209 */ 1210 if(!last_consent_update) 1211 last_consent_update = consent_date; 1212 else 1213 last_consent_update = new Date(); 1214 1215 saved_cookie_content['last_consent_update'] = last_consent_update.toISOString(); 1216 1217 /** 1218 * Update accept type 1219 */ 1220 accept_type = _getAcceptType(_getCurrentCategoriesState()); 1221 1222 _setCookie(_config.cookie_name, JSON.stringify(saved_cookie_content)); 1223 _manageExistingScripts(); 1224 } 1225 1226 if(invalid_consent){ 1227 1228 /** 1229 * Delete unused/"zombie" cookies if consent is not valid (not yet expressed or cookie has expired) 1230 */ 1231 if(_config.autoclear_cookies) 1232 _autoclearCookies(true); 1233 1234 if(typeof onFirstAction === 'function') 1235 onFirstAction(_cookieconsent.getUserPreferences(), saved_cookie_content); 1236 1237 if(typeof onAccept === 'function') 1238 onAccept(saved_cookie_content); 1239 1240 /** 1241 * Set consent as valid 1242 */ 1243 invalid_consent = false; 1244 1245 if(_config.mode === 'opt-in') return; 1246 } 1247 1248 // fire onChange only if settings were changed 1249 if(typeof onChange === "function" && changed_settings.length > 0) 1250 onChange(saved_cookie_content, changed_settings); 1251 1252 /** 1253 * reload page if needed 1254 */ 1255 if(reload_page) 1256 location.reload(); 1257 } 1258 1259 /** 1260 * Returns index of found element inside array, otherwise -1 1261 * @param {Array} arr 1262 * @param {Object} value 1263 * @returns {number} 1264 */ 1265 var _inArray = function(arr, value){ 1266 return arr.indexOf(value); 1267 } 1268 1269 /** 1270 * Helper function which prints info (console.log()) 1271 * @param {Object} print_msg 1272 * @param {Object} [optional_param] 1273 */ 1274 var _log = function(print_msg, optional_param, error){ 1275 ENABLE_LOGS && (!error ? console.log(print_msg, optional_param !== undefined ? optional_param : ' ') : console.error(print_msg, optional_param || "")); 1276 } 1277 1278 /** 1279 * Helper function which creates an HTMLElement object based on 'type' and returns it. 1280 * @param {string} type 1281 * @returns {HTMLElement} 1282 */ 1283 var _createNode = function(type){ 1284 var el = document.createElement(type); 1285 if(type === 'button'){ 1286 el.setAttribute('type', type); 1287 } 1288 return el; 1289 } 1290 1291 /** 1292 * Generate RFC4122-compliant UUIDs. 1293 * https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid?page=1&tab=votes#tab-top 1294 * @returns {string} 1295 */ 1296 var _uuidv4 = function(){ 1297 return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function(c){ 1298 try{ 1299 return (c ^ (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) 1300 }catch(e){ 1301 return ''; 1302 } 1303 }); 1304 } 1305 1306 /** 1307 * Resolve which language should be used. 1308 * 1309 * @param {Object} languages Object with language translations 1310 * @param {string} [requested_language] Language specified by given configuration parameters 1311 * @returns {string} 1312 */ 1313 var _resolveCurrentLang = function (languages, requested_language) { 1314 1315 if (_config.auto_language === 'browser') { 1316 return _getValidatedLanguage(_getBrowserLang(), languages); 1317 } else if (_config.auto_language === 'document') { 1318 return _getValidatedLanguage(document.documentElement.lang, languages); 1319 } else { 1320 if (typeof requested_language === 'string') { 1321 return _config.current_lang = _getValidatedLanguage(requested_language, languages); 1322 } 1323 } 1324 1325 _log("CookieConsent [LANG]: setting current_lang = '" + _config.current_lang + "'"); 1326 return _config.current_lang; // otherwise return default 1327 } 1328 1329 /** 1330 * Get current client's browser language 1331 * @returns {string} 1332 */ 1333 var _getBrowserLang = function(){ 1334 var browser_lang = navigator.language || navigator.browserLanguage; 1335 browser_lang.length > 2 && (browser_lang = browser_lang[0]+browser_lang[1]); 1336 _log("CookieConsent [LANG]: detected_browser_lang = '"+ browser_lang + "'"); 1337 return browser_lang.toLowerCase() 1338 } 1339 1340 /** 1341 * Trap focus inside modal and focus the first 1342 * focusable element of current active modal 1343 */ 1344 var _handleFocusTrap = function(){ 1345 1346 _addEvent(document, 'keydown', function(e){ 1347 1348 // If is tab key => ok 1349 if(e.key !== 'Tab') 1350 return; 1351
1352 if(!consent_modal_visible && !settings_modal_visible) 1353 return; 1354 1355 // If there is any modal to focus 1356 if(focusable_edges){ 1357 1358 var activeElement = getActiveElement(); 1359 1360 // If reached natural end of the tab sequence => restart 1361 // If modal is not focused => focus modal 1362 if(e.shiftKey){ 1363 if (activeElement === focusable_edges[0] || !focused_modal.contains(activeElement)) { 1364 e.preventDefault(); 1365 setFocus(focusable_edges[1]) 1366 } 1367 }else{ 1368 if (activeElement === focusable_edges[1] || !focused_modal.contains(activeElement)) { 1369 e.preventDefault(); 1370 setFocus(focusable_edges[0]); 1371 } 1372 } 1373 } 1374 }); 1375 1376 } 1377 1378 /** 1379 * Manage each modal's layout 1380 * @param {Object} gui_options 1381 */ 1382 var _guiManager = function(gui_options, only_consent_modal){ 1383 1384 // If gui_options is not object => exit 1385 if(typeof gui_options !== 'object') return; 1386 1387 var consent_modal_options = gui_options['consent_modal']; 1388 var settings_modal_options = gui_options['settings_modal']; 1389 1390 /** 1391 * Helper function which adds layout and 1392 * position classes to given modal 1393 * 1394 * @param {HTMLElement} modal 1395 * @param {string[]} allowed_layouts 1396 * @param {string[]} allowed_positions 1397 * @param {string} layout 1398 * @param {string[]} position 1399 */ 1400 function _setLayout(modal, allowed_layouts, allowed_positions, allowed_transitions, layout, position, transition){ 1401 position = (position && position.split(" ")) || []; 1402 1403 // Check if specified layout is valid 1404 if(_inArray(allowed_layouts, layout) > -1){ 1405 1406 // Add layout classes 1407 _addClass(modal, layout); 1408 1409 // Add position class (if specified) 1410 if(!(layout === 'bar' && position[0] === 'middle') && _inArray(allowed_positions, position[0]) > -1){ 1411 for(var i=0; i<position.length; i++){ 1412 _addClass(modal, position[i]); 1413 } 1414 } 1415 } 1416 1417 // Add transition class 1418 (_inArray(allowed_transitions, transition) > -1) && _addClass(modal, transition); 1419 } 1420 1421 if(consent_modal_exists && consent_modal_options){ 1422 _setLayout( 1423 consent_modal, 1424 ['box', 'bar', 'cloud'], 1425 ['top', 'middle', 'bottom'], 1426 ['zoom', 'slide'], 1427 consent_modal_options['layout'], 1428 consent_modal_options['position'], 1429 consent_modal_options['transition'] 1430 ); 1431 } 1432 1433 if(!only_consent_modal && settings_modal_options){ 1434 _setLayout( 1435 settings_container, 1436 ['bar'], 1437 ['left', 'right'], 1438 ['zoom', 'slide'], 1439 settings_modal_options['layout'], 1440 settings_modal_options['position'], 1441 settings_modal_options['transition'] 1442 ); 1443 } 1444 } 1445 1446 /** 1447 * Returns true if cookie category is accepted by the user 1448 * @param {string} cookie_category 1449 * @returns {boolean} 1450 */ 1451 _cookieconsent.allowedCategory = function(cookie_category){ 1452 1453 if(!invalid_consent || _config.mode === 'opt-in') 1454 var allowed_categories = JSON.parse(_getCookie(_config.cookie_name, 'one', true) || '{}')['categories'] || [] 1455 else // mode is 'opt-out' 1456 var allowed_categories = default_enabled_categories; 1457 1458 return _inArray(allowed_categories, cookie_category) > -1; 1459 } 1460 1461 /** 1462 * "Init" method. Will run once and only if modals do not exist 1463 */ 1464 _cookieconsent.run = function(user_config){ 1465 if(!document.getElementById('cc_div')){ 1466 1467 // configure all parameters 1468 _setConfig(user_config); 1469 1470 // if is bot, don't run plugin 1471 if(is_bot) return; 1472 1473 // Retrieve cookie value (if set) 1474 saved_cookie_content = JSON.parse(_getCookie(_config.cookie_name, 'one', true) || "{}"); 1475 1476 // Retrieve "consent_uuid" 1477 consent_uuid = saved_cookie_content['consent_uuid']; 1478 1479 // If "consent_uuid" is present => assume that consent was previously given 1480 var cookie_consent_accepted = consent_uuid !== undefined; 1481 1482 // Retrieve "consent_date" 1483 consent_date = saved_cookie_content['consent_date']; 1484 consent_date && (consent_date = new Date(consent_date)); 1485 1486 // Retrieve "last_consent_update" 1487 last_consent_update = saved_cookie_content['last_consent_update']; 1488 last_consent_update && (last_consent_update = new Date(last_consent_update)); 1489 1490 // Retrieve "data" 1491 cookie_data = saved_cookie_content['data'] !== undefined ? saved_cookie_content['data'] : null; 1492 1493 // If revision is enabled and current value !== saved value inside the cookie =>
1493 revision is not valid 1494 if(revision_enabled && saved_cookie_content['revision'] !== _config.revision){ 1495 valid_revision = false; 1496 } 1497 1498 // If consent is not valid => create consent modal 1499 consent_modal_exists = invalid_consent = (!cookie_consent_accepted || !valid_revision || !consent_date || !last_consent_update || !consent_uuid); 1500 1501 // Generate cookie-settings dom (& consent modal) 1502 _createCookieConsentHTML(); 1503 1504 _getModalFocusableData(); 1505 _guiManager(user_config['gui_options']); 1506 _addDataButtonListeners(); 1507 1508 if(_config.autorun && consent_modal_exists){ 1509 _cookieconsent.show(user_config['delay'] || 0); 1510 } 1511 1512 // Add class to enable animations/transitions 1513 setTimeout(function(){_addClass(main_container, 'c--anim');}, 30); 1514 1515 // Accessibility :=> if tab pressed => trap focus inside modal 1516 setTimeout(function(){_handleFocusTrap();}, 100); 1517 1518 // If consent is valid 1519 if(!invalid_consent){ 1520 var rfc_prop_exists = typeof saved_cookie_content['rfc_cookie'] === "boolean"; 1521 1522 /* 1523 * Convert cookie to rfc format (if `use_rfc_cookie` is enabled) 1524 */ 1525 if(!rfc_prop_exists || (rfc_prop_exists && saved_cookie_content['rfc_cookie'] !== _config.use_rfc_cookie)){ 1526 saved_cookie_content['rfc_cookie'] = _config.use_rfc_cookie; 1527 _setCookie(_config.cookie_name, JSON.stringify(saved_cookie_content)); 1528 } 1529 1530 /** 1531 * Update accept type 1532 */ 1533 accept_type = _getAcceptType(_getCurrentCategoriesState()); 1534 1535 _manageExistingScripts(); 1536 1537 if(typeof onAccept === 'function') 1538 onAccept(saved_cookie_content); 1539 1540 _log("CookieConsent [NOTICE]: consent already given!", saved_cookie_content); 1541 1542 }else{ 1543 if(_config.mode === 'opt-out'){ 1544 _log("CookieConsent [CONFIG] mode='" + _config.mode + "', default enabled categories:", default_enabled_categories); 1545 _manageExistingScripts(default_enabled_categories); 1546 } 1547 _log("CookieConsent [NOTICE]: ask for consent!"); 1548 } 1549 }else{ 1550 _log("CookieConsent [NOTICE]: cookie consent already attached to body!"); 1551 } 1552 } 1553 1554 /** 1555 * This function handles the loading/activation logic of the already 1556 * existing scripts based on the current accepted cookie categories 1557 * 1558 * @param {string[]} [must_enable_categories] 1559 */ 1560 var _manageExistingScripts = function(must_enable_categories){ 1561 1562 if(!_config.page_scripts) return; 1563 1564 // get all the scripts with "cookie-category" attribute 1565 var scripts = document.querySelectorAll('script[' + _config.script_selector + ']'); 1566 var accepted_categories = must_enable_categories || saved_cookie_content['categories'] || []; 1567 1568 /** 1569 * Load scripts (sequentially), using a recursive function 1570 * which loops through the scripts array 1571 * @param {Element[]} scripts scripts to load 1572 * @param {number} index current script to load 1573 */ 1574 var _loadScripts = function(scripts, index){ 1575 if(index < scripts.length){ 1576 1577 var curr_script = scripts[index]; 1578 var curr_script_category = curr_script.getAttribute(_config.script_selector); 1579 1580 /** 1581 * If current script's category is on the array of categories 1582 * accepted by the user => load script 1583 */ 1584 if(_inArray(accepted_categories, curr_script_category) > -1){ 1585 1586 curr_script.type = curr_script.getAttribute('data-type') || 'text/javascript'; 1587 curr_script.removeAttribute(_config.script_selector); 1588 1589 // get current script data-src 1590 var src = curr_script.getAttribute('data-src'); 1591 1592 // some scripts (like ga) might throw warning if data-src is present 1593 src && curr_script.removeAttribute('data-src'); 1594 1595 // create fresh script (with the same code) 1596 var fresh_script = _createNode('script'); 1597 fresh_script.textContent = curr_script.innerHTML; 1598 1599 // Copy attributes over to the new "revived" script 1600 (function(destination, source){ 1601 var attributes = source.attributes; 1602 var len = attributes.length; 1603 for(var i=0; i<len; i++){ 1604 var attr_name = attributes[i].nodeName; 1605 destination.setAttribute(attr_name , source[attr_name] || source.getAttribute(attr_name)); 1606 } 1607 })(fresh_script, curr_script); 1608 1609 // set src (if data-src found) 1610 src ? (fresh_script.src = src) : (src = curr_script.src); 1611 1612 // if script has "src" attribute 1613 // try loading it sequentially 1614 if(src){ 1615 // load script sequentially => the next script will not be loaded 1616 // until the current's script onload event triggers 1617 if(fresh_script.readyState) { // only required for IE <9 1618 fresh_script.onreadystatechange = function() { 1619 if (fresh_script.readyState === "loaded" || fresh_script.readyState === "complete" ) { 1620 fresh_script.onreadystatechange = null; 1621 _loadScripts(scripts, ++index); 1622 } 1623 }; 1624 }else{ // others 1625 fresh_script.onload = function(){ 1626 fresh_script.onload = null; 1627 _loadScripts(scripts, ++index); 1628 }; 1629 } 1630 } 1631 1632 // Replace current "sleeping" script with the new "revived" one 1633 curr_script.parentNode.replaceChild(fresh_script, curr_script); 1634 1635 /** 1636 * If we managed to get here and scr is still set, it means that 1637 * the script is loading/loaded sequentially so don't go any further 1638 */ 1639 if(src) return; 1640 } 1641 1642 // Go to next script right away 1643 _loadScripts(scripts, ++index); 1644 } 1645 } 1646 1647 _loadScripts(scripts, 0); 1648 } 1649 1650 /** 1651 * Save custom data inside cookie 1652 * @param {object|string} new_data 1653 * @param {string} [mode] 1654 * @returns {boolean} 1655 */
1656 var _setCookieData = function(new_data, mode){ 1657 1658 var set = false; 1659 /** 1660 * If mode is 'update': 1661 * add/update only the specified props. 1662 */ 1663 if(mode === 'update'){ 1664 cookie_data = _cookieconsent.get('data'); 1665 var same_type = typeof cookie_data === typeof new_data; 1666 1667 if(same_type && typeof cookie_data === "object"){ 1668 !cookie_data && (cookie_data = {}); 1669 1670 for(var prop in new_data){ 1671 if(cookie_data[prop] !== new_data[prop]){ 1672 cookie_data[prop] = new_data[prop] 1673 set = true; 1674 } 1675 } 1676 }else if((same_type || !cookie_data) && cookie_data !== new_data){ 1677 cookie_data = new_data; 1678 set = true; 1679 } 1680 }else{ 1681 cookie_data = new_data; 1682 set = true; 1683 } 1684 1685 if(set){ 1686 saved_cookie_content['data'] = cookie_data; 1687 _setCookie(_config.cookie_name, JSON.stringify(saved_cookie_content)); 1688 } 1689 1690 return set; 1691 } 1692 1693 /** 1694 * Helper method to set a variety of fields 1695 * @param {string} field 1696 * @param {object} data 1697 * @returns {boolean} 1698 */ 1699 _cookieconsent.set = function(field, data){ 1700 switch(field){ 1701 case 'data': return _setCookieData(data['value'], data['mode']); 1702 default: return false; 1703 } 1704 } 1705 1706 /** 1707 * Retrieve data from existing cookie 1708 * @param {string} field 1709 * @param {string} [cookie_name] 1710 * @returns {any} 1711 */ 1712 _cookieconsent.get = function(field, cookie_name){ 1713 var cookie = JSON.parse(_getCookie(cookie_name || _config.cookie_name, 'one', true) || "{}"); 1714 1715 return cookie[field]; 1716 } 1717 1718 /** 1719 * Read current configuration value 1720 * @returns {any} 1721 */ 1722 _cookieconsent.getConfig = function(field){ 1723 return _config[field] || user_config[field]; 1724 } 1725 1726 /** 1727 * Obtain accepted and rejected categories 1728 * @returns {{accepted: string[], rejected: string[]}} 1729 */ 1730 var _getCurrentCategoriesState = function(){ 1731 1732 // get accepted categories 1733 accepted_categories = saved_cookie_content['categories'] || []; 1734 1735 // calculate rejected categories (all_categories - accepted_categories) 1736 rejected_categories = all_categories.filter(function(category){ 1737 return (_inArray(accepted_categories, category) === -1); 1738 }); 1739 1740 return { 1741 accepted: accepted_categories, 1742 rejected: rejected_categories 1743 } 1744 } 1745 1746 /** 1747 * Calculate "accept type" given current categories state 1748 * @param {{accepted: string[], rejected: string[]}} currentCategoriesState 1749 * @returns {string} 1750 */ 1751 var _getAcceptType = function(currentCategoriesState){ 1752 1753 var type = 'custom'; 1754 1755 // number of categories marked as necessary/readonly 1756 var necessary_categories_length = readonly_categories.length; 1757 1758 // calculate accept type based on accepted/rejected categories 1759 if(currentCategoriesState.accepted.length === all_categories.length) 1760 type = 'all'; 1761 else if(currentCategoriesState.accepted.length === necessary_categories_length) 1762 type = 'necessary' 1763 1764 return type; 1765 } 1766 1767 /** 1768 * @typedef {object} userPreferences 1769 * @property {string} accept_type 1770 * @property {string[]} accepted_categories 1771 * @property {string[]} rejected_categories 1772 */ 1773 1774 /** 1775 * Retrieve current user preferences (summary) 1776 * @returns {userPreferences} 1777 */ 1778 _cookieconsent.getUserPreferences = function(){ 1779 var currentCategoriesState = _getCurrentCategoriesState(); 1780 var accept_type = _getAcceptType(currentCategoriesState); 1781 1782 return { 1783 'accept_type': accept_type, 1784 'accepted_categories': currentCategoriesState.accepted, 1785 'rejected_categories': currentCategoriesState.rejected 1786 } 1787 } 1788 1789 /** 1790 * Function which will run after script load 1791 * @callback scriptLoaded 1792 */ 1793 1794 /** 1795 * Dynamically load script (append to head) 1796 * @param {string} src 1797 * @param {scriptLoaded} callback 1798 * @param {object[]} [attrs] Custom attributes 1799 */ 1800 _cookieconsent.loadScript = function(src, callback, attrs){ 1801 1802 var function_defined = typeof callback === 'function'; 1803 1804 // Load script only if not already loaded 1805 if(!document.querySelector('script[src="' + src + '"]')){ 1806 1807 var script = _createNode('script'); 1808 1809 // if an array is provided => add custom attributes 1810 if(attrs && attrs.length > 0){ 1811 for(var i=0; i<attrs.length; ++i){ 1812 attrs[i] && script.setAttribute(attrs[i]['name'], attrs[i]['value']); 1813 } 1814 } 1815 1816 // if callback function defined => run callback onload 1817 if(function_defined){ 1818 script.onload = callback; 1819 } 1820 1821 script.src = src; 1822 1823 /** 1824 * Append script to head 1825 */ 1826 document.head.appendChild(script); 1827 }else{ 1828 function_defined && callback(); 1829 } 1830 } 1831 1832 /** 1833 * Manage dynamically loaded scripts: https://github.com/orestbida/cookieconsent/issues/101 1834 * If plugin has already run, call this method to enable 1835 * the newly added scripts based on currently selected preferences 1836 */ 1837 _cookieconsent.updateScripts = function(){ 1838 _manageExistingScripts(); 1839 } 1840 1841 /** 1842 * Show cookie consent modal (with delay parameter) 1843 * @param {number} [delay] 1844 * @param {boolean} [create_modal] create modal if it doesn't exist 1845 */ 1846 _cookieconsent.show = function(delay, create_modal){ 1847 1848 if(create_modal === true) 1849 _createConsentModal(_config.current_lang); 1850 1851 if(!consent_modal_exists) 1852 return; 1853 1854 last_elem_before_modal = getActiveElement(); 1855 focusable_edges = consent_modal_focusable; 1856 focused_modal = consent_modal; 1857 1858 consent_modal_visible = true; 1859 consent_modal.removeAttribute('aria-hidden'); 1860 1861 setTimeout(function() { 1862 _addClass(html_dom, "show--consent"); 1863 _log("CookieConsent [MODAL]: show consent_modal"); 1864 }, delay > 0 ? delay : (create_modal ? 30 : 0)); 1865 1866 } 1867 1868 /** 1869 * Hide consent modal 1870 */ 1871 _cookieconsent.hide = function(){ 1872 1873 if(!consent_modal_exists) 1874 return; 1875 1876 consent_modal_visible = false;
1877 1878 setFocus(cmFocusSpan); 1879 1880 consent_modal.setAttribute('aria-hidden', 'true'); 1881 _removeClass(html_dom, "show--consent"); 1882 1883 setFocus(last_elem_before_modal); 1884 last_elem_before_modal = null; 1885 1886 _log("CookieConsent [MODAL]: hide"); 1887 } 1888 1889 /** 1890 * Show settings modal (with optional delay) 1891 * @param {number} delay 1892 */ 1893 _cookieconsent.showSettings = function(delay){ 1894 1895 settings_modal_visible = true; 1896 settings_container.removeAttribute('aria-hidden'); 1897 1898 if(consent_modal_visible){ 1899 last_consent_modal_btn_focus = getActiveElement(); 1900 }else{ 1901 last_elem_before_modal = getActiveElement(); 1902 } 1903 1904 focused_modal = settings_container; 1905 focusable_edges = settings_modal_focusable; 1906 1907 setTimeout(function() { 1908 _addClass(html_dom, "show--settings"); 1909 _log("CookieConsent [SETTINGS]: show settings_modal"); 1910 }, delay > 0 ? delay : 0); 1911 } 1912 1913 /** 1914 * Hide settings modal 1915 */ 1916 _cookieconsent.hideSettings = function(){ 1917 1918 settings_modal_visible = false; 1919 1920 discardUnsavedToggles(); 1921 1922 setFocus(smFocusSpan); 1923 1924 settings_container.setAttribute('aria-hidden', 'true'); 1925 _removeClass(html_dom, "show--settings"); 1926 1927 if(consent_modal_visible){ 1928 1929 setFocus(last_consent_modal_btn_focus); 1930 last_consent_modal_btn_focus = null; 1931 1932 focused_modal = consent_modal; 1933 focusable_edges = consent_modal_focusable; 1934 }else{ 1935 setFocus(last_elem_before_modal); 1936 last_elem_before_modal = null; 1937 } 1938 1939 _log("CookieConsent [SETTINGS]: hide settings_modal"); 1940 } 1941 1942 /** 1943 * Accept cookieconsent function API 1944 * @param {string[]|string} _categories - Categories to accept 1945 * @param {string[]} [_exclusions] - Excluded categories [optional] 1946 */ 1947 _cookieconsent.accept = function(_categories, _exclusions){ 1948 var categories = _categories || undefined; 1949 var exclusions = _exclusions || []; 1950 var to_accept = []; 1951 1952 /** 1953 * Get all accepted categories 1954 * @returns {string[]} 1955 */ 1956 var _getCurrentPreferences = function(){ 1957 var toggles = document.querySelectorAll('.c-tgl') || []; 1958 var states = []; 1959 1960 for(var i=0; i<toggles.length; i++){ 1961 if(toggles[i].checked){ 1962 states.push(toggles[i].value); 1963 } 1964 } 1965 return states; 1966 } 1967 1968 if(!categories){ 1969 to_accept = _getCurrentPreferences(); 1970 }else{ 1971 if( 1972 typeof categories === "object" && 1973 typeof categories.length === "number" 1974 ){ 1975 for(var i=0; i<categories.length; i++){ 1976 if(_inArray(all_categories, categories[i]) !== -1) 1977 to_accept.push(categories[i]); 1978 } 1979 }else if(typeof categories === "string"){ 1980 if(categories === 'all') 1981 to_accept = all_categories.slice(); 1982 else{ 1983 if(_inArray(all_categories, categories) !== -1) 1984 to_accept.push(categories); 1985 } 1986 } 1987 } 1988 1989 // Remove excluded categories 1990 if(exclusions.length >= 1){ 1991 for(i=0; i<exclusions.length; i++){ 1992 to_accept = to_accept.filter(function(item) { 1993 return item !== exclusions[i] 1994 }) 1995 } 1996 } 1997 1998 // Add back all the categories set as "readonly/required" 1999 for(i=0; i<all_categories.length; i++){ 2000 if( 2001 readonly_categories.includes(all_categories[i]) === true && 2002 _inArray(to_accept, all_categories[i]) === -1 2003 ){ 2004 to_accept.push(all_categories[i]); 2005 } 2006 } 2007 2008 _saveCookiePreferences(to_accept); 2009 } 2010 2011 /** 2012 * API function to easily erase cookies 2013 * @param {(string|string[])} _cookies 2014 * @param {string} [_path] - optional 2015 * @param {string} [_domain] - optional 2016 */
2017 _cookieconsent.eraseCookies = function(_cookies, _path, _domain){ 2018 var cookies = []; 2019 var domains = _domain 2020 ? [_domain, "."+_domain] 2021 : [_config.cookie_domain, "."+_config.cookie_domain]; 2022 2023 if(typeof _cookies === "object" && _cookies.length > 0){ 2024 for(var i=0; i<_cookies.length; i++){ 2025 this.validCookie(_cookies[i]) && cookies.push(_cookies[i]); 2026 } 2027 }else{ 2028 this.validCookie(_cookies) && cookies.push(_cookies); 2029 } 2030 2031 _eraseCookies(cookies, _path, domains); 2032 } 2033 2034 /** 2035 * Set cookie, by specifying name and value 2036 * @param {string} name 2037 * @param {string} value 2038 */ 2039 var _setCookie = function(name, value) { 2040 2041 var cookie_expiration = _config.cookie_expiration; 2042 2043 if(typeof _config.cookie_necessary_only_expiration === 'number' && accept_type === 'necessary') 2044 cookie_expiration = _config.cookie_necessary_only_expiration; 2045 2046 value = _config.use_rfc_cookie ? encodeURIComponent(value) : value; 2047 2048 var date = new Date(); 2049 date.setTime(date.getTime() + (1000 * (cookie_expiration * 24 * 60 * 60))); 2050 var expires = "; expires=" + date.toUTCString(); 2051 2052 var cookieStr = name + "=" + (value || "") + expires + "; Path=" + _config.cookie_path + ";"; 2053 cookieStr += " SameSite=" + _config.cookie_same_site + ";"; 2054 2055 // assures cookie works with localhost (=> don't specify domain if on localhost) 2056 if(location.hostname.indexOf(".") > -1 && _config.cookie_domain){ 2057 cookieStr += " Domain=" + _config.cookie_domain + ";"; 2058 } 2059 2060 if(location.protocol === "https:") { 2061 cookieStr += " Secure;"; 2062 } 2063 2064 document.cookie = cookieStr; 2065 2066 _log("CookieConsent [SET_COOKIE]: '" + name + "' expires after " + cookie_expiration + " day(s)"); 2067 } 2068 2069 /** 2070 * Get cookie value by name, 2071 * returns the cookie value if found (or an array 2072 * of cookies if filter provided), otherwise empty string: "" 2073 * @param {string} name 2074 * @param {string} filter 'one' or 'all' 2075 * @param {boolean} [get_value] set to true to obtain its value 2076 * @returns {string|string[]} 2077 */ 2078 var _getCookie = function(name, filter, get_value) { 2079 var found; 2080 2081 if(filter === 'one'){ 2082 found = document.cookie.match("(^|;)\\s*" + name + "\\s*=\\s*([^;]+)"); 2083 found = found ? (get_value ? found.pop() : name) : ""; 2084 2085 if(found && name === _config.cookie_name){ 2086 try{ 2087 found = JSON.parse(found) 2088 }catch(e){ 2089 try { 2090 found = JSON.parse(decodeURIComponent(found)) 2091 } catch (e) { 2092 // if I got here => cookie value is not a valid json string 2093 found = {}; 2094 } 2095 } 2096 found = JSON.stringify(found); 2097 } 2098 }else if(filter === 'all'){ 2099 // array of names of all existing cookies 2100 var cookies = document.cookie.split(/;\s*/); found = []; 2101 for(var i=0; i<cookies.length; i++){ 2102 found.push(cookies[i].split("=")[0]); 2103 } 2104 } 2105 2106 return found; 2107 } 2108 2109 /** 2110 * Delete cookie by name & path 2111 * @param {string[]} cookies 2112 * @param {string} [custom_path] - optional 2113 * @param {string[]} domains - example: ['domain.com', '.domain.com'] 2114 */ 2115 var _eraseCookies = function(cookies, custom_path, domains) { 2116 var path = custom_path ? custom_path : '/'; 2117 var expires = 'Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; 2118 2119 for(var i=0; i<cookies.length; i++){ 2120 for(var j=0; j<domains.length; j++){ 2121 document.cookie = cookies[i] + '=; path=' + path + 2122 (domains[j].indexOf('.') == 0 ? '; domain=' + domains[j] : "") + '; ' + expires; 2123 } 2124 _log("CookieConsent [AUTOCLEAR]: deleting cookie: '" + cookies[i] + "' path: '" + path + "' domain:", domains); 2125 } 2126 } 2127 2128 /** 2129 * Returns true if cookie was found and has valid value (not empty string) 2130 * @param {string} cookie_name 2131 * @returns {boolean} 2132 */ 2133 _cookieconsent.validCookie = function(cookie_name){ 2134 return _getCookie(cookie_name, 'one', true) !== ""; 2135 } 2136 2137 /** 2138 * Function to run when event is fired 2139 * @callback eventFired 2140 */ 2141 2142 /** 2143 * Add event listener to dom object (cross browser function) 2144 * @param {Element} elem 2145 * @param {string} event 2146 * @param {eventFired} fn 2147 * @param {boolean} [isPassive] 2148 */ 2149 var _addEvent = function(elem, event, fn, isPassive) { 2150 elem.addEventListener(event, fn , isPassive === true ? { passive: true } : false); 2151 } 2152 2153 /** 2154 * Get all prop. keys defined inside object 2155 * @param {Object} obj 2156 */ 2157 var _getKeys = function(obj){ 2158 if(typeof obj === "object"){ 2159 return Object.keys(obj); 2160 } 2161 } 2162 2163 /** 2164 * Append class to the specified dom element 2165 * @param {HTMLElement} elem 2166 * @param {string} classname 2167 */ 2168 var _addClass = function (elem, classname){ 2169 elem.classList.add(classname); 2170 } 2171 2172 /** 2173 * Remove specified class from dom element 2174 * @param {HTMLElement} elem 2175 * @param {string} classname 2176 */ 2177 var _removeClass = function (el, className) { 2178 el.classList.remove(className); 2179 } 2180 2181 /** 2182 * Check if html element has class 2183 * @param {HTMLElement} el 2184 * @param {string} className 2185 */ 2186 var _hasClass = function(el, className) { 2187 return el.classList.contains(className); 2188 } 2189 2190 /** 2191 * @param {1 | 2} modal_id 2192 */ 2193 var generateFocusSpan = function(modal_id) { 2194 var span = _createNode('span'); 2195 span.tabIndex = -1; 2196 2197 if(modal_id === 1) 2198 cmFocusSpan = span; 2199 else 2200 smFocusSpan = span; 2201 2202 return span; 2203 } 2204 2205 /** 2206 * @param {HTMLElement} el 2207 */ 2208 var setFocus = function(el) { 2209 el && el instanceof HTMLElement && el.focus(); 2210 } 2211 2212 /** 2213 * https://github.com/orestbida/cookieconsent/issues/481 2214 */ 2215 var discardUnsavedToggles = function() { 2216 2217 /** 2218 * @type {NodeListOf<HTMLInputElement>} 2219 */ 2220 var toggles = settings_inner.querySelectorAll('.c-tgl'); 2221 2222 for(var i=0; i<toggles.length; i++) { 2223 var category = toggles[i].value; 2224 var is_readonly = readonly_categories.includes(category); 2225 2226 toggles[i].checked = is_readonly || _cookieconsent.allowedCategory(category); 2227 } 2228 2229 } 2230 2231 /** 2232 * @returns {Element | null} 2233 */ 2234 var getActiveElement = function() { 2235 return document.activeElement; 2236 } 2237 2238 return _cookieconsent; 2239 }; 2240 2241 var init = 'initCookieConsent'; 2242 /** 2243 * Make CookieConsent object accessible globally 2244 */ 2245 if(typeof window !== 'undefined' && typeof window[init] !== 'function'){ 2246 window[init] = CookieConsent 2247 } 2248})();
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.