views:

1186

answers:

2

Similar to this question, but taking it a step further. I would like to detect clicks outside of a set of items, which I am handling in the following way:

$('#menu div').live('click', function() {
    // Close other open menu items, if any.
    // Toggle the clicked menu item.

    $('body').one('click', function(event) {
        // Hide the menu item.
        event.stopPropagation();
    });
});

This works like a charm, unfortunately, when another menu item is open and a second is clicked, it requires two clicks to open the second item. The first click hides the first menu item that was open, the second shows the second menu item.

The "correct" behavior works in the following way:

  • Clicking a menu item opens it.
  • Clicking the same menu item (or it's children) closes it.
  • Clicking another menu item closes the first, opens the second.
  • Clicking away from (open) menu items closes them.

I have tried the following in place of the above $('body').one() order to ignore clicks on menu items with little success:

// Captures click on menu items in spite of the not.
$('*').not('#menu *').one('click', function() { // Hide menu }
$('*:not(#menu)').one('click', function() { // Hide menu }

As always, thanks for any help!

+5  A: 

Just move the body click handler outside and do something like this:

$('body').bind('click', function(e) {
    if($(e.target).closest('#menu').length == 0) {
        // click happened outside of menu, hide any visible menu items
    }
});

It was incorrectly pointed out in the comments that e.target does not work in IE; this is not true as jQuery's Event object fixes these inconsistencies where necessary (IE, Safari).

Paolo Bergantino
What's the purpose of moving the `body` click handler outside of the menu click handler? It appears to accomplish the same task (without the overhead of catching every body click if the menu is never opened) by applying it after a menu item is opened. Or am I missing something?
chuckg
Forgot a big thanks. :)
chuckg
It's a fair point; I consider the overhead of binding and unbinding an event whenever a user clicks on a menu item unnecessary. Usually there won't be much clicking on a page aside from going to another page, in which case the extra click handler is completely irrelevant. At the end of the day both are okay depending on your needs, so you can stick to the one handler if it fits your needs.
Paolo Bergantino
e.target does not work on IE...
Josh Stodola
@Josh, e.target in the above code should use the jQuery event, not the IE-specific event.
Joe Chung
+3  A: 

I wrote about this some time ago: Determine if an Outside Element was Clicked with Javascript

function clickedOutsideElement(elemId) {
    var theElem = getEventTarget(window.event);

    while(theElem != null) {
        if(theElem.id == elemId)
            return false;

        theElem = theElem.offsetParent;
    }

    return true;
}

function getEventTarget(evt) {
    var targ = (evt.target) ? evt.target : evt.srcElement;

    if(targ != null) {
        if(targ.nodeType == 3)
            targ = targ.parentNode;
    }

    return targ;
}

document.onclick = function() {
    if(clickedOutsideElement('divTest'))
        alert('Outside the element!');
    else
        alert('Inside the element!');
}

Of course, this was written before the glory days of jQuery.

Josh Stodola
jQuery fixes e.target to be cross browser, thus rendering all this hoopla unnecessary. :)
Paolo Bergantino
Thanks for the jQuery'less approach, others may find it useful. :)
chuckg