1

If I have a set of jquery elements var elements = $('.containers'); How can I select everything in the body but that set of elements and attach a click event to it? I've tried something like,

var filtered = $('*').filter(function(index){
    return elements;
});

$(document).ready(function(){
    $('body').on('click', filtered, function(){
         alert('something on body was clicked that wasn't "element"');
     });
});

*This doesn't work at all

1252748
  • 14,597
  • 32
  • 109
  • 229

6 Answers6

1

Using the .not() selector.

$('body').find('*').not('.containers').on('click',function(){
    // do something
});

Preferrably you would attach the click event to something much more specific than $('body').find('*') (which is all child elements for body), but working with what you gave me. Or you could go about it a different way:

$('body').find('*').on('click',function(){
    // do something
});

$('.containers').off('click');

This sets it for everything, and then removes it just for .containers items. A little verbose, but more explicit, and u just need to delete/modify the .off() function if you change your mind.

PlantTheIdea
  • 16,061
  • 5
  • 35
  • 40
  • 1
    Doesn't this selector select any `body` element that is not of class `containers`, i.e. just the `body`? If you want everything that is not `.containers`, I think you would need `$(':not(.containers)')`. – Jeff B Feb 22 '13 at 21:35
  • thats what `body > *` is for ... it selects all children of body. And you would have avoided thinking that about .not() if you went to the documentation link provided. – PlantTheIdea Feb 22 '13 at 21:36
  • Wait. What? *I* know what `.not()` does, but your original answer suggested that *you* did not. – Jeff B Feb 22 '13 at 21:43
  • my original answer had `body` instead of `body > *`, which i immediately corrected. i was talking about the use of `$(':not(.containers)')` ... its unnecessarily verbose, and even the jquery documentation for :not tells you to use .not() http://api.jquery.com/not-selector/. you were right to correct me about my original error in entry, but not regarding the application of .not(). – PlantTheIdea Feb 22 '13 at 21:45
  • I think this was a case of pointing out an error while you were making a correction. – Jeff B Feb 22 '13 at 21:50
  • `body > *` is not incorrect, but it needs to be pointed out for the OP, or anyone else that isn't clear that it only selects immediate children and not descendants. For that you just want `body *`. In this case, a target filter is a better solution, considering the event propagation that will occur. Even if `.containers` does not have a click handler, it's parent will and the event will propagate to the parent. Example: http://jsfiddle.net/5rR5j/ To fix your example, you need to add a `.stopPropagation()`/`return false` handler to the `.containers` elements: http://jsfiddle.net/5rR5j/1/ – Jeff B Feb 22 '13 at 22:04
  • yeah again probably as you were typing it i changed it to `$('body').find('*')`, which will find all elements within the body. haha twice in a posting! – PlantTheIdea Feb 22 '13 at 22:06
1

Just filter out the .containers elements, preferrably before you attach the event handler :

$(document).ready(function(){
    $('*').filter(function() {
         return ! $(this).is('.containers');
    }).on('click', function(){
         alert('something on body was clicked that wasnt "element"');
    });
});

EDIT:

If you're trying to close a dropdown, modal or something similar by clicking outside it, as indicated by the comments, the way to do that would be:

$(function() {
    $(document).on('click', function(e) {
        if ( !$(e.target).closest('.containers').length ) {
            // you clicked outside
        }
    });
});
adeneo
  • 312,895
  • 29
  • 395
  • 388
  • if you're going to use this technique, use `.hasClass('containers')` instead of `.is('.containers')` ... its faster: http://stackoverflow.com/questions/4901553/jquery-hasclass-vs-is – PlantTheIdea Feb 22 '13 at 21:42
  • i don't think this will work.. you are filtering an array of the body element.. which will only contain the body - checking if body is element with class=containers isn't going to do anything – wirey00 Feb 22 '13 at 21:47
  • @LifeInTheGrey - Yeah, the 0.0000000004343 millisecond difference really does mean everything to a function like this. ->Wirey, you're right, that should have been the hopeless asterix selector. – adeneo Feb 22 '13 at 21:48
  • using `'*'` instead of '`body > *'` will include all elements ... including html and body. and my point about hasClass was, if you're going to do something, do it right. – PlantTheIdea Feb 22 '13 at 21:48
  • @LifeInTheGrey - That's how the OP filters it in the question, so I'll assume that it's intended, and the asterix so inefficient, it does'nt really matter one way or the other. – adeneo Feb 22 '13 at 21:50
  • @adeneo - "*This doesn't work at all" - The OP. And accepting easily-correctable inefficiencies is a bad programming mentality to have, just in general. – PlantTheIdea Feb 22 '13 at 21:51
  • I really don't think the asterix selector was the problem the OP was referring to, as that most certainly works. You've submitted your answer, and if you see something wrong, feel free to comment, but being a knowitall when it comes to coding conventions does'nt really get you anywhere! – adeneo Feb 22 '13 at 21:52
  • haha no, that was a comment on the code he provided, he wasn't referring to a selector. and my pointing out hasClass originally was to help improve your answer, you were the one that sarcastically minimized my simple improvements. – PlantTheIdea Feb 22 '13 at 21:53
  • I have a dropdown that I want to remove the class of `active` from if something outside the dropdown is clicked. I used `*` because it seemed relatively easy, and the page is relatively small. But what would you suggest in this case? `$('html')`? How can register clicks that happen anywhere most effectively? Thank you. – 1252748 Feb 22 '13 at 22:08
  • @thomas: `$('html')` is a good option as it will result in a single click handler and will catch all propagating click events from child elements, unless you stop propagation. – Jeff B Feb 22 '13 at 22:20
  • @thomas - You'd use the `document` for this, not `*` or any of the elements on the page. See my edited answer for how this is almost always handled. – adeneo Feb 22 '13 at 22:40
1

if you want to check for .container and descendants of container you can use .filter(). Then check if current element is - or has ancestor elements has class=container using .closest()

$('body *').filter(function(){
    return $(this).closest('.containers').length === 0; 
}).on('click', function(){
     alert('something on body was clicked that wasn\'t "element" or inside element');
});

FIDDLE

Here's another solution

$('body').on('click','*:not(.containers,.containers *)', function(){
     alert('something on body was clicked that wasn\'t "element" or inside element');
});

FIDDLE

wirey00
  • 33,517
  • 7
  • 54
  • 65
  • That's a pretty good answer, but it only filters for descendants, as there's no guarantee an element with the class `.containers` will have a parent with that class. – adeneo Feb 22 '13 at 21:55
  • @adeneo it actually starts the check on itself - FROM DOCS `For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.` – wirey00 Feb 22 '13 at 21:57
  • +1, you're actually right, always forget about that, as I never use it that way. – adeneo Feb 22 '13 at 21:59
  • @adeneo Yeah, wasn't too long ago before I found that out too.. I was pretty shocked – wirey00 Feb 22 '13 at 21:59
0

The better way to do is to have only one handler on document and check for the element which was clicked when event bubbles up to document.

$(document).on('click', function (event) {
    var $target = $(event.target);
    if ($target.hasClass('containers') || $target.closest('.containers').length > 0) {
        alert('something on body was clicked that wasn't "element"');
        return;    
    }

    // Rest of logic
});
Ehtesham
  • 2,967
  • 1
  • 18
  • 20
  • Thank you, but will this work for not triggering the event on all of the children within `containers`. That's sort of why I wanted to use the filter with the elements. – 1252748 Feb 22 '13 at 21:46
  • @thomas I have updated the code to check if the ancestor has class `container` it will be skipped. – Ehtesham Feb 22 '13 at 22:01
0

Another option is simply add a handler to $('html') and then make sure that the container handler either calls event.stopPropagation(), or returns false, which handles the propagation stopping automatically in jQuery:

$('html').on('click', function () {
    alert("Clicked!");
});

$('.containers').on('click', function (event) {
    return false;
});

Example: http://jsfiddle.net/7hGL7/

For what it sounds like you are doing, i.e. closing a menu, this is a simple and straightforward solution.

Jeff B
  • 29,943
  • 7
  • 61
  • 90
-1

One problem in your sample code is the unescaped single-quote in your alert.

As for excluding certain elements, I suggest using the route you have set up (attach the event to the body), then check if the event target has a given class. Like this:

$(document).ready(function(){
    $('body').on('click', function(e){
        if ($(e.target).hasClass('containers') || $(e.target).closest('.containers').length > 0)
            return; // do nothing with elements that have the container class
        alert('something on body was clicked that wasn\'t "element"');
     });
});

Note the escaping of the extra apostrophe in the word "don't" : don\'t.

Documentation and Related Reading

Chris Baker
  • 49,926
  • 12
  • 96
  • 115
  • Thanks for this. Will it prevent the click event being triggered on children of `containers`? – 1252748 Feb 22 '13 at 21:47
  • No, but you can add another condition to check that too. I've edited the sample code to use `closest` in order to check for parents that have the specific class too. Please review the edit, let me know :D – Chris Baker Feb 22 '13 at 21:55