ChatGPT解决这个技术问题 Extra ChatGPT

Avoid dropdown menu close on click inside

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.

1. Your jsfiddle is not working. 2. What exactly you want to achieve?
@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.
@paulalexandru Got it, Right?
@php-dev: i have updated it again for sake of challenge, now it is perfect... see the demo.

M
Mosh Feu

This should help as well

$(document).on('click', 'someyourContainer .dropdown-menu', function (e) {
  e.stopPropagation();
});

I think thats for sure the most simplest solution to this problem. Thumbs up - just tested this one and it works!
This is by far the best solution.
That's the most common solution, but I have a different requirement. Please carefully read my question :)
Best solution for simply keeping the menu open on clicks.
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.
p
php-dev

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/


This solution is working fine. The bounty should be awarded to you if there is no other answer.
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 ;)
@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'); });
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(); });
e.stopPropagation() is the best solution.
G
Gregory Bowers

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>

functionally worked splendidly for me, but broke the styling
this was the fastest solution for me
This isn't a best answer :) an <li> element should not be inside a <form> element.
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
Really great! Who could realised that form tag will prevent closing )))) Thanks a lot!
D
David Passmore

Bootstrap provides the following function:

| This event is fired immediately when the hide instance method 
hide.bs.dropdown | has been called. The toggling anchor element is available as the 
                 | relatedTarget property of the event.

Therefore, implementing this function should be able to disable the dropdown from closing.

$('#myDropdown').on('hide.bs.dropdown', function (e) {
    var target = $(e.target);
    if(target.hasClass("keepopen") || target.parents(".keepopen").length){
        return false; // returning false should stop the dropdown from hiding.
    }else{
        return true;
    }
});

@Vartan, Also the event target property is the li.dropdown, the relatedTarget is the a.dropdown-toggle. So you can't referer to the clicked element within the hide event handler.
hasClass takes a classname, not a CSS selector. target.hasClass(".keepopen") should be target.hasClass("keepopen"). Otherwise the code won’t work.
I have scanned the internet and this is the most intelligent solution by far, for this reason... msot other methods call for an approach that uses stopping the default event or stopping propagation. these approaches are very bad as they will break any other triggered events inside the drop down (i.e. my jquery mobile form elements) using this approach allows you to specifically identify when the menu itself is closing and allow you to take action on that event alone. this answer needs so many stars....
@webfish How much did it take you to scan the internet? (rofl)
Maybe this used to work.. but with bootstrap 4, e.target is always the dropdown itself. Not the clicked target. Even if you click outside the dropdowns, e.target is still the dropdown. So you cannot perform this kind of check. Anyone see a way around this?
S
Samvel Aleqsanyan

This might help:

$("dropdownmenuname").click(function(e){
   e.stopPropagation();
})

This is definitely the simplest/best answer if you want a user to interact with a UI element present on the dropdown (e.g. radio buttons) and don't want the menu to close when they toggle options.
p
php-dev

I also found a solution.

Assuming that the Twitter Bootstrap Components related events handlers are delegated to the document object, I loop the attached handlers and check if the current clicked element (or one of its parents) is concerned by a delegated event.

$('ul.dropdown-menu.mega-dropdown-menu').on('click', function(event){
    var events = $._data(document, 'events') || {};
    events = events.click || [];
    for(var i = 0; i < events.length; i++) {
        if(events[i].selector) {

            //Check if the clicked element matches the event selector
            if($(event.target).is(events[i].selector)) {
                events[i].handler.call(event.target, event);
            }

            // Check if any of the clicked element parents matches the 
            // delegated event selector (Emulating propagation)
            $(event.target).parents(events[i].selector).each(function(){
                events[i].handler.call(this, event);
            });
        }
    }
    event.stopPropagation(); //Always stop propagation
});

Hope it helps any one looking for a similar solution.

Thank you all for your help.


Wow!!! worked perfectly! Was in trouble for a week , I am using mCustomeScrollbar and after clicking on scrollbar in drop down it was closing it, your script worked and now its working fine! Thanks a million.
works great! how does one extend this, so that a user can still press "ESC" (or even click outside) on keyboard to close the DD?
@MaggewDotCom The ESC key press should still work. The same for clicking outside ...
The only downside of this solution is that it doesn't allow nested dropdowns (say you have a dropright inside a dropdown). Is there a workaround for this case?
N
NIKHIL CHANDRA ROY

I just add onclick event like below to not close dropdown-menu.

 <div class="dropdown-menu dropdown-menu-right" onclick="event.stopPropagation()" aria-labelledby="triggerId">

This seems to be a correct answer for Bootstrap@5.0.0-Beta3
This worked for me like a charm, thanks
this should be on top this is by far the simplest and less code answer
T
Terry Lin
$('body').on("click", ".dropdown-menu", function (e) {
    $(this).parent().is(".open") && e.stopPropagation();
});

