DOM Mayhem
Mozilla Gecko sucks, what’s new ? I’m doing some AJAX work for my web development framework (more about it much later), and one thing I encountered with the Mozilla browsers (which means mostly Firefox but also other Gecko based browsers such as Flock and of course SeaMonkey) is that they don’t handle the importNode() call that well..
I mean – it works and all: when you call importNode() to load some nodes from an XML document loaded using XMLHttpRequest it imports them correctly and you can add them to your document – or so you’d think anyway. It looks to behave rather nicely – if your remote XML document defines a <table> element, for example, then importing it into your document will actually create an HTMLTableElement, with all the trappings you’d expect, such as a tBodies and rows collections, insertRow() call and friends, and such.
So – what’s wrong, you ask ? They’re DEAD. As in – dead bodies floating in the river. Poke them with a stick and nothing happens. If you add a new <tbody> element to this new table you’ve just imported, the tBodies collection doesn’t update to reflect that (I usually import an empty table and fills it with content created with createElement), so you can’t see your new tbodies. The rows collection behaves funnily differently – rows imported with the table from the original remote DOM tree aren’t listed in the rows collections, while new rows added are reflected in the collection but are counted from 0, with the first row getting rowIndex = 0, the second row is 1, etc’ – regardless of how many rows there were before. Rows inserted into new tbody elements (like I like to create) aren’t counted at all! Similar behaviour can be observed in other HTML DOM elements.
Anyway – its a huge big mess, and being the sensible upstanding community member that I am, I opened a nice fat Bugzilla report with some test cases which promptly… got silently ignored. Or at least – nobody seems interested enough in DOM bugs to even look at it and ask a question. I know its been only a week or so, but I would expect someone to monitor new DOM bugs, seeing as a correct DOM implementation is (or should be anyway) a very important feature of Mozilla products.
But anyway, enough ranting and raving for now. After thinking about it some more, I came up with this workaround. Its not that pretty, but it is rather simple, clean and mostly complete – and under most cases it will probably be a drop in replacement for importNode(). It still uses importNode() internally for some cases (basically only non-container nodes, so it should be ok) so I’m not expecting this to work on Internet Explorer (though I accept patches 😉 ), but I currently care mostly for browsers that at least pretend to implement the W3C DOM specs. Nuf’ beating around the bush – here’s the code:
function dupNode(oNode) {
if (!oNode || !oNode.nodeType) return;
switch (oNode.nodeType) {
/* I expect all these to just work with importNode(),
mostly as they're not supposed to contain other nodes */
case document.TEXT_NODE:
case document.CDATA_SECTION_NODE:
case document.ENTITY_REFERENCE_NODE:
case document.ENTITY_NODE:
case document.PROCESSING_INSTRUCTION_NODE:
case document.COMMENT_NODE:
case document.NOTATION_NODE:
return document == oNode.ownerDocument ?
oNode.cloneNode(false) : document.importNode(oNode,false);
/* I don't expect to see these, and even if by chance I will,
then I think the caller wants me to ignore them */
case document.DOCUMENT_TYPE_NODE:
case document.ATTRIBUTE_NODE:
case document.DOCUMENT_NODE:
case document.DOCUMENT_FRAGMENT_NODE:
default:
return;
case document.ELEMENT_NODE: /* an element - some assmebly required */
/* dup top level */
var mynode = document.createElement(oNode.tagName);
for (var i = 0; i < oNode.attributes.length; i++)
mynode.setAttribute(oNode.attributes[i].name, oNode.attributes[i].value);
/* dup childs */
for (var oChild = oNode.firstChild; oChild; oChild = oChild.nextSibling)
mynode.appendChild(dupNode(oChild));
return mynode;
}
}
Node that it calls itself recursively, so if you adapt it as a class or object call in your code, you need to change the inlined dupNode() call to reflect that as well.
Licensing terms (as I've been asked about this before) - I'm not going to bother with a license here, as its too small a code for a license to be of any relevance: the code snippet above is in the public domain for all intents and purposes, feel free to do with it as you like. If you have some additions (I prefer simple ones) to better support cases I might have missed (notably broken browsers. Oh - speaking of which - Internet Explorer 7 was released this week 🙂 ), feel free to post them below. I might or might not add them, but at least it will be recorded for posterity.