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