Reputation: 429
I'm currently using this script for my dropdown menu items in a WordPress theme: http://jsfiddle.net/i_like_robots/6JbtX/
$(function()
{
var $dropdowns = $('li.dropdown'); // Specifying the element is faster for older browsers
/**
* Mouse events
*
* @description Mimic hoverIntent plugin by waiting for the mouse to 'settle' within the target before triggering
*/
$dropdowns
.on('mouseover', function() // Mouseenter (used with .hover()) does not trigger when user enters from outside document window
{
var $this = $(this);
if ($this.prop('hoverTimeout'))
{
$this.prop('hoverTimeout', clearTimeout($this.prop('hoverTimeout')));
}
$this.prop('hoverIntent', setTimeout(function()
{
$this.addClass('hover');
}, 250));
})
.on('mouseleave', function()
{
var $this = $(this);
if ($this.prop('hoverIntent'))
{
$this.prop('hoverIntent', clearTimeout($this.prop('hoverIntent')));
}
$this.prop('hoverTimeout', setTimeout(function()
{
$this.removeClass('hover');
}, 250));
});
/**
* Touch events
*
* @description Support click to open if we're dealing with a touchscreen
*/
if ('ontouchstart' in document.documentElement)
{
$dropdowns.each(function()
{
var $this = $(this);
this.addEventListener('touchstart', function(e)
{
if (e.touches.length === 1)
{
// Prevent touch events within dropdown bubbling down to document
e.stopPropagation();
// Toggle hover
if (!$this.hasClass('hover'))
{
// Prevent link on first touch
if (e.target === this || e.target.parentNode === this)
{
e.preventDefault();
}
// Hide other open dropdowns
$dropdowns.removeClass('hover');
$this.addClass('hover');
// Hide dropdown on touch outside
document.addEventListener('touchstart', closeDropdown = function(e)
{
e.stopPropagation();
$this.removeClass('hover');
document.removeEventListener('touchstart', closeDropdown);
});
}
}
}, false);
});
}
});
However, I need these items to be keyboard accessible.
Can anyone point me in the right direction?
Thanks!
Upvotes: 0
Views: 492
Reputation: 7244
You are trying to implement a user interface pattern that is like a Chimera. You have a single element (the top level menu item) that is trying to be both a link and a menu item (with a sub-menu). It is very difficult to make this user experience accessible AND easy to use.
If you do what @tom-usborne suggested and simply open the menu on focusin
and focusout
, then a keyboard-only user will have to tab through each and every single menu item in all of your dropdown menus. Imagine Steven Hawking having to press the tab key that many times using just his cheek muscle! Not very nice to use.
Alternative approaches - such as implementation of ARIA authoring guidelines for menu interaction would mean that the top level item's link could not be accessed at all (because the enter key would open the menu and not click the link).
However, if you simply implement the ARIA pattern, then you will find that on iOS, the menu cannot be interacted-with. I recommend a hybrid whereby you move the top-level link into the menu, allow the menu to be toggled open and closed using a normal click (or touch or mouseover) and then once open, you can tab through all the links in the menu. This approach will work well on all devices.
Upvotes: 1
Reputation: 429
The answer to this is to use the focusin and focusout handlers:
$dropdowns
.on('focusin', function() // Mouseenter (used with .hover()) does not trigger when user enters from outside document window
{
var $this = $(this);
if ($this.prop('hoverTimeout'))
{
$this.prop('hoverTimeout', clearTimeout($this.prop('hoverTimeout')));
}
$this.prop('hoverIntent', setTimeout(function()
{
$this.addClass('hover');
}, 250));
})
.on('focusout', function()
{
var $this = $(this);
if ($this.prop('hoverIntent'))
{
$this.prop('hoverIntent', clearTimeout($this.prop('hoverIntent')));
}
$this.prop('hoverTimeout', setTimeout(function()
{
$this.removeClass('hover');
}, 250));
});
Upvotes: 0