388

I have a Twitter Bootstrap dropdown menu. As all Twitter Bootstrap users know, the dropdown menu closes on click (even clicking inside it).

To avoid this, I can easily attach a click event handler on the dropdown menu and simply add the famous event.stopPropagation().

<ul class="nav navbar-nav">
  <li class="dropdown mega-dropdown">
    <a href="javascript:;" class="dropdown-toggle" data-toggle="dropdown">
      <i class="fa fa-list-alt"></i> Menu item 1
      <span class="fa fa-chevron-down pull-right"></span>
    </a>
    <ul class="dropdown-menu mega-dropdown-menu">
      <li>
        <div id="carousel" class="carousel slide" data-ride="carousel">
          <ol class="carousel-indicators">
            <li data-slide-to="0" data-target="#carousel"></li>
            <li class="active" data-slide-to="1" data-target="#carousel"></li>
          </ol>
          <div class="carousel-inner">
            <div class="item">
              <img alt="" class="img-rounded" src="img1.jpg">
            </div>
            <div class="item active">
              <img alt="" class="img-rounded" src="img2.jpg">
            </div>
          </div>
          <a data-slide="prev" role="button" href="#carousel" 
             class="left carousel-control">
            <span class="glyphicon glyphicon-chevron-left"></span>
          </a>
          <a data-slide="next" role="button" href="#carousel" 
             class="right carousel-control">
            <span class="glyphicon glyphicon-chevron-right"></span>
          </a>
        </div>
      </li>
    </ul>
  </li>
</ul>

This looks easy and a very common behavior, however, and since carousel-controls (as well as carousel indicators) event handlers are delegated to the document object, the click event on these elements (prev/next controls, ...) will be “ignored”.

$('ul.dropdown-menu.mega-dropdown-menu').on('click', function(event){
    // The event won't be propagated up to the document NODE and 
    // therefore delegated events won't be fired
    event.stopPropagation();
});

Relying on Twitter Bootstrap dropdown hide/hidden events is not a solution for the following reasons:

  • The provided event object for both event handlers does not give reference to the clicked element
  • I don't have control over the dropdown menu content so adding a flag class or attribute is not possible

This fiddle is the normal behavior and this fiddle is with event.stopPropagation() added.

Update

Thanks to Roman for his answer. I also found an answer that you can find below.

php-dev
  • 6,998
  • 4
  • 24
  • 38
  • 1. Your jsfiddle is not working. 2. What exactly you want to achieve? – paulalexandru Aug 07 '14 at 13:22
  • 1
    @paulalexandru, Updated, added two fiddle. One default behavior, and one with modification. Try clicking on the next & previous button, or on indicators. For the first example, the menu hides & the carousel slides. or the second example : The menu stays open, but the carousel didn't slide since the `event propagation` has been stopped. – php-dev Aug 07 '14 at 14:25
  • @paulalexandru Got it, Right? – php-dev Aug 07 '14 at 20:08
  • @php-dev: i have updated it again for sake of challenge, now it is perfect... see the demo. – Luca Filosofi Aug 12 '14 at 08:50

40 Answers40

445

This should help as well

$(document).on('click', 'someyourContainer .dropdown-menu', function (e) {
  e.stopPropagation();
});
Mosh Feu
  • 28,354
  • 16
  • 88
  • 135
