Haga clic fuera del menú para cerrar en jquery

107

Así que tengo un menú desplegable que se muestra con un clic, según los requisitos comerciales. El menú se vuelve a ocultar después de alejar el mouse de él.

Pero ahora se me pide que lo mantenga en su lugar hasta que el usuario haga clic en cualquier parte del documento. ¿Cómo se puede lograr esto?

Esta es una versión simplificada de lo que tengo ahora:

$(document).ready(function() {
  $("ul.opMenu li").click(function(){
   $('#MainOptSubMenu',this).css('visibility', 'visible');
  });

  $("ul.opMenu li").mouseleave(function(){
      $('#MainOptSubMenu',this).css('visibility', 'hidden');
  });
});



<ul  class="opMenu">
  <li id="footwo" class="">
    <span id="optImg" style="display: inline-block;"> <img src="http://localhost.vmsinfo.com:8002/insight/images/options-hover2.gif"/> </span>
      <ul id="MainOptSubMenu" style="visibility: hidden; top: 25px; border-top: 0px solid rgb(217, 228, 250); background-color: rgb(217, 228, 250); padding-bottom: 15px;">
        <li>some</li>
       <li>nav</li>
       <li>links</li>
       </ul>
    </li>
</ul> 

Intenté algo como esto $('document[id!=MainOptSubMenu]').click(function()pensando que se activaría en cualquier cosa que no fuera el menú, pero no funcionó.

Jorge
fuente

Respuestas:

196

Eche un vistazo al enfoque que utilizó esta pregunta:

¿Cómo detecto un clic fuera de un elemento?

Adjunte un evento de clic al cuerpo del documento que cierra la ventana. Adjunte un evento de clic separado a la ventana que detiene la propagación al cuerpo del documento.
$('html').click(function() {
  //Hide the menus if visible
});

$('#menucontainer').click(function(event){
    event.stopPropagation();
});

Jon W
fuente
16
es muy bonito, pero debes usar $ ('html'). click () no body. El cuerpo siempre tiene la altura de su contenido. Si no hay mucho contenido o la pantalla es muy alta, solo funciona en la parte que ocupa el cuerpo. Copia de: stackoverflow.com/questions/152975/… - meo 25 de febrero de
2011
gran solucion. ¿Alguna idea de por qué funciona con .click () y no con .live ('click', func ...?
Hat
3
El único problema con este enfoque es que los objetos que ya tienen un detector de clics que detienen la propagación por otras razones no tienen ningún efecto. Entiendo por qué es así, pero sigue siendo una limitación de esta solución.
DanH
53

La respuesta es correcta, pero agregará un oyente que se activará cada vez que se produzca un clic en su página. Para evitar eso, puede agregar el oyente solo una vez:

$('a#menu-link').on('click', function(e) {
    e.preventDefault();
    e.stopPropagation();

    $('#menu').toggleClass('open');

    $(document).one('click', function closeMenu (e){
        if($('#menu').has(e.target).length === 0){
            $('#menu').removeClass('open');
        } else {
            $(document).one('click', closeMenu);
        }
    });
});

Editar: si desea evitar el stopPropagation()botón inicial, puede usar este

var $menu = $('#menu');

$('a#menu-link').on('click', function(e) {
    e.preventDefault();

    if (!$menu.hasClass('active')) {
        $menu.addClass('active');

        $(document).one('click', function closeTooltip(e) {
            if ($menu.has(e.target).length === 0 && $('a#menu-link').has(e.target).length === 0) {
                $menu.removeClass('active');
            } else if ($menu.hasClass('active')) {
                $(document).one('click', closeTooltip);
            }
        });
    } else {
        $menu.removeClass('active');
    }
});
adriendenat
fuente
Veo mucho código como este, ¿no agregaría esto una nueva función de clic al documento cada vez que se hace clic en el enlace del menú? Entonces, si hace clic en el enlace del menú 1000 veces sin actualizar la página, ¿tendrá 1000 funciones ocupando memoria y también ejecutándose?
eselk
No importa, no vi la función "uno", ahora entiendo por qué funciona: api.jquery.com/one
eselk
2
buena solución, pero tuve que usar e.stopPropagation()en Chrome para evitar que el primer evento se disparara en el one()controlador
antfx
18

Las stopPropagationopciones son malas porque pueden interferir con otros controladores de eventos, incluidos otros menús que podrían haber adjuntado controladores cercanos al elemento HTML.

Aquí hay una solución simple basada en la respuesta de user2989143 :

$('html').click(function(event) {
    if ($(event.target).closest('#menu-container, #menu-activator').length === 0) {
        $('#menu-container').hide();
    }
});
Comandante de código
fuente
17

Si el uso de un complemento está bien en su caso, le sugiero que el complemento clickoutside de Ben Alman se encuentra aquí :

su uso es tan simple como esto:

$('#menu').bind('clickoutside', function (event) {
    $(this).hide();
});

espero que esto ayude.

Hachís subliminal
fuente
8

2 opciones que puedes investigar:

  • Al mostrar el menú, coloque un DIV vacío grande detrás de él que cubra el resto de la página y dele un evento al hacer clic para cerrar el menú (y a sí mismo). Esto es similar a los métodos utilizados con las cajas de luz donde al hacer clic en el fondo se cierra la caja de luz.
  • Al mostrar el menú, adjunte un controlador de eventos de un solo clic en el cuerpo que cierra el menú. Usas '.one ()' de jQuery para esto.
DA.
fuente
6

He encontrado una variante de la solución de Grsmto y solución de Dennis fijo mi problema.

$(".MainNavContainer").click(function (event) {
    //event.preventDefault();  // Might cause problems depending on implementation
    event.stopPropagation();

    $(document).one('click', function (e) {
        if(!$(e.target).is('.MainNavContainer')) {
            // code to hide menus
        }
    });
});
cat5dev
fuente
5

Utilizo esta solución con múltiples elementos con el mismo comportamiento en la misma página:

$("html").click(function(event){
    var otarget = $(event.target);
    if (!otarget.parents('#id_of element').length && otarget.attr('id')!="id_of element" && !otarget.parents('#id_of_activator').length) {
        $('#id_of element').hide();
    }
});

stopPropagation () es una mala idea, esto rompe el comportamiento estándar de muchas cosas, incluidos los botones y enlaces.

usuario2989143
fuente
Esto es genial, pero puede simplificarlo como$target.closest('#menu-container, #menu-activator').length === 0
Code Commander
5

Recientemente me he enfrentado al mismo problema. Escribí el siguiente código:

    $('html').click(function(e) {
      var a = e.target;
      if ($(a).parents('.menu_container').length === 0) {
        $('.ofSubLevelLinks').removeClass('active'); //hide menu item
        $('.menu_container li > img').hide(); //hide dropdown image, if any
     }
    });

Me ha funcionado perfectamente.

Namish
fuente
4

que hay de esto

    $(this).mouseleave(function(){  
        var thisUI = $(this);
        $('html').click(function(){
                thisUI.hide();
            $('html').unbind('click');
         });
     });
cgfix
fuente
3

Encuentro más útil usar mousedown-event en lugar de click-event. El evento de clic no funciona si el usuario hace clic en otros elementos de la página con eventos de clic. En combinación con el método one () de jQuery, se ve así:

$("ul.opMenu li").click(function(event){

   //event.stopPropagation(); not required any more
   $('#MainOptSubMenu').show();

   // add one mousedown event to html
   $('html').one('mousedown', function(){
       $('#MainOptSubMenu').hide();
   });
});

// mousedown must not be triggered inside menu
$("ul.opMenu li").bind('mousedown', function(evt){
    evt.stopPropagation();
});
yeeno
fuente
3

Incluso me encontré con la misma situación y uno de mis mentores me expuso esta idea.

paso: 1 al hacer clic en el botón en el que deberíamos mostrar el menú desplegable. luego agregue el siguiente nombre de clase "more_wrap_background" a la página activa actual como se muestra a continuación

$('.ui-page-active').append("<div class='more_wrap_background' id='more-wrap-bg'> </div>");

paso 2 luego agregue un clic para la etiqueta div como

$(document).on('click', '#more-wrap-bg', hideDropDown);

donde hideDropDown es la función que se llamará para ocultar el menú desplegable

El paso 3 y un paso importante mientras oculta el menú desplegable es eliminar esa clase que agregó anteriormente como

$('#more-wrap-bg').remove();

Estoy eliminando usando su identificación en el código anterior

.more_wrap_background {
  top: 0;
  padding: 0;
  margin: 0;
  background: rgba(0, 0, 0, 0.1);
  position: fixed;
  display: block;
  width: 100% !important;
  z-index: 999;//should be one less than the drop down menu's z-index
  height: 100% !important;
}
Abhimaan
fuente
2
$("html").click( onOutsideClick );
onOutsideClick = function( e )
{
    var t = $( e.target );
    if ( !(
        t.is("#mymenu" ) ||     //Where #mymenu - is a div container of your menu
        t.parents( "#mymenu" ).length > 0
        )   )
    {
        //TODO: hide your menu
    }
};

Y es mejor configurar el oyente solo cuando su menú está visible y siempre eliminar el oyente después de que el menú se oculte.

Олег Всильдеревьев
fuente
1

Creo que necesitas algo como esto: http://jsfiddle.net/BeenYoung/BXaqW/3/

$(document).ready(function() {
  $("ul.opMenu li").each(function(){
      $(this).click(function(){
            if($(this).hasClass('opened')==false){          
                $('.opMenu').find('.opened').removeClass('opened').find('ul').slideUp();
                $(this).addClass('opened'); 
                $(this).find("ul").slideDown();
            }else{
                $(this).removeClass('opened'); 
                $(this).find("ul").slideUp();               
            }
      });
  });    
});

¡Espero que te sea de utilidad!

Sakata Gintoki
fuente
1

Utilice el selector ': visible'. Donde .menuitem es el (los) elemento (s) a ocultar ...

$('body').click(function(){
  $('.menuitem:visible').hide('fast');
});

O si ya tiene el (los) elemento (s) .menuitem en una var ...

var menitems = $('.menuitem');
$('body').click(function(){
  menuitems.filter(':visible').hide('fast');
});
ekerner
fuente
0

No tiene por qué ser complicado.

$(document).on('click', function() {
    $("#menu:not(:hover)").hide();
});
Jonathan Gaudé
fuente