1/** 2 * Ensuring collapsible and accessible components on multiple 3 * screen sizes for the responsive lms header. 4*/ 5 6function createMobileMenu() { 7 /** 8 * Dynamically create a mobile menu from all specified mobile links 9 * on the page. 10 */ 11 'use strict'; 12 $('.mobile-nav-item').each(function() { 13 var mobileNavItem = $(this).clone().addClass('mobile-nav-link'); 14 mobileNavItem.removeAttr('role'); 15 mobileNavItem.find('a').attr('role', 'menuitem'); 16 // xss-lint: disable=javascript-jquery-append 17 $('.mobile-menu').append(mobileNavItem); 18 }); 19} 20 21$(document).ready(function() { 22 'use strict'; 23 var $hamburgerMenu; 24 var $mobileMenu; 25 // Toggling visibility for the user dropdown 26 $('.global-header .toggle-user-dropdown, .global-header .toggle-user-dropdown span').click(function(e) { 27 var $dropdownMenu = $('.global-header .nav-item .dropdown-user-menu'); 28 var $userDropdown = $('.global-header .toggle-user-dropdown'); 29 if ($dropdownMenu.is(':visible')) { 30 $dropdownMenu.addClass('hidden'); 31 $userDropdown.attr('aria-expanded', 'false'); 32 } else { 33 $dropdownMenu.removeClass('hidden'); 34 $dropdownMenu.find('.dropdown-item')[0].focus(); 35 $userDropdown.attr('aria-expanded', 'true'); 36 } 37 $('.global-header .toggle-user-dropdown').toggleClass('open'); 38 e.stopPropagation(); 39 }); 40 41 // Hide user dropdown on click away 42 if ($('.global-header .nav-item .dropdown-user-menu').length) { 43 $(window).click(function(e) { 44 var $dropdownMenu = $('.global-header .nav-item .dropdown-user-menu'); 45 var $userDropdown = $('.global-header .toggle-user-dropdown'); 46 if ($userDropdown.is(':visible') && !$(e.target).is('.dropdown-item, .toggle-user-dropdown')) { 47 $dropdownMenu.addClass('hidden'); 48 $userDropdown.attr('aria-expanded', 'false'); 49 } 50 }); 51 } 52 53 // Toggling menu visibility with the hamburger menu 54 $('.global-header .hamburger-menu').click(function() { 55 $hamburgerMenu = $('.global-header .hamburger-menu'); 56 $mobileMenu = $('.mobile-menu'); 57 if ($mobileMenu.is(':visible')) { 58 $mobileMenu.addClass('hidden'); 59 $hamburgerMenu.attr('aria-expanded', 'false'); 60 } else { 61 $mobileMenu.removeClass('hidden'); 62 $hamburgerMenu.attr('aria-expanded', 'true'); 63 } 64 $hamburgerMenu.toggleClass('open'); 65 }); 66 67 // Hide hamburger menu if no nav items (sign in and register pages) 68 if ($('.mobile-nav-item').size() === 0) { 69 $('.global-header .hamburger-menu').addClass('hidden'); 70 } 71 72 createMobileMenu(); 73}); 74 75 76// Accessibility keyboard controls for user dropdown and mobile menu 77$('.mobile-menu, .global-header').on('keydown', function(e) { 78 'use strict'; 79 var isNext, 80 nextLink, 81 loopFirst, 82 loopLast, 83 $curTarget = $(e.target), 84 isLastItem = $curTarget.parent().is(':last-child'), 85 isToggle = $curTarget.hasClass('toggle-user-dropdown'), 86 isHamburgerMenu = $curTarget.hasClass('hamburger-menu'), 87 isMobileOption = $curTarget.parent().hasClass('mobile-nav-link'), 88 isDropdownOption = !isMobileOption && $curTarget.parent().hasClass('dropdown-item'), 89 $userDropdown = $('.global-header .user-dropdown'), 90 $hamburgerMenu = $('.global-header .hamburger-menu'), 91 $toggleUserDropdown = $('.global-header .toggle-user-dropdown'); 92 93 // Open or close relevant menu on enter or space click and focus on first element. 94 if ((e.key === 'Enter' || e.key === 'Space') && (isToggle || isHamburgerMenu)) { 95 e.preventDefault(); 96 e.stopPropagation(); 97 98 $curTarget.click(); 99 if (isHamburgerMenu) { 100 if ($('.mobile-menu').is(':visible')) { 101 $hamburgerMenu.attr('aria-expanded', true); 102 $('.mobile-menu .mobile-nav-link a').first().focus(); 103 } else { 104 $hamburgerMenu.attr('aria-expanded', false); 105 } 106 } else if (isToggle) { 107 if ($('.global-header .nav-item .dropdown-user-menu').is(':visible')) { 108 $userDropdown.attr('aria-expanded', 'true'); 109 $('.global-header .dropdown-item a:first').focus(); 110 } else { 111 $userDropdown.attr('aria-expanded', false); 112 } 113 } 114 } 115 116 // Enable arrow functionality within the menu. 117 if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && (isDropdownOption || isMobileOption || 118 (isHamburgerMenu && $hamburgerMenu.hasClass('open')) || isToggle && $toggleUserDropdown.hasClass('open'))) { 119 isNext = e.key === 'ArrowDown'; 120 if (isNext && !isHamburgerMenu && !isToggle && isLastItem) { 121 // Loop to the start from the final element 122 nextLink = isDropdownOption ? $toggleUserDropdown : $hamburgerMenu; 123 } else if (!isNext && (isHamburgerMenu || isToggle)) { 124 // Loop to the end when up arrow pressed from menu icon 125 nextLink = isHamburgerMenu ? $('.mobile-menu .mobile-nav-link a').last() 126 : $('.global-header .dropdown-user-menu .dropdown-nav-item').last().find('a'); 127 } else if (isNext && (isHamburgerMenu || isToggle)) { 128 // Loop to the first element from the menu icon 129 nextLink = isHamburgerMenu ? $('.mobile-menu .mobile-nav-link a').first() 130 : $('.global-header .dropdown-user-menu .dropdown-nav-item').first().find('a'); 131 } else { 132 // Loop up to the menu icon if first element in menu 133 if (!isNext && $curTarget.parent().is(':first-child') && !isHamburgerMenu && !isToggle) { 134 nextLink = isDropdownOption ? $toggleUserDropdown : $hamburgerMenu; 135 } else { 136 nextLink = isNext ? 137 $curTarget.parent().next().find('a') : // eslint-disable-line newline-per-chained-call 138 $curTarget.parent().prev().find('a'); // eslint-disable-line newline-per-chained-call 139 } 140 } 141 nextLink.focus(); 142 143 // Don't let the screen scroll on navigation 144 e.preventDefault(); 145 e.stopPropagation(); 146 } 147 148 // Escape clears out of the menu 149 if (e.key === 'Escape' && (isDropdownOption || isHamburgerMenu || isMobileOption || isToggle)) { 150 if (isDropdownOption || isToggle) { 151 $('.global-header .nav-item .dropdown-user-menu').addClass('hidden'); 152 $toggleUserDropdown.focus() 153 .attr('aria-expanded', 'false'); 154 $('.global-header .toggle-user-dropdown').removeClass('open'); 155 } else { 156 $('.mobile-menu').addClass('hidden');
157 $hamburgerMenu.focus() 158 .attr('aria-expanded', 'false') 159 .removeClass('open'); 160 } 161 } 162 163 // Loop when tabbing and using arrows 164 if ((e.key === 'Tab') && ((isDropdownOption && isLastItem) || (isMobileOption && isLastItem) || (isHamburgerMenu 165 && $hamburgerMenu.hasClass('open')) || (isToggle && $toggleUserDropdown.hasClass('open')))) { 166 nextLink = null; 167 loopFirst = isLastItem && !e.shiftKey && !isHamburgerMenu && !isToggle; 168 loopLast = (isHamburgerMenu || isToggle) && e.shiftKey; 169 if (!(loopFirst || loopLast)) { 170 return; 171 } 172 e.preventDefault(); 173 if (isDropdownOption || isToggle) { 174 nextLink = loopFirst ? $toggleUserDropdown : 175 $('.global-header .dropdown-user-menu .dropdown-nav-item a').last(); 176 } else { 177 nextLink = loopFirst ? $hamburgerMenu : $('.mobile-menu .mobile-nav-link a').last(); 178 } 179 nextLink.focus(); 180 } 181});
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.