Arbejdsglæde
  • 13,670
  • 26
  • 78
  • 144
  • 3
    That's the most common solution, but I have a different requirement. Please carefully read my question :) – php-dev Feb 23 '16 at 11:56
  • @HaBa-Media I've even talked about this solution in my question second line. – php-dev Jul 25 '16 at 09:56
  • This should definitely be the accepted answer. Cleaner, shorter and doesn't disable normal link behaviour which other answers do – Meules Feb 16 '18 at 14:54
  • simple, elegant and pretty usefull. Just saved me lots of time exploring bootstrap javascript/elements graph. Thanks a lot. – Fernando Jaconete Nov 08 '18 at 01:31
  • 2
    This will not work if the 'click' event is supposed to trigger further JS actions. The dropdown will not close yes, but all the other events triggered by the click event won't fire anymore. – cephei_vv Jun 18 '19 at 05:23
  • 4
    Best solution indeed. I personally used `'.dropdown-menu :not(.dropdown-item)'` so that clicking on a `dropdown-item` does close the popup, but clicking on a `dropdown-header` does not. – BenMorel Jul 18 '19 at 09:10
  • 3
    Everyone needs to stop saying this is the best solution, IT IS NOT. The OP addressed this in the question and correctly does not want to use this solution, so this does not answer OP's question. This is begging for a future bug that can be very difficult to debug. Let's say a future dev comes along and attaches a click handler to this menu. The handler will never fire, because this solution has stopped propagation. The future dev will go crazy trying to understand why this isn't working. Just because code works doesn't mean it's right or the best way. – patrick3853 May 26 '20 at 04:10
  • /**with querySelectorAll => */ document.querySelectorAll('.dropdown-menu').forEach(function (el){ el.addEventListener('click', function (ev) { ev.stopPropagation() }) }) – Fernando Mar 19 '22 at 19:38
  • Problems appear, if you have checkboxes inside. – Boolean_Type Oct 04 '22 at 10:40
  • if you are using bootstrap 5 then it's very easy https://getbootstrap.com/docs/5.0/components/dropdowns/#auto-close-behavior – santu prajapati Jan 13 '23 at 07:04
417

Removing the data attribute data-toggle="dropdown" and implementing the open/close of the dropdown can be a solution.

First by handling the click on the link to open/close the dropdown like this :

$('li.dropdown.mega-dropdown a').on('click', function (event) {
    $(this).parent().toggleClass('open');
});

and then listening the clicks outside of the dropdown to close it like this :

$('body').on('click', function (e) {
    if (!$('li.dropdown.mega-dropdown').is(e.target) 
        && $('li.dropdown.mega-dropdown').has(e.target).length === 0 
        && $('.open').has(e.target).length === 0
    ) {
        $('li.dropdown.mega-dropdown').removeClass('open');
    }
});

Here is the demo : http://jsfiddle.net/RomaLefrancois/hh81rhcm/2/

php-dev
  • 6,998
  • 4
  • 24
  • 38
