4

I have to build a little dropdown system that is triggered with clicks. I've managed how to handle the "click out of the div" question (thanks to this previously posted question).

I can hide the current dropdown clicking on another dropdown's trigger or clicking outside the dropdown itself. So, the problems are these:

  1. Click the current dropdown's trigger it's useless; the dropdown doesn't go away while the other things are working properly
  2. Clicking inside the current dropdown (e.g.: the .divider item), causes the dropdown going away

So, here is the html code (say that I have multiple dropdowns in the header)

    ...
    <div id="header" class="navbar">
        <div class="line">
            <a href="#" class="logo">Site Name</a>
            <ul class="nav right">
                <li>
                    <a href="#" class="has-drop">Item 1</span>
                    <ul class="dropdown-menu">
                        <li><a href="#">Action A</a></li>
                        <li><a href="#">Action B</a></li>
                        <li><a href="#">Action C</a></li>
                        <li class="divider"></li>
                        <li><span>Inline note</span></li>
                    </ul>
                </li>
                <li>
                    <a class="has-drop" href="#">Item 2</a>
                    <ul class="dropdown-menu">
                        <li><a href="#">Action D</a></li>
                        <li><a href="#">Action E</a></li>
                        <li><a href="#">Action F</a></li>
                        <li class="divider"></li>
                        <li><a href="#">Action G</a></li>
                    </ul>
                </li>
            </ul>
        </div>
    </div> <!-- end #header -->
    ...

And this is actually the JS code I'm using, in a dedicated file

    $(document).ready(function(){

        $("html").on({
            click: function(e) {
                $(".dropdown-menu.opened").toggleClass("opened");
            }
        });

        $(".nav > li > a").on({
            click: function(e){
                e.stopPropagation();

                $(".dropdown-menu.opened").toggleClass("opened"); // should close each .dropdowns before opening the current one
                $(e.target).siblings(".dropdown-menu").toggleClass("opened");
            }
        });

    });

Why do you think the dropdown does not hide when I re-click its trigger? Because inside $(".nav > li > a").on() doing practically the same thing?

Also, there is a way to prevent the dropdown hide itself when some of it children are clicked? But maybe this is more related to another question, I don't know.

Last but not least: is this the proper way to do the whole thing, in your opinion?

Thank you all in advance


EDIT: Thanks to @Beetroot-Beetroot I managed how to solve the problem, so here is the current working code $(document).ready(function(){

        $("html").on("click", function(e) {
            $(".dropdown-menu.opened").removeClass("opened");
        });

        $(".nav").find("li :first-child").on("click", function(e) {
            e.preventDefault();
            e.stopPropagation();

            var $thisMenu = $(this).siblings(".dropdown-menu").toggleClass("opened");
            $(".dropdown-menu").not($thisMenu).removeClass("opened");
        });

    });

The "secret" was the clever use of .not(), which I now understood more.

I just changed the target (that can <a> or <span>) into a more generic one. Not sure about the performances in the long term but is certainly a good starting point.

From

$(".nav").find("a.has-drop").on(...

To

$(".nav").find("li :first-child").on(...

Also, here

$(this).closest(".nav").find(".dropdown-menu").not(... I made it more generic like this $(".dropdown-menu").not(... That's because I want to hide all the opened dropdowns. The previous behaviour was considering only one kind of target and only one origin area (e.g.: maybe I need these dropdowns as well in the header and inside some kind of toolbar into the content).


EDIT 2: @Beetroot-Beetroot helped me again. As him suggested, while I was porting the current code in jsFiddle, suddenly the solution came up quite itself. So now the $("html").on("click", ... part looks like this

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

        if ( $(e.target).hasClass("dropdown-menu") || $(e.target).parents(".dropdown-menu").length ) {
            // do nothing, basically
        } else {
            $(".dropdown-menu.opened").removeClass("opened");
        }
    });

Basically I just watch if the click from $("html") has something to do width the dropdown. If yes, do nothing. If no, then this does mean that I have clicked anywere but not where the dropdown was.

Community
  • 1
  • 1

1 Answers1

2

It think something like this should do the job :

$(document).ready(function() {
    $("html").on('click', function(e) {
        $(".dropdown-menu.opened").removeClass("opened");
    });
    $(".nav").find("a.has-drop").on('click', function(e) {
        e.preventDefault();
        e.stopPropagation();
        var $thisMenu = $(this).siblings(".dropdown-menu").toggleClass("opened");
        $(this).closest(".nav").find(".dropdown-menu").not($thisMenu).removeClass("opened");
    });
});

untested

Notes :

  • removeClass() replaces toggleClass() in a couple of places.
  • the secret is to toggle $thisMenu first, then use not($thisMenu) in the following line to ensure that $thisMenu remains in whichever state it was toggled to.
Beetroot-Beetroot
  • 18,022
  • 3
  • 37
  • 44