1

我正在尝试为带有下拉菜单的按钮创建一个简单的跨浏览器插件。当用户单击此类按钮时,下方会出现一个带有各种选项的菜单,用户随后可以从中选择一个选项或将其关闭。

我已经创建了一个简单的 JSFiddle,其中包含三个这样的按钮来举例说明我想要实现的目标。我的 JSFiddle 代码执行了一些我已从下面的代码中排除的额外事件日志记录,但运行 JSFiddle 很明显我正在记录事件发生时。

这是我的 HTML:

我实现代码的方式需要下拉菜单可聚焦,因此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 类,并且当open在容器上设置类时,CSS 提供了自动可见性。

预期行为

这是正确的方法,因为它应该起作用:

  1. 用户单击按钮并出现菜单
  2. 单击同一按钮应关闭菜单
  3. 点击菜单选项应该触发点击甚至选项(并可选择保持菜单打开)
  4. 如果打开,单击其他任何地方都应该关闭菜单。

浏览器问题

不同的浏览器似乎以不同且过度的方式触发事件。事件传播(冒泡)及其顺序阻止了上层步骤按预期执行。Chrome 似乎不会触发过多的事件。

Chrome
Chrome 似乎按预期工作。所有四个步骤都按应有的方式执行。单击菜单中的链接时,不会触发焦点输出作为焦点容器(菜单本身)中的链接。

Firefox 和 IE9
似乎步骤 #1、#2 和 #4 按预期工作,但 #3 失败,因为在检测和执行菜单选项单击之前, focusout首先触发并关闭菜单。

IE8 和 IE7
任何拥有它们的人都可以为我测试并告诉我上面的哪些步骤有效,哪些失败。我没有测试过,但也很想知道。

问题

此脚本的主要问题是focusout事件过早且过于频繁地触发。我不能使用模糊事件,因为它不会从菜单选项传播到菜单本身。

重要- 将点击处理程序绑定到document- 我知道我可以将点击事件绑定到我的document,但我不能使用这种通常的方法,因为:
1.这将非常不可靠,因为我的表单上的一些其他控件可能会停止点击传播,因此菜单不会'在单击此类控件时不会关闭。
2.我的应用程序在一个 中运行iframe,因此在它之外单击,也会使菜单保持打开状态。

有人想以跨浏览器的方式玩这些事件吗?

4

2 回答 2

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')
  }

})

演示http://jsfiddle.net/zMdxw/5/

这可以改进为仅在菜单打开时添加文档单击处理程序,并在所有菜单关闭时将其删除

于 2013-01-13T01:41:22.977 回答
0

跨浏览器解决方案

我想出的解决方案是跨浏览器,适用于 Chrome、Firefox 和 IE7+。它需要处理一个额外的事件,那就是mousedown下拉菜单。单击下拉菜单上的选项通常会focusout在 IE 和 FF 中触发一个事件,即使用户在同一个焦点元素内单击也是如此。这就是为什么我们将 next 设置focusout为忽略而不关闭菜单。

Chrome 不会触发focusout菜单选项点击,因此我们还必须通过在足够短的时间后手动重新启用关闭来处理这个问题。我将它设置为 100 毫秒,但它可以更短,因为它只需要延迟到focusout执行下一个处理程序。看来10ms也够了。如果事件处理程序在开始执行之前都被浏览器排队,则可能更少。在这种情况下,值 0 就足够了。但为了安全起见,我将其设置为 100 毫秒。

这是执行预期的代码:

// 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);
});
于 2013-01-14T15:59:22.260 回答