tags:

views:

72

answers:

4

I have a dropdown menu system setup such as this...

This is the scripting section of the HTML page.

    $('.ui-dropdown').each(function () {
        $(this).dropdown();
    });

Then in the actual HTML...

            <li class="ui-dropdown">
                <a href="#">Dropdown Menu</a>
                <div>
                    Test
                </div>
            </li>

It works very simply. the div is set to display: none;. Then there are methods in the jQuery plugin.

    // drop the menu down so that it can be seen.
    function drop(e) {
        // show the menu section.
        options.menu.show();
    }

    // lift the menu up, hiding it from view.
    function lift(e) {
        if (!options.menu.is(':visible'))
            return;
        options.menu.hide();
    }

Now, this works okay, but I want the menu to vanish when someone clicks anywhere other than the components inside of the div or the menu's triggering button. To try and fix that approach, I added this code.

$(document).click(lift);

This works, a little too well. It is catching (obviously) everything, including clicks to the button, the menu, etc. So I tried to fix it with the following functions.

options is defined as follows.

    options.button = $(this);
    options.menu = $(this).find('> div');
    options.links = $(this).find('> a');

    options.button.click(function (e) {
        options.menu.is(':visible') ? lift() : drop();
        e.stopPropogation(); // prevent event bubbling
    });

    options.links.click(function (e) {
        e.stopPropagation(); //prevent event bubbling
    });

    options.menu.click(function (e) {
        e.stopPropagation(); // prevent event bubbling
    });

But still no avail. How can I get $(document).click(lift) to be ignored when the menu I am wishing to be interacted with is clicked upon?


Below is the entire jQuery Plugin, just for reference.

jQuery.fn.dropdown = function () {
    var defaults = {
        class: null,
        button: null,
        menu: null
    };
    return this.each(function () {

        // initialize options for each dropdown list, since there
        // very well may be more than just one.
        var options = $.extend(defaults, options);

        // specifically assign the option components.
        options.class = '.' + $(this).attr('class');
        options.list = $(this); // keep a constant reference to the root.
        options.button = $(this).find('> a');
        options.menu = $(this).find('> div');

        // bind the lift event to the document click.
        // This will allow the menu to collapse if the user
        // clicks outside of it; but we will stop event bubbling to
        // keep it from being affected by the internal document links.
        $(document).click(function (e) {
            var $target = $(e.target);

            // check to see if we have clicked on one of the dropdowns, and if so, dismiss
            // the execution. We only want to lift one if we're not trying to interact with
            // one.
            if ($target.is(options.class) || $target.closest(options.class).length)
                return false;

            lift(e);
        });

        // when the button is clicked, determine the state of the
        // dropdown, and decide whether or not it needs to be lifted
        // or lowered.
        options.button.click(function (e) {
            options.menu.is(':visible') ? lift() : drop();
            e.stopPropogation(); // prevent event bubbling
        });

        // drop the menu down so that it can be seen.
        function drop(e) {
            // show the menu section.
            options.menu.show();
            // style the button that drops the menu, just for aesthetic purposes.
            options.list.addClass("open");
        }

        // lift the menu up, hiding it from view.
        function lift(e) {
            if (!options.menu.is(':visible'))
                return;
            options.menu.hide();


            // style the button that drops the menu, just for aesthetic purposes.
            options.list.removeClass('open');
        }
    });
};
A: 

In the lift function why not check if the elements id/class is not your dropdown, and if so, hide it.

For example:

function lift()
{
    if(this.getAttribute('class') != 'ui-dropdown')
    {
        // Hide it
    }
}
Wolfy87
Forgive me, but I am a newbie to jQuery. I am unsure of how to go about this. How would I check that?
Stacey
I have appended an example, it might need tweeking because it is not tested but still
Wolfy87
+1  A: 

EDIT: This works, but unfortunately not in this case. Very poor performance, check out Nick Craver's comments below.

You can use the following selector to select everything but your menu.

$(':not(selector)')

You could try the faster equivalent method .not(selector) on your document selector to select everything on the page except the menu. Haven't tested this yet though.

