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
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.
ReplyDelete$('html').click(function() {
//Hide the menus if visible
});
$('#menucontainer').click(function(event){
event.stopPropagation();
});
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:
ReplyDelete$('#menucontainer').click(function(event) {
$('body').one('click',function() {
// Hide the menus
});
event.stopPropagation();
});
More information on jQuery's one() function
You can hook up to the click event of document and then checking whether clicked element is an ancestor of menucontainer.
ReplyDeleteIf 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.
The other solutions here didn't work for me so I had to use:
ReplyDeleteif(!$(event.target).is('#foo'))
{
// hide menu
}
Now there is a plugin for that: outside events (blog post)
ReplyDeleteThe 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.
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?
ReplyDeleteFinding 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;}
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.
ReplyDeleteOr check the position of the click, and see if it's contained within the menu area.
$("#menuscontainer").click(function() {
ReplyDelete$(this).focus();
});
$("#menuscontainer").blur(function(){
$(this).hide();
});
Works for me just fine.
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.
ReplyDelete$(document).click(function() {
//Hide the menus if visible
});
$('#menucontainer').click(function(e){
e.stopPropagation();
});
var go=false;
ReplyDelete$(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();
});
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:
ReplyDeletethis.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;}
function:
ReplyDelete$(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();
}
I've had success with something like this:
ReplyDeletevar $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.
Found this method in some jquery calendar plugin.
ReplyDeletefunction 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);
This worked perfectly fine in time for me :
ReplyDelete$('body').click(function() {
// Hide the menus if visible.
});
Thanks very much!
$(document).click(function() {
ReplyDelete$(".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.
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.
ReplyDeleteI'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);
}
})
});
I did like this in YUI3:
ReplyDelete// 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).
Here is my code:
ReplyDelete// 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();
});
This is my solution to this problem:
ReplyDelete$(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();
});
});
jQuery().ready(function(){
ReplyDelete$('#nav').click(function (event) {
$(this).addClass('activ');
event.stopPropagation();
});
$('html').click(function () {
if( $('#nav').hasClass('activ') ){
$('#nav').removeClass('activ');
}
});
});
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:
ReplyDelete$(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");
}
});