Some time ago, I needed to find out if a DOM element is a descendant of another. A simple and elegant way to accomplish this is:
/**
*
* @param mixed granpa
*
*/
Element.prototype.isDescendantOf = function( granpa ) {
if ( typeof( granpa ) == 'string' ) {
granpa = document.getElementById( granpa );
}
if ( !granpa ) {
return false;
}
child = this;
do {
if (child == granpa ) {
return true;
}
child = child.parentNode;
}
while ( child );
return false;
}
…and afterwards…
var foo = document.getElementById( 'foo' );
var bar = document.getElementById( 'bar' );
if ( foo.isDescendantOf( bar ) ) {
/* whatever */
}
Cool, isn’t it?
Element.prototype in IE
This kind of approach to js apps works perfect in all modern browsers. But not in “that browser”. Not in the browser of the many. Because Microsoft doesn’t allow adding custom methods to the “Element” object. Of course, I’ve tried to use “Object” instead of “Element”, but this doesn’t work either – contrary to the ECMA specifications. In other words, it simply sucks.
But after some googling around, I`ve reach deep into the realm of darkness, the MSDN. And there I’ve read about IE behaviors, a strange mixture of JScript and XML stored in a HTC file. So I’ve decided to make one last attempt to get the script working, before smashing my forehead into the keyboard. First of all, an Element object is needed. So I’ve added these lines to my js file.
if ( !window.Element ) {
Element = function() {};
}
After that, I’ve started writing the HTC file. It looked like:
<PUBLIC:COMPONENT> <PUBLIC:METHOD NAME="isDescendantOf" INTERNALNAME="_isDescendantOf" /> <script type="text/javascript"> var element = new Element; _isDescendantOf = element.isDescendantOf; </script> </PUBLIC:COMPONENT>
Now this behaviour must be added to all elements if the browser is Internet Explorer. Time to use some simple conditional comments and the CSS universal selector:
<!--[if IE]>
<style type="text/css">
* { behavior: url(ie_fix.htc) }
</style>
<![endif]-->
And it worked. Eurika. You can download the gzipped example here. Behaviours are not supported by the wine emulated versions of Internet Explorer, so this must be tested on Microsoft Windows ®.
Prototype’s (the framework) $ function
I consider prototype to be one of best javascript frameworks ever. Yes, I know there are other smaller, less bloated, better documented frameworks out there, but for me, prototype does the job to well to even consider switching.
Using prototype is another way to add custom methods to the Element object. Declare your methods as elements of a javascript hashtable (an array) and then call Element.addMethods().
var methods = {
isDescendantOf: function( granpa ) {
if ( typeof( granpa ) == 'string' ) {
granpa = document.getElementById( granpa );
}
if ( !granpa ) {
return false;
}
child = this;
do {
if ( child == granpa ) {
return true;
}
child = child.parentNode;
}
while ( child );
return false;
}
}
Element.addMethods( methods );
Please note that the custom methods will only be available on nodes retrieved by prototype’s $() function, and not on those retrieved by good old fashioned document.getElementById(). That’s how prototype works.
[...] hacks as well. Like the Internet Explorer hack to get Element.prototype to work. It’s a horrible solution to get round a problem and not a route I would advise or [...]
omg this exact issue was killing me. I kept trying and trying to set Element.prototype.trim = function() { … } but it wouldn’t work (except in every other browser). I’m attempting to build my own framework and am starting to understand why this is so difficult.
[...] you to alter the Element prototype in this way. A little browsing on the internet and I found: http://www.it-base.ro/2007/07/30/ele…rnet-explorer/ which seems to address your specific question. For Googling purposes, you best keywords are going [...]
Not that I’m totally impressed, but this is more than I expected when I stumpled upon a link on Furl telling that the info is quite decent. Thanks.
“After that, I’ve started writing the HTC file. It looked like:”
It looked like what? After all that build up, you end with a run-on sentence and an empty space.
I’m sorry. An automatic update of the Geshi WP plugin screwed things up. It works now :p
Here is a much simpler workaround that will be sufficient in 99% of cases.
It may as well be completed as required by your script :
if ( !window.Element ) { Element = function(){} var __createElement = document.createElement; document.createElement = function(tagName) { var element = __createElement(tagName); for(var key in Element.prototype) element[key] = Element.prototype[key]; return element; } var __getElementById = document.getElementById document.getElementById = function(id) { var element = __getElementById(id); for(var key in Element.prototype) element[key] = Element.prototype[key]; return element; } }[NOTE: BEST SOLUTION YET IS AT BOTTOM OF THIS POST]
That final solution doesn’t work in even close to 99% of the cases because:
1. It relies on users doing all their element finding by using document’s “getElementById method, which you have replaced with a method that lets you add “Element”‘s prototyped methods to the found element. The problem is that elements can be searched for in many ways besides calling “getElementById”… e.g. walking the DOM, getElementByTag, etc., etc.
2. Your “createElement” method tries to do the same filtering, but what if elements are created by cloning? Or calling “eval()” or myriad other ways.
[woops, meant to put this at bottom of previous post]
The best way I found to do this is to build off of the original “behaviors” proposal (because it catches every case) but to simplify it and speed it up.
I started with the suggested approach, and it did work, but reading in the .htc file and binding the behavior for all my ~800 DOM elements slowed document load by a whopping 15 seconds or more.
What I discovered, after some more googling, is that the .htc file is not needed at all. The “behavior” can be created as part of the CSS style, so for the example given here, one could simply do this:
1. Implement the “if ( !window.Element ) Element = function() {};” stuff that the author did
2. Create the “Element.prototype.isDescendantOf ” (for example) as the author did
3. In your IE8-only .css file (or in HTML using the “if IE8″ trick the author uses) do this:
* { behavior: expression(this.isDescendantOf = function(){return Element.prototype.isDescendantOf .apply(this, arguments);})}
Voila! That’s it!
READ THIS:
OK, final followup… it turns out that being able to set a behavior using the “expression” keyword is no longer supported in IE8, so the author’s proposed solution remains the best (though it *really* *really* slows down page load
This was originally posted in 2007, a lot have changed since then. Of course now it might not be the best solution out there, but it has served me well over the years…
I know this is older, but stumbled across it just now. I did a quick check and it seems like IE8 supports the window.Element object, so kbern’s final follow up may not be relevant, as IE8 may just work as other standards-compliant browsers work in this instance. I haven’t fully tried this out, but just something to look into.
The issue here was never whether or not there was a “window.Element” object. The issue was how to add *behaviors* (methods) to all instances (and future instances) of “Element” objects and their subclasses. In Safari, Firefox, and Chrome, this is accomplished by modifying the the prototype of the “Element” class, e.g. Element.prototype.myNewMethod = function() {some code here};
No version of IE (including IE8) allows you to *modify* the prototype of “Element”.
This is very unfortunate especially since the author’s clever idea of using IE “behaviors” is, in practice (with any substantial number of elements) prohibitively slow (this is mentioned by the Dojo and JQuery guys as well).
Internet Explorer 8 is very good because it is as stable as Opera. I hate the previous versions of IE like IE6 because it hangs frequently. .
Interesting share man Thank you
For reasons unknown i’m ending up with a blank page once i try and post a comment,do you know the actual reason why its taking place?i’m utilizing oprea web-browser
why not use the inventors method directly, and ask the DOM a direct question instead of playing around with words such as: “isDescendantOf” instead of going strait to:
and straightforwardly get its parent into the basket?!!
You see, when ie was providing you the means to Fly accross the DOM, the bloody w3c was just starting to learn how to walk, – and still they stumble upon them blody irrelevant nodes and empty characters exposing you to all possible accidents of the fall.
or even more elegant solution, see if:
same as
But this, this is overkill:
Element.prototype.isDescendantOf = function( granpa ) { if ( typeof( granpa ) == 'string' ) { granpa = document.getElementById( granpa ); } if ( !granpa ) { return false; } child = this; do { if (child == granpa ) { return true; } child = child.parentNode; } while ( child ); return false; }