Friday, June 1, 2012

How to detect a click outside an element?


I have some HTML menus, which I show completely when a user clicks on the head of these menus. I would like to hide these elements when the user clicks outside the menus' area.



Is something like this possible with jQuery?




$("#menuscontainer").clickOutsideThisElement(function() {
// hide the menus
});


Source: Tips4all

22 comments:

  1. Attach a click event to the document body which closes the window. Attach a separate click event to the window which stops propagation to the document body.

    $('html').click(function() {
    //Hide the menus if visible
    });

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

    ReplyDelete
  2. I have an application that works similarly to Eran's example, except I attach the click event to the body when I open the menu... Kinda like this:

    $('#menucontainer').click(function(event) {
    $('body').one('click',function() {
    // Hide the menus
    });

    event.stopPropagation();
    });


    More information on jQuery's one() function

    ReplyDelete
  3. You can hook up to the click event of document and then checking whether clicked element is an ancestor of menucontainer.

    If it is not, then element outside of the #menucontainer is clicked and you can safely hide it.

    $(document).click(function(event) {
    if($(event.target).parents().index($('#menucontainer')) == -1) {
    if($('#menucontainer').is(":visible")) {
    $('#menucontainer').hide()
    }
    }
    })


    Hope it helps.

    ReplyDelete
  4. The other solutions here didn't work for me so I had to use:

    if(!$(event.target).is('#foo'))
    {
    // hide menu
    }

    ReplyDelete
  5. Now there is a plugin for that: outside events (blog post)

    The following happens when a clickoutside handler (WLOG) is bound to an element:


    the element is added to an array which holds all elements with clickoutside handlers
    a (namespaced) click handler is bound to the document (if not already there)
    on any click in the document, the clickoutside event is triggered for those elements in that array that are not equal to or a parent of the click-events target
    additionally, the event.target for the clickoutside event is set to the element the user clicked on (so you even know what the user clicked, not just that he clicked outside)


    So no events are stopped from propagation and additional click handlers may be used "above" the element with the outside-handler.

    ReplyDelete
  6. I don't think what you really need is to close the menu when the user clicks outside; what you need is for the menu to close when the user clicks anywhere at all on the page. If you click on the menu, or off the menu it should close right?

    Finding no satisfactory answers above prompted me to write this blog post the other day. For the more pedantic, there are a number of gotchas to take note of:


    If you attach a click event handler to the body element at click time be sure to wait for the 2nd click before closing the menu, and unbinding the event. Otherwise the click event that opened the menu will bubble up to the listener that has to close the menu.
    If you use event.stopPropogation() on a click event, no other elements in your page can have a click-anywhere-to-close feature.
    Attaching a click event handler to the body element indefinitely is not a performant solution
    Comparing the target of the event, and its parents to the handler's creator assumes that what you want is to close the menu when you click off it, when what you really want is to close it when you click anywhere on the page.
    Listening for events on the body element will make your code more brittle. Styling as innocent as this would break it: body { margin-left:auto; margin-right: auto; width:960px;}

    ReplyDelete
  7. Check the window click event target (it should propagate to the window, as long as it's not captured anywhere else), and ensure that it's not any of the menu elements. If it's not, then you're outside your menu.

    Or check the position of the click, and see if it's contained within the menu area.

    ReplyDelete
  8. $("#menuscontainer").click(function() {
    $(this).focus();
    });
    $("#menuscontainer").blur(function(){
    $(this).hide();
    });


    Works for me just fine.

    ReplyDelete
  9. Attach a click event to the document which closes the window. Attaching it to the body only attaches an event to how far the page flows vertically. I used Eran's solution originally but it didn't work for me since my page was very short vertically. Attach a separate click event to the window which stops propagation to the document itself.

    $(document).click(function() {
    //Hide the menus if visible
    });

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

    ReplyDelete
  10. var go=false;
    $(document).click(function(){
    if(go){
    $('#divID').hide();go=false;}
    })
    $("#divID").mouseover(function(){
    go=false;
    });
    $("#divID").mouseout(function (){
    go=true;
    });

    $("btnID").click( function(){
    if($("#divID:visible").length==1) $("#divID").hide(); //toggle
    $("#divID").show();
    });

    ReplyDelete
  11. If you are scripting for IE and FF 3.* and you just want to know if the click occured within a certain box area, you could also use something like:

    this.outsideElementClick = function(objEvent, objElement){
    var objCurrentElement = objEvent.target || objEvent.srcElement;
    var blnInsideX = false;
    var blnInsideY = false;

    if (objCurrentElement.getBoundingClientRect().left >= objElement.getBoundingClientRect().left && objCurrentElement.getBoundingClientRect().right <= objElement.getBoundingClientRect().right)
    blnInsideX = true;

    if (objCurrentElement.getBoundingClientRect().top >= objElement.getBoundingClientRect().top && objCurrentElement.getBoundingClientRect().bottom <= objElement.getBoundingClientRect().bottom)
    blnInsideY = true;

    if (blnInsideX && blnInsideY)
    return false;
    else
    return true;}

    ReplyDelete
  12. function:

    $(function() {
    debugger;
    $.fn.click_inout = function(clickin_handler, clickout_handler) {
    var item = this;
    var is_me = false;
    item.click(function(event) {
    clickin_handler(event);
    is_me = true;
    });
    $(document).click(function(event) {
    if (is_me) {
    is_me = false;
    } else {
    clickout_handler(event);
    }
    });
    return this;
    }
    });


    usage:

    this.input = $('<input>')
    .click_inout(
    function(event) { me.ShowTree(event); },
    function() { me.Hide(); }
    )
    .appendTo(this.node);


    and functions are very simple:

    ShowTree: function(event) {
    this.data_span.show();
    }
    Hide: function() {
    this.data_span.hide();
    }

    ReplyDelete
  13. I've had success with something like this:

    var $menuscontainer = ...;

    $('#trigger').click(function() {
    $menuscontainer.show();

    $('body').click(function(event) {
    var $target = $(event.target);

    if ($target.parents('#menuscontainer').length == 0) {
    $menuscontainer.hide();
    }
    });
    });


    The logic is: when #menuscontainer is shown, bind a click handler to body that hides #menuscontainer only if the target (of the click) isn't a child of #menuscontainer.

    ReplyDelete
  14. Found this method in some jquery calendar plugin.

    function ClickOutsideCheck(e)
    {
    var el = e.target;
    var popup = $('.popup:visible')[0];
    if(popup==undefined) return true;

    while (true){
    if (el == popup ) {
    return true;
    } else if (el == document) {
    $(".popup").hide();
    return false;
    } else {
    el = $(el).parent()[0];
    }
    }
    };

    $(document).bind('mousedown.popup', ClickOutsideCheck);

    ReplyDelete
  15. This worked perfectly fine in time for me :

    $('body').click(function() {
    // Hide the menus if visible.
    });


    Thanks very much!

    ReplyDelete
  16. $(document).click(function() {
    $(".overlay-window").hide();
    });
    $(".overlay-window").click(function() {
    return false;
    });


    If you click on the document, hide a given element, unless you click on that same element.

    ReplyDelete
  17. As another poster said there are a lot of gotchas, especially if the element you are displaying (in this case a menu) has interactive elements.
    I've found the following method to be fairly robust:

    $('#menuscontainer').click(function(event) {
    //your code that shows the menus fully

    //now set up an event listener so that clicking anywhere outside will close the menu
    $('html').click(function(event) {
    //check up the tree of the click target to check whether user has clicked outside of menu
    if ($(event.target).parents('#menuscontainer').length==0) {
    // your code to hide menu

    //this event listener has done its job so we can unbind it.
    $(this).unbind(event);
    }

    })
    });

    ReplyDelete
  18. I did like this in YUI3:

    // detect the click anywhere other than overlay element to close it.
    Y.one(document).on('click', function(e) {

    if(e.target.ancestor('#overlay')=== null && e.target.get('id') != 'show' && overlay.get('visible') == true) {
    overlay.hide();
    }

    });


    I am checking if ancestor is not the widget element container,
    if target is not which open the widget/element,
    if widget/element I want to close is already open (not that important).

    ReplyDelete
  19. Here is my code:

    // listen to every clicks
    $('html').click(function(event) {
    if ( $('#mypopupmenu').is(':visible') ) {
    if (event.target.id != 'click_this_to_show_mypopupmenu') {
    $('#mypopupmenu').hide();
    }
    }
    });

    // listen to selector's clicks
    $('#click_this_to_show_mypopupmenu').click(function() {
    // if the menu is visible, and you clicked the selector again we need to hide
    if ( $('#mypopupmenu').is(':visible') {
    $('#mypopupmenu').hide();
    return true;
    }

    // else we need to show the popup menu
    $('#mypopupmenu').show();
    });

    ReplyDelete
  20. This is my solution to this problem:

    $(document).ready(function() {
    $('#user-toggle').click(function(e) {
    $('#user-nav').toggle();
    e.stopPropagation();
    });

    $('body').click(function() {
    $('#user-nav').hide();
    });

    $('#user-nav').click(function(e){
    e.stopPropagation();
    });
    });

    ReplyDelete
  21. jQuery().ready(function(){
    $('#nav').click(function (event) {
    $(this).addClass('activ');
    event.stopPropagation();
    });

    $('html').click(function () {
    if( $('#nav').hasClass('activ') ){
    $('#nav').removeClass('activ');
    }
    });
    });

    ReplyDelete
  22. Hook a click event listener on the document. Inside the event listener, you can look at the event object, in particular, the event.target to see what element was clicked:

    $(document).click(function(e){
    if ($(e.target).closest("#menuscontainer").length == 0) {
    // .closest can help you determine if the element
    // or one of its ancestors is #menuscontainer
    console.log("hide");
    }
    });

    ReplyDelete