I’ve had this little gadget in my toolbox for a while now, that I always find invaluable when I’m working with mouse events. It evaluates two event targets to determine whether one contains the other:
function contains(node1, node2)
{
if(node2 == node1) { return true; }
if(node2 == null) { return false; }
else { return contains(node1, node2.parentNode); }
}
A blessing and a curse
One of the most beautiful things about JavaScript’s event model is event bubbling — if an event is not captured by the element that fires it, the event bubbles up
to its parent element. From there it might be captured, or it might bubble-up again, and this continues all the way up the DOM, until the event is captured or it bubbles off the top.
However as elegant and useful as this is, it does make mouse events more complicated to deal with; the target of an event may not be the element that the event-listener was actually bound to, or events may appear to fire at the ‘wrong’ time. A common example of this is when moving the mouse from a container element to an element inside it — a mouseout event will fire on the container element, even though the mouse hasn’t actually gone outside it.
In most modern browsers we can differentiate using the eventPhase
property of the event, or by specifying a capture-flag in the addEventListener
method (setting the third argument to true
). There are also specialised properties like originalTarget
and explicitOriginalTarget
that can give us the information we need to tell such events apart.
But none of this is possible in Internet Explorer (at least, not in the versions that people are actually using right now).
Finding something that actually works
What Internet Explorer does have is a built-in method for evaluating the target of events, called … (you guessed it) … contains()
. So let’s make something like that, which works in all browsers. And that way we’ll save ourselves some code-forking:
container.onmouseout = function(e)
{
var target = e ? e.relatedTarget : event.toElement;
if(!contains(this, target))
{
//Mouse has left the container element
}
else
{
//Mouse is still inside
}
};
If we were trying to faithfully re-create IE’s method, we would prototype ours to Object
or HTMLElement
. But we shouldn’t prototype custom methods to built-in objects, especially in code that other people will be working with, because those methods will show up in enumerators, potentially causing major problems for scripting that isn’t expecting them.
For interest though, if we were using it as a prototype it would only need a single argument, since the container object itself would be referrable to as this
:
Object.prototype.contains = function(node)
{
if(node == this) { return true; }
if(node == null) { return false; }
else { return this.contains(node.parentNode); }
}
Anyway, we’re not going to do that here. Quite apart from anything else, we want something that works in all browsers, including Internet Explorer (which doesn’t support native prototyping; and even if it did, doing that might overwrite the native method).
So what I tend to do in practise is to use it in its two-argument form as a method of whatever main object I’m working with; I find this most convenient, and it’s sure not to overwrite any native methods:
var myObject = {
...,
contains: function(node1, node2)
{
if(node2 == node1) { return true; }
if(node2 == null) { return false; }
else { return this.contains(node1, node2.parentNode); }
},
...
};
And there you have it — a short piece of code that belies its usefulness (as short code often tends to). In fact in one form or another, I’d say this code has made it into around one quarter of all the scripts I’ve written! So hooray to that; and kudos to Jason Davis, who had the original Object prototype idea so many years ago — for kicks and giggles
— that helped me to wean myself off Netscape 4.
James is a freelance web developer based in the UK, specialising in JavaScript application development and building accessible websites. With more than a decade's professional experience, he is a published author, a frequent blogger and speaker, and an outspoken advocate of standards-based development.