contains() for Mozilla

One of my minor irritations with Mozilla is that it doesn't support a few DOM methods and properties that, though not officially a part of the spec, are nonetheless extremely useful and supported by all other browsers. I'm especially thinking of the contains() method and the children[] nodeList. While going through the more abstruse parts of the Level 3 Core spec today I found a way to add contains() to Mozilla.

The contains() method comes in handily in all kinds of situations. Its syntax is as follows:

<div id="writeroot">
<form>
<input id="test" />
</form>
</div>

var x = document.getElementById('writeroot');
var y = document.getElementById('test');
alert(x.contains(y));
alert(y.contains(x));

Since node x contains node y, the first alert shows true and the second one false. Very useful, if Mozilla only supported it.

Today I went through the Level 3 Core specification and found the compareDocumentPosition() method. This method works in roughly the same way as contains(), except that it generally compares the positions of two nodes and returns a bitmask:

Let's repeat the previous example:

<div id="writeroot">
<form>
<input id="test" />
</form>
</div>

var x = document.getElementById('writeroot');
var y = document.getElementById('test');
alert(x.compareDocumentPosition(y));
alert(y.compareDocumentPosition(x));

The alerts now return 20 and 10, respectively. y is contained by and follows x, so the first alert says 16 + 4 = 20. x contains and precedes y, so the second alert says 8 + 2 = 10.

Using this method I can now finally add a neat contains() method to the Node prototype if it's not there:

if (window.Node && Node.prototype && !Node.prototype.contains)
{
	Node.prototype.contains = function (arg) {
		return !!(this.compareDocumentPosition(arg) & 16)
	}
}

First I check if the Node prototype is supported. It isn't in IE, so this check is necessary to avoid errors in the next expressions. Then I check if its prototype is exposed (it isn't in Safari 1.3.2). If the browser survives this check, too, and the prototype doesn't already contain a contains() method, I define one.

The custom contains() compares the document positions of this (the node I use it on) and arg (the node I want to compare it to). If arg is contained by this, compareDocumentPosition returns a bitmask that includes 16. I do a bitwise AND 16 (& 16) which returns 16 or 0, and I convert this result to a boolean by doing !! (NOT NOT). The function returns this boolean.

Easy and elegant, and it's the first practical example of bitwise AND in JavaScript I ever found.

(BTW: I love bitmasks. I invented them for myself in my Commodore 64 days, and although later it turned out that this idea was not exactly new, I've always remembered them fondly, and am slightly disappointed that they have no role in modern JavaScript development.)

This is the blog of Peter-Paul Koch, web developer, consultant, and trainer. You can also follow him on Twitter or Mastodon.
Atom RSS

If you like this blog, why not donate a little bit of money to help me pay my bills?

Categories:

Comments

Comments are closed.

1 Posted by Robert Nyman on 27 January 2006 | Permalink

Very nice! Thanks for sharing.

2 Posted by Krijn Hoetmer on 27 January 2006 | Permalink

Handy stuff! Speaking of inventing things; back in 2001 I 'invented' XML for some totally rocking help system, while coding my own HTML editor in VB - the horror. After getting broadband and interested in web development I figured out my editor sucked in more than many ways.. And that most things are already 'there'. And that people like me make pointless comments :]

Btw, is this the first time on quirksmode prototype is mentioned inside a PRE element?

3 Posted by Dustin Diaz on 28 January 2006 | Permalink

Gosh, even though I've only first heard of it (or seen someone mention it) I can see its usefulness for manipulating styles, eg document hierarchy. Now I'm saddened that it's not supported beacuse it looks so easy.

4 Posted by James Newton-King on 28 January 2006 | Permalink

Nice find. I had been using a simple isParent function and testing the parentNode in a while loop, but this is much nicer.

I can't say I have seen anyone _ever_ use bitmasks in JavaScript.

5 Posted by Martin Sutherland on 28 January 2006 | Permalink

The MD5 Javascript algorithm (http://pajhome.org.uk/crypt/md5/md5src.html) uses bitmasks. I've seen them in some other ports of math-intensive algorithms to JS, too, but this is the first time I can remember seeing one in code directly related to DOM scripting. Nice one!

6 Posted by Joe Ngo on 28 January 2006 | Permalink

I think you missed something. The object method detection code blindly assumes that Node.compareDocumentPosition will always be available if Node.contains is not.

7 Posted by Keith on 29 January 2006 | Permalink

Clever use of the bitmask, but why not just do this:

return (this.compareDocumentPosition(arg) ==16);

8 Posted by Kanashii on 29 January 2006 | Permalink

in response to comment #7

Because compareDocumentPosition wont return 16 it will most likely be 20(16+4) but its still better to use the mask to check its value

9 Posted by Keith Gaughan on 29 January 2006 | Permalink

Keith wrote:

> Clever use of the bitmask, but why not just do this:

Precisely because it's a bitmap. Other bits in it besides 2^4 could be set. Better would be:

return (this.compareDocumentPosition(arg) & 16) != 0;

10 Posted by Jacob on 29 January 2006 | Permalink

It is all very well talking about the best way to use bitmasks, but that is quite irrelevant methinks. Besides, PPK's bitmask is fine :-)

I, like many others, have used custom-made "contains()" functions in the past as they are very useful for a number of applications. This discovery will make some things much easier to do :-) Thanks!

11 Posted by Joris on 29 January 2006 | Permalink

post #6 is correct as far as I see; this script will cause errors if both 'contains()' and 'compareDocumentPosition()' are undefined

12 Posted by Nikola Klaric on 30 January 2006 | Permalink

Actually, when you're programming against Mozilla's XPCOM interfaces in JavaScript you'll use bitmasks quite often.

13 Posted by Antonio Bueno on 3 February 2006 | Permalink

I didn't know about Node.prototype

Is there something equivalent in IE?

SYS 64738 :D