PDA

View Full Version : Loading with DOM method instead of innerHTML



molendijk
01-14-2008, 10:24 PM
When you have something like this:


<iframe src="included.html" style="width:0px;height:0px" frameborder="0"></iframe>
<div id="loader" ></div>

in your page, then you can load the content of 'included.html' in it by something like this:


onload="document.getElementById('loader').innerHTML = frames[0].document.body.innerHTML;"

Simple and easy. But I'd like to obtain this result with pure DOM methods? How would I do that?

Thanks,
Arie.

jscheuer1
01-14-2008, 10:46 PM
That can get complicated, as has been mentioned elsewhere. Twey seems to think (if I understood him correctly) something called json will parse XML into DOM level 3 code. However, think about what you are doing. You are already loading the iframe. If you are bothering with that, why not just display it?

Another thing to consider is that iframe is deprecated in HTML 4.01 Strict and above, so there really isn't any need to use a more standard method than innerHTML to deal with it.

Also, I don't think:


frames[0].document.body

will work, and if it does, it isn't cross browser. I'm not sure, but I believe that the iframe must be 'gotten' as a document element for that, not as an implied part of the window.frames object/function/collection. You could give it an id and get it with document.getElementByID(), for example, or with document.all where supported, etc. And there is the matter of the iframe's content document, I'm certain that it is referred to in different ways in different browsers.

molendijk
01-14-2008, 11:30 PM
John,
Thanks for your reply. I was afraid that it would be complicated.

Loading the iframe instead of just showing it has certain advantages if the loaded file is a menu: if the menu sticks in the iframe, then certain things are (often) not possible.

As for 'frames[0].document.body...', that was just to illustrate my question. It can easily be replaced by something that's crossbrowser.

Iframes are getting deprecated, yes. But <object>...</objects> is OK. I could do the innerHTML-thing with an object.

So I would be very pleased if replacing innerHTML with DOM would yield the intended result. But, as you said, that would get complicated.

Arie.

