3

I am trying to add a bootstrap dropdown inside another clickable parent. It's kind of tricky I guess. Here is the Codepen I created with the issue recreated.

Codepen with the issue recreated

Expected behavior: On clicking the dropdown, the dropdown will fire, so will by clicking the other button (optional if too tricky) and none will trigger a click on the parent.

    <div class="card-body parent" style="min-height: 300px;">
      <!-- ORIGINAL DROPPER BUTTON -->
      <div class="dropdown original-dropper mb-5">
        <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>
        </div>
      </div>

      <!-- OTHER DROPPER BUTTON -->
      <button class="btn btn-danger other-dropper mt-5 d-inline">Other Button</button>
    </div>

JS:

 $('.parent').on('click', function(e) {
  $(this).css('backgroundColor', 'darkorange');
});

$('.other-dropper').on('click', function(e) {
  e.stopPropagation();
  $('.original-dropper .dropdown-toggle').dropdown();
  console.log('clicked');
});
6
  • Ugh. Codepen forces login to edit and save. Anyway, what about click on the dropdown itself, should it also not propagate to parent? Sometimes it's better to filter propagated events at the parent than block them from the child nodes. Commented Sep 5, 2019 at 15:56
  • You don't need to call dropdown(), that will create dropdown menu, you need to add classes to open dropdown, The easiest way is open dev tools open dropdown and see what changing (show class are added to two elements) so you need to add same classes with jQuery. Best is to use .toggleClass('show') Commented Sep 5, 2019 at 15:57
  • @jcubic Wouldn't that interfere with the dropdowns internal state, eg. causing it not to close properly? Commented Sep 5, 2019 at 16:12
  • @TomášZato It seems you're right, check How to open Bootstrap dropdown programmatically Commented Sep 5, 2019 at 17:19
  • @jcubic Yep, that QA helped me with the answer, although there seems to be a lot of uncertainty and the answers vary. Commented Sep 5, 2019 at 17:22

1 Answer 1

1

First, it's better to filter an event at the target instead of calling stopPropagation. If you stop propagation of event, it prevents any other global listeners from capturing them. For example, other dropdowns will not close if click event propagation was stopped.

So instead I introduced this neat filter method, that allows you to check if the source of event or it's parents has a class:

/**
 * Checks if any of parent nodes of elm has a class name
 * @param {HTMLElement} elm the starting node
 * @param {string} className
 * @param {HTMLElement} stopAtElm if this parent is reached, search stops
**/
function treeHasClass(elm, className, stopAtElm) {
  while(elm != null && elm != stopAtElm) {
    if(elm.classList.contains(className)) {
      return true;
    }
    elm = elm.parentNode;
  }
  return false;
}

Unfortunately you still need stopPropagation for the button, else the click event would close the drop-down immediately.

Second, you want to call .dropdown("toggle"):

$('.original-dropper .dropdown-toggle').dropdown("toggle");

All together, the javascript is:

$('.parent').on('click', function(e) {
  // Do not trigger if target elm or it's child has `no-orange` class
  if(!treeHasClass(e.target, "no-orange", this)) {
    $(this).css('backgroundColor', 'darkorange');
  }
});

$('.other-dropper').on('click', function(e) {
  e.stopPropagation();
  e.preventDefault();
  $('.original-dropper .dropdown-toggle').dropdown("toggle");
  console.log('clicked');
});

And you want to put no-orange to elements that shouldn't change background color:

<button class="no-orange btn btn-danger other-dropper mt-5 d-inline">Other Button</button>

All together here: https://codepen.io/MXXIV/pen/rNBpepd

Sign up to request clarification or add additional context in comments.

5 Comments

Elegantly done! One follow up question, is e.preventDefault(); necessary in the .other-dropper click function? And here in the Bootstrap documentation it's missing the 'toggle' part of the function call - getbootstrap.com/docs/4.0/components/dropdowns/#via-javascript. Or there is something I missed? Thanks a bunch.
@RPMcMurphy I'm not a bootsrap expert. All my google searches have shown that there's a bit of chaos regarding how to do that. The preventDefault is sadly needed - otherwise the propagated click event immediately causes the menu to close again, as it counts as click outside the dropdown.
If you really needed to not use the prevent default, you could instead use setTimeout(()=>{/*toggle menu here*/}), but I think that's even uglier.
@Zato I edited the pen and clicking on the original dropdown button is not triggering the parent background to go orange though clicking on child should do that as I am not filtering anything. You think Bootstrap already took care of what the helper function you created does? Could you take a look- codepen.io/rpmcmurphy/pen/OJLzMrY?editors=0010 I am stopping propagation only on the other button, not the original dropdown button. Just trying to understand if the helper function becomes redundant.
@RPMcMurphy The helper function is for the dropdown itself and all it's items. In the other button, the stopPropagation does the job, but that's only a side effect. Real reason for stopPropagation is to prevent the dropdown from being closed by the click event.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.