How do I detect a click outside an element?
Asked Answered
I

91

2940

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
});
Ishii answered 30/9, 2008 at 13:17 Comment(7)
Here's a sample of this strategy: jsfiddle.net/tedp/aL7Xe/1Winegrower
As Tom mentioned, you'll want to read css-tricks.com/dangers-stopping-event-propagation before using this approach. That jsfiddle tool is pretty cool though.Mineralogy
get a reference to the element and then event.target, and finally != or == both of them then execute code accordingly..Louvar
Try to use event.path. https://mcmap.net/q/40435/-how-do-i-detect-a-click-outside-an-element/43405204#43405204Frontlet
Vanilla JS solution with event.target and without event.stopPropagation.Anaerobe
Since this isn't mentioned anywhere in the answers but it's useful to me in this context: when the mouse moves out of the window, event.relatedTarget is null in the mouseout event, and it's the element that the mouse is now over otherwise.Blandishments
All answers based on a click event fail to work when the click happens in another window or applicationReverential
I
2017

Note: Using stopPropagation is something that should be avoided as it breaks normal event flow in the DOM. See this CSS Tricks article for more information. Consider using this method instead.

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

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

$('#menucontainer').click(function(event){
  event.stopPropagation();
});
Industrial answered 30/9, 2008 at 13:17 Comment(11)
I prefer to bind the document to the click event, then unbind the event when needed. its more efficient.Redfin
This breaks standard behaviour of many things, including buttons and links, contained within #menucontainer. I am surprised this answer is so popular.Considerable
This doesn't break behavior of anything inside #menucontainer, since it is at the bottom of the propagation chain for anything inside of it.Industrial
its very beautyfull but you should use $('html').click() not body. The body always has the height of its content. It there is not a lot of content or the screen is very high, it only works on the part filled by the body.Kendyl
I am also surprised that this solution got so many votes. This will fail for any element outside that has stopPropagation jsfiddle.net/Flandre/vaNFw/3Arguseyed
Philip Walton explains very well why this answer isn't the best solution: css-tricks.com/dangers-stopping-event-propagationJinx
@Jinx Seems The developer should know if an event needs to be propagated, and if it does then they can just choose not to stop propagation. If they know that it's okay to stop propagation (which they should), then there's no issue.Indic
Never, ever (unless you really know what you're doing - which is not your case) use Event.stopPropagation(). There's not a single valid reason for an app to not be notified of occurring events.Choosy
@Considerable is right here. It breaks the behaviour of many things. And I am too surprised how this could be marked as the correct answer.Lanta
@Jinx #menucontainer children elements click also listening. I want only outside click listeners events.Cockroach
This solution completely blocks all clicks inside the div, for example, <a> clicksCitral
C
1621

You can listen for a click event on document and then make sure #menucontainer is not an ancestor or the target of the clicked element by using .closest().

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

$(document).click(function(event) { 
  var $target = $(event.target);
  if(!$target.closest('#menucontainer').length && 
  $('#menucontainer').is(":visible")) {
    $('#menucontainer').hide();
  }        
});

Edit – 2017-06-23

You can also clean up after the event listener if you plan to dismiss the menu and want to stop listening for events. This function will clean up only the newly created listener, preserving any other click listeners on document. With ES2015 syntax:

export function hideOnClickOutside(selector) {
  const outsideClickListener = (event) => {
    const $target = $(event.target);
    if (!$target.closest(selector).length && $(selector).is(':visible')) {
        $(selector).hide();
        removeClickListener();
    }
  }

  const removeClickListener = () => {
    document.removeEventListener('click', outsideClickListener);
  }

  document.addEventListener('click', outsideClickListener);
}

Edit – 2018-03-11

For those who don't want to use jQuery. Here's the above code in plain vanillaJS (ECMAScript6).

function hideOnClickOutside(element) {
    const outsideClickListener = event => {
        if (!element.contains(event.target) && isVisible(element)) { // or use: event.target.closest(selector) === null
          element.style.display = 'none';
          removeClickListener();
        }
    }

    const removeClickListener = () => {
        document.removeEventListener('click', outsideClickListener);
    }

    document.addEventListener('click', outsideClickListener);
}

const isVisible = elem => !!elem && !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); // source (2018-03-11): https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js 

NOTE: This is based on Alex comment to just use !element.contains(event.target) instead of the jQuery part.

But element.closest() is now also available in all major browsers (the W3C version differs a bit from the jQuery one). Polyfills can be found here: Element.closest()

Edit – 2020-05-21

In the case where you want the user to be able to click-and-drag inside the element, then release the mouse outside the element, without closing the element:

      ...
      let lastMouseDownX = 0;
      let lastMouseDownY = 0;
      let lastMouseDownWasOutside = false;

      const mouseDownListener = (event: MouseEvent) => {
        lastMouseDownX = event.offsetX;
        lastMouseDownY = event.offsetY;
        lastMouseDownWasOutside = !$(event.target).closest(element).length;
      }
      document.addEventListener('mousedown', mouseDownListener);

And in outsideClickListener:

const outsideClickListener = event => {
        const deltaX = event.offsetX - lastMouseDownX;
        const deltaY = event.offsetY - lastMouseDownY;
        const distSq = (deltaX * deltaX) + (deltaY * deltaY);
        const isDrag = distSq > 3;
        const isDragException = isDrag && !lastMouseDownWasOutside;

        if (!element.contains(event.target) && isVisible(element) && !isDragException) { // or use: event.target.closest(selector) === null
          element.style.display = 'none';
          removeClickListener();
          document.removeEventListener('mousedown', mouseDownListener); // Or add this line to removeClickListener()
        }
    }
Considerable answered 30/9, 2008 at 13:17 Comment(7)
I tried many of the other answers, but only this one worked. Thanks. The code I ended up using was this: $(document).click( function(event) { if( $(event.target).closest('.window').length == 0 ) { $('.window').fadeOut('fast'); } } );Gilliette
I actually ended up going with this solution because it better supports multiple menus on the same page where clicking on a second menu while a first is open will leave the first open in the stopPropagation solution.Viveca
Excellent answer. This is the way to go when you have multiple items which you wish to close.Tigerish
Without jQuery - !element.contains(event.target) using Node.contains()Horology
If you're reading this then you should probably check out some of the more modern answers to solve this that are way more readable than this answer.Bluebeard
Thank you. As a user I always want to be able to click-and-drag inside the element, then release the mouse outside the element, without closing the element.Bellhop
This answer is great, but I think what the final variant will be important addition. Because some last edits have only parts of result. The final code composition can be unclear.Carpel
G
433

How to detect a click outside an element?

The reason that this question is so popular and has so many answers is that it is deceptively complex. After almost eight years and dozens of answers, I am genuinely surprised to see how little care has been given to accessibility.

I would like to hide these elements when the user clicks outside the menus' area.

This is a noble cause and is the actual issue. The title of the question—which is what most answers appear to attempt to address—contains an unfortunate red herring.

Hint: it's the word "click"!

You don't actually want to bind click handlers.

If you're binding click handlers to close the dialog, you've already failed. The reason you've failed is that not everyone triggers click events. Users not using a mouse will be able to escape your dialog (and your pop-up menu is arguably a type of dialog) by pressing Tab, and they then won't be able to read the content behind the dialog without subsequently triggering a click event.

So let's rephrase the question.

How does one close a dialog when a user is finished with it?

This is the goal. Unfortunately, now we need to bind the userisfinishedwiththedialog event, and that binding isn't so straightforward.

So how can we detect that a user has finished using a dialog?

focusout event

A good start is to determine if focus has left the dialog.

Hint: be careful with the blur event, blur doesn't propagate if the event was bound to the bubbling phase!

jQuery's focusout will do just fine. If you can't use jQuery, then you can use blur during the capturing phase:

element.addEventListener('blur', ..., true);
//                       use capture: ^^^^

Also, for many dialogs you'll need to allow the container to gain focus. Add tabindex="-1" to allow the dialog to receive focus dynamically without otherwise interrupting the tabbing flow.

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on('focusout', function () {
  $(this).removeClass('active');
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>

If you play with that demo for more than a minute you should quickly start seeing issues.

The first is that the link in the dialog isn't clickable. Attempting to click on it or tab to it will lead to the dialog closing before the interaction takes place. This is because focusing the inner element triggers a focusout event before triggering a focusin event again.

The fix is to queue the state change on the event loop. This can be done by using setImmediate(...), or setTimeout(..., 0) for browsers that don't support setImmediate. Once queued it can be cancelled by a subsequent focusin:

$('.submenu').on({
  focusout: function (e) {
    $(this).data('submenuTimer', setTimeout(function () {
      $(this).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function (e) {
    clearTimeout($(this).data('submenuTimer'));
  }
});

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  }
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>

The second issue is that the dialog won't close when the link is pressed again. This is because the dialog loses focus, triggering the close behavior, after which the link click triggers the dialog to reopen.

Similar to the previous issue, the focus state needs to be managed. Given that the state change has already been queued, it's just a matter of handling focus events on the dialog triggers:

This should look familiar
$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  }
});

$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>

Esc key

If you thought you were done by handling the focus states, there's more you can do to simplify the user experience.

This is often a "nice to have" feature, but it's common that when you have a modal or popup of any sort that the Esc key will close it out.

keydown: function (e) {
  if (e.which === 27) {
    $(this).removeClass('active');
    e.preventDefault();
  }
}

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  },
  keydown: function (e) {
    if (e.which === 27) {
      $(this).removeClass('active');
      e.preventDefault();
    }
  }
});