molendijk
01-14-2008, 11:42 PM
John,
Here (http://www.let.rug.nl/molendyk/include_menu/file1.html)'s an illustration of what I can do with innerHTML & what I would like to replace with DOM methods.

Arie.

Aaron
01-15-2008, 01:22 PM
I had a nightmare with trying to do the "the right way". Then went to innerHTML and it works! The "right way" used to suddenly crash IE7 ... so avoid that way. I'm sticking with innerHTML whenever possible.

Twey
01-15-2008, 05:25 PM
var nods = frames[0].document.body.cloneNode(true).childNodes, op = document.getElementById('loader'); while(op.hasChildNodes()) op.removeChild(op.firstChild); for(var i = 0, n = nods.length; i < n; ++i) op.appendChild(nods[i]);Probably best abstracted into some functions:
var Dom = (function() {
function clearChildren(nod) {
while(nod.hasChildNodes())
nod.removeChild(nod.firstChild);
return nod;
}

function cloneChildren(inp, out) {
var nods = inp.cloneNode(true).childNodes;

Dom.clearChildren(out);

for(var i = 0, n = nods.length; i < n; ++i)
out.appendChild(nods[i]);

return inp;
}

function getElementById(id) {
var f;

return document.getElementById(id)
|| (f = document.getElementsByName(id) && f[0]);
}

getElementById.clearChildren = clearChildren;
getElementById.cloneChildren = cloneChildren;

return getElementById;
})();

onload = function() {
Dom.cloneChildren(frames[0].document.body, Dom("loader"));
};

molendijk
01-15-2008, 06:59 PM
Well, Twey's code looks very promising (at first sight), so I'm going to test it as soon as I have some time.

Thanks,
Arie

molendijk
01-15-2008, 08:41 PM
Hello Twey,
Works fine in non-IE, but not in IE6. I have not tried IE7 yet.
It says that there is an invalid argument somewhere in:

for(var i = 0, n = nods.length; i < n; ++i)
op.appendChild(nods[i]);

Arie.

Twey
01-15-2008, 09:08 PM
Sorry, op should be out. Shouldn't have worked on anything else either. Edited.

jscheuer1
01-15-2008, 10:39 PM
Unless a way can be found to make IE do this with the DOM, this is probably about as good as it gets:


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>domFrame</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<script type="text/javascript">
function domFrame(n, id){
if(!document.cloneNode||!window.frames||!window.frames[n])return;
var nods=window.frames[n].document.body.childNodes, o=document.getElementById(id),
doIt=toDoIt=function(){for (var i = 0; i < nods.length; i++)o.appendChild(nods[i].cloneNode(true));}
for (var i = o.childNodes.length-1; i > -1; --i)
o.removeChild(o.childNodes[i]);
/*@cc_on @*/
/*@if(@_jscript_version >= 5)
domFrame.ie=true;
try{toDoIt();};
catch(e){o.innerHTML=window.frames[n].document.body.innerHTML;};
@end @*/
if(!domFrame.ie)
doIt();
}
</script>
</head>
<body>
<iframe style="display:none;" src="ext.htm" width="300" height="300" scrolling="auto" frameborder="1"></iframe>
<input type="button" onclick="alert(window.frames[0].document.body.innerHTML);" value="iH"><br>
<input type="button" onclick="domFrame(0, 'output');" value="DOM">
<div id="output"></div>
</body>
</html>

The code isn't polished, but it works. The 'iH' button alerts the iframe's document's innerHTML, the 'DOM' button inserts the iframe's body's children onto the page via the DOM if possible, falling back to innerHTML in IE, but only if IE won't do it like everybody else.

I'll have to take back what I said about:


window.frames[n].document.body

It seems much more universally supported for this than I realized.

Twey
01-15-2008, 10:44 PM
The code isn't polished, but it works.You've got it backwards: innerHTML is the proprietary feature. The code could use innerHTML first (it is faster, after all) and fall back onto the standard DOM.

Irrelevant in this case, though, since the cause of the problem was just a typo on my part.

molendijk
01-16-2008, 12:10 AM
Well, I've tried both Twey's and John's scripts. John's works in IE and non-IE. Twey's script continues to refuse to work in IE. So it seems to be a IE-Dom issue after all.

Thanks for your replies. I'll continue to work on this. Any further suggestions?

Arie.

molendijk
01-16-2008, 01:49 AM
Hello Twey / John,

It's an IE cloneNode-issue. Works allright in non-IE, but not in IE.
So

function loadNonIE(){
document.getElementById("loader").appendChild(frames[0].document.body.cloneNode(true));
}
onload=loadNonIE
is OK in non-IE (loads the content of the frame into the page), but not in IE. For IE, we must use the old innerHTML:

function loadIE(){
document.getElementById('loader').innerHTML = frames[0].document.body.innerHTML;}
onload=loadIE.

See:


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<!--[if !IE]><!-->
<script type="text/javascript">
function loadNonIE(){
document.getElementById("loader").appendChild(frames[0].document.body.cloneNode(true));
}
onload=loadNonIE
</script>
<!--<![endif]-->
<!--[if IE]>
<script type="text/javascript">
function loadIE(){
document.getElementById('loader').innerHTML = frames[0].document.body.innerHTML;}
onload=loadIE
</script>
<![endif]-->
</head>

<body >

<iframe src="somefile.html" style="width:0px;height:0px" frameborder="0"></iframe>
<div id="loader" ></div>
<!-- functions for IE -->
<button onclick=alert(loadIE)>alert IE function</button><br><br>
<!-- functions for non-IE -->
<button onclick=alert(loadNonIE)>alert non-IE function</button><br><br>
</body>
</html>

jscheuer1
01-16-2008, 04:22 AM
If the purpose of this exercise is as the title of this thread implies, I prefer my approach. It allows IE to use the DOM when and if it ever becomes capable of it.

But I'm actually more concerned with the fun of:



try{toDoIt();};

and:



if(!domFrame.ie)
doIt();

I even considered making up another internal function:


var reak=function(){o.innerHTML=window.frames[n].document.body.innerHTML;};

Which would have allowed:


try{toDoIt();}
catch(a_B){reak();};

I'm surprised no one mentioned it. ;)

molendijk
01-17-2008, 02:51 PM
Hello John & Twey,
I like your ideas, but the only thing that preoccupies me now is to find a DOM-method for both IE and non-IE corresponding to:

onload="document.getElementById('loader').innerHTML = frames[0].document.body.innerHTML;"
The only thing I can get working in all browsers is this (raw code)

onload="if(document.getElementById('loader').childNodes[1])document.getElementById('loader').removeChild(document.getElementById('loader').childNodes[1]);document.getElementById('loader').firstChild.nodeValue=frames[0].document.getElementById('cloneMe').firstChild.nodeValue";

But of course, that only gives me plain text (no markup etc.).

Any ideas on how to work that out?

Thanks,
Arie

jscheuer1
01-17-2008, 06:02 PM
Wait for IE 8.

Twey
01-17-2008, 06:45 PM
I'm not sure why the above code isn't working for you. It doesn't contain any IE-specific bugs insofar as I'm aware.

molendijk
01-17-2008, 07:05 PM
Well, I've googled around, and found that IE has trouble with 'cloneNode(true)' in a number of cases, including comboboxes, tables etc.
So I'll have to wait until IE8 indeed.

Anyway, thanks a lot,
Arie Molendijk.

Twey
01-17-2008, 08:32 PM
Hm, that's irritating. Do a try{try{}catch}catch{} then.

jscheuer1
01-18-2008, 04:18 AM
Though it may have seemed flip, I was serious when I said wait for 8. IE doesn't have any problem with cloneNode(true) that I am aware of, other than with attached events (attachEvent), but there could be other problems I haven't run across yet, or that I am at the moment forgetting.

I did play around with my code, and in the IE only area (green) I did:


function domFrame(n, id){
if(!document.cloneNode||!window.frames||!window.frames[n])return;
var toDoIt, nods=window.frames[n].document.body.childNodes, o=document.getElementById(id),
doIt=toDoIt=function(){for (var i = 0; i < nods.length; i++)o.appendChild(nods[i].cloneNode(true));}
for (var i = o.childNodes.length-1; i > -1; --i)
o.removeChild(o.childNodes[i]);
/*@cc_on @*/
/*@if(@_jscript_version >= 5)
domFrame.ie=true;
for (var i = 0; i < nods.length; i++){
try {
var rq=nods[i].cloneNode(true);
}catch(e){alert('1: '+e.description);};
try {
o.appendChild(rq);
}catch(e){alert ('2: '+e.description);};
}
@end @*/
if(!domFrame.ie)
doIt();
}

The only errors were:


2: Invalid argument.

So, you see there is no problem with cloneNode here. In fact if you were to do (addition red):


var rq=nods[i].cloneNode(true);
alert(rq.outerHTML);

you would see that the rq variable holds an accurate clone of the node.

The real problem is in appending the external node to the top document. There are ways to do that in IE, but they are convoluted at best:

http://msdn2.microsoft.com/en-us/library/ms762232(VS.85).aspx?pull=/msdnmag/issues/0500/security/default.aspx

Over my head, at least for the time being.

jscheuer1
01-18-2008, 05:44 AM
I found the answer:

http://mroch.com/programming/javascript/usingimportnodeininternetexplorer.html

Incorporated into a cross browser demo:


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>domFrame</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<script type="text/javascript">
function domFrame(n, id){
if(!document.importNode||!window.frames||!window.frames[n])return;
var nods=window.frames[n].document.body.childNodes, o=document.getElementById(id);
for (var i = o.childNodes.length-1; i > -1; --i)o.removeChild(o.childNodes[i]);
for (var i = 0; i < nods.length; i++)o.appendChild(document.importNode(nods[i], true));
};

if(typeof document.importNode != "function" && document.createElement){
document.importNode = function(importedNode, deep){
var newNode;

if(importedNode.nodeType == 1) { // Node.ELEMENT_NODE
newNode = document.createElement(importedNode.nodeName);
for(var i = 0; i < importedNode.attributes.length; i++){
var attr = importedNode.attributes[i];
if (attr.nodeValue != null && attr.nodeValue != '') {
newNode.setAttribute(attr.name, attr.nodeValue);
}
}
if (typeof importedNode.style != "undefined")
newNode.style.cssText = importedNode.style.cssText;
} else if(importedNode.nodeType == 3) { // Node.TEXT_NODE
newNode = document.createTextNode(importedNode.nodeValue);
}

if(deep && importedNode.hasChildNodes()){
for(var i = 0; i < importedNode.childNodes.length; i++) {
newNode.appendChild(
document.importNode(importedNode.childNodes[i], true)
);
}
}

return newNode;
}
}
</script>
</head>
<body>
<iframe style="display:none;" src="ext.htm" width="300" height="300" scrolling="auto" frameborder="1"></iframe>
<input type="button" onclick="alert(window.frames[0].document.body.innerHTML);" value="iH"><br>
<input type="button" onclick="domFrame(0, 'output');" value="DOM">
<div id="output"></div>
</body>
</html>

jscheuer1
01-18-2008, 08:48 AM
After playing around with this for a bit, I've come up with an improved version of this document.importNode function:


if(!document.importNode&&document.createElement)
document.importNode = function(iNode, deep){
var nNode; if(iNode.nodeType == 1) { // Node.ELEMENT_NODE
nNode = document.createElement(iNode.nodeName);
for(var a = iNode.attributes, i = 0; i < a.length; i++) // Grab attributes
if (a[i].nodeValue != null && a[i].nodeValue != '')
nNode.setAttribute(a[i].name, a[i].nodeValue);
if (typeof iNode.style != "undefined")nNode.style.cssText = iNode.style.cssText; // Grab style
for(var p in iNode)if(/^on/i.test(p)&&iNode[p]!=null)nNode[p]=iNode[p]; // Pick up hard coded events
} else if(iNode.nodeType == 3) // Node.TEXT_NODE
nNode = document.createTextNode(iNode.nodeValue);
else if(iNode.nodeType == 8) // Node.COMMENT_NODE
nNode = document.createComment(iNode.nodeValue);
else nNode = document.createTextNode(''); // Skip anything else and prepare ro return an empty text node
if(deep && iNode.hasChildNodes()){
for(var i = 0; i < iNode.childNodes.length; i++)
nNode.appendChild(document.importNode(iNode.childNodes[i], true));
;};return nNode;};

About the only thing it won't pick up is internal scripts, which other browsers will. Other browsers can do this because they have a native document.importNode() and don't need this function, but also because, even if they used it, they would pick up and create internal scripts. This inability to pick up and create script elements with their text nodes is an IE limitation. If the script tag is an external script tag, it will pick up the src and other attributes, and in testing, a simple alert fired on import, but it of course also fired when the iframe loaded. Other browsers (except Safari 3 Win) only fired it on the iframe.

molendijk
01-18-2008, 12:13 PM
John,
Thanks, you're very close. But now it only works in IE!! In FF, I get 'no permission to read HTML.document.body'. Weird!

Arie.

molendijk
01-18-2008, 12:51 PM
John, skip my previous post. I got confused in the number of '}'. It works perfectly (crossbrowser). Thank you so much!

Arie.

(By the way: it's IE that picks up internal scripts (belonging to the iframes doc.). FF doesn't).

Twey
01-18-2008, 06:03 PM
Aha! Good work, John! I wasn't aware that cloneNode() worked like that.

jscheuer1
01-18-2008, 07:27 PM
I've noticed some minor bugs with this approach. In IE for:


<input type="button">

there is an erroneous default height of 82px in the style and some huge random number height attribute. I'm not sure of the best way to deal with that. One can just set its style height auto inline on the external page (this is my current solution), but I would like to deal with it in the script - however, this risks missing/overriding real height styles and/or attributes. The attribute class may not be set in IE, that's any easy fix (included below). If a hard coded event on the external page was added with any uppercase letters in its name, that would probably be a problem in some browsers (fixed below). I added an option to the domFrame function to import scripts from the head of the external document to the output area. Here's a current working version, now named document.bringNode because it targets all browsers:


domFrame.headScripts=true; // Set (true/false) to import scripts from the head of external page to the content element
function domFrame(n, id){
if(!document.bringNode||!window.frames||!window.frames[n])return;
var nods=window.frames[n].document.body.childNodes, o=document.getElementById(id),
scps=window.frames[n].document.getElementsByTagName('head')[0].getElementsByTagName('script');
for (var i = o.childNodes.length-1; i > -1; --i)o.removeChild(o.childNodes[i]);
if(domFrame.headScripts)
for (var i = 0; i < scps.length; i++)o.appendChild(document.bringNode(scps[i], true));
for (var i = 0; i < nods.length; i++)o.appendChild(document.bringNode(nods[i], true));
};

if(document.createElement)
document.bringNode = function(iNode, deep){
var nNode; if(iNode.nodeType == 1) { // Node.ELEMENT_NODE
nNode = document.createElement(iNode.nodeName);
if(iNode.className)nNode.className=iNode.className; // Grab class attribute properly for IE (others will do it like this too)
for(var a = iNode.attributes, i = 0; i < a.length; i++) // Grab other attributes avoiding class, which can be erroneous
if (a[i].nodeValue != null && a[i].nodeValue != '' && !/^class$/.test(a[i].name))
nNode.setAttribute(a[i].name, a[i].nodeValue);
if (typeof iNode.style != "undefined")nNode.style.cssText = iNode.style.cssText; // Grab style
for(var p in iNode)if(/^on/i.test(p)&&iNode[p]!=null)nNode[p.toLowerCase()]=iNode[p]; // Pick up hard coded events
} else if(iNode.nodeType == 3) // Node.TEXT_NODE
nNode = document.createTextNode(iNode.nodeValue);
else if(iNode.nodeType == 8) // Node.COMMENT_NODE
nNode = document.createComment(iNode.nodeValue);
else nNode = document.createTextNode(''); // Skip anything else and prepare to return an empty text node
if(deep && iNode.hasChildNodes()){
for(var i = 0; i < iNode.childNodes.length; i++)
nNode.appendChild(document.bringNode(iNode.childNodes[i], true));
;};return nNode;};

molendijk
01-18-2008, 10:56 PM
John,
That is a major improvement. Scripts of the external page also work now in the output area in non-IE.
I don't quite understand what you said about the button problem in IE. I know that buttons are not well handled in IE, especially with long text (in the button), but that can be fixed with something like 'button,input{padding:0 .25em 0 .25em; width:auto; overflow:visible;}'.
Or didn't you mean that?
Arie.

jscheuer1
01-19-2008, 01:07 AM
The confusion over scripts before was because I was testing that part last thing before going to bed and I wasn't paying enough attention to how the various browsers treat a refresh of the top page visa vis refreshing the external page.

Now the bit about <input type="button"> in IE. This probably includes other elements in IE, hopefully not. I'm not sure why this is a problem but, consider this test page - run it in IE 7:


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<script type="text/javascript">
var b=document.createElement('input');
b.setAttribute('type', 'button');
alert('height='+b.height);
</script>

I haven't been able duplicate on a test page the action of the bringNode function that also sets the style.height to 82px, but if you try it out with the full function and an external page with a plain button input on it, you will probably see it happen. Setting the button input to be copied's style height inline to auto avoids any problems from all of this, ex:


<input style="height:auto;" type="button">

Setting an explicit style height would probably also work as would, perhaps setting an explicit attribute height.

molendijk
01-19-2008, 02:32 PM
As far as I can see, only all sorts of <input type="..."> are affected (in IE). The problem can be avoided the way you indicate. Note that there's no problem with '<button>bla</button>'.

As for importing scripts from the head of the external document, I guess that the scripts don't work well if the external document has this:
<script type="text/javascript" src="bla.js"></script>
I did some testing, and noticed that 'bla.js' was not executed.

Arie.

jscheuer1
01-19-2008, 02:51 PM
Any imported script, whether it was internal or external to the external page it was imported from, will be most useful on the top page if it consists only of data (variables, functions, etc.). Once imported, imported or existing events on the top page may activate its functions and/or make use of its variables. I've had this working quite well as far as a function goes.

To import a script and hope that it fires would be a crap shoot, IMO.

molendijk
02-22-2008, 05:45 PM
John,
When I tried to apply your domFrame-script to <object> instead of <iframe>, I noticed that for IE we must have window.frames[n].body (if we use objects) instead of window.frames[n].document.body (no difference between working with frames or object in the non-IE case). So I added a try...catch block to your script to have things crossbrowser. Here (http://molendijk.110mb.com/include_object_scheuer/include_object_scheuer.html) is a demo.
The script does not work in Opera if <object> is used. If you click on the Opera-link of my demo, you'll see a possible explanation. I ran into the same problem when I wrote a innerHTML-version of extracting content from an object, see here (http://www.dynamicdrive.com/forums/showthread.php?t=29559) and here (http://molendijk.110mb.com/include_object/extract2.html) (end of page).
Does this Opera-problem sound familiar to you?

Arie.