我正在尝试使用下拉菜单为按钮创建一个简单的跨浏览器插件。当用户点击这样的按钮时,下面会出现一个带有各种选项的菜单,用户可以随后从中选择一个选项或关闭它。
我创建了一个simple JSFiddle,其中包含三个这样的按钮,可以说明我想要实现的目标。我的JSFiddle代码执行了一些我从代码中排除的额外事件日志记录,但是运行JSFiddle使我很明显我正在记录事件。
我实现代码的方式我需要下拉菜单可调焦,因此容器上的tabindex
属性。
<div class="dropdown">
<a href="#" class="dropdown-toggle">Open sesame</a>
<ul class="dropdown-menu" tabindex="0">
<li><a href="#">Some option</a></li>
<li><a href="#">Option with longer text</a></li>
</ul>
</div>
// menu opening and closing
$(".dropdown-toggle").mousedown(function(evt) {
evt.preventDefault();
var c = $(this).closest(".dropdown").toggleClass("open");
c.hasClass("open") && c.find(".dropdown-menu")[0].focus();
});
// menu closing when clicking anywhere
$(".dropdown-menu").focusout(function(evt) {
evt.stopPropagation();
$(this).closest(".dropdown").removeClass("open");
})
显示菜单由CSS完成。如您所见,我几乎没有在容器上设置CSS类,而CSS在容器上设置open
类时提供自动可见性。
这是正确的方法:
不同的浏览器似乎以不同和过度的方式触发事件。事件传播(冒泡)及其序列可防止上面的步骤按预期执行。 Chrome似乎没有引发过多的事件。
铬
Chrome似乎按预期工作。所有四个步骤都按预期执行。单击菜单中的链接时,没有焦点作为焦点容器内的链接(菜单本身)被触发。
Firefox和IE9
似乎步骤#1,#2和#4按预期工作,但#3失败,因为在检测并执行菜单选项单击之前, focusout 首先触发并关闭菜单。
IE8和IE7
任何拥有它们的人都可以测试我并告诉我哪些上层步骤有效,哪些失败。我还没有测试,但也非常想知道。
此脚本的主要问题是 focusout 事件过早且频繁地触发。我无法使用 blur 事件,因为它不会从菜单选项传播到菜单本身。
重要 - 将点击处理程序绑定到
document
- 我知道我可以将点击事件绑定到我的document
,但我无法使用这种通常的做法是因为:
1。这将非常不可靠,因为我的表单上的某些其他控件可能会停止点击传播,因此单击此类控件时菜单不会关闭。
2。我的应用程序在iframe
内运行,因此单击它外部也会保持菜单打开。
有人想以跨浏览器方式玩这些事件吗?
答案 0 :(得分:0)
可以使用文档上的单击处理程序替换focusout
代码。不完全确定你想要的行为,但试试这个:
$(document).click(function(e){
var $tgt=$(e.target)
if( !$tgt.closest('.container').length){
log('non menu el clicked')
}else{
/* close other open menus when a new one clicked*/
$tgt.closest('.container').siblings().removeClass('open')
}
})
DEMO http://jsfiddle.net/zMdxw/5/
这可以改进为仅在菜单打开时添加文档点击处理程序,并在所有菜单关闭时将其删除
答案 1 :(得分:0)
Solution I've come up with是跨浏览器,适用于Chrome,Firefox和IE7 +。它需要处理一个额外的事件,这是下拉菜单的mousedown
。单击下拉菜单上的选项通常会在IE和FF中触发focusout
事件,即使用户在焦点对应的相同元素内单击也是如此。这就是我们将下一个focusout
设置为忽略而不关闭菜单的原因。
Chrome不会触发focusout
菜单选项点击,因此我们还必须通过在足够短的时间后手动重新启用关闭来解决此问题。我已将其设置为100毫秒,但它可以更短,因为它只需要延迟,直到执行下一个focusout
处理程序。似乎10毫秒也足够了。如果事件处理程序在开始执行之前都被浏览器排队,那么可能更少。在这种情况下,值为0就足够了。但为了安全起见,我把它留在了100ms。
这是执行预期的代码:
// toggle dropdown menu display
$(".dropdown-toggle").mousedown(function(evt) {
evt.preventDefault();
log("Menu toggle");
var dd = $(this).parent().toggleClass("open");
// only focus it when visible
dd.hasClass("open") && dd.children(".dropdown-menu")[0].focus();
});
// dropdown closing on focusout
$(".dropdown-menu").focusout(function(evt) {
log("Menu focus out");
var m = $(this);
// check that closing is not cancelled this time
m.data("cancel-close") === true && m.removeData("cancel-close").length || m.parent().removeClass("open");
});
// cancel dropdown closing when user clicks a menu option
$(".dropdown-menu").mousedown(function(evt) {
log("Cancel next focusout");
var m = $(this);
// cancel next focusout event
m.data("cancel-close", true);
// reenable closing for browsers that don't focusout ie. Chrome
window.setTimeout((function(context) {
return function() {
log("Focusout is reenabled.");
context.removeData("cancel-close");
};
})(m), 100);
});