1var exalt = exalt || {}; 2 3// polyfill closest 4// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill 5if ( ! Element.prototype.closest ) { 6 Element.prototype.closest = function( s ) { 7 var el = this; 8 9 do { 10 if ( el.matches( s ) ) { 11 return el; 12 } 13 14 el = el.parentElement || el.parentNode; 15 } while ( el !== null && el.nodeType === 1 ); 16 17 return null; 18 }; 19} 20 21// polyfill forEach 22// https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill 23if ( window.NodeList && ! NodeList.prototype.forEach ) { 24 NodeList.prototype.forEach = function( callback, thisArg ) { 25 var i; 26 var len = this.length; 27 28 thisArg = thisArg || window; 29 30 for ( i = 0; i < len; i++ ) { 31 callback.call( thisArg, this[ i ], i, this ); 32 } 33 }; 34} 35 36/** 37 * Menu keyboard navigation 38 */ 39exalt.menuAccessKeyboard = { 40 41 init: function() { 42 this.focusMenuWithChildren(); 43 }, 44 45 // The focusMenuWithChildren() function implements Keyboard Navigation in the Primary Menu 46 // by adding the '.focus' class to all 'li.menu-item-has-children' when the focus is on the 'a' element. 47 focusMenuWithChildren: function() { 48 // Get all the link elements within the primary menu. 49 var links, i, len, 50 menus = document.querySelectorAll( '.exalt-menu' ); 51 52 if ( ! menus.length ) { 53 return false; 54 } 55 56 menus.forEach( function( menu ) { 57 58 links = menu.getElementsByTagName( 'a' ); 59 60 // Each time a menu link is focused or blurred, toggle focus. 61 for ( i = 0, len = links.length; i < len; i++ ) { 62 links[i].addEventListener( 'focus', toggleFocus, true ); 63 links[i].addEventListener( 'blur', toggleFocus, true ); 64 } 65 66 } ); 67 68 // Sets or removes the .focus class on an element. 69 function toggleFocus() { 70 var self = this; 71 72 // Move up through the ancestors of the current link until we hit .main-navigation. 73 while ( -1 === self.className.indexOf( 'exalt-menu' ) ) { 74 // On li elements toggle the class .focus. 75 if ( 'li' === self.tagName.toLowerCase() ) { 76 if ( -1 !== self.className.indexOf( 'focus' ) ) { 77 self.className = self.className.replace( ' focus', '' ); 78 } else { 79 self.className += ' focus'; 80 } 81 } 82 self = self.parentElement; 83 } 84 } 85 86 } 87}; 88 89/** 90 * Search Toggle. 91 */ 92exalt.searchToggle = { 93 init: function() { 94 this.addListener(); 95 }, 96 97 toggle: function( element ) { 98 99 var searchBoxContainer = element.parentElement, 100 header = document.getElementById( 'masthead' ), 101 searchBoxContainer = searchBoxContainer.querySelector('#exalt-search-box'), 102 inputField = searchBoxContainer.querySelector('input.search-field'); 103 104 // Toggle header search class. 105 header.classList.toggle( 'hide-header-search' ); 106 107 searchBoxContainer.classList.toggle( 'active' ); 108 109 // Focus the input field if the search box is displayed. 110 if( searchBoxContainer.classList.contains( 'active' ) ) { 111 inputField.focus(); 112 } 113 114 }, 115 116 addListener: function() { 117 118 var searchButton = document.getElementById( 'exalt-search-toggle' ); 119 120 if ( ! searchButton ) { 121 return false; 122 } 123 124 var self = this; 125 126 searchButton.addEventListener( 'click', function() { 127 self.toggle( this ); 128 } ); 129 130 } 131}; 132 133/** 134 * Slideout Sidebar 135 */ 136exalt.slideOutSidebar = { 137 138 init: function() { 139 this.addListeners(); 140 }, 141 142 createOverlay: function( maskId ) { 143 var mask = document.createElement( 'div' ); 144 mask.setAttribute( 'class', 'exalt-overlay-mask' ); 145 mask.setAttribute( 'id', maskId ); 146 document.body.appendChild( mask ); 147 }, 148 149 removeOverlay: function( maskId ) { 150 var mask = document.getElementById( maskId ); 151 mask.parentNode.removeChild( mask ); 152 }, 153 154 addListeners: function() { 155 var _doc = document, 156 body = _doc.body, 157 slideOutToggles = _doc.querySelectorAll( '.exalt-slideout-toggle' ); 158 159 if ( ! slideOutToggles.length ) { 160 return false; 161 } 162 163 var slideOutSidebar = _doc.getElementById( 'exalt-slideout-sidebar' ); 164 165 if ( ! slideOutSidebar ) { 166 return false; 167 } 168 169 var closeBtn = slideOutSidebar.querySelector( '.exalt-slideout-toggle' ), 170 header = _doc.getElementById( 'masthead' ), 171 openBtn = header.querySelector( '.exalt-slideout-toggle' ), 172 self = this; 173 174 // Function to handle keydown events inside the mobile menu sidebar. 175 function handleSidebarKeydown(e) { 176 var focusableElements = slideOutSidebar.querySelectorAll('a, button, input, select, textarea, [tabindex]:not
176([tabindex="-1"])'); 177 var firstFocusable = focusableElements[0]; 178 var lastFocusable = focusableElements[focusableElements.length - 1]; 179 180 if (e.key === 'Tab') { 181 if (e.shiftKey && document.activeElement === firstFocusable) { 182 // If SHIFT+TAB and first focusable element is focused, focus last element 183 e.preventDefault(); 184 lastFocusable.focus(); 185 } else if (!e.shiftKey && document.activeElement === lastFocusable) { 186 // If TAB and last focusable element is focused, focus first element 187 e.preventDefault(); 188 firstFocusable.focus(); 189 } 190 } 191 } 192 193 // Add keydown event listener to the mobile menu sidebar 194 slideOutSidebar.addEventListener('keydown', handleSidebarKeydown); 195 196 197 slideOutToggles.forEach( function( button ) { 198 button.addEventListener( 'click', function() { 199 if ( body.classList.contains( 'slideout-opened' ) ) { 200 body.classList.remove( 'slideout-opened' ); 201 openBtn.focus(); 202 self.removeOverlay( 'exalt-slideout-mask' ); 203 } else { 204 body.classList.add( 'slideout-opened' ); 205 closeBtn.focus(); 206 self.createOverlay( 'exalt-slideout-mask' ); 207 } 208 } ); 209 } ); 210 211 document.addEventListener( 'click', function( e ) { 212 if ( e.target && e.target.className === 'exalt-overlay-mask' ) { 213 214 var maskId = e.target.id; 215 216 body.classList.remove( 'slideout-opened' ); 217 self.removeOverlay( maskId ); 218 } 219 } ); 220 } 221}; 222 223/** 224 * Mobile Menu 225 */ 226exalt.mobileMenu = { 227 228 init: function() { 229 this.addListeners(); 230 }, 231 232 createOverlay: function( maskId ) { 233 var mask = document.createElement( 'div' ); 234 mask.setAttribute( 'class', 'exalt-overlay-mask' ); 235 mask.setAttribute( 'id', maskId ); 236 document.body.appendChild( mask ); 237 }, 238 239 removeOverlay: function( maskId ) { 240 var mask = document.getElementById( maskId ); 241 mask.parentNode.removeChild( mask ); 242 }, 243 244 addListeners: function() { 245 var _doc = document, 246 menuToggleButtons = _doc.querySelectorAll( '.exalt-mobile-menu-toggle' ), 247 body = _doc.body, 248 mobileMenuSidebar = _doc.getElementById( 'exalt-mobile-sidebar' ); 249 250 if ( ! mobileMenuSidebar ) { 251 return false; 252 } 253 254 var mobileMenuSidebarCloseBtn = mobileMenuSidebar.querySelector( '.exalt-mobile-menu-toggle' ), 255 header = _doc.getElementById( 'masthead' ), 256 mobileMenuSidebarOpenBtn = header.querySelector( '.exalt-mobile-menu-toggle' ), 257 self = this; 258 259 260 menuToggleButtons.forEach( function( button ) { 261 button.addEventListener( 'click', function() { 262 if ( body.classList.contains( 'mobile-menu-opened' ) ) { 263 body.classList.remove( 'mobile-menu-opened' ); 264 mobileMenuSidebarOpenBtn.focus(); 265 self.removeOverlay( 'exalt-mobile-menu-mask' ); 266 } else { 267 body.classList.add( 'mobile-menu-opened' ); 268 mobileMenuSidebarCloseBtn.focus(); 269 self.createOverlay( 'exalt-mobile-menu-mask' ); 270 } 271 } ); 272 } ); 273 274 // Function to handle keydown events inside the mobile menu sidebar. 275 function handleSidebarKeydown(e) { 276 var focusableElements = mobileMenuSidebar.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])'); 277 var firstFocusable = focusableElements[0]; 278 var lastFocusable = focusableElements[focusableElements.length - 1]; 279 280 if (e.key === 'Tab') { 281 if (e.shiftKey && document.activeElement === firstFocusable) { 282 // If SHIFT+TAB and first focusable element is focused, focus last element 283 e.preventDefault(); 284 lastFocusable.focus(); 285 } else if (!e.shiftKey && document.activeElement === lastFocusable) { 286 // If TAB and last focusable element is focused, focus first element 287 e.preventDefault(); 288 firstFocusable.focus(); 289 } 290 } 291 } 292 293 // Add keydown event listener to the mobile menu sidebar 294 mobileMenuSidebar.addEventListener('keydown', handleSidebarKeydown); 295 296 document.addEventListener( 'click', function( e ) { 297 if ( e.target && e.target.className === 'exalt-overlay-mask' ) { 298 299 var maskId = e.target.id; 300 301 body.classList.remove( 'mobile-menu-opened' ); 302 self.removeOverlay( maskId ); 303 } 304 } ); 305 } 306}; 307 308/** 309 * Mobile Menu Toggles. 310 */ 311exalt.mobileMenuToggle = { 312 313 init: function() { 314 // Do the toggle. 315 this.toggle(); 316 }, 317 318 performToggle: function( element ) { 319 320 var target = element.dataset.toggleTarget, 321 parentNavContainer = element.closest( '.exalt-mobile-menu' ); 322 activeClass = 'toggled-on'; 323 324 // Get the target element. 325 targetElement = parentNavContainer.querySelector(target); 326 327 element.classList.toggle(activeClass); 328 329 exaltToggleAttribute(element, 'aria-expanded', 'true', 'false'); 330 331 targetElement.classList.toggle(activeClass); 332 333 }, 334 335 toggle: function() { 336 var self = this; 337 338 document.querySelectorAll('*[data-toggle-target]').forEach( function( element ) { 339 element.addEventListener( 'click', function( event ) { 340 event.preventDefault(); 341 self.performToggle( element ); 342 } ); 343 } ); 344 } 345}; 346 347/** 348 * Toggle an attribute 349 */ 350function exaltToggleAttribute( element, attribute, trueVal, falseVal ) { 351 if ( trueVal === undefined ) { 352 trueVal = true; 353 } 354 if ( falseVal === undefined ) { 355 falseVal = false; 356 } 357 if ( element.getAttribute( attribute ) !== trueVal ) { 358 element.setAttribute( attribute, trueVal ); 359 } else { 360 element.setAttribute( attribute, falseVal ); 361 } 362} 363 364/** 365 * Is the DOM ready? 366 * 367 * This implementation is coming from https://gomakethings.com/a-native-javascript-equivalent-of-jquerys-ready-method/ 368 * 369 * @param {Function} fn Callback function to run. 370 */ 371function exaltDomReady( fn ) { 372 if ( typeof fn !== 'function' ) { 373 return; 374 } 375 376 if ( document.readyState === 'interactive' || document.readyState === 'complete' ) { 377 return fn(); 378 } 379 380 document.addEventListener( 'DOMContentLoaded', fn, false ); 381} 382 383exaltDomReady( function() { 384 exalt.menuAccessKeyboard.init();
385 exalt.searchToggle.init(); 386 exalt.mobileMenu.init(); 387 exalt.mobileMenuToggle.init(); 388 exalt.slideOutSidebar.init(); 389} );
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.