我一直在找出使下拉菜单键盘可以访问的逻辑。
HTML的结构如此(为清晰起见使用额外的类名称):
<ul>
<li class="primaryMenuItem">
<a href="">Link 1</a>
<ul class="popUpMenu">
<li><a href="">Sub Link 1</a></li>
<li><a href="">Sub Link 2</a></li>
</ul>
</li>
<li class="primaryMenuItem">
<a href="">Link 2</a>
<ul class="popUpMenu">
<li><a href="">Sub Link 1</a></li>
<li><a href="">Sub Link 2</a></li>
</ul>
</li>
</ul>
链接1和链接2,当悬停时,将显示子菜单列表(下拉菜单)。我用一些jQuery和jQuery hoverIntent插件就可以了。
问题是,这只适用于鼠标。
接下来的挑战是通过键盘让它工作。
我可以轻松地将焦点事件添加到顶级链接,然后触发辅助菜单:
$('ul.primaryMenuItem a:first').focus([call showMenu function])
工作正常。
要关闭菜单,一个选项是,当打开另一个菜单时,检查是否已经打开另一个菜单,如果是,请关闭它。
这也很好。
但是,如果你打开了最后一个菜单,那么它就会失败。由于您没有标记到另一个菜单,这个菜单保持打开状态。
挑战在于弄清楚如何/何时关闭菜单以及需要的逻辑(jQuery)来弄清楚。理想情况下,当焦点位于页面上的元素上时,我会关闭菜单,而不是任何菜单的子元素。
从逻辑上讲,我正在寻找:
$('li.primaryMenuItem').blur([close $(this).find('ul.popUpMenu'))
但是,你不能这样做,因为LI实际上没有焦点,而是其中的锚标签。
有什么建议吗?
更新:
或许是一种更好/更简单的方式来提问:
通过jQuery,有没有办法“观察”以查看焦点是否移动到特定对象的所有子节点之外?
答案 0 :(得分:6)
您可以使用事件冒泡来检查焦点在focusin事件上的内容。我使用以下代码取得了成功:
$("li:has(ul.popUpMenu)").focusin(function(e) {
$(this).children().fadeIn('slow');
});
$('body').focusin(function(e) {
if (!$(e.target).parent().is('ul.popUpMenu li')) {
$('ul.popUpMenu').fadeOut('slow');
}
});
你可以(应该)让它更优化,但它有效。
答案 1 :(得分:2)
使用新的jquery 1.4函数:focusin
和focusout
而不是blur
和focus
。以下是focusout
的不同之处:
焦点输出事件被发送到 元素何时或其中的任何元素 它失去了焦点。这是截然不同的 从它的模糊事件 支持检测失去焦点 来自父元素(换句话说, 它支持事件冒泡)。
答案 2 :(得分:2)
如果您执行以下操作怎么样:
$('#link_A_id, #link_A_id > *').focusout(function () {
if ($(document.activeElement).closest('#link_A_id').length == 0)
//focus is out of link A and it's children
});
答案 3 :(得分:0)
试试这个
$('li.primaryMenuItem:last li a:last').blur([do whatever you need to do])
从逻辑上讲,如果您的用户标记出来,他一定是在关注最后一个锚点。
您甚至可以设置自己的事件处理程序:
$('li.primaryMenuItem:last').bind('myblur', function() ...);
并在最后一个锚点模糊事件中调用它:
...blur(function() {
$(this).parents('li.primaryMenuItem').trigger('myblur'); ...
答案 4 :(得分:0)
这帮助了我...... http://plugins.jquery.com/project/focus
它会自动检测您是否仍在父母中。它基本上改变了jQuery焦点以这种方式工作,我觉得它应该如何工作。
<div class="parent">
<input type="text" />
<input type="text" />
</div>
$('#parent').focusout(function () {
console.log('focusout of parent');
});
我不明白为什么按Tab键在子元素之间移动文本字段应该触发父节点上的焦点,因为你仍然在父节点内。必须发生一些事情,让你暂时离开它,我怀疑这是一个错误......有人和我在一起吗?好吧无论如何上面的插件修复它。只需在代码之前包含它即可“修复”此问题。如果不是这样的话,会爱一个人解释为什么这不是一个错误。
谢谢,Dom
答案 5 :(得分:0)
我有一个类似的问题......我创建了一个jsfiddle来确定父字段集何时失去焦点然后调用一个函数。它当然可以优化,但它是一个开始。
function saveFields() {
$.each($('fieldset.save'),function(index, value) {
// WHERE THE POST WOULD GO
alert('saving fieldset with id '+ value.id);
$(value).removeClass('save');
});
}
$('.control-group').focusin(function(){
var thefield = $(this).parent('fieldset');
if (!thefield.hasClass('active')) {
if($('fieldset.active').length > 0){
$('fieldset.active').removeClass('active').addClass('save');
saveFields();
}
thefield.addClass('active');
} else {
console.log('already active');
}
});
答案 6 :(得分:0)
我想出了这个配方(香草JS),它完全符合OP的要求(提供一种监听“容器元素外部的焦点移动”的方法),并且通用性足以适用于任何用例。 / p>
在OP的示例中,它将像这样使用:
for (const primaryMenu of Array.from($('.primaryMenuItem'))) {
onFocusOutsideOf(primaryMenu, () => closeMenu(primaryMenu));
}
这是onFocusOutsideOf
的代码:
/**
* Invokes `callback` when focus moves outside the given element's DOM
* subtree.
*
* Returns a function that can optionally be called to remove the
* event listener.
*/
function onFocusOutsideOf(container, callback) {
const eventListener = () => {
// setTimeout is used to allow `document.activeElement` to
// be updated to the newly focused element. This is needed
// since the 'focusout' event within the container happens
// before the 'focus' event on the new element.
setTimeout(() => {
if (!isDescendantOf(container, document.activeElement)) {
callback();
}
})
};
container.addEventListener('focusout', eventListener);
return function unsubscribe() {
container.removeEventListener('focusout', eventListener);
}
}
/**
* Utility function which returns whether a given DOM element
* has another DOM element as a descendant.
*/
function isDescendantOf(ancestor: Element, potentialDescendant: Element) {
let parent = potentialDescendant.parentNode;
while (parent) {
if (parent === ancestor) return true;
parent = parent.parentElement;
}
return false;
}