Rickjaah
Is there any way to cascade this with multiple things? Could you provide an example? I am a little confused as to how you are proposing I use this.
Stacey
Please don't do this, it's a very, *very* bad way to solve the problem, binding potentially thousands of event handlers.
Nick Craver
The not selector is not supported by IE
Wolfy87
@Wolfy87 - This is jQuery, it's supported everywhere, but it remains a very bad idea in this case.
Nick Craver
That is a fair comment, didn't think about that. Well, so much for my two cents.
Rickjaah
@Stacey...if by cascade you mean chain? Then yes. All functions of the jQuery object return the same list...this is called "chaining". This way you can do cool things like $(".contentWrapper").css("color", "green").show(); and so on.
Kamikaze Mercenary
I have updated the post to show the entire plugin, if it helps.
Stacey
+1  A: 

You need to check the target on the click first to make sure that they didn't click in the list.

jQuery.fn.dropdown = function () {
    var defaults = {
        button: null,
        menu: null,
        links: null,
        identClass: 'my-dropdown'
    };
    var options = $.extend(defaults, options);

    return this.each(function () {
        $(this).addClass( options.identClass );
        /* ... */
    });

    /* ... */

    // lift the menu up, hiding it from view.
    function lift() {
        if (!options.menu.is(':visible'))
            return;
        options.menu.hide();
    }
    $(document).click(function(){
        var $target = $(e.target);
        if ( $target.is('.' + options.identClass) || $target.closest('.' + options.identClass).length ) {
            return;
        }
        lift();
    });
};
BBonifield
Hrnm, this doesn't seem to do it, either...
Stacey
I have updated the post to show the entire plugin, if it helps.
Stacey
@Stacey I believe the new example I have will work for you. It checks to see if you clicked on a `.ui-dropdown` element first and then to see if it is a child of a `.ui-dropdown`.
BBonifield
This does solve the problem of the $(document).click. But using this method, the lift() method does not work if directly invoked by clicking on the menu again.
Stacey
What if I wrap the target checking code in (document).click(function(e){ });? Would that cause too many event wirings?
Stacey
@Stacey I didn't notice you posted the full source. I updated my example to work with your situation.
BBonifield
Err, try again. I made the last edit too fast.
BBonifield
Hrnm, unfortunately, no, this still isn't doing anything. I don't see where it is calling the lift(e) function. I have tried replacing 'return true' with that call and it still doesn't work.
Stacey
Ah! I was missing the 'e' parameter in the click(function() statement. I believe this solves it. Let me post the updated source.
Stacey
Can you explain what the '.closest' is for? I do not understand the use of this function in the statement.
Stacey
@Stacey - It checks to see if you clicked on a child of `.ui-dropdown`, like the `a` tags, for instance.
BBonifield
Also, is there any way to use the options. elements inside the plugin, instead of hard coded css classes? I am trying it that way and that fails. The plugin needs to be independent of the css classes.
Stacey
For instance, I have tried if ($target.is(options.button) || $target.closest(options.button).length) - and it simply does not work. it only works if I put in the hard-coded .css values.
Stacey
Also, the 'closest' thing isn't doing that. If I click on anything that is a child of the parent element, it still fires the 'lift' function.
Stacey
I thought I had it working, but no, this still isn't quite going to function right. The .closest doesn't work - I can put child elements in, and any attempt to interact with them triggers the lift.
Stacey
Okay, actually, I think I've figured out the issue. I'll post an update momentarily explaining the confusion.
Stacey
Have your plugin add a class. Reference my example now.
BBonifield
Yes, you are exactly correct.I did that in a little bit of a different way. I have updated the plugin code to illustrate my solution. Thank you so much for your help!
Stacey
A: 

What I did in this instance is close all the menus and then display the one that was actually clicked.

For example:

$('.ui-dropdown').click(function() {
   $(this).siblings('.ui-dropdown').hide();
   $(this).show();
   return false;
});

$(document).click(function() {
    $('ui-dropdown').hide();
);
js1568
Wouldnt this provoke a 'flash' on the screen?
Rickjaah
I am not sure how to incorporate this into the jQuery plugin code. I have this all wrapped into a simple plugin method named .dropdown();
Stacey
@Rickjaah No there is no 'flash' of any kind. Think about clicking on a menu: 1) close all menus, 2) show the selected menu. This is intuitive functionality.
js1568
I'm still a bit confused. Would it help if I posted the entire plugin?
Stacey
@Stacey Just use this code instead of the code you use for options.*.click();
js1568
But if the user decides to click on the same menu again, this does occur. You would be correct if you would use something like $(this).siblings(.ui-dropdown).hide() if all these selectors would have the same parent.
Rickjaah
have updated the post to show the entire plugin, if it helps.
Stacey