Roma
  • 4,392
  • 1
  • 11
  • 6
  • 2
    This solution is working fine. The bounty should be awarded to you if there is no other answer. – php-dev Aug 08 '14 at 06:55
  • You seem also to be the only one who understood the situation/requirement :) – php-dev Aug 08 '14 at 06:59
  • 1
    Your solution is working, generic and also "popular". The bounty should be awarded to your answer. I also found an answer, look at it below ;) – php-dev Aug 11 '14 at 22:01
  • Just a tiny update: $('a.dropdown-toggle:not([data-toggle])').on('click', function (e) { var a = $(e.delegateTarget); var li = a.parent(); li.parent().find('.open').removeClass('open'); li.addClass('open'); }); – Martijn H. Mar 15 '15 at 13:03
  • and what with multi-item in primary navigation ? e.g. http://jsfiddle.net/cichy380/hh81rhcm/61/ (if you click **Menu item 1** and after **Menu item 2**) – Cichy Apr 20 '15 at 09:28
  • 1
    @Cichy...just modify the first handler like this...$('li.dropdown.mega-dropdown a').on('click', function (event) { $('li.dropdown.mega-dropdown.open').removeClass('open'); $(this).parent().toggleClass('open'); }); – dsaket Jun 30 '15 at 21:30
  • `$(this).parent().toggleClass('open');` didn't work for me, I used this instead `$(this).parents('.btn-group').toggleClass('open');` – Bogdanio Jul 24 '15 at 23:07
  • 88
    This preferred answer is a hack. wow!!! Here is a simpler BOOTSTRAP 4 ALPHA answer to prevent inside click in a mega-dropdown. `// PREVENT INSIDE CLICK DROPDOWN` `$('.dropdown-menu').on("click.bs.dropdown", function (e) { e.stopPropagation(); e.preventDefault(); });` – Frank Thoeny Mar 29 '16 at 04:04
  • @FrankThoeny your example works fine in Bootstrap 3 too, had similar issue and works perfectly fine. "Roma" you could add this to the answer as well. Easier and cleaner. ex: attach a class to links and then do $('.LINK_CLASS').on('click', function(e) { e.stopPropagation() }); – lesandru Sep 25 '16 at 23:13
  • This one didn't work for me so I published my own answer down the page which fixed the problem without bringing any side problem. – chickens Sep 27 '17 at 20:02
  • 13
    `e.stopPropagation()` is the best solution. – nacholibre Mar 29 '18 at 11:19
  • can you please help me to fix this https://stackoverflow.com/questions/51552712/angular-4-mega-menu-is-not-closed-after-clicked-on-a-menu-inside-it @Roma – Zhu Jul 27 '18 at 08:40
  • You need to add " mega-dropdown " class to that li you removed the data-toggle="dropdown". in order for it to work – Shamseer Ahammed Oct 11 '18 at 09:06
  • This solution does not respect aria-expended attribute. – ya.teck Jun 21 '19 at 16:18
  • Thanks, @nacholibre - your comment works for me with just adding stopPropagation – PokatilovArt Dec 10 '19 at 06:56
  • For bootstrap 5 change data-toggle attrib to data-bs-toggle and use code like this $("#idOfDropdown").dropdown('toggle'); – Sabel Aug 17 '22 at 16:46
  • @FrankThoeny Please read carefully the requirement! I don't need the dropdown to close, but I need other behavious to keep working (such as carousel slides, tabs navigation, ... whatever ) – php-dev Aug 25 '22 at 18:48
53

The absolute best answer is to put a form tag after the class dropdown-menu

so your code is

<ul class="dropdown-menu">
  <form>
    <li>
      <div class="menu-item">bla bla bla</div>
    </li>
  </form>
</ul>
Gregory Bowers
  • 786
  • 6
  • 6
  • 1
    See Илья Баранов's answer, where bootstrap stops a click for dropdown with a form. It can be at any level, so the form could go inside the li. Still, this probably breaks quite a few layouts. – goodeye Sep 04 '17 at 00:16
  • 3
    This is not a semanticlly correct solution - the only valid child of a UL is an LI - if anything the form tags should be inside the li tags - but that may not allwo the desired action - either way a ul > form > li is not correct markup – gavgrif Feb 01 '18 at 05:14
  • 2
    Really great! Who could realised that form tag will prevent closing )))) Thanks a lot! – Nazariy Feb 23 '18 at 11:08
  • 2
    Thnaks for this fast solution – Herman Demsong May 24 '18 at 09:25
  • 1
    Again menu closed while clicking top or down on padding of ul :D. So I removed padding of ul and added same padding on form tag. Isn't the best answer but wow it is not using any script. – vusan Sep 07 '18 at 06:13
  • This is not a correct solution.Why a form?can you explain? – MadPapo Sep 26 '18 at 22:53
  • If something works, how can it NOT be a correct solution? Why it works is because bootstrap js and jquery both manipulate DOM and neither really care about semantics. – Gregory Bowers Oct 10 '19 at 12:12
  • I've replace ul and li tags with div and used form, it's still working fine and now it's valid html too. As for why it works for form because bootstrap support showing a form inside a dropdown (for example login form), in that case it doesn't make sense to hide the popup when clicking on an input field like your username – Ramast Nov 18 '19 at 15:30
  • Put your entire `
      ` inside `
    – Nasser Sadraee Mar 20 '20 at 13:33
  • This simply works, other answers are simply too much work and didn't work. – Ozan Kurt Jan 01 '21 at 08:26
  • Or just replace `
      ` and `
    • ` with `
      `.
    – Twisted Whisper Nov 25 '21 at 17:03