下拉焦点未按预期在后代元素上起作用

时间:2018-08-09 11:04:09

标签: jquery dropdown jquery-focusout

我知道这是一个常见问题,但是我所见的答案都无法解决我的问题,如果我错过了一个,可以道歉,很明显可以将其删除/标记为重复...

标记

<div class="has-dropdown">
    <button class="js-dropdown-trigger">
        Dropdown
    </button>

    <div class="dropdown">
        <div class="dropdown__item">
            Some random text with a <a href="#" class="stop-propagation">link</a> in it.
        </div>
        <div class="dropdown__divider"></div>
        <div class="dropdown__item">
            <a href="#">Item One</a>
        </div>
        <div class="dropdown__item">
            <a href="#">Item Two</a>
        </div>
        <div class="dropdown__item">
            <a href="#">Item Three</a>
        </div>
    </div>
</div>

脚本

$('.has-dropdown').off().on('click', '.js-dropdown-trigger', (event) => {
    const $dropdown = $(event.currentTarget).next('.dropdown');

    if (!$dropdown.hasClass('is-active')) {
        $dropdown.addClass('is-active');
    } else {
        $dropdown.removeClass('is-active');
    }
});

$('.has-dropdown').on('focusout', (event) => {
    const $dropdown = $(event.currentTarget).children('.dropdown');

    $dropdown.removeClass('is-active');
});

样式

.has-dropdown {
    display: inline-flex;
    position: relative;
}

.dropdown {
    background-color: #eee;
    border: 1px solid #999;
    display: none;
    flex-direction: column;
    position: absolute;
    top: 100%;
    left: 0;
    width: 300px;
    margin-top: 5px;
}

.dropdown.is-active {
    display: flex;
}

.dropdown__item {
    padding: 10px;
}

.dropdown__divider {
    border-bottom: 1px solid #999;
}

小提琴

http://jsfiddle.net/joemottershaw/3yzadmek/

这非常简单,单击js-dropdown-trigger可以使is-active下拉菜单很好地切换,在has-dropdown容器外部单击也可以删除is-active下拉菜单。

除了,我期望发生的事情集中在has-dropdown元素的后代元素(单击或制表符)上,这意味着不应focusout事件处理程序被触发,因为您仍然专注于has-dropdown容器的后代元素。

  

focusout事件在或任何元素时发送到元素   内部,失去了焦点。这与中的模糊事件不同   它支持检测对后代元素的焦点丢失

我知道我可以删除focusout事件处理程序,并使用类似以下内容的方法:

$(document).on('click', (event) =>{
    const $dropdownContainer = $('.has-dropdown');

    if (!$dropdownContainer.is(event.target) && $dropdownContainer.has(event.target).length === 0) {
        $dropdownContainer.find('.dropdown').removeClass('is-active');
    }
});

这有效,但是如果您单击触发器,然后通过链接进行制表,则当您通过最后一个链接进行制表时,下拉列表仍然可见。只是努力寻找最佳的解决方案来保持事物的可访问性。

如果可能的话,我想坚持使用focusout方法。

根据darshanags答案进行了更新

尽管更新的脚本适用于单个元素,但将其他元素添加到body会导致focusout不再按预期工作。我认为这是因为if语句似乎是正确的,即使焦点集中在has-dropdown容器之后的任何元素上,而不仅仅是后代吗?如果您要更新HTML并在下拉菜单之后添加更多可聚焦元素(例如输入),则为原因。当从has-dropdown容器中的最后一个可聚焦元素到输入之间切换时,下拉菜单保持活动状态。仅当下拉列表是DOM中的最后一个元素时,它才起作用,并且仅在完全失去对DOM的焦点时才触发。

1 个答案:

答案 0 :(得分:1)

您的代码已经差不多了-但我认为需要更加清楚focusout与本机不可聚焦元素(即div,p)及其后代的工作方式,成为可聚焦的元素(输入,锚点)。

