2010-02-03 10 views
6

Sono bloccato a capire la logica per rendere accessibile un menu a discesa.jQuery capendo se il genitore ha perso 'focus'

Il codice HTML è strutturato come tali (i nomi delle classi più utilizzati per chiarezza):

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

collegamento 1 e Link 2, quando aleggiava, mostrerà le liste sotto-menu (menu a discesa). Ho questo funziona bene con alcuni jQuery e il plugin hoverIntent jQuery.

Il problema è che funziona solo con il mouse al momento.

La prossima sfida è far funzionare tutto ciò tramite la tastiera.

posso facilmente aggiungere un evento di messa a fuoco per i collegamenti di alto livello che poi scatenano i menu secondari:

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

che funziona bene.

Per chiudere il menu, un'opzione è, quando si apre un altro menu, controllare se c'è già un altro aperto e, in tal caso, chiuderlo.

Anche questo funziona correttamente.

Se ciò non riesce, tuttavia, è se l'ultimo menu è aperto e viene escluso. Dato che non hai inserito un altro menu, questo rimane aperto.

La sfida è capire come/quando chiudere il menu e la logica necessaria (jQuery) per capirlo. Idealmente, chiuderei il menu quando l'elemento attivo è su un elemento nella pagina AL DI FUORI di qualsiasi elemento figlio del menu.

Logicamente, sto cercando questo:

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

Tuttavia, non è possibile farlo, dal momento che la LI in realtà non hanno messa a fuoco, ma piuttosto il tag di ancoraggio all'interno di esso.

Qualche suggerimento?

UPDATE:

forse un/modo migliore più semplice di porre la domanda:

Via jQuery, c'è un modo per 'orologio' per vedere se focus si è spostato al di fuori di tutti i figli di un particolare oggetto ?

+0

C'è un errore di battitura? '$ ('ul.primaryMenuItem a: first'). focus ([chiama showMenu function])' -> '$ ('li.primaryMenuItem a: first'). focus ...' – superjos

risposta

2

Utilizzare le nuove funzioni jquery 1.4: focusin e focusout anziché blur e focus. Ecco come focusout differisce:

L'evento focusOut viene inviato ad un elemento quando, o qualsiasi elemento all'interno di esso, non è più attivo. Si tratta di distinto dall'evento di sfocatura in quanto supporta il rilevamento della perdita di messa a fuoco dagli elementi padre (in altre parole, supporta eventi che ribolliscono).

+0

@Keltex Ho visto che . Tuttavia, non è quello di cui ho bisogno. Voglio sapere se il contenitore genitore ha perso il focus. il focusout si innesca se QUALSIASI degli elementi del bambino perde la concentrazione. Ciò significa che la tabulazione dal sottomenu al sottomenu attiverà quell'evento. Ho bisogno di qualcosa sulla falsariga dell'evento di tipo "ha la persona esclusa dal contenitore". –

0

Prova questa

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

Logicamente, se le schede di utente fuori che devono essere state concentrando l'ultimo ancoraggio.

Si potrebbe anche impostare il proprio gestore di eventi in questo modo:

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

e lo chiamano negli ultimi tasselli sfocatura evento:

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

Il problema è che ci sono molti modi per lasciare il menu prima di uscire dall'ultimo elemento. Ad esempio, potresti eseguire il tabging all'indietro, il che significa che puoi escludere l'ultimo elemento, ma essere sempre nello stesso menu. Inoltre, è possibile utilizzare un comando da tastiera o un clic del mouse per sfocare a metà del menu. –

6

È possibile utilizzare bubbling degli eventi per controllare ciò che ha il focus sull'evento focusin. Ho avuto successo con il seguente codice:


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

Si potrebbe (dovrebbe) probabilmente rendere più ottimizzato, ma funziona.

+0

Interessante! Mi sembra strano, tuttavia, associare un gestore di eventi al corpo e tutti gli elementi figlio di esso. C'è qualche tipo di problema di prestazioni che lo fa? In definitiva, la tua soluzione è 'su ogni messa a fuoco, vedere se è nel menu. In caso contrario, chiudilo '. Che certamente ha un senso. –

+0

Beh, attiverà l'evento focusin ogni volta che si verifica un evento di messa a fuoco all'interno del corpo, ma di solito la messa a fuoco non cambia molto rapidamente e ci sarà un numero limitato di elementi che possono essere il bersaglio di un evento di messa a fuoco (link/form elements) quindi personalmente non penso che chiamare questo confronto su ogni evento di focus possa influire troppo sulle prestazioni. Potresti provare a ottimizzare il confronto (non sono sicuro quale sia più veloce $ (e.target) .is ('ul.popUpMenu li a') o esempio), e dovresti memorizzare nella cache la query dell'elemento. Se le prestazioni sono un problema reale, è necessario eseguire alcuni benchmark per verificare l'impatto. – emmychan

+2

focusin non attiva in alcuni browser il tag body (o molti altri tag). L'impostazione del tabindex su -1 sembra risolvere il problema e rende questa soluzione un buon adattamento: $ ("body"). Attr ("tabindex", -1); – John

0

Questo mi ha aiutato ... http://plugins.jquery.com/project/focus

E 'in grado di rilevare se siete ancora all'interno del genitore automaticamente. In pratica cambia il focusout di jQuery in questo modo invece, il che credo sia come dovrebbe funzionare.

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

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

non vedo il motivo per cui scheda pressante per spostarsi campo di testo tra gli elementi figlio dovrebbe attivare focusOut sul genitore perché sei ancora all'interno di quel genitore. Sta succedendo qualcosa che ti toglie di mezzo per un attimo e sospetto che sia un insetto ... qualcuno con me su questo? Bene comunque il plugin sopra lo corregge. Basta includerlo prima del codice per "risolverlo". Mi piacerebbe che qualcuno spiegasse perché questo non è un bug se non lo è.

Grazie, Dom

2

Che ne dite se si fa la seguente:

$('#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 Questo mi ha fatto andare nella giusta direzione. Non vedo perché sarebbe necessario il secondo selettore ('#link_A_id> *') e non l'ho usato. Ho anche dovuto racchiudere l'istruzione if in un timeout perché l'elemento 'body' * ruba * l'attenzione prima che l'elemento successivo ottenga l'attenzione. – toxalot

0

ho avuto un problema simile ... ho creato una jsfiddle per determinare quando un fieldset genitore non è più attivo e quindi chiamando un funzione. Potrebbe certamente essere ottimizzato, ma è un inizio.

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