let cookie_url = 'https://onetrust.techservices.illinois.edu/1.2.0'; // This is replaced with the correct URL during GitHub Action runs. // cookie_url = '.' // Uncomment when testing specific domain using local host entries. if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { cookie_url = '.'; // For local testing only console.warn('Cookie Banner is in development mode: ' + 'Please set cookie_url in production.'); } var this_script = document.currentScript; // Must run before any function calls async function openCookieB(cookiebId) { // Do not show if our 'dismiss' cookie is set let skip = await getDismissCookieNotice(); if(skip) { return; } let cookieb = document.getElementById(cookiebId); cookieb.classList.remove('ila-cookieb--closed'); cookieb.classList.add('ila-cookieb--open'); // remove property from older elements so newest element appears on bottom const olderElements = document.querySelectorAll('.ila-cookieb--first'); olderElements.forEach((element) => { element.classList.remove('ila-cookieb--first'); }); cookieb.classList.add('ila-cookieb--first'); manageAutoclose(cookiebId); // Used to disable scroll on the page document.body.classList.add('ila-cookieb-noscroll'); // Used to enable a modal background on the page let modalIDvar = document.getElementById('ilaCookieModal'); modalIDvar.classList.add('ila-cookieb-modal'); // Start the focus on the X button so that reading can continue from there. let cookie_focus = document.getElementById('ilaCookieNoticeDiv'); cookie_focus.setAttribute('tabindex', '-1'); // Focusable, but outside tab order if(cookie_focus){ cookie_focus.focus(); } } function closeCookieB(cookiebId) { let cookieb = document.getElementById(cookiebId); // Only move forward if the Cookie Banner is Open if (cookieb.classList.contains('ila-cookieb--open')) { cookieb.classList.remove('ila-cookieb--open'); cookieb.classList.add('ila-cookieb--closed'); // Remember that the notice has been dismissed setDismissCookieNotice(); // Used to enable scroll on the page document.body.classList.remove('ila-cookieb-noscroll'); // Used to disable a modal background on the page let modalIDvar = document.getElementById('ilaCookieModal'); modalIDvar.classList.remove('ila-cookieb-modal'); // Put focus back to the page body on close document.body.setAttribute('tabindex', '-1'); // Focusable but outside tab order document.body.focus(); } } function manageAutoclose(cookiebId) { /* setTimeout(() => closeCookieB(cookiebId), 8000); */ } async function getCookieBannerContent(content_path) { /* Tip: It may be necssary to expand content_path to include the full final web URL of the partial file. */ let banner_response = await fetch(content_path); let banner_content = await banner_response.text(); return banner_content; } function getBaseDomain() { const hostname = window.location.hostname; const parts = hostname.split('.'); if (parts.length >= 2) { return '.' + parts.slice(-2).join('.'); // returns last two terms of url } else { return hostname; // fallback for localhost, etc. } } function getCookieString(expires) { return "cookie_notice=hide;Path=/;SameSite=Lax;domain=" + getBaseDomain() + ";expires=" + expires.toUTCString(); } function getFallBackCookieString(expires) { return "cookie_notice=hide;Path=/;SameSite=Lax;expires=" + expires.toUTCString(); } async function setDismissCookieNotice() { var expires = new Date(); expires.setMonth(expires.getMonth() + 6); document.cookie = getCookieString(expires); if(!await getDismissCookieNotice()) { // Site headers may reject our attempt to set a domain-wide cookie. (github.io does) // Recover by setting a cookie only for the current sub-domain. console.debug("Browser rejected domain-wide cookie. Setting local cookie to suppress dismissed notice."); document.cookie = getFallBackCookieString(expires); } } async function getDismissCookieNotice() { let result = ('; '+document.cookie).split(`; cookie_notice=`).pop().split(';')[0]; if(result == "") { return false; } return true; } function unsetCookieNoticeCookie() { // Helper for Demo Pages - Call this to make the Notice appear again. var expires = new Date(); expires.setMonth(expires.getMonth() - 1); // Use getFallBackCookieString because getCookieString does not work on // github.io domains. document.cookie = getFallBackCookieString(expires); location.reload(); } async function addCookieBanner() { let about_button = document.getElementById("ot-sdk-btn"); let data_fetch = this_script.getAttribute("data-cookie-fetch"); if(data_fetch != "no"){ /* Appends to the end of the page. */ let theme = this_script.getAttribute("data-domain-script"); switch(theme) { case "c2f2262d-b694-4eba-8f4b-142c102b685a": // UIC case "uic": css_content = await getCookieBannerContent(cookie_url + '/css/ila-cookie-uic-colors.css'); break; case "698d1fb7-b06b-4591-adbf-ac44ae3ef77b": // UIS case "uis": css_content = await getCookieBannerContent(cookie_url + '/css/ila-cookie-uis-colors.css'); break; default: css_content = await getCookieBannerContent(cookie_url + '/css/ila-cookie-uiuc-colors.css'); break; } // Add cookie banner to page let banner_content = ""; banner_content += await getCookieBannerContent(cookie_url + '/partials/ila-cookie-banner-content.part.html'); if (!banner_content.includes("Cookie Notice")) { console.warn("Unexpected Cookie Notice:", banner_content); if (about_button) { about_button.addEventListener("click", function() { alert("Cookie Notice is down for maintenance."); }); } return; /* Prevents showing any S3 error messages at the end of every campus webpage. */ } document.body.insertAdjacentHTML("beforeend", banner_content); } document.querySelectorAll("[data-cookie-action]").forEach(button => { switch (button.getAttribute("data-cookie-action")) { case "close-banner": button.addEventListener("click", function() { closeCookieB('ilaCookieBOne'); }); break; case "open-about": button.addEventListener("click", function() { openSlideover('ilaCookieSlideover', 'ilaCookieAboutButton'); }); break; case "close-about": button.addEventListener("click", function() { closeSlideover('ilaCookieSlideover'); }); break; case "unhide-banner": button.addEventListener("click", function() { unsetCookieNoticeCookie(); }); break; } }); // Show cookie banner openCookieB('ilaCookieBOne'); // Open the 'About Cookies' slide-over when existing legacy 'About Cookies' buttons are clicked. if (about_button) { about_button.addEventListener("click", function() { openSlideover('ilaCookieSlideover', about_button); }); } // Dismiss the Cookie banner when `Escape` is pressed document.addEventListener("keydown", function(event) { if (event.key === "Escape") { closeCookieB('ilaCookieBOne'); } }); createCookieNoticeFocusCycle(); } window.addEventListener("load", function(event){ addCookieBanner(); }); function createCookieNoticeFocusCycle() { first = document.getElementById("ilaCookieBXButton"); first.addEventListener('keydown', function(e){ if (e.keyCode===9 && e.shiftKey) { last.focus(); e.preventDefault(); }}); last = document.getElementById("ilaCookieCloseButton"); last.addEventListener('keydown', function(e){ if (e.keyCode===9 && !e.shiftKey) { first.focus(); e.preventDefault(); }}); } /* Accessiblity adapted from https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/examples/alertdialog/ */ ('use strict'); var aria = aria || {}; aria.Utils = aria.Utils || {}; window.openSlideover = function (dialogId, returnFocus) { button_to_return_focus_to = returnFocus if(returnFocus) { if(!returnFocus.focus) { button_to_return_focus_to = document.getElementById(returnFocus); } } new aria.Dialog(dialogId, button_to_return_focus_to); }; window.closeSlideover = function () { aria.openedDialog.close(); }; // end closeDialog /** * @description Set focus on descendant nodes until the first focusable element is * found. * @param element * DOM node for which to find the first focusable descendant. * @returns {boolean} * true if a focusable element is found and focus is set. */ aria.Utils.focusFirstDescendant = function (element) { for (var i = 0; i < element.childNodes.length; i++) { var child = element.childNodes[i]; if ( aria.Utils.attemptFocus(child) || aria.Utils.focusFirstDescendant(child) ) { return true; } } return false; }; // end focusFirstDescendant aria.Utils.focusLastDescendant = function (element) { for (var i = element.childNodes.length - 1; i >= 0; i--) { var child = element.childNodes[i]; if ( aria.Utils.attemptFocus(child) || aria.Utils.focusLastDescendant(child) ) { return true; } } return false; }; // end focusLastDescendant /** * @description Set Attempt to set focus on the current node. * @param element * The node to attempt to focus on. * @returns {boolean} * true if element is focused. */ aria.Utils.attemptFocus = function (element) { if (!aria.Utils.isFocusable(element)) { // console.debug( // 'Attempted to focus on an element that is not focusable.', // element // ); return false; } aria.Utils.IgnoreUtilFocusChanges = true; try { element.focus(); } catch (e) { // continue regardless of error console.warn('Error focusing element:', e); } aria.Utils.IgnoreUtilFocusChanges = false; // console.debug('Focused element:', element); return document.activeElement === element; }; // end attemptFocus aria.handleEscape = function (event) { var key = event.which || event.keyCode; if (key === aria.KeyCode.ESC && aria.openedDialog) { aria.openedDialog.close(); event.stopPropagation(); } }; document.addEventListener('keyup', aria.handleEscape); /** * @class * @description Dialog object providing modal focus management. * * Assumptions: The element serving as the dialog container is present in the * DOM and hidden. The dialog container has role='dialog'. * @param dialogId * The ID of the element serving as the dialog container. */ aria.Dialog = function (dialogId, returnFocus) { this.dialogNode = document.getElementById(dialogId); if (this.dialogNode === null) { throw new Error('No element found with id="' + dialogId + '".'); } var validRoles = ['dialog', 'alertdialog']; var isDialog = (this.dialogNode.getAttribute('role') || '') .trim() .split(/\s+/g) .some(function (token) { return validRoles.some(function (role) { return token === role; }); }); if (!isDialog) { throw new Error( 'Dialog() requires a DOM element with ARIA role of dialog or alertdialog.' ); } // Disable scroll on the body element document.body.classList.add(aria.Utils.dialogOpenClass); // Bracket the dialog node with two invisible, focusable nodes. // While this dialog is open, we use these to make sure that focus never // leaves the document even if dialogNode is the first or last node. var preDiv = document.createElement('div'); this.preNode = this.dialogNode.parentNode.insertBefore( preDiv, this.dialogNode ); this.preNode.tabIndex = 0; var postDiv = document.createElement('div'); this.postNode = this.dialogNode.parentNode.insertBefore( postDiv, this.dialogNode.nextSibling ); this.postNode.tabIndex = 0; // Set the initial focus for screen-readers // (Preferred pattern because this is a modal alert) let slide_div = document.getElementById(dialogId); slide_div.setAttribute('tabindex', '-1'); // Focusable, but outside tab order setTimeout(function(){ slide_div.focus(); },500); this.addListeners(); aria.openedDialog = this; this.dialogNode.classList.add('ila-slideover--open'); // make visible this.dialogNode.classList.remove('ila-slideover--closed'); this.lastFocus = document.activeElement; this.returnFocus = returnFocus; }; // end Dialog constructor /** * @description * Hides the current top dialog, * removes listeners of the top dialog, * restore listeners of a parent dialog if one was open under the one that just closed */ aria.Dialog.prototype.close = function () { aria.openedDialog = null; this.removeListeners(); aria.Utils.remove(this.preNode); aria.Utils.remove(this.postNode); this.dialogNode.classList.add('ila-slideover--closed'); this.dialogNode.classList.remove('ila-slideover--open'); document.body.classList.remove(aria.Utils.dialogOpenClass); // return focus to the button that opened the dialog this.returnFocus.focus(); }; // end close aria.Dialog.prototype.addListeners = function () { document.addEventListener('focus', this.trapFocus, true); }; // end addListeners aria.Dialog.prototype.removeListeners = function () { document.removeEventListener('focus', this.trapFocus, true); }; // end removeListeners aria.Dialog.prototype.trapFocus = function (event) { if (aria.Utils.IgnoreUtilFocusChanges) { return; } var opened = aria.openedDialog; if (opened.dialogNode.contains(event.target)) { opened.lastFocus = event.target; } else { aria.Utils.focusFirstDescendant(opened.dialogNode); if (opened.lastFocus == document.activeElement) { aria.Utils.focusLastDescendant(opened.dialogNode); } opened.lastFocus = document.activeElement; } }; // end trapFocus ('use strict'); /** * @namespace aria */ var aria = aria || {}; /** * @description * Key code constants */ aria.KeyCode = { BACKSPACE: 8, TAB: 9, RETURN: 13, SHIFT: 16, ESC: 27, SPACE: 32, PAGE_UP: 33, PAGE_DOWN: 34, END: 35, HOME: 36, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40, DELETE: 46, }; aria.Utils = aria.Utils || {}; aria.Utils.IgnoreUtilFocusChanges = false; aria.Utils.dialogOpenClass = 'ila-body--has-dialog'; // Polyfill src https://developer.mozilla.org/en-US/docs/Web/API/Element/matches aria.Utils.matches = function (element, selector) { if (!Element.prototype.matches) { Element.prototype.matches = Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector || function (s) { var matches = element.parentNode.querySelectorAll(s); var i = matches.length; while (--i >= 0 && matches.item(i) !== this) { // empty } return i > -1; }; } return element.matches(selector); }; aria.Utils.remove = function (item) { if (item.remove && typeof item.remove === 'function') { return item.remove(); } if ( item.parentNode && item.parentNode.removeChild && typeof item.parentNode.removeChild === 'function' ) { return item.parentNode.removeChild(item); } return false; }; aria.Utils.isFocusable = function (element) { if (element.tabIndex < 0) { return false; } if (element.disabled) { return false; } switch (element.nodeName) { case 'A': return !!element.href && element.rel != 'ignore'; case 'INPUT': return element.type != 'hidden'; case 'BUTTON': case 'SELECT': case 'TEXTAREA': return true; default: if (element.tabIndex >= 0) { return true; } return false; } }; aria.Utils.getAncestorBySelector = function (element, selector) { if (!aria.Utils.matches(element, selector + ' ' + element.tagName)) { // Element is not inside an element that matches selector return null; } // Move up the DOM tree until a parent matching the selector is found var currentNode = element; var ancestor = null; while (ancestor === null) { if (aria.Utils.matches(currentNode.parentNode, selector)) { ancestor = currentNode.parentNode; } else { currentNode = currentNode.parentNode; } } return ancestor; }; aria.Utils.hasClass = function (element, className) { return new RegExp('(\\s|^)' + className + '(\\s|$)').test( element.className ); }; aria.Utils.addClass = function (element, className) { if (!aria.Utils.hasClass(element, className)) { element.className += ' ' + className; } }; aria.Utils.removeClass = function (element, className) { var classRegex = new RegExp('(\\s|^)' + className + '(\\s|$)'); element.className = element.className.replace(classRegex, ' ').trim(); }; aria.Utils.bindMethods = function (object /* , ...methodNames */) { var methodNames = Array.prototype.slice.call(arguments, 1); methodNames.forEach(function (method) { object[method] = object[method].bind(object); }); };