2010-02-03 17 views
6

Estoy atascado en descifrar la lógica para hacer accesible un teclado de menú desplegable.jQuery averiguando si parent ha perdido 'focus'

El HTML está estructurado como tal (nombres de clase extra que se usa para mayor claridad):

<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 y Link 2, cuando se cernía, mostrará las listas de sub-menú (menú desplegable). Tengo esto funcionando bien con algunos jQuery y el jQuery hoverIntent plugin.

El problema es que esto solo funciona con el mouse en este momento.

El siguiente reto es hacer que esto funcione a través del teclado.

puedo añadir fácilmente un evento de foco a los enlaces de nivel superior, que luego desencadenan los menús secundarios:

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

que funciona bien.

Para cerrar el menú, una opción es, al abrir otro menú, verificar si ya hay otro abierto y, de ser así, cerrarlo.

Eso también funciona bien.

Donde eso falla, sin embargo, es si tiene el último menú abierto, y la tabulación fuera de él. Como no has tabulado en otro menú, este permanece abierto.

El desafío es descubrir cómo/cuándo cerrar el menú y la lógica necesaria (jQuery) para resolverlo. Idealmente, cerraría el menú cuando el foco esté en un elemento de la página OTROS que cualquiera de los elementos secundarios del menú.

Lógicamente, estoy buscando para ello:

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

Sin embargo, no se puede hacer eso, ya que el LI no tiene realmente el foco, sino más bien la etiqueta de anclaje dentro de ella.

¿Alguna sugerencia?

ACTUALIZACIÓN:

tal vez una manera mejor/más fácil de hacer la pregunta:

Via jQuery, ¿hay una forma de 'reloj' para ver si el foco se ha movido fuera de todos los hijos de un objeto particular ?

+0

¿Hay un error tipográfico? '$ ('ul.primaryMenuItem a: first'). focus ([call showMenu function])' -> '$ ('li.primaryMenuItem a: first'). focus ...' – superjos

Respuesta

2

Utilice las nuevas funciones de jquery 1.4: focusin y focusout en lugar de blur y focus. Así es como focusout difiere:

El evento focusOut se envía a un elemento cuando ésta, o cualquier elemento dentro de de ella, pierde el foco. Esto es distinto del evento de desenfoque en el que admite la detección de la pérdida de foco desde elementos principales (en otras palabras, admite el burbujeo de eventos).

+0

@Keltex Miré eso . Sin embargo, eso no es lo que necesito. Quiero saber si el contenedor padre ha perdido el foco. el enfoque se disparará si CUALQUIERA de los elementos secundarios pierde el foco. Lo que significa que tabular desde el submenú al submenú activará ese evento. Necesito algo similar al evento de tipo 'tiene la persona con pestañas fuera del contenedor'. –

0

probar este

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

Lógicamente, si las pestañas de usuario que él debe haber estado centrando la última ancla.

Incluso se puede configurar su propio controlador de eventos, así:

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

y lo llaman en los últimos anclajes caso desenfoque:

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

El problema es que hay muchas maneras de salir del menú antes de tabular el último elemento. Por ejemplo, podría estar desplazándose hacia atrás, lo que significa que podría eliminar el último elemento, pero aún así estar en el mismo menú. Además, uno podría usar un comando de teclado o un clic del mouse para desenfocar a la mitad del menú. –

6

Puede utilizar la propagación de eventos para comprobar lo que tiene el foco en el evento focusin. Yo tenía éxito con el siguiente código:


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

Se podría (debería) probablemente que sea más optimizado, pero funciona.

+0

¡Interesante! Sin embargo, me siento extraño al conectar un controlador de eventos al cuerpo y a todos los elementos secundarios del mismo. ¿Hay algún tipo de problema de rendimiento haciendo eso? En definitiva, tu solución está 'en cada foco, mira si está en el menú. Si no, ciérrelo '. Lo cual ciertamente tiene sentido. –

+0

Bueno, activará el evento de enfoque en cualquier momento que ocurra un evento de enfoque dentro del cuerpo, pero el enfoque generalmente no cambia muy rápidamente, y habrá un número limitado de elementos que pueden ser el objetivo de un evento de enfoque (enlaces/elementos de la forma) así que personalmente no creo que llamar a esta comparación en cada evento de enfoque afectará demasiado el rendimiento. Podría tratar de optimizar la comparación (no estoy seguro de cuál es más rápido $ (e.target) .is ('ul.popUpMenu li a') o ejemplo), y debe almacenar en caché la consulta del elemento. Si el rendimiento es un problema real, debe ejecutar algunos puntos de referencia para verificar el impacto. – emmychan

+2

focusin no se activará en algunos navegadores para la etiqueta de cuerpo (u otras muchas etiquetas). Establecer el tabindex en -1 parece solucionarlo y hace que esta solución encaje bien: $ ("cuerpo"). Attr ("tabindex", -1); – John

0

Esto me ayudó ... http://plugins.jquery.com/project/focus

Se detectará si usted todavía está dentro de la matriz de forma automática. Básicamente, el enfoque de jQuery cambia para funcionar de esta manera, lo que creo es cómo debería funcionar.

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

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

no veo por qué pestaña apremiante para mover el campo de texto entre los elementos secundarios deben dar lugar a focusOut en la matriz porque estás todavía dentro de ese padre. Algo debe estar pasando que te saca por un momento y sospecho que es un error ... ¿alguien conmigo en esto? Bueno, de todos modos, el complemento anterior lo arregla. Simplemente inclúyelo antes de tu código para 'arreglarlo'. Me encantaría que alguien explicara por qué esto no es un error si no lo es.

Gracias, Dom

2

¿Qué tal si lo hace lo siguiente:

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

+1 Esto me ayudó a ir en la dirección correcta. No veo por qué el segundo selector ('#link_A_id> *') sería necesario y no lo utilicé. También tuve que ajustar la declaración if en un tiempo de espera porque el elemento 'body' * roba * el foco antes de que el siguiente elemento gane el foco. – toxalot

0

tuve un problema similar ... He creado un jsFiddle para determinar cuando un grupo de campos padre pierde el foco y luego llamar a una función. Sin duda podría ser optimizado, pero es un comienzo.

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