//based on: https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2b.html /* * This content is licensed according to the W3C Software License at * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document * * File: TreeLinks.js * * Desc: Tree widget that implements ARIA Authoring Practices * for a tree being used as a file viewer */ /** * ARIA Treeview example * @function onload * @desc after page has loaded initialize all treeitems based on the role=treeitem */ window.addEventListener('load', function () { var trees = document.querySelectorAll('[role="tree"]'); for (var i = 0; i < trees.length; i++) { var t = new TreeLinks(trees[i]); t.init(); } }); /* * @constructor * * @desc * Tree item object for representing the state and user interactions for a * tree widget * * @param node * An element with the role=tree attribute */ var TreeLinks = function (node) { // Check whether node is a DOM element if (typeof node !== 'object') { return; } this.domNode = node; this.treeitems = []; this.firstChars = []; this.firstTreeitem = null; this.lastTreeitem = null; }; TreeLinks.prototype.init = function () { function findTreeitems (node, tree, group) { var elem = node.firstElementChild; var ti = group; while (elem) { if ((elem.tagName.toLowerCase() === 'li' && elem.firstElementChild.tagName.toLowerCase() === 'span') || elem.tagName.toLowerCase() === 'a') { ti = new TreeitemLink(elem, tree, group); ti.init(); tree.treeitems.push(ti); tree.firstChars.push(ti.label.substring(0, 1).toLowerCase()); } if (elem.firstElementChild) { findTreeitems(elem, tree, ti); } elem = elem.nextElementSibling; } } // initialize pop up menus if (!this.domNode.getAttribute('role')) { this.domNode.setAttribute('role', 'tree'); } findTreeitems(this.domNode, this, false); this.updateVisibleTreeitems(); this.firstTreeitem.domNode.tabIndex = 0; }; TreeLinks.prototype.setFocusToItem = function (treeitem) { for (var i = 0; i < this.treeitems.length; i++) { var ti = this.treeitems[i]; if (ti === treeitem) { ti.domNode.tabIndex = 0; ti.domNode.focus(); } else { ti.domNode.tabIndex = -1; } } }; TreeLinks.prototype.setFocusToNextItem = function (currentItem) { var nextItem = false; for (var i = (this.treeitems.length - 1); i >= 0; i--) { var ti = this.treeitems[i]; if (ti === currentItem) { break; } if (ti.isVisible) { nextItem = ti; } } if (nextItem) { this.setFocusToItem(nextItem); } }; TreeLinks.prototype.setFocusToPreviousItem = function (currentItem) { var prevItem = false; for (var i = 0; i < this.treeitems.length; i++) { var ti = this.treeitems[i]; if (ti === currentItem) { break; } if (ti.isVisible) { prevItem = ti; } } if (prevItem) { this.setFocusToItem(prevItem); } }; TreeLinks.prototype.setFocusToParentItem = function (currentItem) { if (currentItem.groupTreeitem) { this.setFocusToItem(currentItem.groupTreeitem); } }; TreeLinks.prototype.setFocusToFirstItem = function () { this.setFocusToItem(this.firstTreeitem); }; TreeLinks.prototype.setFocusToLastItem = function () { this.setFocusToItem(this.lastTreeitem); }; TreeLinks.prototype.expandTreeitem = function (currentItem) { if (currentItem.isExpandable) { currentItem.domNode.setAttribute('aria-expanded', true); this.updateVisibleTreeitems(); } }; TreeLinks.prototype.expandAllSiblingItems = function (currentItem) { for (var i = 0; i < this.treeitems.length; i++) { var ti = this.treeitems[i]; if ((ti.groupTreeitem === currentItem.groupTreeitem) && ti.isExpandable) { this.expandTreeitem(ti); } } }; TreeLinks.prototype.collapseTreeitem = function (currentItem) { var groupTreeitem = false; if (currentItem.isExpanded()) { groupTreeitem = currentItem; } else { groupTreeitem = currentItem.groupTreeitem; } if (groupTreeitem) { groupTreeitem.domNode.setAttribute('aria-expanded', false); this.updateVisibleTreeitems(); this.setFocusToItem(groupTreeitem); } }; TreeLinks.prototype.updateVisibleTreeitems = function () { this.firstTreeitem = this.treeitems[0]; for (var i = 0; i < this.treeitems.length; i++) { var ti = this.treeitems[i]; var parent = ti.domNode.parentNode; ti.isVisible = true; while (parent && (parent !== this.domNode)) { if (parent.getAttribute('aria-expanded') == 'false') { ti.isVisible = false; } parent = parent.parentNode; } if (ti.isVisible) { this.lastTreeitem = ti; } } }; TreeLinks.prototype.setFocusByFirstCharacter = function (currentItem, char) { var start, index, char = char.toLowerCase(); // Get start index for search based on position of currentItem start = this.treeitems.indexOf(currentItem) + 1; if (start === this.treeitems.length) { start = 0; } // Check remaining slots in the menu index = this.getIndexFirstChars(start, char); // If not found in remaining slots, check from beginning if (index === -1) { index = this.getIndexFirstChars(0, char); } // If match was found... if (index > -1) { this.setFocusToItem(this.treeitems[index]); } }; TreeLinks.prototype.getIndexFirstChars = function (startIndex, char) { for (var i = startIndex; i < this.firstChars.length; i++) { if (this.treeitems[i].isVisible) { if (char === this.firstChars[i]) { return i; } } } return -1; }; /* * This content is licensed according to the W3C Software License at * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document * * File: TreeitemLink.js * * Desc: Treeitem widget that implements ARIA Authoring Practices * for a tree being used as a file viewer */ /* * @constructor * * @desc * Treeitem object for representing the state and user interactions for a * treeItem widget * * @param node * An element with the role=tree attribute */ var TreeitemLink = function (node, treeObj, group) { // Check whether node is a DOM element if (typeof node !== 'object') { return; } node.tabIndex = -1; this.tree = treeObj; this.groupTreeitem = group; this.domNode = node; this.label = node.textContent.trim(); this.stopDefaultClick = false; if (node.getAttribute('aria-label')) { this.label = node.getAttribute('aria-label').trim(); } this.isExpandable = false; this.isVisible = false; this.inGroup = false; if (group) { this.inGroup = true; } var elem = node.firstElementChild; while (elem) { if (elem.tagName.toLowerCase() == 'ul') { elem.setAttribute('role', 'group'); this.isExpandable = true; break; } elem = elem.nextElementSibling; } this.keyCode = Object.freeze({ RETURN: 13, SPACE: 32, PAGEUP: 33, PAGEDOWN: 34, END: 35, HOME: 36, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40 }); }; TreeitemLink.prototype.init = function () { this.domNode.tabIndex = -1; if (!this.domNode.getAttribute('role')) { this.domNode.setAttribute('role', 'treeitem'); } this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); this.domNode.addEventListener('click', this.handleClick.bind(this)); this.domNode.addEventListener('focus', this.handleFocus.bind(this)); this.domNode.addEventListener('blur', this.handleBlur.bind(this)); if (this.isExpandable) { this.domNode.firstElementChild.addEventListener('mouseover', this.handleMouseOver.bind(this)); this.domNode.firstElementChild.addEventListener('mouseout', this.handleMouseOut.bind(this)); } else { this.domNode.addEventListener('mouseover', this.handleMouseOver.bind(this)); this.domNode.addEventListener('mouseout', this.handleMouseOut.bind(this)); } }; TreeitemLink.prototype.isExpanded = function () { if (this.isExpandable) { return this.domNode.getAttribute('aria-expanded') === 'true'; } return false; }; /* EVENT HANDLERS */ TreeitemLink.prototype.handleKeydown = function (event) { var tgt = event.currentTarget, flag = false, char = event.key, clickEvent; function isPrintableCharacter (str) { return str.length === 1 && str.match(/\S/); } function printableCharacter (item) { if (char == '*') { item.tree.expandAllSiblingItems(item); flag = true; } else { if (isPrintableCharacter(char)) { item.tree.setFocusByFirstCharacter(item, char); flag = true; } } } this.stopDefaultClick = false; if (event.altKey || event.ctrlKey || event.metaKey) { return; } if (event.shift) { if (event.keyCode == this.keyCode.SPACE || event.keyCode == this.keyCode.RETURN) { event.stopPropagation(); this.stopDefaultClick = true; } else { if (isPrintableCharacter(char)) { printableCharacter(this); } } } else { switch (event.keyCode) { case this.keyCode.SPACE: case this.keyCode.RETURN: if (this.isExpandable) { if (this.isExpanded()) { this.tree.collapseTreeitem(this); } else { this.tree.expandTreeitem(this); } flag = true; } else { event.stopPropagation(); this.stopDefaultClick = true; } break; case this.keyCode.UP: this.tree.setFocusToPreviousItem(this); flag = true; break; case this.keyCode.DOWN: this.tree.setFocusToNextItem(this); flag = true; break; case this.keyCode.RIGHT: if (this.isExpandable) { if (this.isExpanded()) { this.tree.setFocusToNextItem(this); } else { this.tree.expandTreeitem(this); } } flag = true; break; case this.keyCode.LEFT: if (this.isExpandable && this.isExpanded()) { this.tree.collapseTreeitem(this); flag = true; } else { if (this.inGroup) { this.tree.setFocusToParentItem(this); flag = true; } } break; case this.keyCode.HOME: this.tree.setFocusToFirstItem(); flag = true; break; case this.keyCode.END: this.tree.setFocusToLastItem(); flag = true; break; default: if (isPrintableCharacter(char)) { printableCharacter(this); } break; } } if (flag) { event.stopPropagation(); event.preventDefault(); } }; TreeitemLink.prototype.handleClick = function (event) { // only process click events that directly happened on this treeitem if (event.target !== this.domNode && event.target !== this.domNode.firstElementChild) { return; } if (this.isExpandable) { if (this.isExpanded()) { this.tree.collapseTreeitem(this); } else { this.tree.expandTreeitem(this); } event.stopPropagation(); } }; TreeitemLink.prototype.handleFocus = function (event) { var node = this.domNode; if (this.isExpandable) { node = node.firstElementChild; } node.classList.add('focus'); }; TreeitemLink.prototype.handleBlur = function (event) { var node = this.domNode; if (this.isExpandable) { node = node.firstElementChild; } node.classList.remove('focus'); }; TreeitemLink.prototype.handleMouseOver = function (event) { event.currentTarget.classList.add('hover'); }; TreeitemLink.prototype.handleMouseOut = function (event) { event.currentTarget.classList.remove('hover'); }; /* ==== START MOBILE ONLY FUNCTIONS ==== */ // Define our media query var mediaQueryList = window.matchMedia('(max-width: 991.98px)'); // Define our function to lunch mobile or desktop JavaScript function screenTest(e) { console.log("..."); if (e.matches) { mobile_only_functions(); console.log('mobile functions') } else { desktop_only_functions() //console.log('desktop functions') } } // Detect Safari (addeventlistener not working on old versions) var isSafari = navigator.vendor && navigator.vendor.indexOf('Apple') > -1 && navigator.userAgent && navigator.userAgent.indexOf('CriOS') == -1 && navigator.userAgent.indexOf('FxiOS') == -1; if (isSafari) { mediaQueryList.addListener(screenTest); // If safari use addListener document.body.className += " safari"; }else{ mediaQueryList.addEventListener('change', screenTest); // Else use addEventListener } setTimeout(function(){ screenTest(mediaQueryList); }, 1000); function desktop_only_functions() { const sidebar = document.querySelector('.sidebar'); const overlay = document.querySelector('.sidebar-overlay'); if (sidebar) { sidebar.style.display = "block"; sidebar.classList.remove('show-sidebar'); } if (overlay) { overlay.classList.remove('show-overlay'); } } function mobile_only_functions() { /* ==== START SLIDING SIDEBAR ==== */ const toggleBtn = document.querySelector('.pws__btn-menu'); const openBtn = document.querySelector('.pws__btn-menu--open'); const closeBtn = document.querySelector('.pws__btn-menu--close'); const sidebar = document.querySelector('.sidebar'); const overlay = document.querySelector('.sidebar-overlay'); const pws__navigation = document.querySelector('.pws__navigation'); console.log("1"); if (!document.querySelector('.pws__navigation')) { return; } console.log("2"); const firstItem = document.querySelector('.pws__navigation').firstChild; sidebar.classList.add('sidebar-ready'); /* ADD custom scrollbar */ pws__navigation.setAttribute('ss-container', true); /* set default */ sidebar.style.display = "none"; toggleBtn.classList.remove('opened'); /*closeBtn.setAttribute('tabindex', '-1'); openBtn.setAttribute('tabindex', '0');*/ openBtn.addEventListener('click', function(){ //using toggle sidebar.style.display = "block"; setTimeout(function(){ sidebar.classList.add('show-sidebar'); overlay.classList.add('show-overlay'); toggleBtn.classList.add('opened'); closeBtn.setAttribute('tabindex', '0'); openBtn.setAttribute('tabindex', '-1'); firstItem.focus(); }, 300); }); closeBtn.addEventListener('click', function(){ sidebar.classList.remove('show-sidebar'); overlay.classList.remove('show-overlay'); toggleBtn.classList.remove('opened'); closeBtn.setAttribute('tabindex', '-1'); openBtn.setAttribute('tabindex', '0'); openBtn.focus(); setTimeout(function(){ sidebar.style.display = "none"; }, 300); }); // When the user clicks anywhere outside of the sliding sidebar, close it window.onclick = function(event) { if (-1 !== sidebar.className.indexOf( 'show-sidebar' ) && event.target !== toggleBtn && event.target !== openBtn && !sidebar.contains(event.target)) { sidebar.classList.remove('show-sidebar'); overlay.classList.remove('show-overlay'); setTimeout(function(){ sidebar.style.display = "none"; }, 300); toggleBtn.classList.remove('opened'); closeBtn.setAttribute('tabindex', '-1'); openBtn.setAttribute('tabindex', '0'); } } //Force focus close button when leaving sidebar.addEventListener('keydown', function(event) { if (event.keyCode == 9) { event.preventDefault(); closeBtn.focus(); } }); /* //log current focus (debug only) document.addEventListener('keydown', function(event) { const currentFocus = document.activeElement; console.log(currentFocus); });*/ /* FIX SIDEBAR MARGIN ON SCROLL */ function fixSidebarPosition() { var currScrollPos = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; if (currScrollPos > 62) { sidebar.style.paddingTop = '0'; }else{ var addPadding = 62 - currScrollPos; sidebar.style.paddingTop = addPadding + 'px'; } } fixSidebarPosition(); window.addEventListener('scroll', function() { fixSidebarPosition(); }); /* ==== END SLIDING SIDEBAR ==== */ } /* if (currentBrowser == 'safari'){ //document.body.className += " safari"; console.log('safari') }*/ /* Load sidebar */ const sidebar = document.querySelector('.sidebar'); if (sidebar) { setTimeout(function(){ sidebar.classList.add('initialized'); console.log("sidebar initialized"); }, 1500); }