navigation.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /**
  2. * File navigation.js.
  3. *
  4. * Handles toggling the navigation menu for small screens and enables TAB key
  5. * navigation support for dropdown menus.
  6. */
  7. /* global altoFocusScreenReaderText */
  8. ( function( $ ) {
  9. var body,
  10. masthead = $( '#masthead' ),
  11. menuToggle = masthead.find( '.menu-toggle' ),
  12. siteMenu = masthead.find( '.top-navigation' ),
  13. siteNavigation = masthead.find( '.top-navigation > div' ),
  14. siteBrandingHeight = masthead.find( '.site-branding-wrap' ).outerHeight(),
  15. topNavigation = $( '.top-navigation' );
  16. /**
  17. * Initialize the main navigation
  18. */
  19. function initMainNavigation( container ) {
  20. // Add parent class to sub-menu parent items
  21. container.find( '.sub-menu, .children' ).parents( 'li' ).addClass( 'menu-item-has-children' );
  22. // Add dropdown toggle button
  23. var dropdownToggle = $( '<button />', {
  24. 'class': 'dropdown-toggle',
  25. 'aria-expanded': false
  26. } ).append( $( '<span />', {
  27. 'class': 'screen-reader-text',
  28. text: altoFocusScreenReaderText.expand
  29. })).append( '<span class="meta-nav" aria-hidden="true"><svg class="dropdown-icon dropdown-icon-open" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><polyline class="line" points="2.5,3.8 5,6.2 7.5,3.8 "/></svg><svg class="dropdown-icon dropdown-icon-close" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><line class="line" x1="2.5" y1="7.5" x2="7.5" y2="2.5"/><line class="line" x1="2.5" y1="2.5" x2="7.5" y2="7.5"/></svg></span>' );
  30. container.find( '.menu-item-has-children > a' ).after( dropdownToggle );
  31. // Change menu items with submenus to aria-haspopup="true".
  32. container.find( '.menu-item-has-children' ).attr( 'aria-haspopup', 'true' );
  33. // Drop down toggle setup
  34. container.find( '.dropdown-toggle' ).click( function( e ) {
  35. var _this = $( this ),
  36. otherSubMenus = _this.parents( '.menu-item-has-children' ).siblings( '.menu-item-has-children' ),
  37. screenReaderSpan = _this.find( '.screen-reader-text' );
  38. // Disable default behavior
  39. e.preventDefault();
  40. // Stop click outside area function
  41. e.stopPropagation();
  42. // Reveal sub-menus
  43. _this.not( '.menu-toggle' ).toggleClass( 'toggled-on' );
  44. _this.not( '.menu-toggle' ).parent().toggleClass( 'toggled-on' );
  45. _this.next( '.children, .sub-menu' ).toggleClass( 'toggled-on' );
  46. // Close other sub-menus if they're open
  47. otherSubMenus.removeClass( 'toggled-on' );
  48. otherSubMenus.find( '.toggled-on' ).removeClass( 'toggled-on' );
  49. // jscs:disable
  50. _this.attr( 'aria-expanded', _this.attr( 'aria-expanded' ) === 'false' ? 'true' : 'false' );
  51. // jscs:enable
  52. // Update screen reader text
  53. screenReaderSpan.text( screenReaderSpan.text() === altoFocusScreenReaderText.expand ? altoFocusScreenReaderText.collapse : altoFocusScreenReaderText.expand );
  54. } );
  55. // Navigation height should never be shorter than site branding height
  56. container.css( "min-height", siteBrandingHeight );
  57. // Close sub-menus when click outside of menus
  58. $( 'html' ).click( function() {
  59. container.find( '.toggled-on' ).removeClass( 'toggled-on' );
  60. });
  61. // Close expanded sub-menus when clicking links
  62. container.find( 'a' ).click( function( e ) {
  63. var _this = $( this ),
  64. anchor = _this.attr( 'href' ),
  65. otherSubMenus = container.find( '.toggled-on' );
  66. //console.log(anchor);
  67. e.preventDefault();
  68. otherSubMenus.removeClass( 'toggled-on' ).attr( 'aria-expanded', 'false' ).attr( 'aria-haspopup', 'false' );
  69. window.location.href = anchor;
  70. } );
  71. }
  72. /**
  73. * Enable menuToggle
  74. */
  75. function enableMenuToggle() {
  76. // Return early if menuToggle is missing.
  77. if ( ! menuToggle.length ) {
  78. return;
  79. }
  80. // Add an initial values for the attribute.
  81. menuToggle.add( siteNavigation ).attr( 'aria-expanded', 'false' );
  82. menuToggle.on( 'click.altofocus', function() {
  83. $( this ).toggleClass( 'toggled-on' );
  84. $( this ).add( siteMenu ).add( siteNavigation ).toggleClass( 'toggled-on' );
  85. // jscs:disable
  86. $( this ).add( siteMenu ).add( siteNavigation ).attr( 'aria-expanded', $( this ).add( siteNavigation ).attr( 'aria-expanded' ) === 'false' ? 'true' : 'false' );
  87. // jscs:enable
  88. } );
  89. }
  90. /**
  91. * Fix sub-menus for touch devices and better focus for hidden submenu items for accessibility
  92. */
  93. function addTouchSupport() {
  94. if ( ! siteNavigation.length || ! siteNavigation.children().length ) {
  95. return;
  96. }
  97. // Toggle `focus` class to allow submenu access on tablets.
  98. function toggleFocusClassTouchScreen() {
  99. // if ( window.innerWidth >= 896 ) {
  100. $( document.body ).on( 'touchstart.altofocus', function( e ) {
  101. if ( ! $( e.target ).closest( '.top-navigation li' ).length ) {
  102. $( '.top-navigation li' ).removeClass( 'focus' );
  103. }
  104. } );
  105. siteNavigation.find( '.menu-item-has-children > a' ).on( 'touchstart.altofocus', function( e ) {
  106. var el = $( this ).parent( 'li' );
  107. if ( ! el.hasClass( 'focus' ) ) {
  108. e.preventDefault();
  109. el.toggleClass( 'focus' );
  110. el.siblings( '.focus' ).removeClass( 'focus' );
  111. }
  112. } );
  113. }
  114. if ( 'ontouchstart' in window ) {
  115. $( window ).on( 'resize.altofocus', toggleFocusClassTouchScreen );
  116. toggleFocusClassTouchScreen();
  117. }
  118. siteNavigation.find( 'a' ).on( 'focus.altofocus blur.altofocus', function() {
  119. $( this ).parents( '.menu-item' ).toggleClass( 'focus' );
  120. } );
  121. }
  122. /**
  123. * Add the default ARIA attributes for the menu toggle and the navigations
  124. */
  125. function onResizeARIA() {
  126. if ( window.innerWidth < 896 ) {
  127. if ( menuToggle.hasClass( 'toggled-on' ) ) {
  128. menuToggle.attr( 'aria-expanded', 'true' );
  129. siteMenu.attr( 'aria-expanded', 'true' );
  130. siteNavigation.attr( 'aria-expanded', 'true' );
  131. } else {
  132. menuToggle.attr( 'aria-expanded', 'false' );
  133. siteMenu.attr( 'aria-expanded', 'false' );
  134. siteNavigation.attr( 'aria-expanded', 'false' );
  135. }
  136. } else {
  137. menuToggle.removeAttr( 'aria-expanded' );
  138. siteMenu.removeAttr( 'aria-expanded' );
  139. siteNavigation.removeAttr( 'aria-expanded' );
  140. }
  141. }
  142. /**
  143. * Re-initialize the main navigation when it is updated in the customizer
  144. * - Borrowed from twentysixteen: https://goo.gl/O6msL1
  145. */
  146. $( document ).on( 'customize-preview-menu-refreshed', function( e, params ) {
  147. if ( 'menu-1' === params.wpNavMenuArgs.theme_location ) {
  148. initMainNavigation( params.newContainer );
  149. }
  150. });
  151. /**
  152. * Execute functions
  153. */
  154. $( document )
  155. .ready( initMainNavigation( topNavigation ) )
  156. .ready( enableMenuToggle )
  157. .ready( addTouchSupport )
  158. .ready( function() {
  159. body = $( document.body );
  160. $( window )
  161. .on( 'load.altofocus', onResizeARIA )
  162. .on( 'resize.altofocus', onResizeARIA );
  163. });
  164. } )( jQuery );