1export function MenubarAction(aria) { 2 /* 3 * This content is licensed according to the W3C Software License at 4 * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document 5 * 6 * File: MenubarAction.js 7 * 8 * Desc: Menubar widget that implements ARIA Authoring Practices 9 */ 10 11 /* 12 * @constructor MenubarAction 13 * 14 * @desc 15 * Wrapper object for a menubar 16 * 17 * @param domNode 18 * The DOM element node that serves as the menubar container. 19 * Each child element of domNode that represents a menubaritem 20 * must be an A element. 21 */ 22 (function () { 23 aria.MenubarAction = function (domNode) { 24 var msgPrefix = 'Menubar constructor argument domNode '; 25 26 // Check whether domNode is a DOM element 27 if (!domNode instanceof Element) { 28 throw new TypeError(msgPrefix + 'is not a DOM Element.'); 29 } 30 31 // Check whether domNode has descendant elements 32 if (domNode.childElementCount === 0) { 33 throw new Error(msgPrefix + 'has no element children.'); 34 } 35 36 // Check whether domNode's descendant elements contain A elements 37 var e = domNode.firstElementChild; 38 while (e) { 39 e = e.nextElementSibling; 40 } 41 42 this.domNode = domNode; 43 44 this.menubarItems = []; // see Menubar init method 45 this.firstChars = []; // see Menubar init method 46 47 this.firstItem = null; // see Menubar init method 48 this.lastItem = null; // see Menubar init method 49 }; 50 51 /* 52 * @method MenubarAction.prototype.init 53 * 54 * @desc 55 * Adds ARIA role to the menubar node 56 * Traverse menubar children for A elements to configure each A element as an ARIA menuitem 57 * and populate menuitems array. Initialize firstItem and lastItem properties. 58 */ 59 aria.MenubarAction.prototype.init = function (actionManager) { 60 var menubarItem, menuElement, textContent, numItems; 61 62 this.actionManager = actionManager; 63 64 this.domNode.setAttribute('role', 'menubar'); 65 66 this.domNode.addEventListener('focusin', this.handleFocusin.bind(this)); 67 this.domNode.addEventListener('focusout', this.handleFocusout.bind(this)); 68 69 // Traverse the element children of the menubar domNode: configure each with 70 // menuitem role behavior and store reference in menuitems array. 71 var e = this.domNode.firstElementChild; 72 73 while (e) { 74 menuElement = e.firstElementChild; 75 76 if (menuElement && menuElement.tagName === 'A') { 77 menubarItem = new aria.MenubarItemAction(menuElement, this); 78 menubarItem.init(); 79 this.menubarItems.push(menubarItem); 80 textContent = menuElement.textContent.trim(); 81 this.firstChars.push(textContent.substring(0, 1).toLowerCase()); 82 } 83 84 e = e.nextElementSibling; 85 } 86 87 // Use populated menuitems array to initialize firstItem and lastItem. 88 numItems = this.menubarItems.length; 89 if (numItems > 0) { 90 this.firstItem = this.menubarItems[0]; 91 this.lastItem = this.menubarItems[numItems - 1]; 92 } 93 this.firstItem.domNode.tabIndex = 0; 94 }; 95 96 /* FOCUS MANAGEMENT METHODS */ 97 98 aria.MenubarAction.prototype.setFocusToItem = function (newItem, hover) { 99 var isOpen = false; 100 var hasFocus = this.domNode.contains(document.activeElement); 101 for (var i = 0; i < this.menubarItems.length; i++) { 102 var mbi = this.menubarItems[i]; 103 isOpen = isOpen || (mbi.popupMenu && mbi.popupMenu.isOpen()); 104 if (!hover || hasFocus) { 105 mbi.domNode.tabIndex = -1; 106 } 107 if (mbi.popupMenu) { 108 mbi.popupMenu.close(); 109 } 110 } 111 if (!hover || hasFocus) { 112 newItem.domNode.focus(); 113 newItem.domNode.tabIndex = 0; 114 } 115 if (isOpen && newItem.popupMenu) { 116 newItem.popupMenu.open(); 117 } 118 }; 119 120 aria.MenubarAction.prototype.setFocusToFirstItem = function () { 121 this.setFocusToItem(this.firstItem); 122 }; 123 aria.MenubarAction.prototype.setFocusToLastItem = function () { 124 this.setFocusToItem(this.lastItem); 125 }; 126 127 aria.MenubarAction.prototype.setFocusToPreviousItem = function (currentItem) { 128 var newItem, index; 129 130 if (currentItem === this.firstItem) { 131 newItem = this.lastItem; 132 } 133 else { 134 index = this.menubarItems.indexOf(currentItem); 135 newItem = this.menubarItems[ index - 1 ]; 136 } 137 138 this.setFocusToItem(newItem); 139 }; 140 141 aria.MenubarAction.prototype.setFocusToNextItem = function (currentItem) { 142 var newItem, index; 143 144 if (currentItem === this.lastItem) { 145 newItem = this.firstItem; 146 } 147 else { 148 index = this.menubarItems.indexOf(currentItem); 149 newItem = this.menubarItems[ index + 1 ]; 150 } 151 this.setFocusToItem(newItem); 152 }; 153 154 aria.MenubarAction.prototype.setFocusByFirstCharacter = function (currentItem, char) { 155 var start, index, char = char.toLowerCase(); 156 157 // Get start index for search based on position of currentItem 158 start = this.menubarItems.indexOf(currentItem) + 1; 159 if (start === this.menubarItems.length) { 160 start = 0; 161 } 162 163 // Check remaining slots in the menu 164 index = this.getIndexFirstChars(start, char); 165 166 // If not found in remaining slots, check from beginning 167 if (index === -1) { 168 index = this.getIndexFirstChars(0, char); 169 } 170 171 // If match was found... 172 if (index > -1) { 173 this.menubarItems[index].domNode.focus(); 174 this.menubarItems[index].domNode.tabIndex = 0; 175 currentItem.tabIndex = -1; 176 } 177 }; 178 179 aria.MenubarAction.prototype.getIndexFirstChars = function (startIndex, char) { 180 for (var i = startIndex; i < this.firstChars.length; i++) { 181 if (char === this.firstChars[i]) { 182 return i; 183 } 184 } 185 return -1; 186 }; 187 188 aria.MenubarAction.prototype.handleFocusin = function (event) { 189 // if the menubar or any of its menus has focus, add styling hook for hover 190 this.domNode.classList.add('focus'); 191 }; 192 193 aria.MenubarAction.prototype.handleFocusout = function (event) { 194 // if the next element to get focus is not in the menubar or its menus, then close menu 195 if (!this.domNode.contains(event.relatedTarget)) { 196 for (var i = 0; i < this.menubarItems.length; i++) { 197 var mbi = this.menubarItems[i]; 198 if (mbi.popupMenu && mbi.popupMenu.isOpen()) { 199 mbi.popupMenu.close(); 200 } 201 } 202 } 203 // remove styling hook for hover on menubar item 204 this.domNode.classList.remove('focus'); 205 }; 206 207 window.activateItemMenuBarAction = function (menuId, itemsIds) { 208 const menu = document.getElementById(menuId); 209 if (menu) { 210 const menuInstance = new aria.MenubarAction(menu); 211 menuInstance.activateElements(menuId, itemsIds); 212 return [menu, itemsIds]; 213 } else { 214 console.log('There is no menu with this id in the document.'); 215 return null; 216 } 217 }; 218 219 aria.MenubarAction.prototype.activateElements = function (menuId, activateElements) { 220 const getAllElements = this.domNode.querySelectorAll(`#${menuId} ul > li `); 221 const filterElements = [...getAllElements].filter((element) =>
221 element.id !== '' && element.getAttribute('role') !== 'separator'); 222 [...filterElements].forEach((element) => { 223 if(isActive(element)) { 224 element.setAttribute('aria-checked', 'true'); 225 element.innerHTML = `<strong class="font-bold">${element.textContent}</strong>`; 226 } else { 227 const getElementParent = element.parentElement.getAttribute('role') === 'group' 228 ? element.parentElement.parentElement.parentElement.parentElement.querySelector('a') 229 : element.parentElement.parentElement.querySelector('a'); 230 231 getElementParent.classList.remove('c-menubar__button--has-selection'); 232 getElementParent.firstChild.innerHTML = `${getElementParent.textContent}`; 233 element.setAttribute('aria-checked', 'false'); 234 element.innerHTML = `${element.textContent}`; 235 } 236 }); 237 [...filterElements].forEach((element) => { 238 if(element.getAttribute("aria-checked") === "true"){ 239 const getElementParent = element.parentElement.getAttribute('role') === 'group' 240 ? element.parentElement.parentElement.parentElement.parentElement.querySelector('a') 241 : element.parentElement.parentElement.querySelector('a'); 242 getElementParent.classList.add('c-menubar__button--has-selection'); 243 getElementParent.firstChild.innerHTML = `<strong class="font-bold">${getElementParent.textContent}</strong>`; 244 } 245 }); 246 247 function isActive(element){ 248 const { id } = element; 249 return typeof activateElements === "string" 250 ? id === activateElements 251 : activateElements.includes(id); 252 } 253 }; 254 }()); 255} 256
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.