【问题标题】:Cross browser button with popup menu using focusout event使用 focusout 事件的带有弹出菜单的跨浏览器按钮
【发布时间】:2013-01-15 17:29:14
【问题描述】:

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

我创建了一个simple 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 似乎按预期工作。所有四个步骤都按应有的方式执行。单击菜单中的链接时,不会触发 focusout 作为焦点容器(菜单本身)中的链接。

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

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

问题

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

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

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

【问题讨论】:

    标签: jquery drop-down-menu popupmenu


    【解决方案1】:

    可以在文档上使用点击处理程序来替换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/

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

    【讨论】:

    • 谢谢,但正如我在最后一段中所说的那样,我不能使用它,因为其他控件会停止点击传播(因为它们必须以这种方式工作)所以点击这些控件不会'不要关闭菜单,因为事件不会冒泡到文档中。
    • 顺便说一句:我再次更改了我的示例,因此欢迎再次单击它,因为我已设法使其在 FF 和 IE9 中的工作率为 75%。如果你有 IE8 或 IE7,如果你提供一些关于它们的信息,我会很高兴......
    • 抱歉没有阅读完整的规范,错过了最后一段。我很少发现在构建 UI 时需要停止点击传播,所以当我做这样的事情时,更高级别的点击处理程序是最简单的方法。当传播是一个问题时,我通常使用target,就像我在这里所做的那样
    • 在 IE 7&8 中尝试了最新的案例。事件工作正常。 IE7 顶部按钮上不显示箭头
    • 您的方法还有另一个问题。我的应用程序在 IFRAME 中运行,因此每当用户单击顶部框架时,我的菜单都不会关闭,因为用户不会单击框架内的任何位置。另一方面,focusout 按预期工作。每当用户单击任何地方(甚至在开发人员工具中)时,元素都会失去焦点并且菜单会隐藏。顺便说一句,您的代码有一个错误,因为只需在框架内单击并不会真正关闭菜单。仅当单击不同的下拉按钮时,菜单才会关闭。
    【解决方案2】:

    跨浏览器解决方案

    Solution I've come up with 是跨浏览器,适用于 Chrome、Firefox 和 IE7+。它需要处理一个额外的事件,即下拉菜单的mousedown。单击下拉菜单上的选项通常会在 IE 和 FF 中触发 focusout 事件,即使用户在同一焦点元素内单击也是如此。这就是为什么我们将 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);
    });
    

    【讨论】:

      猜你喜欢
      • 2010-09-19
      • 2014-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多