Whereas constructing a browser slideshow object for an indication on dynamically pulling picture info from an internet server, I bumped into issue with the DOM-compliant strategy I had envisioned. A two-day journey into the world of XML DOM help for net browsers lay between me and a passable answer.
Article Continues Under
My plan was to move an XMLHttpRequest
(XHR) with the identify of a picture to the server, which might return, from a mixture of a database and the picture itself, a title for the picture, an outline, and all meta knowledge saved for the picture. This knowledge could be despatched in XHTML format in order that the shopper may merely import the XML response and append it to some container parts, thus dashing up the slideshow software. The information would look one thing like this:
<image_info id="image_number"> <title>image_title</title> <description>image_description</description> <meta>image_meta</meta> </image_info> the place image_title: <div id="imageTitle">The title of the picture</div> image_description: <div id="imageDescription"> <p class="para">Paragraph 1</p> <p class="para">Paragraph 2</p> ... <p class="para">Paragraph n</p> </div> image_meta: <div id="imageMeta"> <!-- formatted meta info right here --> </div>
Even the best-laid plans can go awry, and this one definitely did. I’d deliberate to take the XHR response from the server and its responseXML
property, parse out the completely different sections utilizing the doc.getElementsByTagName()
property, and put them the place they wanted to go. Easy, proper?
Nope.
The frustration of the DOM#section2
Although I wouldn’t name myself a standards-compliance zealot, I imagine requirements have their place in growth, and I needed to do that utilizing W3C DOM requirements. I do know an enormous query is “Why use the DOM when we have now the useful innerHTML
property?” The reply, in three components:
- The
innerHTML
property isn’t standards-compliant. - To keep away from the XML facet of this software, I might need to both make a separate name for every a part of the information I needed, or to parse the
responseText
property that got here with the XHR response. - Relying on what was contained within the response,
innerHTML
may not even work accurately.
The primary level is self-evident, so let’s flip our consideration to the opposite two. I may make a separate XHR name for the title, description, and meta knowledge, however this could possibly be slower, as there are three requests for the server as a substitute of 1—and as you scale an software upwards, even a minor velocity discrepancy will develop. Parsing the responseText
is not any speedier, as you’d need to wade by way of an unknown quantity of textual content to get what you wanted. Which ends up in the third level: innerHTML
isn’t applied on TBODY
parts with Web Explorer, so if the container component is a desk chances are you’ll encounter issues. innerHTML
additionally has issues rendering SELECT
parts in Web Explorer (see the bug report from Microsoft on this subject).
I wanted to make use of DOM performance, thus opening the door to the not-so-standard world of JavaScript the place cross-browser compatibility is however a tantalizing dream. Importing paperwork from two completely different ownerDocument
properties (which is what I wanted to do in my slideshow software) requires the usage of the DOM Stage 2 methodology importNode()
, since in these instances the DOM is not going to permit a easy doc.appendChild()
.
Sadly, even in Web Explorer 7, Microsoft has not but applied any of the lacking DOM strategies which can be sorely wanted. So the event group should watch for the following launch and hope that the DOM is finally upgraded. This doesn’t, nonetheless, assist with an answer now. There must be options for the issues we face when writing an internet software for all browsers, and importing nodes is not any exception.
The W3C DOM Stage 2 import strategy#section3
To import a DOM doc into an present DOM doc, there may be the useful importNode() methodology that was launched in DOM Stage 2 as a part of the Doc Object Mannequin Core. This imports a brand new doc, A, into an present doc, B, by creating a brand new occasion of A that has the ownerDocument
property set to the B’s ownerDocument
. The brand new occasion can then be appended to the prevailing doc utilizing appendChild()
. (Line wraps marked » —Ed.)
var newNode = null, importedNode = null; /* All through the examples, our new doc will » come from an XHR response */ newNode = xhrResponse.responseXML.getElementsByTagName » ('title')[0].childNodes[0]; if (newNode.nodeType != doc.ELEMENT_NODE) newNode = newNode.nextSibling; if (newNode) { importedNode = doc.importNode(newNode, true); doc.getElementById('divTitleContainer') » .appendChild(importedNode); }
The issue with implementing this code is that though it could work in Firefox, Opera, Netscape, and Safari (to call a couple of), it doesn’t work in Web Explorer for any model. Why? As a result of Web Explorer doesn’t perceive the DOM Stage 2 methodology importNode()
.
Making an attempt a special W3C methodology#section4
I figured there needed to be a way round this subject that Microsoft had already supplied, so I regarded. I didn’t need to dig lengthy earlier than I remembered the cloneNode()
methodology. So I attempted the next: (Line wraps marked » —Ed.)
var newNode = null, importedNode = null; newNode = xhrResponse.responseXML.getElementsByTagName » ('title')[0].childNodes[0]; if (newNode.nodeType != doc.ELEMENT_NODE) newNode = newNode.nextSibling; if (newNode) { importedNode = newNode.cloneNode(true); doc.getElementById('divTitleContainer') » .appendChild(importedNode); }
I acquired the next error: No such interface supported
. In addition to which, there would have been a difficulty with the variations in ownerDocument
, proper?
What a few Microsoft hack?#section5
Satisfied that the reply didn’t lie some place else within the W3C DOM requirements, I started in search of a Microsoft-friendly workaround. It was not lengthy earlier than I discovered a publish from James Edwards, co-author of The JavaScript Anthology: 101 Important Suggestions, Methods & Hacks, with an answer to the dilemma. (Line wraps marked » —Ed.)
var newNode = null, importedNode = null; newNode = xhrResponse.responseXML.getElementsByTagName » ('title')[0].childNodes[0]; if (newNode.nodeType != doc.ELEMENT_NODE) newNode = newNode.nextSibling; if (newNode) { importedNode = doc.cloneNode(true); doc.getElementById('divTitleContainer') » .innerHTML += importedNode.outerHTML; }
So why does this work, when the opposite cloneNode()
answer didn’t? Really, in Web Explorer 6, it doesn’t, although it could have again in 2004 when the weblog publish was made. After debugging the code for some time I seen what ought to have been an apparent downside, in all probability a typo within the publish: the node being cloned was improper; doc
has no methodology cloneNode()
. I modified line 7
of the above code to clone the newNode
as a substitute of doc
:
importedNode = newNode.cloneNode(true);
With this modification, the innerHTML
of doc.getElementById('divTitleContainer')
was undefined
. It regarded like a Microsoft hack was not a viable answer. I used to be not fully sorry that I wouldn’t be utilizing innerHTML
, and was left with the choice of implementing an IE model of importNode()
.
A brand new importNode()
methodology#section6
Following the W3C DOM Stage 2 requirements for doc.importNode()
, I needed to make it possible for my methodology may deal with the completely different node sorts it would encounter. This was after I seen yet another inconsistency—Web Explorer didn’t outline doc.ELEMENT_NODE
and the opposite node sorts as a part of its DOM implementation. Cripes! I rapidly rectified this case with:
if (doc.ELEMENT_NODE == null) { doc.ELEMENT_NODE = 1; doc.ATTRIBUTE_NODE = 2; doc.TEXT_NODE = 3; doc.CDATA_SECTION_NODE = 4; doc.ENTITY_REFERENCE_NODE = 5; doc.ENTITY_NODE = 6; doc.PROCESSING_INSTRUCTION_NODE = 7; doc.COMMENT_NODE = 8; doc.DOCUMENT_NODE = 9; doc.DOCUMENT_TYPE_NODE = 10; doc.DOCUMENT_FRAGMENT_NODE = 11; doc.NOTATION_NODE = 12; }
Every bit of code I attempted had a take a look at for doc.ELEMENT_NODE
, so I went again and examined all of my earlier makes an attempt to see if this was the issue with Web Explorer. It wasn’t—even with this addition, not one of the earlier makes an attempt labored. Writing my very own methodology was the best way I must go.
My importNode()
methodology had to have the ability to create a brand new node, and if obligatory any little one nodes as effectively. It additionally wanted to import all attributes related to the node. The end result was the next: (Line wraps marked » —Ed.)
if (!doc.importNode) { doc.importNode = operate(node, allChildren) { change (node.nodeType) { case doc.ELEMENT_NODE: var newNode = doc.createElement(node » .nodeName); /* does the node have any attributes so as to add? */ if (node.attributes && node.attributes.size > 0) for (var i = 0; il = node.attributes.size; i < il) newNode.setAttribute(node.attributes[i] » .nodeName, node.getAttribute(node.attributes[i++] » .nodeName)); /* are we going after kids too, and does » the node have any? */ if (allChildren && node.childNodes && node » .childNodes.size > 0) for (var i = 0; il = node.childNodes.size; » i < il) newNode.appendChild(doc.importNode » (node.childNodes[i++], allChildren)); return newNode; break; case doc.TEXT_NODE: case doc.CDATA_SECTION_NODE: case doc.COMMENT_NODE: return doc.createTextNode(node.nodeValue); break; } }; }
Utilizing the format from the start of this text and importing from this xhrResponse.responseXML
: (Line wraps marked » —Ed.)
<image_info id="002"> <title>Trying Via the Window</title> <description> <p class="para"> This picture was taken taking a look at my yard » from contained in the kitchen of my home. It jogs my memory of » one thing from a <a href="https://alistapart.com/article/crossbrowserscripting/dummy.html" onclick ="return » openNewWindow(this.href);">fantasy world</a>. </p> </description> <meta>image_meta</meta> </image_info>
The ends in Web Explorer regarded as I had anticipated them to! I felt reduction wash over me and I used to be very content material…till a code overview uncovered one other downside.
Please bear in mind the occasions#section7
The onclick
occasion that I had within the <a>
component wouldn’t work, and I couldn’t determine why. I went again to the opposite browsers for reassurance, and none of them—not a single Gecko browser, Opera, or anything—would fireplace the occasion both. Whereas importing nodes, plainly the DOM doesn’t register the occasion handlers within the parts correctly. This grew to become clear after looking blogs and documentation; occasion handlers usually are not activated in imported parts.
I discovered affirmation in a Microsoft article referred to as “Quicker DHTML in 12 Steps.” It said: “In case you are making use of a block of HTML textual content, versus accessing particular person parts, then the HTML parser should be invoked.” What a disappointment.
On a whim, I made a decision to attempt my importNode()
methodology in opposition to Firefox, simply to see what would occur…and what occurred was it labored—even the occasion handlers! The implementation for importNode()
present in browsers doesn’t import occasion handlers or default types that may be hooked up to parts like robust
, as in my instance. I ought to have realized this earlier; the phrase “one thing” was by no means made daring in any of the browsers. Apparently, in all browsers, parts should be handed by way of the HTML parser earlier than occasions and magnificence will probably be activated.
Web Explorer’s occasion dealing with troubles nonetheless loomed. I needed the answer to be fully cross-browser appropriate, and I knew Web Explorer applied the occasion
object in a different way than the W3C advice. An answer was not forthcoming, however simply as I used to be prepared to surrender, a foolish answer got here to me. I remembered seeing in a weblog way back that a number of life’s little issues with Web Explorer could possibly be solved by merely setting a component’s innerHTML
property to itself. So I attempted it.
doc.getElementById('divTitleContainer') » .innerHTML = doc.getElementById('divTItleContainer') » .innerHTML;
It was foolish, I do know. But it surely labored.
The answer to all of my issues was to not use a DOM methodology in any case, and as a substitute use my very own implementation. Right here, in all of its glory, is my ultimate answer to the importNode()
downside coded in a cross-browser compliant approach: (Line wraps marked » —Ed.)
if (!doc.ELEMENT_NODE) { doc.ELEMENT_NODE = 1; doc.ATTRIBUTE_NODE = 2; doc.TEXT_NODE = 3; doc.CDATA_SECTION_NODE = 4; doc.ENTITY_REFERENCE_NODE = 5; doc.ENTITY_NODE = 6; doc.PROCESSING_INSTRUCTION_NODE = 7; doc.COMMENT_NODE = 8; doc.DOCUMENT_NODE = 9; doc.DOCUMENT_TYPE_NODE = 10; doc.DOCUMENT_FRAGMENT_NODE = 11; doc.NOTATION_NODE = 12; } doc._importNode = operate(node, allChildren) { change (node.nodeType) { case doc.ELEMENT_NODE: var newNode = doc.createElement(node » .nodeName); /* does the node have any attributes so as to add? */ if (node.attributes && node.attributes » .size > 0) for (var i = 0; il = node.attributes.size; » i < il) newNode.setAttribute(node.attributes[i] » .nodeName, node.getAttribute(node.attributes[i++] » .nodeName)); /* are we going after kids too, and does » the node have any? */ if (allChildren && node.childNodes && » node.childNodes.size > 0) for (var i = 0; il = node.childNodes.size; » i < il) newNode.appendChild(doc._importNode » (node.childNodes[i++], allChildren)); return newNode; break; case doc.TEXT_NODE: case doc.CDATA_SECTION_NODE: case doc.COMMENT_NODE: return doc.createTextNode(node.nodeValue); break; } };
Right here it’s in use:
var newNode = null, importedNode = null; newNode = xhrResponse.responseXML.getElementsByTagName » ('title')[0].childNodes[0]; if (newNode.nodeType != doc.ELEMENT_NODE) newNode = newNode.nextSibling; if (newNode) { importedNode = doc._importNode(newNode, true); doc.getElementById('divTitleContainer') » .appendChild(importedNode); if (!doc.importNode) doc.getElementById('divTitleContainer') » .innerHTML = doc.getElementById('divTitleContainer') » .innerHTML; }
Let’s get sensible#section9
That is all effectively and good in idea, is it price utilizing this answer in the true world? For builders creating Ajax net purposes or web sites, I imagine it’s. An Ajax software can clearly make the most of the doc._importNode()
answer if it receives chunks of XHTML from the server in response to a shopper request. In these conditions, it will be significant that any occasions constructed into the chunks of markup coming from the server operate accurately and built-in model parts must also show correctly.
We are able to assume, for the sake of this instance, that the shopper is requesting new knowledge to put inside a <DIV>
component with id="xhrText"
. The server will ship the response as a bit of XHTML to be positioned instantly into this component, surrounded by a guardian XML node that may be successfully ignored. (Line wraps marked » —Ed.)
var newNode = null, importedNode = null; newNode = xhrResponse.responseXML.getElementsByTagName » ('response')[0].childNodes[0]; if (newNode.nodeType != doc.ELEMENT_NODE) newNode = newNode.nextSibling; if (newNode) { importedNode = doc._importNode(newNode, true); doc.getElementById('xhrText').innerHTML = ''; doc.getElementById('xhrText').appendChild » (importedNode); if (!doc.importNode) doc.getElementById('xhrText').innerHTML = » doc.getElementById('xhrText').innerHTML; }
The tactic described above will make sure that occasions hooked up to parts contained within the server response fireplace when wanted, and that any model related to any parts will probably be utilized to the imported markup. Instance one reveals it in motion.
You may also use this methodology in net web page that makes use of Ajax and XHTML to interchange frames or iframes. When the primary web page accommodates giant graphics, model sheets, or JavaScript that the developer would slightly not require the shopper to load repeatedly if the shopper’s cache isn’t set as much as deal with all of it. Ajax fetches the contents of a whole web page after which locations the contents of the <physique>
right into a predetermined “body” component. There should be a whole web page to be retrieved in order that the web site stays accessible to browsers which have JavaScript disabled. A hyperlink, for instance, would seem like this: (Line wraps marked » —Ed.)
<a href="https://alistapart.com/article/crossbrowserscripting/page2.xhtml" onclick="return gotoPage » (this.href);">Web page 2</a>
The gotoPage()
operate will all the time return false
so as to cease the browser from shifting to page2.xhtml
. If JavaScript is disabled, this hyperlink nonetheless works, because the browser will go to page2.xhtml
, after which it should reload all the giant information the developer is attempting to keep away from. The gotoPage()
operate would make an Ajax name for the brand new web page:
var xhr = false; operate gotoPage(p_url) { if (window.XMLHttpRequest) { xhr = new XMLHttpRequest(); } else { attempt { xhr = new ActiveXObject('Msxml2.XMLHTTP'); } catch (ex) { attempt { xhr = new ActiveXObject('Microsoft.XMLHTTP'); } catch (ex) { xhr = false; } } } if (!xhr) return (false); else { xhr.open('get', p_url, true); xhr.onreadystatechange = showPageContent; xhr.ship(null); } return (false); }
The operate to name for a response handles the entire web page, importing solely the half it wants. For my pages, I hold all the content material separate from headers and footers by protecting it in a separate <DIV>
component with id="documentBodyContent"
. (Line wraps marked » —Ed.)
operate showPageContent() { if (xhr.readyState 4 && xhr.standing 200) { var newNode = null, tempNode = null, importedNode » = null; tempNode = xhr.responseXML.getElementsByTagName » (‘div’); for (var i = 0; il = tempNode.size; i < il; i++) if (tempNode[i].getAttribute(‘id’) == » ‘documentBodyContent’) { newNode = tempNode[i]; break; } if (newNode.nodeType != doc.ELEMENT_NODE) newNode = newNode.nextSibling; if (newNode) { importedNode = doc._importNode(newNode, » true); doc.getElementById(‘xhrFrame’).innerHTML = ’’; doc.getElementById(‘xhrFrame’).appendChild » (importedNode); if (!doc.importNode) doc.getElementById(‘xhrFrame’).innerHTML = » doc.getElementById(‘xhrFrame’).innerHTML; } } }
Instance two demonstrates the code in motion. There’s a snag with Web Explorer as a result of IE doesn’t acknowledge XHTML accurately. A workaround supplied within the code parses the responseText
and hundreds the string into an XML doc that may then be used.
So the place can we stand? It’s sophisticated. If you could import nodes from two completely different doc homeowners, you’ll uncover that no browser will get it proper; neither utilizing cloneNode()
, importNode()
nor saving the outerHTML
of the brand new node to the innerHTML
of the prevailing node. Utilizing doc._importNode()
works for me, however your mileage could fluctuate.
Please word: The doc._importNode()
that I offered can not deal with the whole set of node sorts, solely the commonest ones discovered when importing a DOM doc. By following the W3C definition for importNode()
, it might not be an excessive amount of hassle so as to add the lacking sorts into this methodology. Bear in mind, although, that sorts doc.DOCUMENT_NODE
and doc.DOCUMENT_TYPE_NODE
can’t be imported and could also be excluded. Additionally the kinds doc.ENTITY_NODE
and doc.NOTATION_NODE
can’t be fully imported because of DocumentType
being read-only within the present implementation of the DOM.
Obtain the ultimate answer on this doc and take a look at it out for your self. I’ve examined this code in Mozilla 1.7.13, Firefox 2.0 and 1.5, Web Explorer 7.0, 6.0, 5.5, and 5.0, Opera 9.10 and eight.53, Netscape 8.1, Flock 0.7, Okay-Meleon 1.02, and SeaMonkey 1.0; it labored for me in all instances. Please let me know should you run into any issues with different browsers or uncover issues within the code!