This may work for any conditions.


this should be the correct answer as it uses fewer lines of code rather than the selected answer
@MuhammadOmerAslam Nah! As it doesn't fulfill my requirement
T
TheVigilant

I tried this simple thing and it worked like a charm.

I changed the dropdown-menu element from <div> to <form> and it worked well.

<div class="nav-item dropdown" >
  <a href="javascript:;" class="nav-link dropdown-toggle" data-toggle="dropdown">
   Click to open dropdown
 </a>
 <form class="dropdown-menu   ">
  <ul class="list-group text-black">
     <li class="list-group-item"  >
     </li>
     <li class="list-group-item"   >
     </li>
  </ul>
</form>

K
Kerry Johnson

jQuery:

<script>
  $(document).on('click.bs.dropdown.data-api', '.dropdown.keep-inside-clicks-open', function (e) {
    e.stopPropagation();
  });
</script>

HTML:

<div class="dropdown keep-inside-clicks-open">
  <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">
     Dropdown Example
    <span class="caret"></span>
  </button>
  <ul class="dropdown-menu">
    <li><a href="#">HTML</a></li>
    <li><a href="#">CSS</a></li>
    <li><a href="#">JavaScript</a></li>
  </ul>
</div>

Demo:

Generic: https://jsfiddle.net/kerryjohnson/omefq68b/1/

Your demo with this solution: http://jsfiddle.net/kerryjohnson/80oLdtbf/101/


K
Konrad Kalemba

I modified @Vartan's answer to make it work with Bootstrap 4.3. His solution doesn't work anymore with the latest version as target property always returns dropdown's root div no matter where the click was placed.

Here is the code:

$('.dropdown-keep-open').on('hide.bs.dropdown', function (e) {
  if (!e.clickEvent) {
    // There is no `clickEvent` property in the `e` object when the `button` (or any other trigger) is clicked. 
    // What we usually want to happen in such situations is to hide the dropdown so we let it hide. 
    return true;
  }

  var target = $(e.clickEvent.target);

  return !(target.hasClass('dropdown-keep-open') || target.parents('.dropdown-keep-open').length);
});
<div class="dropdown dropdown-keep-open">
  <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
    Dropdown button
  </button>
  <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
    <a class="dropdown-item" href="#">Action</a>
    <a class="dropdown-item" href="#">Another action</a>
    <a class="dropdown-item" href="#">Something else here</a>
  </div>
</div>

oneline: $('.dropdown-keep-open').on('hide.bs.dropdown', ev => !(ev.clickEvent && $(ev.target).has(ev.clickEvent.target).length))
@MatveevDmitriy I'm sorry but no one, simply no one can read this one liner. Writing code this way is imho a bad practice.
The last line can be simplified using return !target.closest('.dropdown-keep-open').length). That said, this is a great solution! Found this needed to solve this problem.
N
Nimantha
$('body').on("click", ".dropdown-menu", function (e) {
    $(this).parent().is(".show") && e.stopPropagation();
});

First, thank you for your answer. The question is already answered. Also, I have a different requirement as described in the question than the common requirement.
S
S1awek

In the new Bootstrap 5 the solution is trivially simple.

Quote from the documentation page:

By default, the dropdown menu is closed when clicking inside or outside the dropdown menu. You can use the autoClose option to change this behavior of the dropdown.

In addition to the default behavior, we have 3 options available here:

Clickable outside: data-bs-auto-close="outside"

Clickable inside: data-bs-auto-close="inside"

Manual close: data-bs-auto-close="false"

More info: https://getbootstrap.com/docs/5.1/components/dropdowns/#auto-close-behavior


N
Nate

I've got a similar problem recently and tried different ways to solve it with removing the data attribute data-toggle="dropdown" and listening click with event.stopPropagation() calling.

The second way looks more preferable. Also Bootstrap developers use this way. In the source file I found initialization of the dropdown elements:

// APPLY TO STANDARD DROPDOWN ELEMENTS
$(document)
.on('click.bs.dropdown.data-api', clearMenus)
.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
.on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
}(jQuery);

So, this line:

.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })

suggests you can put a form element inside the container with class .dropdown to avoid closing the dropdown menu.


F
Frank Thoeny

Like for instance Bootstrap 4 Alpha has this Menu Event. Why not use?

// PREVENT INSIDE MEGA DROPDOWN
$('.dropdown-menu').on("click.bs.dropdown", function (e) {
    e.stopPropagation();
    e.preventDefault();                
});

e.preventDefault(); blocks links, you can skip it
@czachor you're Right! e.preventDefault(); Prevent a link from following the URL.
D
Daniel Schlaug

