开发者

jQuery figuring out if parent has lost 'focus'

I'm stuck on figuring out the logic to make a drop down menu keyboard accessible.

The HTML is structured as such (extra class names used for clarity):

<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>

Link 1 and Link 2, when hovered, will show the sub-menu lists (pull down menu). I have this working just fine with some jQuery and the jQuery hoverIntent plugin.

The catch is that this only works with the mouse at the moment.

Next challenge is to get this to work via the keyboard.

I can easily add a focus event to the top level links that then trigger the secondary menus:

$('ul.primaryMenuItem a:first').focus([call showMenu function]) 

That works fine.

To close the menu, one option is to, when opening another menu, check to see if there is another open already and, if so, close it.

That also works fine.

Where that fails, however, is if you have the last menu open, and tab out of it. Since you haven't tabbed into another menu, this one stays open.

The challenge is to figure out how/when to close the menu and the logic needed (jQuery) to figure it out. Ideally, I'd close the menu when the focus is on an element on the page OTHER than any of 开发者_Python百科the menu's child elements.

Logically, I'm looking for this:

$('li.primaryMenuItem').blur([close $(this).find('ul.popUpMenu'))

However, you can't do that, since the LI doesn't actually have focus, but rather the anchor tag within it.

Any suggestions?

UPDATE:

perhaps a better/simpler way to ask the question:

Via jQuery, is there a way to 'watch' to see if focus has moved outside of all children of a particular object?


You can use event bubbling to check what has focus on the focusin event. I had success with the following code:


$("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');
    }
  });

You could(should) probably make it more optimized, but it works.


Use the new jquery 1.4 functions: focusin and focusout instead of blur and focus. Here's how focusout differs:

The focusout event is sent to an element when it, or any element inside of it, loses focus. This is distinct from the blur event in that it supports detecting the loss of focus from parent elements (in other words, it supports events bubbling).


How about if you do the following:

$('#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
});


Try this

$('li.primaryMenuItem:last li a:last').blur([do whatever you need to do])

Logically, if your user tabs out he must have been focusing the last anchor.

You could even set up your own event handler like so:

$('li.primaryMenuItem:last').bind('myblur', function() ...);

and call it within the last anchors blur event:

...blur(function() {
    $(this).parents('li.primaryMenuItem').trigger('myblur'); ...


This helped me... http://plugins.jquery.com/project/focus

It will detect if you're still within the parent automatically. It basically changes jQuery focusout to work this way instead, which I feel is how it should work.

<div class="parent">
   <input type="text" />
   <input type="text" />
</div>

$('#parent').focusout(function () {
    console.log('focusout of parent');
});

I don't see why pressing tab to move textfield between the child elements should trigger focusout on the parent because you're still within that parent. Something must be happening that takes you out of it for a moment and I suspect it's a bug... anyone with me on this? Well anyway the plugin above fixes it. Just include it before your code to 'fix' this. Would love someone to explain why this isn't a bug if it isn't.

Thanks, Dom


I had a similar issue... I created a jsfiddle to determine when a parent fieldset loses focus and then calling a function. It could certainly be optimized, but it's a start.

http://jsfiddle.net/EKhLc/10/

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


I came up with this recipe (vanilla JS) which does exactly what the OP is asking for (provide a way to listen to "focus moves outside a container element") and is generic enough to work for any use case.

In OP's example, it would be used like this:

for (const primaryMenu of Array.from($('.primaryMenuItem'))) {
  onFocusOutsideOf(primaryMenu, () => closeMenu(primaryMenu));
}

Here is the code for 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;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