Skip to content

Instantly share code, notes, and snippets.

@RadGH
Last active August 29, 2015 14:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RadGH/6e7ab8c86a4cb258745b to your computer and use it in GitHub Desktop.
Save RadGH/6e7ab8c86a4cb258745b to your computer and use it in GitHub Desktop.
Keyboard-friendly and intent-friendly dropdown navigation menu using jQuery
/*
Usage:
friendly_dropdown_menu( "container", "item", "link", "submenu", (boolean) );
Demo:
http://codepen.io/RadGH/pen/xboxaw
*/
function friendly_dropdown_menu( container_selector, item_selector, link_selector, submenu_selector, close_toggle ) {
// Use WordPress default nav menu classes if parameters undefined
if ( typeof container_selector == 'undefined' ) container_selector = 'nav-menu';
if ( typeof item_selector == 'undefined' ) item_selector = '.menu-item';
if ( typeof link_selector == 'undefined' ) link_selector = 'a';
if ( typeof submenu_selector == 'undefined' ) submenu_selector = '.sub-menu';
if ( typeof close_toggle == 'undefined' ) close_toggle = false; // If true, clicking on an open menu will close it. Otherwise the link will be opened.
// Abort if we don't have a menu
var $container = jQuery( container_selector );
if ( $container.length < 1 ) return;
// On click / enter / spacebar for a link, open a submenu
$container.on('click keyup', link_selector, function(e) {
// Skip keyboard events EXCEPT FOR the enter key and the tab key. These will open the dropdown menu.
if ( e.type == 'keyup' && (e.which !== 13 && e.which !== 9) ) return true;
var $link = jQuery(this);
var $parent_item = $link.closest(item_selector);
var $submenu = $parent_item.children(submenu_selector).first();
if ( $submenu.length > 0 ) {
if ( $parent_item.hasClass('active') ) {
// Sub menu is already active.
if ( close_toggle ) {
// Close submenu
$parent_item.removeClass('active');
return false;
}else{
// Go to submenu link
return true;
}
}else{
// Remove "active" state from all other submenus within the same level as the clicked element
$parent_item.siblings(item_selector).removeClass('active');
// Make the clicked submenu active
$parent_item.addClass('active');
return false;
}
}
});
// On hover, open submenu automatically (after 140ms delay)
$container.on('mouseover', link_selector, function(e) {
var $link = jQuery(this);
var $parent_item = $link.closest(item_selector);
// Do not open menus if the link does not have a submenu
if ( $parent_item.children(submenu_selector).length < 1 ) return;
if ( $parent_item.siblings(item_selector).filter('.active').length > 0 ) {
// At least one sibling is open. Wait a second before activating this menu
// Make siblings menus no longer be active
$parent_item.siblings(item_selector).removeClass('active');
// Activate the hovered item after a short delay
jQuery(this).data('timeout-mouseover', setTimeout(function() {
$parent_item.addClass('active');
}, 150));
}else{
// No siblings are open. Activate immediately.
$parent_item.addClass('active');
}
});
// Stop activating on hover if the element loses mouse
$container.on('mouseout', link_selector, function (e) {
clearTimeout( jQuery(this).data('timeout-mouseover') );
});
// On mouseout from a submenu's container, deactive the submenu
$container.on('mouseout blur', item_selector, function(e) {
var $menu_item = jQuery(this);
clearTimeout( jQuery(this).data('timeout-blur') );
jQuery(this).data('timeout-blur', setTimeout(function(e) {
$menu_item.removeClass('active');
}, 120));
});
// Allow users to move mouse back over the menu during the timeout to keep the menu open
$container.on('mouseover focus', item_selector, function(e) {
clearTimeout( jQuery(this).data('timeout-blur') );
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment