0

I have created a very simple jQuery "dropdown list" that consists of two div elements. When the first div is clicked, the second div is displayed. Now I want the second div to be closed again, when I click outside that div. But I can't (and actually don't want to, if it's not necessary) use a document- or body-event-listener like this:

$('#div2').click(function(e) {
    doSomething();
    e.stopPropagation();
});
$(document).click(function() {
    $("#div2").hide();
});

Reason: I have other elements on the page that use e.stopPropagation(); and if a user clicked on those elements after opening the dropdown, it wouldn't close again.

I found this plugin, jALDropdown, that somehow handled to close the dropdown even if I click on e.stopPropagation();, or even somewhere outside the page or the browser, and it doesn't seem to use any document/body-event-listener: http://india.assigninfo.com/assignlabs/jaldropdown

Sorrily it's not working with Opera, so I can't use it. And I didn't find a version of the sourcecode that was not minimized, thus I was not able to find out how it works.

Do you have any ideas how to archieve this behaviour, so that the dropdown closes (element is hidden) if I click outside the element, page or even browser, without a document/body-event-listener? Thanks!

René Schubert
  • 1,302
  • 2
  • 13
  • 31

2 Answers2

1

Why not keep the $(document).click() function to hide your division, then add this function to your code and call it instead of event.stopPropagation():

function stopPropAndCloseDiv2(event) {
    $('#div2').hide();
    event.stopPropagation();
};

I tested it, and it seems that it would work fine. You'd just have to remember to use it.

UPDATE:

This gnawed at me, and I found another answer on this great website I know. Here's the link:

add code to a function programatically with JS

I tried that approach and it seems to work: http://jsfiddle.net/kXkFS/8/

Here's the code (there are two buttons in the test page markup):

jQuery.Event.prototype.stopPropagation = (function() {
    var cached_function = jQuery.Event.prototype.stopPropagation;

    return function() { 
        if ( this.type == "click") {
            // Your logic here.
            console.log("I am a modified version of event.stopPropagation()!");
        }
        cached_function.apply(this, arguments); // use .apply() to call it.
    };
}());

$(document).click( function() { console.log("The document caught the event, too."); });
$('#clicker').click( function() { console.log("I'm going to let this event bubble."); });
$('#anotherClicker').click( function(event) { event.stopPropagation(); });

Spoiler: when you click the "clicker" button, the document logs to the console also. When you click the "anotherClicker" button, the custom event.stopPropagation() function logs to the console, and the document doesn't log to the console. Now, you can add your click event handler to the document.

One thing: this might add some overhead, but only if you're calling stopPropagation() pretty darned often.

If anybody wants to weigh in on the wisdom of screwing with the jQuery object like this, please do so. I wonder, but I can't see that an innocuous change such as this one will blow up the web page in the future.

Community
  • 1
  • 1
jCyCle
  • 466
  • 4
  • 7
  • Yeah it would work, but I'd have to touch the other event handlers and replace the stopPropagation call with this new function. If possible I don't want to do that, but try without $(document/body/window).click() - just like jALDropdown does it. – René Schubert Apr 01 '12 at 19:54
  • @RenéSchubert: The update I added should allow you to leave other event handlers alone. Instead, the event object that is passed to them will have changed. – jCyCle Apr 03 '12 at 00:42
  • Interesting, thanks a lot! There's only one logical problem: I need the stopPropagation function to not close the dropdown if I click inside the dropdown (which is not typical dropdown behavior, but still might be a use case scenario). And also if I define `$(document).click(function(){$('#div2').hide();});`, it's not possible to open the dropdown. This is why I extended your code with a function parameter like this: `return function(noHide) { if (this.type == 'click' && !noHide) ...` this way I can prevent the dropdown from being closed, but still don't have to touch the other event handlers. – René Schubert Apr 10 '12 at 21:54
0
//generic drop-down menu
//NOTE: only pass a menu object if there are multiple menus per toggle object
var dropDown = function( toggle, menus )
{
  toggle.click( function( e ) {
    e.preventDefault();
    //NOTE: container is assuming that the next sibling is the dropdown.
    //change container to suit your needs
    var container = $( this ).next();
    if( menus !== undefined && !container.is( ':visible' ) ){ menus.hide(); }

    if( !container.is( ':visible' ) )
    {
      container.fadeIn( 'fast' );
    }
    else
    {
      container.fadeOut( 'fast' );
    }
  });
}

//close stuff when the window is clicked
var window_click = function( obj )
{
  $( window ).click( function( e ) {
    if( obj.is( ':visible' ) )
    {
      obj.fadeOut( 250 );
    }
  });
}

//override the window_click function when needed
var stop_prop = function( obj )
{
  obj.click( function( e ) {
    e.stopPropagation();
  });
}

Now, just use them wherever you want like this:

var toggle = $( '#myToggle' );
var menu = $( '#myMenu' );

dropDown( toggle, menu );
window_click( menu );
stop_prop( toggle );
stop_prop( menu );

For my dropdown markup, I almost always use this model:

<span class='toggle_name'></span>
<ul class='menu_name' style='display:none;'>
  <li>cool man</li>
</ul>      

To get around your scattered stopPropagation(), you could also create a little function that manually closed all menu classes. Super simple:

var menus = {
  //add any dropdown menu ids or classes here
}

//run this method on any conflicting stopProp clicks
var closeMenus = function()
{
  for( var key in menus )
  {
    if( menus.hasOwnProperty( key ) )
    {
      menus[key].hide();
    }
  }
}
Matthew Blancarte
  • 8,251
  • 2
  • 25
  • 34
  • I tried this out, but it doesn't seem to work at all. It toggles the dropdown, but doesn't close it, when I click outside. Can you please provide a working jsfiddle-example? Apart from that: $(window).click() does't work with stopPropagation used on other elements (except maybe I use your closeMenus-function, but I am looking for a solution where I don't have to touch the other event handlers). And the jALDropdown also works without that kind of stuff. – René Schubert Apr 01 '12 at 15:53
  • Thank you. When I add an element that stops propagation when I click on it, your solution does no longer work: http://jsfiddle.net/Dg2UN/3/ That's what I want to get around without having to add extra code to the element event-listener that stops propagation. – René Schubert Apr 01 '12 at 20:02
  • You forgot to implement my closeMenus method I set up for you. :) check it now http://jsfiddle.net/Dg2UN/6/ Well, if you want to get around extra code, you should probably consider optimizing your current event handlers to account for the behavior you desire. :) – Matthew Blancarte Apr 01 '12 at 20:18
  • Yeah, I kinda "forgot" it on purpose. Because that's all my question is about: How to do it without extra code (actually, without a body/document/window-event-listener). But thank you for your Idea, anyway. I may keep it in mind, if nobody else comes up with a better solution (like the one used for jALDropdown, which I can't figure out)... – René Schubert Apr 01 '12 at 21:09