$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>

If you know you have focusable elements within the dialog, you won't need to focus the dialog directly. If you're building a menu, you could focus the first menu item instead.

click: function (e) {
  $(this.hash)
    .toggleClass('submenu--active')
    .find('a:first')
    .focus();
  e.preventDefault();
}

$('.menu__link').on({
  click: function (e) {
    $(this.hash)
      .toggleClass('submenu--active')
      .find('a:first')
      .focus();
    e.preventDefault();
  },
  focusout: function () {
    $(this.hash).data('submenuTimer', setTimeout(function () {
      $(this.hash).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('submenuTimer'));  
  }
});

$('.submenu').on({
  focusout: function () {
    $(this).data('submenuTimer', setTimeout(function () {
      $(this).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('submenuTimer'));
  },
  keydown: function (e) {
    if (e.which === 27) {
      $(this).removeClass('submenu--active');
      e.preventDefault();
    }
  }
});
.menu {
  list-style: none;
  margin: 0;
  padding: 0;
}
.menu:after {
  clear: both;
  content: '';
  display: table;
}
.menu__item {
  float: left;
  position: relative;
}

.menu__link {
  background-color: lightblue;
  color: black;
  display: block;
  padding: 0.5em 1em;
  text-decoration: none;
}
.menu__link:hover,
.menu__link:focus {
  background-color: black;
  color: lightblue;
}

.submenu {
  border: 1px solid black;
  display: none;
  left: 0;
  list-style: none;
  margin: 0;
  padding: 0;
  position: absolute;
  top: 100%;
}
.submenu--active {
  display: block;
}

.submenu__item {
  width: 150px;
}

.submenu__link {
  background-color: lightblue;
  color: black;
  display: block;
  padding: 0.5em 1em;
  text-decoration: none;
}

.submenu__link:hover,
.submenu__link:focus {
  background-color: black;
  color: lightblue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="menu">
  <li class="menu__item">
    <a class="menu__link" href="#menu-1">Menu 1</a>
    <ul class="submenu" id="menu-1" tabindex="-1">
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#1">Example 1</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#2">Example 2</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#3">Example 3</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#4">Example 4</a></li>
    </ul>
  </li>
  <li class="menu__item">
    <a  class="menu__link" href="#menu-2">Menu 2</a>
    <ul class="submenu" id="menu-2" tabindex="-1">
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#1">Example 1</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#2">Example 2</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#3">Example 3</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#4">Example 4</a></li>
    </ul>
  </li>
</ul>
lorem ipsum <a href="http://example.com/">dolor</a> sit amet.

WAI-ARIA Roles and Other Accessibility Support

This answer hopefully covers the basics of accessible keyboard and mouse support for this feature, but as it's already quite sizable I'm going to avoid any discussion of WAI-ARIA roles and attributes, however I highly recommend that implementers refer to the spec for details on what roles they should use and any other appropriate attributes.

Georgeta answered 30/9, 2008 at 13:17 Comment(4)
This is the most complete answer, with explanations and accessibility in mind. I think this should be the accepted answer since most of the other answers only handle click and are just code snippet dropped without any explanations.Winola
You don't actually want to bind click handlers. You can bind click handlers and also handle cases where users doesn't have a mouse. It doesn't hurt accessibility, it only adds functionality to users with a mouse. Adding functionality to one group of users doesn't hurt the users who can't use that functionality. You can provide more than one way of closing a diablog This is actually a pretty common logical fallacy. It's totally fine to give a feature to one group of users even when others don't benefit. I agree that all users should be able to have a good experience thoughIndic
@ICW, by using blur or focusout handlers, you will still fully support mouse and touch users, and it has the added benefit of supporting keyboard users. At no point did I suggest that you ought to not support mouse users.Georgeta
It would be very helpful to see what this looks like in vanilla JS. I know the question is in JQuery, but 14 years have passed, and JQuery answers dominate SO so much that it's hard to learn the up-to-date solution.Transude
P
171

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

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

Edit: Plain Javascript variant (2021-03-31)

I used this method to handle closing a drop down menu when clicking outside of it.

First, I created a custom class name for all the elements of the component. This class name will be added to all elements that make up the menu widget.

const className = `dropdown-${Date.now()}-${Math.random() * 100}`;

I create a function to check for clicks and the class name of the clicked element. If clicked element does not contain the custom class name I generated above, it should set the show flag to false and the menu will close.

const onClickOutside = (e) => {
  if (!e.target.className.includes(className)) {
    show = false;
  }
};

Then I attached the click handler to the window object.

// add when widget loads
window.addEventListener("click", onClickOutside);

... and finally some housekeeping

// remove listener when destroying the widget
window.removeEventListener("click", onClickOutside);
Picco answered 30/9, 2008 at 13:17 Comment(1)
This worked for me, except I added && !$(event.target).parents("#foo").is("#foo") inside the IF statement so that any child elements won't close the menu when clicked.Wilbert
S
153

It's 2020 and you can use event.composedPath()

From: Event.composedPath()

The composedPath() method of the Event interface returns the event’s path, which is an array of the objects on which listeners will be invoked.

const target = document.querySelector('#myTarget')

document.addEventListener('click', (event) => {
  const withinBoundaries = event.composedPath().includes(target)

  if (withinBoundaries) {
    target.innerText = 'Click happened inside element'
  } else {
    target.innerText = 'Click happened **OUTSIDE** element'
  }
})
/* Just to make it good looking. You don't need this */
#myTarget {
  margin: 50px auto;
  width: 500px;
  height: 500px;
  background: gray;
  border: 10px solid black;
}
<div id="myTarget">
  Click me (or not!)
</div>
Schrock answered 30/9, 2008 at 13:17 Comment(2)
So many answers, but this is by far the best one in 2023 still. ty (Yes, it doesn't cover all scenarios but it's straight forward and modern.Justiciable
Best solution and more accurate than if (el === event.target || el.contains(event.target)) ...Diagonal
U
135

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) {
  $('html').one('click',function() {
    // Hide the menus
  });

  event.stopPropagation();
});

More information on jQuery's one() function

Ununa answered 30/9, 2008 at 13:17 Comment(1)
but then if you click on the menu itself, then outside, it won't work :)Redfin
C
60

After research, I have found three working solutions

First solution

<script>
    //The good thing about this solution is it doesn't stop event propagation.

    var clickFlag = 0;
    $('body').on('click', function () {
        if(clickFlag == 0) {
            console.log('hide element here');
            /* Hide element here */
        }
        else {
            clickFlag=0;
        }
    });
    $('body').on('click','#testDiv', function (event) {
        clickFlag = 1;
        console.log('showed the element');
        /* Show the element */
    });
</script>

Second solution

<script>
    $('body').on('click', function(e) {
        if($(e.target).closest('#testDiv').length == 0) {
           /* Hide dropdown here */
        }
    });
</script>

Third solution

<script>
    var specifiedElement = document.getElementById('testDiv');
    document.addEventListener('click', function(event) {
        var isClickInside = specifiedElement.contains(event.target);
        if (isClickInside) {
          console.log('You clicked inside')
        }
        else {
          console.log('You clicked outside')
        }
    });
</script>
Cut answered 30/9, 2008 at 13:17 Comment(3)
The third solution is by far the most elegant way of checking. It also doesn't involve any overhead of jQuery. Very nice. It helped a lot. Thanks.Vendace
Re "I forgot the page links for reference": I haven't seen that excuse from plagiarisers before.Geniegenii
@PeterMortensen I don't get it. what is there to plagiarise here? I didn't post a blog post or ditto code here to plagiarize... Anyways, for your pleasure, I have removed that line :)Cut
O
43
$("#menuscontainer").click(function() {
    $(this).focus();
});
$("#menuscontainer").blur(function(){
    $(this).hide();
});

Works for me just fine.

Oidium answered 30/9, 2008 at 13:17 Comment(1)
If trying to use this with a custom built select and options menu, blur will trigger before click so nothing will get selectedNadda
D
39

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.

Deuteranope answered 30/9, 2008 at 13:17 Comment(0)
T
33

This worked for me perfectly!!

$('html').click(function (e) {
    if (e.target.id == 'YOUR-DIV-ID') {
        //do something
    } else {
        //do something
    }
});
Tongs answered 30/9, 2008 at 13:17 Comment(0)
S
28

A simple solution for the situation is:

$(document).mouseup(function (e)
{
    var container = $("YOUR SELECTOR"); // Give you class or ID
    
    if (!container.is(e.target) &&            // If the target of the click is not the desired div or section
        container.has(e.target).length === 0) // ... nor a descendant-child of the container
    {
        container.hide();
    }
});

The above script will hide the div if outside of the div click event is triggered.

Sheffy answered 30/9, 2008 at 13:17 Comment(0)
D
28

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:

  1. 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.
  2. If you use event.stopPropogation() on a click event, no other elements in your page can have a click-anywhere-to-close feature.
  3. Attaching a click event handler to the body element indefinitely is not a performant solution
  4. 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.
  5. 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;}
Democritus answered 30/9, 2008 at 13:17 Comment(1)
"If you click on the menu, or off the menu it should close right?" not always. Cancelling a click by dragging off an element will still trigger a document level click, but the intent would not be to continue closing the menu. There are also plenty of other types of dialogs that could use the "click-out" behavior that would allow for clicking internally.Georgeta
K
27

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

    })
});
Kenon answered 30/9, 2008 at 13:17 Comment(0)
S
25