当容器绑定到包含可聚焦元素的focusout事件时,{strong}每次都会触发focusout事件它的任何可聚焦子元素都失去焦点-这可能通过键盘导航或单击另一个子元素或容器本身。我设置了一个小提琴来演示这一点:https://jsfiddle.net/darshanags/v5gk2cz8/-每次容器获得焦点或失去焦点时,都会发出控制台消息。

问:那么,为什么菜单隐藏在问题中给出的示例中?

A:当您单击按钮时,该菜单将变为可见,单击该按钮可使按钮将焦点对准自身。但是,一旦您单击菜单或菜单元素内的锚点,该按钮就会失去焦点-这又触发了父元素的'focusout'事件并导致菜单隐藏。发生这种情况是因为focusout事件支持事件冒泡。

问:我们如何解决这个问题?

A1.1:我们通过给它一个tabindex来使父元素可聚焦:

<div class="has-dropdown" tabindex="0">

这解决了一些重要的事情:

  1. div元素提供焦点轮廓-依次增强元素的可访问性。
  2. 帮助点击事件在div元素上注册与焦点相关的事件-在本例中为focusout。如果省略了tabindex,我们将需要添加其他JavaScript来弥补丢失的功能。

A1.2:在focusout事件处理程序中,我们检查获得焦点的元素是否是父元素的后代,如果是,则不删除{{1} }菜单中的课程。

完整的示例代码:

is-active
$('.has-dropdown').off().on('click', '.js-dropdown-trigger', (event) => {
  const $dropdown = $(event.currentTarget).next('.dropdown');

  if (!$dropdown.hasClass('is-active')) {
    $dropdown.addClass('is-active');
  } else {
    $dropdown.removeClass('is-active');
  }
});

$('.has-dropdown').on('focusout', function(event) {
  const $reltarget = $(event.relatedTarget);
  const $currenttarget = $(event.currentTarget);
  const $dropdown = $currenttarget.children('.dropdown');

  // remove 'is-active' class only if the element
  // that is gaining focus is not a child of the parent.
  // parent = div.has-dropdown
  if (!$reltarget.closest('.has-dropdown').is($currenttarget)) {
    $dropdown.removeClass('is-active');
  }

});
.has-dropdown {
  display: inline-flex;
  position: relative;
}

.dropdown {
  background-color: #eee;
  border: 1px solid #999;
  display: none;
  flex-direction: column;
  position: absolute;
  top: 100%;
  left: 0;
  width: 300px;
  margin-top: 5px;
}

.dropdown.is-active {
  display: flex;
}

.dropdown__item {
  padding: 10px;
}

.dropdown__divider {
  border-bottom: 1px solid #999;
}

我已经分叉了原始的小提琴,并对其进行了修改以显示其实际效果。您可以在这里找到经过修改的小提琴:http://jsfiddle.net/darshanags/60jnusvk/

其他有用信息:

我使用<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div class="has-dropdown" tabindex="0"> <button class="js-dropdown-trigger"> Dropdown </button> <div class="dropdown"> <div class="dropdown__item"> Some random text with a <a href="#" class="stop-propagation">link</a> in it. </div> <div class="dropdown__divider"></div> <div class="dropdown__item"> <a href="#">Item One</a> </div> <div class="dropdown__item"> <a href="#">Item Two</a> </div> <div class="dropdown__item"> <a href="#">Item Three</a> </div> </div> </div> <div class="has-dropdown" tabindex="0"> <button class="js-dropdown-trigger"> Dropdown </button> <div class="dropdown"> <div class="dropdown__item"> Some random text with a <a href="#" class="stop-propagation">link</a> in it. </div> <div class="dropdown__divider"></div> <div class="dropdown__item"> <a href="#">Item One</a> </div> <div class="dropdown__item"> <a href="#">Item Two</a> </div> <div class="dropdown__item"> <a href="#">Item Three</a> </div> </div> </div> <input name="tf" type="text"/>来确定每次触发event.relatedTarget事件时都会获得焦点的元素。有关focusout的更多信息,请参见:https://api.jquery.com/event.relatedTarget/

更新

我已经重构了一些原始代码,现在更加简单了:http://jsfiddle.net/darshanags/60jnusvk/24/。我将把这个小提琴和原始的东西留作参考。