48

I'm creating a document fragment as follow:

var aWholeHTMLDocument = '<!doctype html> <html><head></head><body><h1>hello world</h1></body></html>';
var frag = document.createDocumentFragment();
frag.innerHTML = aWholeHTMLDocument;

The variable aWholeHTMLDocument contains a long string that is the entire html document of a page, and I want to insert it inside my fragment in order to generate and manipulate the DOM dynamically.

My question is, once I have added that string to frag.innerHTML, shouldn't it load this string and convert it to a DOM object?

After setting innerHTML, shouldn't I have access to the DOM through a property?

I tried frag.childNodes but it doesn't seem to contain anything, and all I want is to just access that newly created DOM.

vsync
  • 118,978
  • 58
  • 307
  • 400
Loic Duros
  • 5,472
  • 10
  • 43
  • 56
  • I'm not sure that it's possible for the "innerHTML" of any DOM element (and a document fragment is really just a DOM element that can't be part of the DOM) to be a complete HTML document. The answer to your question would be "yes" if it were possible. – Pointy Nov 20 '11 at 14:40
  • Instead of .appendChild(frag) you do .innerHTML once for all. It is faster than creating document fragment, because String handling is faster. –  May 29 '19 at 13:59
  • Proof: https://jsperf.com/fragments-vs-html-strings –  May 29 '19 at 16:54

9 Answers9

48

While DocumentFragment does not support innerHTML, <template> does.

The content property of a <template> element is a DocumentFragment so it behaves the same way. For example, you can do:

var tpl = document.createElement('template');
tpl.innerHTML = '<tr><td>Hello</td><td>world</td></tr>';
document.querySelector('table').appendChild(tpl.content);

The above example is important because you could not do this with innerHTML and e.g. a <div>, because a <div> does not allow <tr> elements as children.


NOTE: A DocumentFragment will still strip the <head> and <body> tags, so it won't do what you want either. You really need to create a whole new Document.

chowey
  • 9,138
  • 6
  • 54
  • 84
  • 1
    Really, this should be the accepted answer, as it also provides a very good solution. @Chowey: `tr` elements inside a `table` live in either `thead`, `tbody` or `tfoot`. – connexo Jul 10 '21 at 08:09
  • This is the only answer I found that does not add a wrapper div around the content being inserted. Super helpful when programmatically inserting html content into the slot of a web component – borie88 Apr 07 '22 at 04:40
36

You can't set the innerHTML of a document fragment like you would do with a normal node, that's the problem. Adding a standard div and setting the innerHTML of that is the common solution.

Johan
  • 3,202
  • 1
  • 23
  • 19
  • 1
    So what you are saying is that I should do the following? var aWholeHTMLDocument = '

    hello world

    '; var div = document.createElement('div'); div.innerHTML = aWholeHTMLDocument; This will strip everything except the

    though...

    – Loic Duros Nov 20 '11 at 16:27
  • 4
    @Loic - He never said something like this ... He just explained your mistake. Your suggested solution in your comment is absolutely not recommended. – ˈvɔlə Mar 03 '14 at 11:04
15

DocumentFragment inherits from Node, but not from Element that contains the .innerHTML property.

In your case I would use the <template> tag. In inherits from Element and it has a nifty HTMLTemplateElement.content property that gives you a DocumentFragment.

Here's a simple helpermethod you could use:

export default function StringToFragment(string) {
    var renderer = document.createElement('template');
    renderer.innerHTML = string;
    return renderer.content;
}
pekaaw
  • 2,309
  • 19
  • 18
6

I know this question is old, but I ran into the same issue while playing with a document fragment because I didn't realize that I had to append a div to it and use the div's innerHTML to load strings of HTML in and get DOM Elements from it. I've got other answers on how to do this sort of thing, better suited for whole documents.

In firefox (23.0.1) it appears that setting the innerHTML property of the document fragment doesn't automatically generate the elements. It is only after appending the fragment to the document that the elements are created.

To create a whole document use the document.implementation methods if they're supported. I've had success doing this on Firefox, I haven't really tested it out on other browsers though. You can look at HTMLParser.js in the AtropaToolbox for an example of using document.implementation methods. I've used this bit of script to XMLHttpRequest pages and manipulate them or extract data from them. Scripts in the page are not executed though, which is what I wanted though it may not be what you want. The reason I went with this rather verbose method instead of trying to use the parsing available from the XMLHttpRequest object directly was that I ran into quite a bit of trouble with parsing errors at the time and I wanted to specify that the doc should be parsed as HTML 4 Transitional because it seems to take all kinds of slop and produce a DOM.

There is also a DOMParser available which may be easier for you to use. There is an implementation by Eli Grey on the page at MDN for browsers that don't have the DOMParser but do support document.implementation.createHTMLDocument. The specs for DOMParser specify that scripts in the page are not executed and the contents of noscript tags be rendered.

If you really need scripts enabled in the page you could create an iFrame with 0 height, 0 width, no borders, etc. It would still be in the page but you could hide it pretty well.

There's also the option of using window.open() with document.write, DOM methods or whatever you like. Some browsers even let you do data URI's now.

var x = window.open( 'data:text/html;base64,' + btoa('<h1>hi</h1>') );
// wait for the document to load. It only takes a few milliseconds
// but we'll wait for 5 seconds so you can watch the child window
// change.
setTimeout(function () {
    console.log(x.document.documentElement.outerHTML);
    x.console.log('this is the console in the child window');
    x.document.body.innerHTML = 'oh wow';
}, 5000);

So, you do have a few options for creating whole documents offscreen/hidden and manipulating them, all of which support loading the document from strings.

There's also phantomjs, an awesome project producing a headless scriptable web browser based on webkit. You'll have access to the local filesystem and be able to do pretty much whatever you want. I don't really know what you're trying to accomplish with your full page scripting and manipulation.

Kastor
  • 617
  • 5
  • 14
2

For a Firefox add-on, it probably makes more sense to use the document.implementation.createHTMLDocument method, and then go from the DOM that gives you.

Gijs
  • 5,201
  • 1
  • 27
  • 42
1

Use appendChild

see https://developer.mozilla.org/en-US/docs/Web/API/Document/createDocumentFragment

var fragment = document.createDocumentFragment();
... fragment.appendChild(some element);
document.querySelector('blah').appendChild(fragment);
Tony Jin
  • 59
  • 3
1

Here is a solution for converting a HTML string into a DOM object:

let markup = '<!doctype html><html><head></head><body><h1>hello world</h1></body></html>';
let range = document.createRange();
let fragment = range.createContextualFragment(markup); //Creates a DOM object

The string does not need to be a complete HTML document.

frankichiro
  • 71
  • 2
  • 4
1

With a document fragment you would append elements that you had created with document.createElement('yourElement'). aWholeHTMLDocument is merely text. Also, unless your using frames I'm not sure why you would need to create the whole HTML document just use what is inside the <body> tags.

qw3n
  • 6,236
  • 6
  • 33
  • 62
  • This is for a Firefox add-on. I'm placing aWholeHTMLDocument into an iFrame, but I need it whole, not just the tags, the tag also contains stuff of interest, and I want to keep the whole DOM intact. – Loic Duros Nov 20 '11 at 16:29
  • @Loic then what you want is to get a reference to the `iframe` and set it's innerHTML to `aWholeHTMLDocument` – qw3n Nov 20 '11 at 17:17
0

Use querySelector() to get a child of the document fragment (you probably want the body, or some child of the body). Then get the innerHTML.

document.body.innerHTML = aWholeHTMLDocument.querySelector("body").innerHTML

or

aWholeHTMLDocument.querySelector("body").childNodes;

See https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment.querySelector

Timo Tijhof
  • 10,032
  • 6
  • 34
  • 48
Phyxx
  • 15,730
  • 13
  • 73
  • 112