There is also the focusout event:

var button = document.getElementById('button');
button.addEventListener('click', function(e){
  e.target.style.backgroundColor = 'green';
});
button.addEventListener('focusout', function(e){
  e.target.style.backgroundColor = '';
});
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
</head>
<body>
  <button id="button">Click</button>
</body>
</html>
Servile answered 30/9, 2008 at 13:17 Comment(1)
This example doesn't work in Safari, and I get the feeling that I should be learning something from that.Transude
S
24

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.

Seow answered 30/9, 2008 at 13:17 Comment(0)
S
23

Solution1

Instead of using event.stopPropagation() which can have some side affects, just define a simple flag variable and add one if condition. I tested this and worked properly without any side affects of stopPropagation:

var flag = "1";
$('#menucontainer').click(function(event){
    flag = "0"; // flag 0 means click happened in the area where we should not do any action
});

$('html').click(function() {
    if(flag != "0"){
        // Hide the menus if visible
    }
    else {
        flag = "1";
    }
});

Solution2

With just a simple if condition:

$(document).on('click', function(event){
    var container = $("#menucontainer");
    if (!container.is(event.target) &&            // If the target of the click isn't the container...
        container.has(event.target).length === 0) // ... nor a descendant of the container
    {
        // Do whatever you want to do when click is outside the element
    }
});
Sg answered 30/9, 2008 at 13:17 Comment(2)
I used this solution with a boolean flag and it's good also with a articulated DOm and also if inside #menucontainer there are a lot of other elementsExpiry
Solution 1 works better, because it handles cases when click target is removed from DOM by the time event propagates to the document.Josejosee
V
22

2020 solution using native JS API closest method.

document.addEventListener('click', ({ target }) => {
  if (!target.closest('#menupop')) {
    document.querySelector('#menupop').style.display = 'none'
  }
})
#menupop {
    width: 300px;
    height: 300px;
    background-color: red;
}
<div id="menupop">
clicking outside will close this
</div>
Vehement answered 30/9, 2008 at 13:17 Comment(1)
How does this work sorry? I'm not sure what '.el1' etc are referencing here.Vehement
N
19

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 the body that hides #menuscontainer only if the target (of the click) isn't a child of it.

Numerate answered 30/9, 2008 at 13:17 Comment(0)
M
17

Use focusout for accessibility

There is one answer here that says (quite correctly) that focusing on click events is an accessibility problem since we want to cater for keyboard users. The focusout event is the correct thing to use here, but it can be done much more simply than in the other answer (and in pure JavaScript too):

A simpler way of doing it:

The 'problem' with using focusout is that if an element inside your dialog/modal/menu loses focus, to something also 'inside', the event will still get fired. We can check that this isn't the case by looking at event.relatedTarget (which tells us what element will have gained focus).

dialog = document.getElementById("dialogElement")

dialog.addEventListener("focusout", function (event) {
    if (
        // We are still inside the dialog so don't close
        dialog.contains(event.relatedTarget) ||
        // We have switched to another tab so probably don't want to close
        !document.hasFocus()
    ) {
        return;
    }
    dialog.close();  // Or whatever logic you want to use to close
});

There is one slight gotcha to the above, which is that relatedTarget may be null. This is fine if the user is clicking outside the dialog, but will be a problem if unless the user clicks inside the dialog and the dialog happens to not be focusable. To fix this you have to make sure to set tabIndex=0 so your dialog is focusable.

