Someone in the #mootoos irc channel wanted an event triggered only when clicked outside of an element but there's nothing built-in, so I thought that it's an interesting event to implement. And since it has at least some reusability I'm going to share the code along with a description of what it's doing.
(function(){ var events; var check = function(e){ var target = $(e.target); var parents = target.getParents(); events.each(function(item){ var element = item.element; if (element != target && !parents.contains(element)) item.fn.call(element, e); }); }; Element.Events.outerClick = { onAdd: function(fn){ if(!events) { document.addEvent('click', check); events = []; } events.push({element: this, fn: fn}); }, onRemove: function(fn){ events = events.filter(function(item){ return item.element != this || item.fn != fn; }, this); if (!events.length) { document.removeEvent('click', check); events = null; } } }; })(); // usage window.addEvent('domready', function(){ $('test').addEvent('outerClick', function(){ alert('You clicked outside of $("test").'); }); });
First thing you may notice is the (function(){ /* code here */ })() pattern. That creates a private area for the events variable, which will store all the added outerClick events and the check function. This way the global namespace is not polluted.
Now let's take a look at onAdd. It checks if the events variable is set, and if not it initializes it with an empty array and adds the check function as a document click event listener. Note that this is only done once for all outerClick events added. The element and the user supplied event listener fn are pushed to the events array as the last step of onAdd.
onRemove does the same things onAdd does but in reverse. It filters the element with the function and element out of the events array and if the array is empty it removes the document click listener.
Last part of the code to discuss is the check function. The idea behind it is to check if the event target is outside the the element in events and if so it calls the event function. The target is 'inside' the element if its the element itself or if the element is one of the targets parents. In order to do the search for the parents only once per click target.getParents() is saved to a temporary variable.
The post is now way longer than expected, though I hope you've learned a bit from my first serious post.
This is neat and extremely useful, specially for dropdowns, and popups. I always found myself programming a huge cms or extended profile page for users where there's a lot of divs and dropdowns where on clicking outside they have to be closed also.
Nice snippet and a great description, like every post.
One suggestion: Array::filter can be replaced by Array::some.
@Harald: I think you got it wrong, the filter is there because the event should be removed from the events array. (All outerClick events are stored in the same events array.)
Ok, then what about only using the fn as index and saving the element to fn.element. When you remove the fn later on, you can read element from fn.element. Would shorten the code.
don't works with the IE7 :-( i replace window.addEvent .. with document.addEvent - and it works crossbrowser. Maybe it helps someone else.
Thanks for the report Jens, i've updated the post.
I have created a script that tells me if the cursor is outside the page main menu. The difference is that I used event.relatedTarget, not event.target (new Event( event ).relatedTarget). Is there a difference between them? Why do they keep them both? Is it only for the backward compatibility?
@Dumitru event.relatedTarged is the element you left (in mouseenter and mouseleave events). Have a look at the docs for more details.
thanks a lot for the lesson... I've been so dump not to see the difference between them both... :) That' why my script has some bugs...
http://subasalt.ro/mercury/js/mercury.js
Great script. Really helped me out and works flawlessly!
Keep up the good work!
Great work!! Thanx!!
That's right what I was looking for.
Great work!! Thanx!
That's right that what I was looking for!
Great work!! Thanx!
That's right that what I was looking for!
very valuable Information, thank you very much!
one question in my mind:
what will happen if on a link I stop the event ? I guess event won't bubble till the document and then the check will not be performed.
anyway, i'll test this.
outerClick is useless if we call stop() on event like in this example.
actually, I prefer to play with things like focus/blur to achieve the same goal.
window.addEvent('domready', function () {
$$('a.outerclick').addEvent('outerClick', function(){
alert('You clicked outside of $("test").');
});
//outerClick is not fired on this link
$$('a.innerclick').addEvent('click', function (e) {
e.stop();
});
});
Just added my own blog this month. I need some inspiration. Thx.