Bootstrap has solved this problem themselves in their support for <form> tags in dropdowns. Their solution is quite graspable and you can read it here: https://github.com/twbs/bootstrap/blob/v4-dev/js/src/dropdown.js

It boils down to preventing propagation at the document element and doing so only for events of type 'click.bs.dropdown.data-api' that match the selector '.dropdown .your-custom-class-for-keep-open-on-click-elements'.

Or in code

$(document).on('click.bs.dropdown.data-api', '.dropdown .keep-open-on-click', (event) => {
    event.stopPropagation();
});

Thank you thank you thank you for the link to the source. For others wondering why the form approach from the examples isn't working for you - I was using class dropleft, but it also needs dropdown in order for the form-selector to match.
I
Ivan Kara

You can also use form tag. Example:

<div class="dropdown-menu">
    <form>
        Anything inside this wont close the dropdown!
        <button class="btn btn-primary" type="button" value="Click me!"/>
    </form>
    <div class="dropdown-divider"></div>
    <a class="dropdown-item" href="#">Clik this and the dropdown will be closed</a>
    <a class="dropdown-item" href="#">This too</a>
</div>

Source: https://getbootstrap.com/docs/5.0/components/dropdowns/#forms


R
Ramast

You could simply execute event.stopPropagation on click event of the links themselves. Something like this.

    $(".dropdown-menu a").click((event) => {
         event.stopPropagation()
         let url = event.target.href
         //Do something with the url or any other logic you wish
    })

Edit: If someone saw this answer and is using react, it will not work. React handle the javascript events differently and by the time your react event handler is being called, the event has already been fired and propagated. To overcome that you should attach the event manually like that

handleMenuClick(event) {
   event.stopPropagation()
   let menu_item = event.target
   //implement your logic here.
}
componentDidMount() {
    document.getElementsByClassName("dropdown-menu")[0].addEventListener(
        "click", this.handleMenuClick.bind(this), false)
   }
}

N
Neil

You can stop click on the dropdown from propagating and then manually reimplement the carousel controls using carousel javascript methods.

$('ul.dropdown-menu.mega-dropdown-menu').on('click', function(event) {
    event.stopPropagation();
});

$('a.left').click(function () {
    $('#carousel').carousel('prev');
});

$('a.right').click(function () {
    $('#carousel').carousel('next');
});

$('ol.carousel-indicators li').click(function (event) {
    var index = $(this).data("slide-to");
    $('#carousel').carousel(index);
});

Here is the jsfiddle.


Your example works fine but the problem is that the dropdown can hold any content and not only images carousel ...
L
Luca Filosofi

demo: http://jsfiddle.net/4y8tLgcp/

$('ul.nav.navbar-nav').on('click.bs.dropdown', function(e){
    var $a  = $(e.target), is_a = $a.is('.is_a');
    if($a.hasClass('dropdown-toggle')){   
        $('ul.dropdown-menu', this).toggle(!is_a);
        $a.toggleClass('is_a', !is_a);
    }
}).on('mouseleave', function(){
    $('ul.dropdown-menu',this).hide();
    $('.is_a', this).removeClass('is_a');
});

i have updated it once again to be the smartest and functional as possible. it now close when you hover outside the nav, remaining open while you are inside it. simply perfect.


Your solution is smart due to its shortness, however, after added the class show, the dropdown doesn't close on click outside, and we need to click on the dropdown-toggle before ... +1 anyway
i have updated it, look at the demo. it is the best cause don't check against the body click event everytime. affect only element inside each single nav element not outside of it.
My solution also don't rely on body click event ^^
yes, it relay in part on body click event, it try to check what element has been clicked trought the body delegation. $('body').on('click', function (e) {
Nah! That's not my answer. My answer is the last posted one.
R
Radu Ciobanu

I know there already is a previous answer suggesting to use a form but the markup provided is not correct/ideal. Here's the easiest solution, no javascript needed at all and it doesn't break your dropdown. Works with Bootstrap 4.

<form class="dropdown-item"> <!-- Your elements go here --> </form>


@RosdiKasim I have it working fine on a website with Bootstrap 4.0.0. Haven't tested with 4.1
Actually it should be like :
j
jtate

I know this question was specifically for jQuery, but for anyone using AngularJS that has this problem you can create a directive that handles this:

angular.module('app').directive('dropdownPreventClose', function() {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
          element.on('click', function(e) {
            e.stopPropagation(); //prevent the default behavior of closing the dropdown-menu
          });
        }
    };
});

Then just add the attribute dropdown-prevent-close to your element that is triggering the menu to close, and it should prevent it. For me, it was a select element that automatically closed the menu:

<div class="dropdown-menu">
  <select dropdown-prevent-close name="myInput" id="myInput" ng-model="myModel">
    <option value="">Select Me</option>
  </select>
</div>