Machado answered 30/9, 2008 at 13:17 Comment(4)
This is the best solution so far, since it takes accessibility into account.Site
... or `tabIndex="-1" so it is not inserted in the tabbing sequenceReverential
Note: you can't use this for non-focusable elementsAlcoholicity
I saw this answer after I figured out the solution myself. The other solutions that bind an event to document/window/body have an issue: if an element catches the event with stop propagate, the listener won't be executed.Caryl
F
17

The event has a property called event.path of the element which is a "static ordered list of all its ancestors in tree order". To check if an event originated from a specific DOM element or one of its children, just check the path for that specific DOM element. It can also be used to check multiple elements by logically ORing the element check in the some function.

$("body").click(function() {
  target = document.getElementById("main");
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  })
  if (flag) {
    console.log("Inside")
  } else {
    console.log("Outside")
  }
});
#main {
  display: inline-block;
  background: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="main">
  <ul>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
  </ul>
</div>
<div id="main2">
  Outside Main
</div>

So for your case it should be

$("body").click(function() {
  target = $("#menuscontainer")[0];
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  });
  if (!flag) {
    // Hide the menus
  }
});
Frontlet answered 30/9, 2008 at 13:17 Comment(0)
J
16

As a variant:

var $menu = $('#menucontainer');
$(document).on('click', function (e) {

    // If element is opened and click target is outside it, hide it
    if ($menu.is(':visible') && !$menu.is(e.target) && !$menu.has(e.target).length) {
        $menu.hide();
    }
});

It has no problem with stopping event propagation and better supports multiple menus on the same page where clicking on a second menu while a first is open will leave the first open in the stopPropagation solution.

Jane answered 30/9, 2008 at 13:17 Comment(0)
H
14

I 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);
Hermia answered 30/9, 2008 at 13:17 Comment(0)
H
13

Here is the vanilla JavaScript solution for future viewers.

Upon clicking any element within the document, if the clicked element's id is toggled, or the hidden element is not hidden and the hidden element does not contain the clicked element, toggle the element.

(function () {
    "use strict";
    var hidden = document.getElementById('hidden');
    document.addEventListener('click', function (e) {
        if (e.target.id == 'toggle' || (hidden.style.display != 'none' && !hidden.contains(e.target))) hidden.style.display = hidden.style.display == 'none' ? 'block' : 'none';
    }, false);
})();

(function () {
    "use strict";
    var hidden = document.getElementById('hidden');
    document.addEventListener('click', function (e) {
        if (e.target.id == 'toggle' || (hidden.style.display != 'none' && !hidden.contains(e.target))) hidden.style.display = hidden.style.display == 'none' ? 'block' : 'none';
    }, false);
})();
<a href="javascript:void(0)" id="toggle">Toggle Hidden Div</a>
<div id="hidden" style="display: none;">This content is normally hidden. click anywhere other than this content to make me disappear</div>

If you are going to have multiple toggles on the same page you can use something like this:

  1. Add the class name hidden to the collapsible item.
  2. Upon document click, close all hidden elements which do not contain the clicked element and are not hidden
  3. If the clicked element is a toggle, toggle the specified element.

(function () {
    "use strict";
    var hiddenItems = document.getElementsByClassName('hidden'), hidden;
    document.addEventListener('click', function (e) {
        for (var i = 0; hidden = hiddenItems[i]; i++) {
            if (!hidden.contains(e.target) && hidden.style.display != 'none')
                hidden.style.display = 'none';
        }
        if (e.target.getAttribute('data-toggle')) {
            var toggle = document.querySelector(e.target.getAttribute('data-toggle'));
            toggle.style.display = toggle.style.display == 'none' ? 'block' : 'none';
        }
    }, false);
})();
<a href="javascript:void(0)" data-toggle="#hidden1">Toggle Hidden Div</a>
<div class="hidden" id="hidden1" style="display: none;" data-hidden="true">This content is normally hidden</div>
<a href="javascript:void(0)" data-toggle="#hidden2">Toggle Hidden Div</a>
<div class="hidden" id="hidden2" style="display: none;" data-hidden="true">This content is normally hidden</div>
<a href="javascript:void(0)" data-toggle="#hidden3">Toggle Hidden Div</a>
<div class="hidden" id="hidden3" style="display: none;" data-hidden="true">This content is normally hidden</div>
Headwaters answered 30/9, 2008 at 13:17 Comment(0)
T
8

If someone is curious, here is a JavaScript solution (ES6):

window.addEventListener('mouseup', e => {
    if (e.target != yourDiv && e.target.parentNode != yourDiv) {
        yourDiv.classList.remove('show-menu');
        // Or yourDiv.style.display = 'none';
    }
})

And ES5, just in case:

window.addEventListener('mouseup', function (e) {
    if (e.target != yourDiv && e.target.parentNode != yourDiv) {
        yourDiv.classList.remove('show-menu');
        // Or yourDiv.style.display = 'none';
    }
});
Tomfoolery answered 30/9, 2008 at 13:17 Comment(0)
L
8

Instead using flow interruption, blur/focus event or any other tricky technics, simply match event flow with element's kinship:

$(document).on("click.menu-outside", function(event){
    // Test if target and it's parent aren't #menuscontainer
    // That means the click event occur on other branch of document tree
    if(!$(event.target).parents().andSelf().is("#menuscontainer")){
        // Click outisde #menuscontainer
        // Hide the menus (but test if menus aren't already hidden)
    }
});

To remove click outside event listener, simply:

$(document).off("click.menu-outside");
Lackadaisical answered 30/9, 2008 at 13:17 Comment(0)
T
8

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;
}
Tendentious answered 30/9, 2008 at 13:17 Comment(0)
S
7

Use:

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();
});
Shakira answered 30/9, 2008 at 13:17 Comment(0)
P
6

I have used the below script done with jQuery.

jQuery(document).click(function(e) {
    var target = e.target; // Target div recorded
    if (!jQuery(target).is('#tobehide') ) {
        jQuery(this).fadeOut(); // If the click element is not the above id, it will hide
    }
})

Below find the HTML code:

<div class="main-container">
    <div>Hello, I am the title</div>
    <div class="tobehide">I will hide when you click outside of me</div>
</div>

You can read the tutorial here.

Philbrook answered 30/9, 2008 at 13:17 Comment(0)
L
6

Here is a simple solution by pure javascript. It is up-to-date with ES6:

var isMenuClick = false;
var menu = document.getElementById('menuscontainer');
document.addEventListener('click',()=>{
    if(!isMenuClick){
       //Hide the menu here
    }
    //Reset isMenuClick 
    isMenuClick = false;
})
menu.addEventListener('click',()=>{
    isMenuClick = true;
})
Lepper answered 30/9, 2008 at 13:17 Comment(3)
"Up-to-date with ES6" is a pretty bold claim, when the only thing up-to-date with ES6 is doing () => {} instead of function() {}. What you have there is classified as plain JavaScript with a twist of ES6.Faraway
@MortenMoulder: Ya. It's just for attention even though it is actually ES6. But just look at the solution. I think it is good.Lepper
It's vanilla JS and works for event target removed from DOM (e.g. when value from inner popup is selected, immediately closing the popup). +1 from me!Josejosee
H
6

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");
    }
});
Homopolar answered 30/9, 2008 at 13:17 Comment(0)
F
5

For easier use, and more expressive code, I created a jQuery plugin for this:

$('div.my-element').clickOut(function(target) { 
    //do something here... 
});

Note: target is the element the user actually clicked. But callback is still executed in the context of the original element, so you can utilize this as you'd expect in a jQuery callback.

Plugin:

$.fn.clickOut = function (parent, fn) {
    var context = this;
    fn = (typeof parent === 'function') ? parent : fn;
    parent = (parent instanceof jQuery) ? parent : $(document);

    context.each(function () {
        var that = this;
        parent.on('click', function (e) {
            var clicked = $(e.target);
            if (!clicked.is(that) && !clicked.parents().is(that)) {
                if (typeof fn === 'function') {
                    fn.call(that, clicked);
                }
            }
        });

    });
    return context;
};

By default, the click event listener is placed on the document. However, if you want to limit the event listener scope, you can pass in a jQuery object representing a parent level element that will be the top parent at which clicks will be listened to. This prevents unnecessary document level event listeners. Obviously, it won't work unless the parent element supplied is a parent of your initial element.

Use like so:

$('div.my-element').clickOut($('div.my-parent'), function(target) { 
    //do something here...
});
Fallacious answered 30/9, 2008 at 13:17 Comment(0)
H
5

Upvote for the most popular answer, but add

&& (e.target != $('html').get(0)) // ignore the scrollbar

so, a click on a scroll bar does not [hide or whatever] your target element.

Hanuman answered 30/9, 2008 at 13:17 Comment(1)
Related: Why do I need 50 reputation to comment? What can I do instead?.Geniegenii
W
5

We implemented a solution, partly based off a comment from a user above, which works perfectly for us. We use it to hide a search box / results when clicking outside those elements, excluding the element that originally.

// HIDE SEARCH BOX IF CLICKING OUTSIDE
$(document).click(function(event){ 
    // IF NOT CLICKING THE SEARCH BOX OR ITS CONTENTS OR SEARCH ICON 
    if ($("#search-holder").is(":visible") && !$(event.target).is("#search-holder *, #search")) {
        $("#search-holder").fadeOut('fast');
        $("#search").removeClass('active');
    }
});

It checks if the search box is already visible first also, and in our case, it's also removing an active class on the hide/show search button.

Wieche answered 30/9, 2008 at 13:17 Comment(0)
C
5

I did it like this in YUI 3:

// Detect the click anywhere other than the 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).

Codon answered 30/9, 2008 at 13:17 Comment(0)
N
5
$(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.

Nazar answered 30/9, 2008 at 13:17 Comment(0)
E
4

Let's say the div you want to detect if the user clicked outside or inside has an id, for example: "my-special-widget".

Listen to body click events:

document.body.addEventListener('click', (e) => {
    if (isInsideMySpecialWidget(e.target, "my-special-widget")) {
        console.log("user clicked INSIDE the widget");
    }
    console.log("user clicked OUTSIDE the widget");
});

function isInsideMySpecialWidget(elem, mySpecialWidgetId){
    while (elem.parentElement) {
        if (elem.id === mySpecialWidgetId) {
            return true;
        }
        elem = elem.parentElement;
    }
    return false;
}

In this case, you won't break the normal flow of click on some element in your page, since you are not using the "stopPropagation" method.

Exsanguinate answered 30/9, 2008 at 13:17 Comment(0)
S
4

I just want to make Pistos' answer more apparent since it's hidden in the comments.

This solution worked perfectly for me. Plain JavaScript:

var elementToToggle = $('.some-element');
$(document).click( function(event) {
  if( $(event.target).closest(elementToToggle).length === 0 ) {
    elementToToggle.hide();
  }
});

In CoffeeScript:

elementToToggle = $('.some-element')
$(document).click (event) ->
  if $(event.target).closest(elementToToggle).length == 0
    elementToToggle.hide()
Seidule answered 30/9, 2008 at 13:17 Comment(0)
B
4

I ended up doing something like this:

$(document).on('click', 'body, #msg_count_results .close',function() {
    $(document).find('#msg_count_results').remove();
});
$(document).on('click','#msg_count_results',function(e) {
    e.preventDefault();
    return false;
});

I have a close button within the new container for end users friendly UI purposes. I had to use return false in order to not go through. Of course, having an A HREF on there to take you somewhere would be nice, or you could call some ajax stuff instead. Either way, it works ok for me. Just what I wanted.

Blowout answered 30/9, 2008 at 13:17 Comment(0)
O
4

The solutions here work fine when only one element is to be managed. If there are multiple elements, however, the problem is much more complicated. Tricks with e.stopPropagation() and all the others will not work.

I came up with a solution, and maybe it is not so easy, but it's better than nothing. Have a look:

$view.on("click", function(e) {

    if(model.isActivated()) return;

        var watchUnclick = function() {
            rootView.one("mouseleave", function() {
                $(document).one("click", function() {
                    model.deactivate();
                });
                rootView.one("mouseenter", function() {
                    watchUnclick();
                });
            });
        };
        watchUnclick();
        model.activate();
    });
Osseous answered 30/9, 2008 at 13:17 Comment(1)
I think onmouseenter and onmouseleave is the solution, check this out jsfiddle.net/1r73jm8mMindszenty
G
4

This should work:

$('body').click(function (event) {
    var obj = $(event.target);
    obj = obj['context']; // context : clicked element inside body
    if ($(obj).attr('id') != "menuscontainer" && $('#menuscontainer').is(':visible') == true) {
        //hide menu
    }
});
Galvanic answered 30/9, 2008 at 13:17 Comment(1)
$("body > div:not(#dvid)").click(function (e) { //your code });Unceremonious
P
4

Function:

$(function() {
    $.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();
}
Procora answered 30/9, 2008 at 13:17 Comment(1)
Wouldn't this trigger the clickout event also in the case when a child element of the container is clicked?Idleman
P
3

All of these answers solve the problem, but I would like to contribute with a moders es6 solution that does exactly what is needed. I just hope to make someone happy with this runnable demo.

window.clickOutSide = (element, clickOutside, clickInside) => {
  document.addEventListener('click', (event) => {
    if (!element.contains(event.target)) {
      if (typeof clickInside === 'function') {
        clickOutside();
      }
    } else {
      if (typeof clickInside === 'function') {
        clickInside();
      }
    }
  });
};

window.clickOutSide(document.querySelector('.block'), () => alert('clicked outside'), () => alert('clicked inside'));
.block {
  width: 400px;
  height: 400px;
  background-color: red;
}
<div class="block"></div>
Periodic answered 30/9, 2008 at 13:17 Comment(0)
P
3

If you are using tools like "Pop-up", you can use the "onFocusOut" event.

window.onload = function() {
    document.getElementById("inside-div").focus();
}
function loseFocus() {
    alert("Clicked outside");
}
#container {
    background-color: lightblue;
    width: 200px;
    height: 200px;
}

#inside-div {
    background-color: lightgray;
    width: 100px;
    height: 100px;
}
<div id="container">
    <input type="text" id="inside-div" onfocusout="loseFocus()">
</div>
Pest answered 30/9, 2008 at 13:17 Comment(2)
Where was this copied from? Missing indentation is usually a sign of having been copied straight off a blog post (or similar).Geniegenii
@PeterMortensen The question you asked after 6 yearsPest
W
3

Here is what I do to solve to problem.

$(window).click(function (event) {
    //To improve performance add a checklike 
    //if(myElement.isClosed) return;
    var isClickedElementChildOfMyBox = isChildOfElement(event,'#id-of-my-element');

    if (isClickedElementChildOfMyBox)
        return;

    //your code to hide the element 
});

var isChildOfElement = function (event, selector) {
    if (event.originalEvent.path) {
        return event.originalEvent.path[0].closest(selector) !== null;
    }

    return event.originalEvent.originalTarget.closest(selector) !== null;
}
Wicketkeeper answered 30/9, 2008 at 13:17 Comment(0)
S
3
$(document).on("click", function (event)
{
  console.log(event);
  if ($(event.target).closest('.element').length == 0)
  {
    // Your code here
    if ($(".element").hasClass("active"))
    {
      $(".element").removeClass("active");
    }
  }
});

Try this coding for getting the solution.

Sandrasandro answered 30/9, 2008 at 13:17 Comment(0)
A
3

I know there are a million answers to this question, but I've always been a fan of using HTML and CSS to do most of the work. In this case, z-index and positioning. The simplest way that I have found to do this is as follows:

$("#show-trigger").click(function(){
  $("#element").animate({width: 'toggle'});
  $("#outside-element").show();
});
$("#outside-element").click(function(){
  $("#element").hide();
  $("#outside-element").hide();
});
#outside-element {
  position:fixed;
  width:100%;
  height:100%;
  z-index:1;
  display:none;
}
#element {
  display:none;
  padding:20px;
  background-color:#ccc;
  width:300px;
  z-index:2;
  position:relative;
}
#show-trigger {
  padding:20px;
  background-color:#ccc;
  margin:20px auto;
  z-index:2;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="outside-element"></div>
<div id="element">
  <div class="menu-item"><a href="#1">Menu Item 1</a></div>
  <div class="menu-item"><a href="#2">Menu Item 1</a></div>
  <div class="menu-item"><a href="#3">Menu Item 1</a></div>
  <div class="menu-item"><a href="#4">Menu Item 1</a></div>
</div>
<div id="show-trigger">Show Menu</div>

This creates a safe environment, since nothing is going to get triggered unless the menu is actually open and the z-index protects any of the content within the element from creating any misfires upon being clicked.

Additionally, you're not requiring jQuery to cover all of your bases with propagation calls and having to purge all of the inner elements from misfires.

Amends answered 30/9, 2008 at 13:17 Comment(0)
M
3

The answer marked as the accepted answer does not take into account that you can have overlays over the element, like dialogs, popovers, datepickers, etc. Clicks in these should not hide the element.

I have made my own version that does take this into account. It's created as a KnockoutJS binding, but it can easily be converted to jQuery-only.

It works by the first query for all elements with either z-index or absolute position that are visible. It then hit tests those elements against the element I want to hide if click outside. If it's a hit I calculate a new bound rectangle which takes into account the overlay bounds.

ko.bindingHandlers.clickedIn = (function () {
    function getBounds(element) {
        var pos = element.offset();
        return {
            x: pos.left,
            x2: pos.left + element.outerWidth(),
            y: pos.top,
            y2: pos.top + element.outerHeight()
        };
    }

    function hitTest(o, l) {
        function getOffset(o) {
            for (var r = { l: o.offsetLeft, t: o.offsetTop, r: o.offsetWidth, b: o.offsetHeight };
                o = o.offsetParent; r.l += o.offsetLeft, r.t += o.offsetTop);
            return r.r += r.l, r.b += r.t, r;
        }

        for (var b, s, r = [], a = getOffset(o), j = isNaN(l.length), i = (j ? l = [l] : l).length; i;
            b = getOffset(l[--i]), (a.l == b.l || (a.l > b.l ? a.l <= b.r : b.l <= a.r))
                && (a.t == b.t || (a.t > b.t ? a.t <= b.b : b.t <= a.b)) && (r[r.length] = l[i]));
        return j ? !!r.length : r;
    }

    return {
        init: function (element, valueAccessor) {
            var target = valueAccessor();
            $(document).click(function (e) {
                if (element._clickedInElementShowing === false && target()) {
                    var $element = $(element);
                    var bounds = getBounds($element);

                    var possibleOverlays = $("[style*=z-index],[style*=absolute]").not(":hidden");
                    $.each(possibleOverlays, function () {
                        if (hitTest(element, this)) {
                            var b = getBounds($(this));
                            bounds.x = Math.min(bounds.x, b.x);
                            bounds.x2 = Math.max(bounds.x2, b.x2);
                            bounds.y = Math.min(bounds.y, b.y);
                            bounds.y2 = Math.max(bounds.y2, b.y2);
                        }
                    });

                    if (e.clientX < bounds.x || e.clientX > bounds.x2 ||
                        e.clientY < bounds.y || e.clientY > bounds.y2) {

                        target(false);
                    }
                }
                element._clickedInElementShowing = false;
            });

            $(element).click(function (e) {
                e.stopPropagation();
            });
        },
        update: function (element, valueAccessor) {
            var showing = ko.utils.unwrapObservable(valueAccessor());
            if (showing) {
                element._clickedInElementShowing = true;
            }
        }
    };
})();
Malley answered 30/9, 2008 at 13:17 Comment(0)
R
3

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();
  });
});
Raimondo answered 30/9, 2008 at 13:17 Comment(0)
A
2

A way to write in pure JavaScript

let menu = document.getElementById("menu");

document.addEventListener("click", function(){
    // Hide the menus
    menu.style.display = "none";
}, false);

document.getElementById("menuscontainer").addEventListener("click", function(e){
    // Show the menus
    menu.style.display = "block";
    e.stopPropagation();
}, false);
Advertise answered 30/9, 2008 at 13:17 Comment(0)
C
2

This is the simplest answer I have found to this question:

window.addEventListener('click', close_window = function () {
  if(event.target !== windowEl){
    windowEl.style.display = "none";
    window.removeEventListener('click', close_window, false);
  }
});

And you will see I named the function "close_window" so that I could remove the event listener when the window closes.

Coax answered 30/9, 2008 at 13:17 Comment(0)
S
2

Still looking for that perfect solution for detecting clicking outside? Look no further! Introducing Clickout-Event, a package that provides universal support for clickout and other similar events, and it works in all scenarios: plain HTML onclickout attributes, .addEventListener('clickout') of vanilla JavaScript, .on('clickout') of jQuery, v-on:clickout directives of Vue.js, you name it. As long as a front-end framework internally uses addEventListener to handle events, Clickout-Event works for it. Just add the script tag anywhere in your page, and it simply works like magic.

HTML attribute

<div onclickout="console.log('clickout detected')">...</div>

Vanilla JavaScript

document.getElementById('myId').addEventListener('clickout', myListener);

jQuery

$('#myId').on('clickout', myListener);

Vue.js

<div v-on:clickout="open=false">...</div>

Angular

<div (clickout)="close()">...</div>
Sin answered 30/9, 2008 at 13:17 Comment(0)
V
2

This works for me

$("body").mouseup(function(e) {
    var subject = $(".main-menu");
    if(e.target.id != subject.attr('id') && !subject.has(e.target).length) {
        $('.sub-menu').hide();
    }
});
Veteran answered 30/9, 2008 at 13:17 Comment(0)
L
2

I believe the best way of doing it is something like this.

$(document).on("click", function(event) {
  clickedtarget = $(event.target).closest('#menuscontainer');
  $("#menuscontainer").not(clickedtarget).hide();
});

This type of solution could easily be made to work for multiple menus and also menus that are dynamically added through javascript. Basically it just allows you to click anywhere in your document, and checks which element you clicked in, and selects it's closest "#menuscontainer". Then it hides all menuscontainers but excludes the one you clicked in.

Not sure about exactly how your menus are built, but feel free to copy my code in the JSFiddle. It's a very simple but thoroughly functional menu/modal system. All you need to do is build the html-menus and the code will do the work for you.

https://jsfiddle.net/zs6anrn7/

Leacock answered 30/9, 2008 at 13:17 Comment(0)
A
2

This might be a better fix for some people.

$(".menu_link").click(function(){
    // show menu code
});

$(".menu_link").mouseleave(function(){
    //hide menu code, you may add a timer for 3 seconds before code to be run
});

I know mouseleave does not only mean a click outside, it also means leaving that element's area.

Once the menu itself is inside the menu_link element then the menu itself should not be a problem to click on or move on.

Anaerobe answered 30/9, 2008 at 13:17 Comment(1)
mouseleave and some sort of hack might solve it for some people, here's a test jsfiddle.net/1r73jm8mMindszenty
A
2

For touch devices like iPad and iPhone we can use this code:

$(document).on('touchstart', function (event) {
    var container = $("YOUR CONTAINER SELECTOR");

    if (!container.is(e.target) &&            // If the target of the click isn't the container...
        container.has(e.target).length === 0) // ... nor a descendant of the container
    {
        container.hide();
    }
});
Antisocial answered 30/9, 2008 at 13:17 Comment(0)
J
2

The broadest way to do this is to select everything on the web page except the element where you don't want clicks detected and bind the click event those when the menu is opened.

Then when the menu is closed remove the binding.

Use .stopPropagation to prevent the event from affecting any part of the menuscontainer.

$("*").not($("#menuscontainer")).bind("click.OutsideMenus", function ()
{
    // hide the menus

    //then remove all of the handlers
    $("*").unbind(".OutsideMenus");
});

$("#menuscontainer").bind("click.OutsideMenus", function (event) 
{
    event.stopPropagation(); 
});
Jospeh answered 30/9, 2008 at 13:17 Comment(0)
J
2

One more solution is here:

http://jsfiddle.net/zR76D/

Usage:

<div onClick="$('#menu').toggle();$('#menu').clickOutside(function() { $(this).hide(); $(this).clickOutside('disable'); });">Open / Close Menu</div>
<div id="menu" style="display: none; border: 1px solid #000000; background: #660000;">I am a menu, whoa is me.</div>

Plugin:

(function($) {
    var clickOutsideElements = [];
    var clickListener = false;

    $.fn.clickOutside = function(options, ignoreFirstClick) {
        var that = this;
        if (ignoreFirstClick == null) ignoreFirstClick = true;

        if (options != "disable") {
            for (var i in clickOutsideElements) {
                if (clickOutsideElements[i].element[0] == $(this)[0]) return this;
            }

            clickOutsideElements.push({ element : this, clickDetected : ignoreFirstClick, fnc : (typeof(options) != "function") ? function() {} : options });

            $(this).on("click.clickOutside", function(event) {
                for (var i in clickOutsideElements) {
                    if (clickOutsideElements[i].element[0] == $(this)[0]) {
                        clickOutsideElements[i].clickDetected = true;
                    }
                }
            });

            if (!clickListener) {
                if (options != null && typeof(options) == "function") {
                    $('html').click(function() {
                        for (var i in clickOutsideElements) {
                            if (!clickOutsideElements[i].clickDetected) {
                                clickOutsideElements[i].fnc.call(that);
                            }
                            if (clickOutsideElements[i] != null) clickOutsideElements[i].clickDetected = false;
                        }
                    });
                    clickListener = true;
                }
            }
        }
        else {
            $(this).off("click.clickoutside");
            for (var i = 0; i < clickOutsideElements.length; ++i) {
                if (clickOutsideElements[i].element[0] == $(this)[0]) {
                    clickOutsideElements.splice(i, 1);
                }
            }
        }

        return this;
    }
})(jQuery);
Jinx answered 30/9, 2008 at 13:17 Comment(0)
S
2

To be honest, I didn't like any of previous the solutions.

The best way to do this, is binding the "click" event to the document, and comparing if that click is really outside the element (just like Art said in his suggestion).

However, you'll have some problems there: You'll never be able to unbind it, and you cannot have an external button to open/close that element.

That's why I wrote this small plugin (click here to link), to simplify these tasks. Could it be simpler?

<a id='theButton' href="#">Toggle the menu</a><br/>
<div id='theMenu'>
    I should be toggled when the above menu is clicked,
    and hidden when user clicks outside.
</div>

<script>
$('#theButton').click(function(){
    $('#theMenu').slideDown();
});
$("#theMenu").dClickOutside({ ignoreList: $("#theButton") }, function(clickedObj){
    $(this).slideUp();
});
</script>
Scatterbrain answered 30/9, 2008 at 13:17 Comment(0)
H
2

This worked perfectly fine in time for me:

$('body').click(function() {
    // Hide the menus if visible.
});
Haley answered 30/9, 2008 at 13:17 Comment(1)
the problem whit this its that no matter where you click, you always gonna have the event bubble down to the body. so you should use event.stopPropagation but that would break that natural way of how the DOM Behave I dont think this is a good practicePolygenesis
B
1

const button = document.querySelector('button')
const box = document.querySelector('.box');

const toggle = event => {
  event.stopPropagation();
  
  if (!event.target.closest('.box')) {
    console.log('Click outside');

    box.classList.toggle('active');

    box.classList.contains('active')
      ? document.addEventListener('click', toggle)
      : document.removeEventListener('click', toggle);
  } else {
    console.log('Click inside');
  }
}

button.addEventListener('click', toggle);
.box {
  position: absolute;
  display: none;
  margin-top: 8px;
  padding: 20px;
  background: lightgray;
}

.box.active {
  display: block;
}
<button>Toggle box</button>

<div class="box">
  <form action="">
    <input type="text">
    <button type="button">Search</button>
  </form>
</div>
Barbaraanne answered 30/9, 2008 at 13:17 Comment(1)
For those wondering how this works, the callback toggle is relying on the browser's default event propagation. If the click is inside the "box" do not hide the box. Else, toggle the active class. See we need to enter the condition if the user clicks outside the box OR on the button. If we toggled active ON register the same callback on the document root, ELSE remove the callback from the root.Haircloth
S
1
$('#propertyType').on("click",function(e){
          self.propertyTypeDialog = !self.propertyTypeDialog;
          b = true;
          e.stopPropagation();
          console.log("input clicked");
      });

      $(document).on('click','body:not(#propertyType)',function (e) {
          e.stopPropagation();
          if(b == true)  {
              if ($(e.target).closest("#configuration").length == 0) {
                  b = false;
                  self.propertyTypeDialog = false;
                  console.log("outside clicked");
              }
          }
        // console.log($(e.target).closest("#configuration").length);
      });
Solothurn answered 30/9, 2008 at 13:17 Comment(0)
R
1

Simple plugin:

$.fn.clickOff = function(callback, selfDestroy) {
    var clicked = false;
    var parent = this;
    var destroy = selfDestroy || true;

    parent.click(function() {
        clicked = true;
    });

    $(document).click(function(event) {
        if (!clicked && parent.is(':visible')) {
            if(callback) callback.call(parent, event)
        }
        if (destroy) {
            //parent.clickOff = function() {};
            //parent.off("click");
            //$(document).off("click");
            parent.off("clickOff");
        }
        clicked = false;
    });
};

Use:

$("#myDiv").clickOff(function() {
   alert('clickOff');
});
Rotor answered 30/9, 2008 at 13:17 Comment(1)
That's not what Stack Overflow is for. "Explanation is vital for a good answer."Geniegenii
P
1

This is a more general solution that allows multiple elements to be watched, and dynamically adding and removing elements from the queue.

It holds a global queue (autoCloseQueue) - an object container for elements that should be closed on outside clicks.

Each queue object key should be the DOM Element id, and the value should be an object with 2 callback functions:

 {onPress: someCallbackFunction, onOutsidePress: anotherCallbackFunction}

Put this in your document ready callback:

window.autoCloseQueue = {}  

$(document).click(function(event) {
    for (id in autoCloseQueue){
        var element = autoCloseQueue[id];
        if ( ($(e.target).parents('#' + id).length) > 0) { // This is a click on the element (or its child element)
            console.log('This is a click on an element (or its child element) with  id: ' + id);
            if (typeof element.onPress == 'function') element.onPress(event, id);
        } else { //This is a click outside the element
            console.log('This is a click outside the element with id: ' + id);
            if (typeof element.onOutsidePress == 'function') element.onOutsidePress(event, id); //call the outside callback
            delete autoCloseQueue[id]; //remove the element from the queue
        }
    }
});

Then, when the DOM element with id 'menuscontainer' is created, just add this object to the queue:

window.autoCloseQueue['menuscontainer'] = {onOutsidePress: clickOutsideThisElement}
Phenix answered 30/9, 2008 at 13:17 Comment(0)
M
1

Just a warning that using this:

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

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

It prevents the Ruby on Rails UJS driver from working properly. For example, link_to 'click', '/url', :method => :delete will not work.

This might be a workaround:

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

$('#menucontainer').click(function(event){
  if (!$(event.target).data('method')) {
    event.stopPropagation();
  }
});
Minna answered 30/9, 2008 at 13:17 Comment(1)
Possibly related: Why do I need 50 reputation to comment? What can I do instead?.Geniegenii
G
1
jQuery().ready(function(){
    $('#nav').click(function (event) {
        $(this).addClass('activ');
        event.stopPropagation();
    });

    $('html').click(function () {
        if( $('#nav').hasClass('activ') ){
            $('#nav').removeClass('activ');
        }
    });
});
Gurnard answered 30/9, 2008 at 13:17 Comment(0)
A
1

Here is my code:

// Listen to every click
$('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();
});
Abbreviation answered 30/9, 2008 at 13:17 Comment(0)
A
0

For those who want a short solution to integrate into their JavaScript code, a small library without jQuery:

Usage:

// Demo code
var htmlElem = document.getElementById('my-element')
function doSomething(){ console.log('outside click') }

// Use the library
var removeListener = new elemOutsideClickListener(htmlElem, doSomething);

// Deregister on your wished event
$scope.$on('$destroy', removeListener);

Here is the library:


function elemOutsideClickListener (element, outsideClickFunc, insideClickFunc) {
   function onClickOutside (e) {
      var targetEl = e.target; // clicked element
      do {
         // click inside
         if (targetEl === element) {
            if (insideClickFunc) insideClickFunc();
            return;

         // Go up the DOM
         } else {
            targetEl = targetEl.parentNode;
         }
      } while (targetEl);

      // click outside
      if (!targetEl && outsideClickFunc) outsideClickFunc();
   }

   window.addEventListener('click', onClickOutside);

   return function () {
      window.removeEventListener('click', onClickOutside);
   };
}

I took the code from here and put it in a function: How to Detect a Click Outside an Element

Autotoxin answered 30/9, 2008 at 13:17 Comment(0)
E
0

You don't need (much) JavaScript, just the :focus-within selector:

  • Use .sidebar:focus-within to display your sidebar.
  • Set tabindex=-1 on your sidebar and body elements to make them focussable.
  • Set the sidebar visibility with sidebarEl.focus() and document.body.focus().

const menuButton = document.querySelector('.menu-button');
const sidebar = document.querySelector('.sidebar');

menuButton.onmousedown = ev => {
  ev.preventDefault();
  (sidebar.contains(document.activeElement) ?
    document.body : sidebar).focus();
};
* { box-sizing: border-box; }

.sidebar {
  position: fixed;
  width: 15em;
  left: -15em;
  top: 0;
  bottom: 0;
  transition: left 0.3s ease-in-out;
  background-color: #eef;
  padding: 3em 1em;
}

.sidebar:focus-within {
  left: 0;
}

.sidebar:focus {
  outline: 0;
}

.menu-button {
  position: fixed;
  top: 0;
  left: 0;
  padding: 1em;
  background-color: #eef;
  border: 0;
}

body {
  max-width: 30em;
  margin: 3em;
}
<body tabindex='-1'>
  <nav class='sidebar' tabindex='-1'>
    Sidebar content
    <input type="text"/>
  </nav>
  <button class="menu-button">☰</button>
  Body content goes here, Lorem ipsum sit amet, etc
</body>
Electrolytic answered 30/9, 2008 at 13:17 Comment(0)
A
0

I've read all on 2021, but if not wrong, nobody suggested something easy like this, to unbind and remove event. Using two of the previous answers and a more small tricks, so I put all in one (it could also be added as a parameter to the function to pass selectors, for more popups).

Maybe it is useful for someone to know that the joke. It could also be done this way:

<div id="container" style="display:none"><h1>my menu is nice, but it disappears if I click outside it</h1></div>

<script>
  function printPopup() {
    $("#container").css({"display": "block"});
    var remListener = $(document).mouseup(function (e) {
      if ($(e.target).closest("#container").length === 0 &&
          (e.target != $('html').get(0)))
      {
        //alert('closest call');
        $("#container").css({"display": "none"});
        remListener.unbind('mouseup'); // Isn't it?
      }
    });
  }

  printPopup();

</script>
Advance answered 30/9, 2008 at 13:17 Comment(0)
A
0

If you just want to display a window when you click on a button and undisp this window when you click outside (or on the button again), this below works good:

document.body.onclick = function() { undisp_menu(); };
var menu_on = 0;

function menu_trigger(event) {

    if (menu_on == 0)
    {
        // Otherwise you will call the undisp on body when
        // click on the button
        event.stopPropagation();

        disp_menu();
    }
    else {
        undisp_menu();
    }
}


function disp_menu() {

    menu_on = 1;
    var e = document.getElementsByClassName("menu")[0];
    e.className = "menu on";
}

function undisp_menu() {

    menu_on = 0;
    var e = document.getElementsByClassName("menu")[0];
    e.className = "menu";
}

Don't forget this for the button:

<div class="button" onclick="menu_trigger(event)">

<div class="menu">

And the CSS:

.menu {
    display: none;
}

.on {
    display: inline-block;
}
Amplifier answered 30/9, 2008 at 13:17 Comment(0)
T
0

To hide fileTreeClass if clicked outside of it:

jQuery(document).mouseup(function (e) {
    var container = $(".fileTreeClass");
    if (!container.is(e.target) && // If the target of the click isn't the container...
         container.has(e.target).length === 0) // ... nor a descendant of the container
    {
        container.hide();
    }
});
Theodosia answered 30/9, 2008 at 13:17 Comment(0)
T
0

Subscribe capturing phase of click to handle click on elements which call preventDefault.
Retrigger it on document element using the other name click-anywhere.

document.addEventListener('click', function (event) {
  event = $.event.fix(event);
  event.type = 'click-anywhere';
  $document.trigger(event);
}, true);

Then where you need click outside functionality subscribe on click-anywhere event on document and check if the click was outside of the element you are interested in:

$(document).on('click-anywhere', function (event) {
  if (!$(event.target).closest('#smth').length) {
    // Do anything you need here
  }
});

Some notes:

  • You have to use document as it would be a perfomance fault to trigger event on all elements outside of which the click occured.

  • This functionality can be wrapped into special plugin, which calls some callback on outside click.

  • You can't subscribe capturing phase using jQuery itself.

  • You don't need document load to subscribe as subscription is on document, even not on its body, so it exists always independently ащкь script placement and load status.

Theodoratheodore answered 30/9, 2008 at 13:17 Comment(1)
+1 for mentioning the capture phase -- certainly reduces the risk of stopPropagation() killing things (depending on where you attach the listener).Gaseous
P
0

Outside click plugin!

Usage:

$('.target-element').outsideClick(function(event){
    //code that fires when user clicks outside the element
    //event = the click event
    //$(this) = the '.target-element' that is firing this function 
}, '.excluded-element')

The code for it:

(function($) {

//when the user hits the escape key, it will trigger all outsideClick functions
$(document).on("keyup", function (e) {
    if (e.which == 27) $('body').click(); //escape key
});

//The actual plugin
$.fn.outsideClick = function(callback, exclusions) {
    var subject = this;

    //test if exclusions have been set
    var hasExclusions = typeof exclusions !== 'undefined';

    //switches click event with touch event if on a touch device
    var ClickOrTouchEvent = "ontouchend" in document ? "touchend" : "click";

    $('body').on(ClickOrTouchEvent, function(event) {
        //click target does not contain subject as a parent
        var clickedOutside = !$(event.target).closest(subject).length;

        //click target was on one of the excluded elements
        var clickedExclusion = $(event.target).closest(exclusions).length;

        var testSuccessful;

        if (hasExclusions) {
            testSuccessful = clickedOutside && !clickedExclusion;
        } else {
            testSuccessful = clickedOutside;
        }

        if(testSuccessful) {
            callback.call(subject, event);
        }
    });

    return this;
};

}(jQuery));

Adapted from this answer https://mcmap.net/q/40435/-how-do-i-detect-a-click-outside-an-element

Permatron answered 30/9, 2008 at 13:17 Comment(1)
Please don’t do this ClickOrTouchEvent thing. There are devices that have both click and touch, but you’re only binding one.Lyingin
V
0

This will toggle the Nav menu when you click on/off the element.

$(document).on('click', function(e) {
  var elem = $(e.target).closest('#menu'),
    box = $(e.target).closest('#nav');
  if (elem.length) {
    e.preventDefault();
    $('#nav').toggle();
  } else if (!box.length) {
    $('#nav').hide();
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<li id="menu">
  <a></a>
</li>
<ul id="nav">
  <!--Nav will toggle when you Click on Menu(it can be an icon in this example)-->
  <li class="page"><a>Page1</a></li>
  <li class="page"><a>Page2</a></li>
  <li class="page"><a>Page3</a></li>
  <li class="page"><a>Page4</a></li>
</ul>
Vardhamana answered 30/9, 2008 at 13:17 Comment(0)
T
-1

Here is solution within a container or whole document. If click target is out of your element (with class 'yourClass'), element is hidden.

$('yourContainer').on('click', function(e) {
  if (!$(e.target).hasClass('yourClass')) {
    $('.yourClass').hide();
  }
});
Trimer answered 30/9, 2008 at 13:17 Comment(2)
Your answer could be improved by adding more information on what the code does and how it helps the OP.Esoterica
That's not what Stack Overflow is for. "Explanation is vital for a good answer."Geniegenii
S
-1

This works fine for me. I am not an expert.

$(document).click(function(event) {
  var $target = $(event.target);
  if(!$target.closest('#hamburger, a').length &&
  $('#hamburger, a').is(":visible")) {
    $('nav').slideToggle();
  }
});
Salzburg answered 30/9, 2008 at 13:17 Comment(0)
B
-1

First you have to track wether the mouse is inside or outside your element1, using the mouseenter and mouseleave events. Then you can create an element2 which covers the whole screen to detect any clicks, and react accordingly depending on wether you are inside or outside element1.

I strongly recommend to handle both initialization and cleanup, and that the element2 is made as temporary as possible, for obvious reasons.

In the example below, the overlay is an element positionned somewhere, which can be selected by clicking inside, and unselected by clicking outside. The _init and _release methods are called as part of an automatic initialisation/cleanup process. The class inherits from a ClickOverlay which has an inner and outerElement, don't worry about it. I used outerElement.parentNode.appendChild to avoid conflicts.

import ClickOverlay from './ClickOverlay.js'

/* CSS */
// .unselect-helper {
//  position: fixed; left: -100vw; top: -100vh;
//  width: 200vw; height: 200vh;
// }
// .selected {outline: 1px solid black}

export default class ResizeOverlay extends ClickOverlay {
    _init(_opts) {
        this.enterListener = () => this.onEnter()
        this.innerElement.addEventListener('mouseenter', this.enterListener)
        this.leaveListener = () => this.onLeave()
        this.innerElement.addEventListener('mouseleave', this.leaveListener)
        this.selectListener = () => {
            if (this.unselectHelper)
                return
            this.unselectHelper = document.createElement('div')
            this.unselectHelper.classList.add('unselect-helper')
            this.unselectListener = () => {
                if (this.mouseInside)
                    return
                this.clearUnselectHelper()
                this.onUnselect()
            }
            this.unselectHelper.addEventListener('pointerdown'
                , this.unselectListener)
            this.outerElement.parentNode.appendChild(this.unselectHelper)
            this.onSelect()
        }
        this.innerElement.addEventListener('pointerup', this.selectListener)
    }

    _release() {
        this.innerElement.removeEventListener('mouseenter', this.enterListener)
        this.innerElement.removeEventListener('mouseleave', this.leaveListener)
        this.innerElement.removeEventListener('pointerup', this.selectListener)
        this.clearUnselectHelper()
    }

    clearUnselectHelper() {
        if (!this.unselectHelper)
            return
        this.unselectHelper.removeEventListener('pointerdown'
            , this.unselectListener)
        this.unselectHelper.remove()
        delete this.unselectListener
        delete this.unselectHelper
    }

    onEnter() {
        this.mouseInside = true
    }

    onLeave() {
        delete this.mouseInside
    }

    onSelect() {
        this.innerElement.classList.add('selected')
    }

    onUnselect() {
        this.innerElement.classList.remove('selected')
    }
}
Breastwork answered 30/9, 2008 at 13:17 Comment(1)
This is way too much code for the answer that was already given... Why would you make this? Wasting a lot of event listeners, just for what would be adding 2 onclicks, one on the element, another on the body closing the element that you want closedClout
J
-1
$(document).on('click.menu.hide', function(e){
  if ( !$(e.target).closest('#my_menu').length ) {
    $('#my_menu').find('ul').toggleClass('active', false);
  }
});

$(document).on('click.menu.show', '#my_menu li', function(e){
  $(this).find('ul').toggleClass('active');
});
div {
  float: left;
}

ul {
  padding: 0;
  position: relative;
}
ul li {
  padding: 5px 25px 5px 10px;
  border: 1px solid silver;
  cursor: pointer;
  list-style: none;
  margin-top: -1px;
  white-space: nowrap;
}
ul li ul:before {
  margin-right: -20px;
  position: absolute;
  top: -17px;
  right: 0;
  content: "\25BC";
}
ul li ul li {
  visibility: hidden;
  height: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-width: 0 0 1px 0;
}
ul li ul li:last-child {
  border: none;
}
ul li ul.active:before {
  content: "\25B2";
}
ul li ul.active li {
  display: list-item;
  visibility: visible;
  height: inherit;
  padding: 5px 25px 5px 10px;
}
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<div>
  <ul id="my_menu">
    <li>Menu 1
      <ul>
        <li>subMenu 1</li>
        <li>subMenu 2</li>
        <li>subMenu 3</li>
        <li>subMenu 4</li>
      </ul>
    </li>
    <li>Menu 2
      <ul>
        <li>subMenu 1</li>
        <li>subMenu 2</li>
        <li>subMenu 3</li>
        <li>subMenu 4</li>
      </ul>
    </li>
    <li>Menu 3</li>
    <li>Menu 4</li>
    <li>Menu 5</li>
    <li>Menu 6</li>
  </ul>
</div>

Here is jsbin version http://jsbin.com/xopacadeni/edit?html,css,js,output

Jere answered 30/9, 2008 at 13:17 Comment(2)
That's not what Stack Overflow is for. "Explanation is vital for a good answer."Geniegenii
The link almost the same old as the answer 😅Jere
H
-1

Have a try of this:

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

https://jsfiddle.net/4cj4jxy0/

But note that this cannot work if the click event cannot reach the html tag. (Maybe other elements have stopPropagation()).

Henriettehenriha answered 30/9, 2008 at 13:17 Comment(0)
W
-1

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

$('#menucontainer').click(function(event){
    event.stopPropagation();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
 <button id='#menucontainer'>Ok</button> 
</html>
Wine answered 30/9, 2008 at 13:17 Comment(1)
This is just a verbatim copy of this revision of the accepted answer, at the time this was written. This doesn’t add anything, apart from a misunderstood HTML example: the #menucontainer is supposed to be the element containing the entire menu; and //Hide the menus if visible isn’t implemented, so there is no actual demonstration.Lyingin
S
-1

As a wrapper to this great answer from Art, and to use the syntax originally requested by OP, here's a jQuery extension that can record wether a click occured outside of a set element.

$.fn.clickOutsideThisElement = function (callback) {
    return this.each(function () {
        var self = this;
        $(document).click(function (e) {
            if (!$(e.target).closest(self).length) {
                callback.call(self, e)
            }
        })
    });
};

Then you can call like this:

$("#menuscontainer").clickOutsideThisElement(function() {
    // handle menu toggle
});

Here's a demo in fiddle

Sapp answered 30/9, 2008 at 13:17 Comment(0)
S
-2
    $('#menucontainer').click(function(e){
        e.stopPropagation();
     });

    $(document).on('click',  function(e){
        // code
    });
Shelburne answered 30/9, 2008 at 13:17 Comment(1)
Doesn’t add much beyond what the accepted answer already provides.Lyingin
B
-3

The easiest way: mouseleave(function())

More info: https://www.w3schools.com/jquery/jquery_events.asp

Barksdale answered 30/9, 2008 at 13:17 Comment(3)
A link to a solution is welcome, but please ensure your answer is useful without it: add context around the link so your fellow users will have some idea what it is and why it’s there, then quote the most relevant part of the page you're linking to in case the target page is unavailable. Answers that are little more than a link may be deleted.Parallel
@Daniil this is not a link-only answer, though. If the link were removed, the first sentence would still constitute an answer.Drandell
I agree this is not a link only answer, but it's not an answer to this question as this answer is when the mouse leaves the element, and not on click as was asked ;)Involucrum
H
-3

This is a classic case of where a tweak to the HTML would be a better solution. Why not set the click on the elements which don't contain the menu item? Then you don't need to add the propagation.

$('.header, .footer, .main-content').click(function() {
//Hide the menus if visible
});
Halves answered 30/9, 2008 at 13:17 Comment(1)
It's bad idea to do this because if later on you decide to add some kind of footer2 or something else in between you will have to remember to add it also. $('html').click() easier and makes it work in every case.Villainous
D
-4
$("html").click(function(){
    if($('#info').css("opacity")>0.9) {
        $('#info').fadeOut('fast');
    }
});
Dozier answered 30/9, 2008 at 13:17 Comment(1)
This seems unrelated to the question.Lyingin
G
-4
$("body > div:not(#dvid)").click(function (e) {
    //your code
}); 
Gallaway answered 30/9, 2008 at 13:17 Comment(2)
Add a click handler to every other element? You will kill performance. There are much better ways to do this.Runin
This add a click handler to all div element except #dvid, it is very expensive for the page and if you have some div inside #divid also they have the handler and it can be an unexpected effectExpiry
A
-4

Using not():

$("#id").not().click(function() {
    alert('Clicked other that #id');
});
Affirmation answered 30/9, 2008 at 13:17 Comment(1)
This doesn't work. not() removes an element from the list of selected elements (api.jquery.com/not). Without a selector as it's parameter it does nothing, thus returning $('#id') which is exactly the opposite of what we're trying to accomplish.Kirit
L
-4

Standard HTML:

Surround the menus by a <label> and fetch focus state changes.

http://jsfiddle.net/bK3gL/

Plus: you can unfold the menu by Tab.

Loreleilorelie answered 30/9, 2008 at 13:17 Comment(2)
plus: you can unfold the menu by <kbd>tab</kbd>Sagittal
This is invalid HTML.Lyingin
L
-4

You can set a tabindex to the DOM element. This will trigger a blur event when the user click outside the DOM element.

Demo

<div tabindex="1">
    Focus me
</div>

document.querySelector("div").onblur = function(){
   console.log('clicked outside')
}
document.querySelector("div").onfocus = function(){
   console.log('clicked inside')
}
Lunisolar answered 30/9, 2008 at 13:17 Comment(1)
tabindex breaks accessibility; there are much better options than this hack, so it’s not worth using it for this purpose.Lyingin
D
-4

Try this code:

if ($(event.target).parents().index($('#searchFormEdit')) == -1 &&
    $(event.target).parents().index($('.DynarchCalendar-topCont')) == -1 &&
    (_x < os.left || _x > (os.left + 570) || _y < os.top || _y > (os.top + 155)) &&
    isShowEditForm) {

    setVisibleEditForm(false);
}
Dolerite answered 30/9, 2008 at 13:17 Comment(0)
A
-4
 <div class="feedbackCont" onblur="hidefeedback();">
        <div class="feedbackb" onclick="showfeedback();" ></div>
        <div class="feedbackhide" tabindex="1"> </div>
 </div>

function hidefeedback(){
    $j(".feedbackhide").hide();
}

function showfeedback(){
    $j(".feedbackhide").show();
    $j(".feedbackCont").attr("tabindex",1).focus();
}

This is the simplest solution I came up with.

Attired answered 30/9, 2008 at 13:17 Comment(1)
If <div class="feedbackCont"> doesn’t also have tabindex, then it can never blur; but if it does, then it will also blur if it has focus within, e.g. <div class="feedbackhide"> as intended, but also on every other element that is focusable. This won’t work.Lyingin

© 2022 - 2024 — McMap. All rights reserved.