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 Edwards
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.
  • Dan

    Your contains method is so simple I did a double take before I got it :)

    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.

    I also stay away from doing this in my code, but when using libraries working with other existing code I have got into the habit of using hasOwnProperty when iterating.

    Anyway, nice article.

  • ashwin

    Can you provide a simple example?

  • http://xslt2processor.sourceforge.net boen_robot

    I’m confused… IE7 and below don’t support prototyping (IE8 does btw), and other browsers do… yet, IE has the method we want to define, right?

    So… can’t we just do
    if (!Object.prototype.contains) {
    //Define the contains() method as part of the prototype
    }

    And then use the contains() method as in IE. IE will use their built-in method (even in IE8, since the contains() method still exists), and everyone else will be using our method.

  • http://www.brothercake.com/ brothercake

    Yeah we totally could do that, but:

    – prototyping to built-ins is not a good idea generally, for the reasons discussed

    – I personally prefer to work with consistent code-base – to have every browser do the same thing, rather than one use a custom method and one use its own – if that’s possible; it just makes me feel more confident

Stay ahead of the game Exclusive content for developers and digital experts Go Premium