<div class="dropdown-menu" ng-click="$event.stopPropagation()"> works for me in 1.2.x
N
Nir Soudry

With Angular2 Bootstrap, you can use nonInput for most scenarios:

<div dropdown autoClose="nonInput">

nonInput - (default) automatically closes the dropdown when any of its elements is clicked — as long as the clicked element is not an input or a textarea.

https://valor-software.com/ng2-bootstrap/#/dropdowns


J
Jacky Tong

[Bootstrap 4 Alpha 6][Rails] For rails developer, e.stopPropagation() will lead to undesirable behavior for link_to with data-method not equal to get since it will by default return all your request as get.

To remedy this problem, I suggest this solution, which is universal

$('.dropdown .dropdown-menu').on('click.bs.dropdown', function() {
  return $('.dropdown').one('hide.bs.dropdown', function() {
    return false;
  });
});

$('.dropdown .dropdown-menu').on('click.bs.dropdown', function() { return $('.dropdown').one('hide.bs.dropdown', function() { return false; }); });


A
Anjana Silva

This helped me,

$('.dropdown-menu').on('click', function (e) {
     if ($(this).parent().is(".open")) {
         var target = $(e.target);
         if (target.hasClass("keepopen") || target.parents(".keepopen").length){
                    return false; 
                }else{
                    return true;
                }
            }            
});

Your drop down menu element needs to be like this, (take a note of the classes dropdown-menu and keepopen.

<ul role="menu" class="dropdown-menu topmenu-menu eserv_top_notifications keepopen">

The above code prevents biding on the whole <body>, instead to the specfic element with the class dropdown-menu.

Hope this helps someone.

Thanks.


Thanks Updated As Per Bootstrap 4 $('.dropdown-menu[data-handledropdownclose="true"]').on('click', function (e) { if ($(this).parent().is(".show")) { var target = $(e.target); return (target.hasClass("CloseDropDown") || target.parents(".CloseDropDown").length); } });
T
Tom James

Bootstrap 5

If anyone comes to this via Google wanting a Bootstrap 5 version like I was, it's built in by adding data-bs-auto-close="outside". Note the option is autoClose but when passing as a data attribute the camelcasing is removed & separated by a dash.

I have a collapse widget in a dropdown & adding data-bs-auto-close="outside" to the parent data-bs-toggle="dropdown" trigger keeps the dropdown open while the collapse is toggled.

See official Bootstrap docs: https://getbootstrap.com/docs/5.1/components/dropdowns/#options And this codepen for example code (not my pen): https://codepen.io/SitePoint/pen/BaReWGe


G
Gee-Bee

The simplest working solution for me is:

adding keep-open class to elements that should not cause dropdown closing

and this piece of code do the rest:

$('.dropdown').on('click', function(e) {
    var target = $(e.target);
    var dropdown = target.closest('.dropdown');
    return !dropdown.hasClass('open') || !target.hasClass('keep-open');
});

can you please help me to fix this stackoverflow.com/questions/51552712/… @Gee-Bee
M
Mike

I've found none of the solutions worked as I would like using default bootstrap nav. Here is my solution to this problem:

       $(document).on('hide.bs.dropdown', function (e) {
        if ($(e.currentTarget.activeElement).hasClass('dropdown-toggle')) {
          $(e.relatedTarget).parent().removeClass('open');
          return true;
        }
        return false;
       });

a
ashwaniKumar

Instead of writing some javascript or jquery code(reinventing the wheel). The above scenario can be managed by bootstrap auto-close option. You can provide either of the values to auto-close:

always - (Default) automatically closes the dropdown when any of its elements is clicked. outsideClick - closes the dropdown automatically only when the user clicks any element outside the dropdown. disabled - disables the auto close

Take a look at the following plunkr :

http://plnkr.co/edit/gnU8M2fqlE0GscUQtCWa?p=preview

Set

uib-dropdown auto-close="disabled" 

Hope this helps :)


It should be explicitly noted that this is only good for people using ui-bootstrap, which is probably not the case for very many people viewing this question.
You well described but I think you should write solution as uib-dropdown auto-close="outsideClick". Menu close on click inside is just a little disconfort but menu not close on click outside is quite upsetting.
D
Dixán Santiesteban Feria

In .dropdown content put the .keep-open class on any label like so:

$('.dropdown').on('click', function (e) {
    var target = $(e.target);
    var dropdown = target.closest('.dropdown');
    if (target.hasClass('keep-open')) {
        $(dropdown).addClass('keep-open');
    } else {
        $(dropdown).removeClass('keep-open');
    }
});

$(document).on('hide.bs.dropdown', function (e) {
    var target = $(e.target);
    if ($(target).is('.keep-open')) {
        return false
    }
});

The previous cases avoided the events related to the container objects, now the container inherits the class keep-open and check before being closed.