using document.createDocumentFragment() and innerHTML to manipulate a DOM

38,730

Solution 1

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.

Solution 2

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.

Solution 3

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;
}

Solution 4

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.

Solution 5

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.

Share:
38,730
Loic Duros
Author by

Loic Duros

Updated on July 09, 2022

Comments

  • Loic Duros
    Loic Duros almost 2 years

    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.

    • Pointy
      Pointy over 12 years
      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.
    • Admin
      Admin almost 5 years
      Instead of .appendChild(frag) you do .innerHTML once for all. It is faster than creating document fragment, because String handling is faster.
    • Admin
      Admin almost 5 years
  • Loic Duros
    Loic Duros over 12 years
    So what you are saying is that I should do the following? var aWholeHTMLDocument = '<!doctype html> <html><head></head><body><h1>hello world</h1></body></html>'; var div = document.createElement('div'); div.innerHTML = aWholeHTMLDocument; This will strip everything except the <h1> though...
  • Loic Duros
    Loic Duros over 12 years
    This is for a Firefox add-on. I'm placing aWholeHTMLDocument into an iFrame, but I need it whole, not just the <body> tags, the <head> tag also contains stuff of interest, and I want to keep the whole DOM intact.
  • qw3n
    qw3n over 12 years
    @Loic then what you want is to get a reference to the iframe and set it's innerHTML to aWholeHTMLDocument
  • ˈvɔlə
    ˈvɔlə about 10 years
    @Loic - He never said something like this ... He just explained your mistake. Your suggested solution in your comment is absolutely not recommended.
  • Jorge Fuentes González
    Jorge Fuentes González about 6 years
    This not addresses the "process string" problem.
  • connexo
    connexo almost 3 years
    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.
  • borie88
    borie88 about 2 years
    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