responsive-menus.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. /**
  2. * This script adds the accessibility-ready responsive menus Genesis Framework child themes.
  3. *
  4. * @author StudioPress
  5. * @link https://github.com/copyblogger/responsive-menus
  6. * @version 1.1.2
  7. * @license GPL-2.0+
  8. */
  9. var genesisMenuParams = typeof genesis_responsive_menu === 'undefined' ? '' : genesis_responsive_menu,
  10. genesisMenusUnchecked = genesisMenuParams.menuClasses,
  11. genesisMenus = {},
  12. menusToCombine = [];
  13. ( function ( document, $, undefined ) {
  14. 'use strict';
  15. // Make our menus unique if there's more than one instance on the page.
  16. /**
  17. * Validate the menus passed by the theme with what's being loaded on the page,
  18. * and pass the new and accurate information to our new data.
  19. * @param {genesisMenusUnchecked} Raw data from the localized script in the theme.
  20. * @return {array} genesisMenus array gets populated with updated data.
  21. * @return {array} menusToCombine array gets populated with relevant data.
  22. */
  23. $.each( genesisMenusUnchecked, function( group ) {
  24. // Mirror our group object to populate.
  25. genesisMenus[group] = [];
  26. // Loop through each instance of the specified menu on the page.
  27. $.each( this, function( key, value ) {
  28. var menuString = value,
  29. $menu = $(value);
  30. // If there is more than one instance, append the index and update array.
  31. if ( $menu.length > 1 ) {
  32. $.each( $menu, function( key, value ) {
  33. var newString = menuString + '-' + key;
  34. $(this).addClass( newString.replace('.','') );
  35. genesisMenus[group].push( newString );
  36. if ( 'combine' === group ) {
  37. menusToCombine.push( newString );
  38. }
  39. });
  40. } else if ( $menu.length == 1 ) {
  41. genesisMenus[group].push( menuString );
  42. if ( 'combine' === group ) {
  43. menusToCombine.push( menuString );
  44. }
  45. }
  46. });
  47. });
  48. // Make sure there is something to use for the 'others' array.
  49. if ( typeof genesisMenus.others == 'undefined' ) {
  50. genesisMenus.others = [];
  51. }
  52. // If there's only one menu on the page for combining, push it to the 'others' array and nullify our 'combine' variable.
  53. if ( menusToCombine.length == 1 ) {
  54. genesisMenus.others.push( menusToCombine[0] );
  55. genesisMenus.combine = null;
  56. menusToCombine = null;
  57. }
  58. var genesisMenu = {},
  59. mainMenuButtonClass = 'menu-toggle',
  60. subMenuButtonClass = 'sub-menu-toggle',
  61. responsiveMenuClass = 'genesis-responsive-menu';
  62. // Initialize.
  63. genesisMenu.init = function() {
  64. // Exit early if there are no menus to do anything.
  65. if ( $( _getAllMenusArray() ).length == 0 ) {
  66. return;
  67. }
  68. var menuIconClass = typeof genesisMenuParams.menuIconClass !== 'undefined' ? genesisMenuParams.menuIconClass : 'dashicons-before dashicons-menu',
  69. subMenuIconClass = typeof genesisMenuParams.subMenuIconClass !== 'undefined' ? genesisMenuParams.subMenuIconClass : 'dashicons-before dashicons-arrow-down-alt2',
  70. toggleButtons = {
  71. menu : $( '<button />', {
  72. 'class' : mainMenuButtonClass,
  73. 'aria-expanded' : false,
  74. 'aria-pressed' : false,
  75. 'role' : 'button'
  76. } )
  77. .append( genesisMenuParams.mainMenu ),
  78. submenu : $( '<button />', {
  79. 'class' : subMenuButtonClass,
  80. 'aria-expanded' : false,
  81. 'aria-pressed' : false,
  82. 'role' : 'button'
  83. } )
  84. .append( $( '<span />', {
  85. 'class' : 'screen-reader-text',
  86. 'text' : genesisMenuParams.subMenu
  87. } ) )
  88. };
  89. // Add the responsive menu class to the active menus.
  90. _addResponsiveMenuClass();
  91. // Add the main nav button to the primary menu, or exit the plugin.
  92. _addMenuButtons( toggleButtons );
  93. // Setup additional classes.
  94. $( '.' + mainMenuButtonClass ).addClass( menuIconClass );
  95. $( '.' + subMenuButtonClass ).addClass( subMenuIconClass );
  96. $( '.' + mainMenuButtonClass ).on( 'click.genesisMenu-mainbutton', _mainmenuToggle ).each( _addClassID );
  97. $( '.' + subMenuButtonClass ).on( 'click.genesisMenu-subbutton', _submenuToggle );
  98. $( window ).on( 'resize.genesisMenu', _doResize ).triggerHandler( 'resize.genesisMenu' );
  99. };
  100. /**
  101. * Add menu toggle button to appropriate menus.
  102. * @param {toggleButtons} Object of menu buttons to use for toggles.
  103. */
  104. function _addMenuButtons( toggleButtons ) {
  105. // Apply sub menu toggle to each sub-menu found in the menuList.
  106. $( _getMenuSelectorString( genesisMenus ) ).find( '.sub-menu' ).before( toggleButtons.submenu );
  107. if ( menusToCombine !== null ) {
  108. var menusToToggle = genesisMenus.others.concat( menusToCombine[0] );
  109. // Only add menu button the primary menu and navs NOT in the combine variable.
  110. $( _getMenuSelectorString( menusToToggle ) ).before( toggleButtons.menu );
  111. } else {
  112. // Apply the main menu toggle to all menus in the list.
  113. $( _getMenuSelectorString( genesisMenus.others ) ).before( toggleButtons.menu );
  114. }
  115. }
  116. /**
  117. * Add the responsive menu class.
  118. */
  119. function _addResponsiveMenuClass() {
  120. $( _getMenuSelectorString( genesisMenus ) ).addClass( responsiveMenuClass );
  121. }
  122. /**
  123. * Execute our responsive menu functions on window resizing.
  124. */
  125. function _doResize() {
  126. var buttons = $( 'button[id^="genesis-mobile-"]' ).attr( 'id' );
  127. if ( typeof buttons === 'undefined' ) {
  128. return;
  129. }
  130. _maybeClose( buttons );
  131. _superfishToggle( buttons );
  132. _changeSkipLink( buttons );
  133. _combineMenus( buttons );
  134. }
  135. /**
  136. * Add the nav- class of the related navigation menu as
  137. * an ID to associated button (helps target specific buttons outside of context).
  138. */
  139. function _addClassID() {
  140. var $this = $( this ),
  141. nav = $this.next( 'nav' ),
  142. id = 'class';
  143. $this.attr( 'id', 'genesis-mobile-' + $( nav ).attr( id ).match( /nav-\w*\b/ ) );
  144. }
  145. /**
  146. * Combine our menus if the mobile menu is visible.
  147. * @params buttons
  148. */
  149. function _combineMenus( buttons ){
  150. // Exit early if there are no menus to combine.
  151. if ( menusToCombine == null ) {
  152. return;
  153. }
  154. // Split up the menus to combine based on order of appearance in the array.
  155. var primaryMenu = menusToCombine[0],
  156. combinedMenus = $( menusToCombine ).filter( function(index) { if ( index > 0 ) { return index; } });
  157. // If the responsive menu is active, append items in 'combinedMenus' object to the 'primaryMenu' object.
  158. if ( 'none' !== _getDisplayValue( buttons ) ) {
  159. $.each( combinedMenus, function( key, value ) {
  160. $(value).find( '.menu > li' ).addClass( 'moved-item-' + value.replace( '.','' ) ).appendTo( primaryMenu + ' ul.genesis-nav-menu' );
  161. });
  162. $( _getMenuSelectorString( combinedMenus ) ).hide();
  163. } else {
  164. $( _getMenuSelectorString( combinedMenus ) ).show();
  165. $.each( combinedMenus, function( key, value ) {
  166. $( '.moved-item-' + value.replace( '.','' ) ).appendTo( value + ' ul.genesis-nav-menu' ).removeClass( 'moved-item-' + value.replace( '.','' ) );
  167. });
  168. }
  169. }
  170. /**
  171. * Action to happen when the main menu button is clicked.
  172. */
  173. function _mainmenuToggle() {
  174. var $this = $( this );
  175. _toggleAria( $this, 'aria-pressed' );
  176. _toggleAria( $this, 'aria-expanded' );
  177. $this.toggleClass( 'activated' );
  178. $this.next( 'nav' ).slideToggle( 'fast' );
  179. }
  180. /**
  181. * Action for submenu toggles.
  182. */
  183. function _submenuToggle() {
  184. var $this = $( this ),
  185. others = $this.closest( '.menu-item' ).siblings();
  186. _toggleAria( $this, 'aria-pressed' );
  187. _toggleAria( $this, 'aria-expanded' );
  188. $this.toggleClass( 'activated' );
  189. $this.next( '.sub-menu' ).slideToggle( 'fast' );
  190. others.find( '.' + subMenuButtonClass ).removeClass( 'activated' ).attr( 'aria-pressed', 'false' );
  191. others.find( '.sub-menu' ).slideUp( 'fast' );
  192. }
  193. /**
  194. * Activate/deactivate superfish.
  195. * @params buttons
  196. */
  197. function _superfishToggle( buttons ) {
  198. var _superfish = $( '.' + responsiveMenuClass + ' .js-superfish' ),
  199. $args = 'destroy';
  200. if ( typeof _superfish.superfish !== 'function' ) {
  201. return;
  202. }
  203. if ( 'none' === _getDisplayValue( buttons ) ) {
  204. $args = {
  205. 'delay': 100,
  206. 'animation': {'opacity': 'show', 'height': 'show'},
  207. 'dropShadows': false,
  208. 'speed': 'fast'
  209. };
  210. }
  211. _superfish.superfish( $args );
  212. }
  213. /**
  214. * Modify skip link to match mobile buttons.
  215. * @param buttons
  216. */
  217. function _changeSkipLink( buttons ) {
  218. // Start with an empty array.
  219. var menuToggleList = _getAllMenusArray();
  220. // Exit out if there are no menu items to update.
  221. if ( ! $( menuToggleList ).length > 0 ) {
  222. return;
  223. }
  224. $.each( menuToggleList, function ( key, value ) {
  225. var newValue = value.replace( '.', '' ),
  226. startLink = 'genesis-' + newValue,
  227. endLink = 'genesis-mobile-' + newValue;
  228. if ( 'none' == _getDisplayValue( buttons ) ) {
  229. startLink = 'genesis-mobile-' + newValue;
  230. endLink = 'genesis-' + newValue;
  231. }
  232. var $item = $( '.genesis-skip-link a[href="#' + startLink + '"]' );
  233. if ( menusToCombine !== null && value !== menusToCombine[0] ) {
  234. $item.toggleClass( 'skip-link-hidden' );
  235. }
  236. if ( $item.length > 0 ) {
  237. var link = $item.attr( 'href' );
  238. link = link.replace( startLink, endLink );
  239. $item.attr( 'href', link );
  240. } else {
  241. return;
  242. }
  243. });
  244. }
  245. /**
  246. * Close all the menu toggles if buttons are hidden.
  247. * @param buttons
  248. */
  249. function _maybeClose( buttons ) {
  250. if ( 'none' !== _getDisplayValue( buttons ) ) {
  251. return true;
  252. }
  253. $( '.' + mainMenuButtonClass + ', .' + responsiveMenuClass + ' .sub-menu-toggle' )
  254. .removeClass( 'activated' )
  255. .attr( 'aria-expanded', false )
  256. .attr( 'aria-pressed', false );
  257. $( '.' + responsiveMenuClass + ', ' + responsiveMenuClass + ' .sub-menu' )
  258. .attr( 'style', '' );
  259. }
  260. /**
  261. * Generic function to get the display value of an element.
  262. * @param {id} $id ID to check
  263. * @return {string} CSS value of display property
  264. */
  265. function _getDisplayValue( $id ) {
  266. var element = document.getElementById( $id ),
  267. style = window.getComputedStyle( element );
  268. return style.getPropertyValue( 'display' );
  269. }
  270. /**
  271. * Toggle aria attributes.
  272. * @param {button} $this passed through
  273. * @param {aria-xx} attribute aria attribute to toggle
  274. * @return {bool} from _ariaReturn
  275. */
  276. function _toggleAria( $this, attribute ) {
  277. $this.attr( attribute, function( index, value ) {
  278. return 'false' === value;
  279. });
  280. }
  281. /**
  282. * Helper function to return a comma separated string of menu selectors.
  283. * @param {itemArray} Array of menu items to loop through.
  284. * @param {ignoreSecondary} boolean of whether to ignore the 'secondary' menu item.
  285. * @return {string} Comma-separated string.
  286. */
  287. function _getMenuSelectorString( itemArray ) {
  288. var itemString = $.map( itemArray, function( value, key ) {
  289. return value;
  290. });
  291. return itemString.join( ',' );
  292. }
  293. /**
  294. * Helper function to return a group array of all the menus in
  295. * both the 'others' and 'combine' arrays.
  296. * @return {array} Array of all menu items as class selectors.
  297. */
  298. function _getAllMenusArray() {
  299. // Start with an empty array.
  300. var menuList = [];
  301. // If there are menus in the 'menusToCombine' array, add them to 'menuList'.
  302. if ( menusToCombine !== null ) {
  303. $.each( menusToCombine, function( key, value ) {
  304. menuList.push( value.valueOf() );
  305. });
  306. }
  307. // Add menus in the 'others' array to 'menuList'.
  308. $.each( genesisMenus.others, function( key, value ) {
  309. menuList.push( value.valueOf() );
  310. });
  311. if ( menuList.length > 0 ) {
  312. return menuList;
  313. } else {
  314. return null;
  315. }
  316. }
  317. $(document).ready(function () {
  318. if ( _getAllMenusArray() !== null ) {
  319. genesisMenu.init();
  320. }
  321. });
  322. })( document, jQuery );