Inserting arbitrary HTML into a DocumentFragment

JavascriptJqueryDocumentfragment

Javascript Problem Overview


I know that http://lists.w3.org/Archives/Public/public-webapps/2011OctDec/0663">adding innerHTML to document fragments has been recently discussed, and will hopefully see inclusion in the DOM Standard. But, what is the workaround you're supposed to use in the meantime?

That is, take

var html = '<div>x</div><span>y</span>';
var frag = document.createDocumentFragment();

I want both the div and the span inside of frag, with an easy one-liner.

Bonus points for no loops. jQuery is allowed, but I've already tried $(html).appendTo(frag); frag is still empty afterward.

Javascript Solutions


Solution 1 - Javascript

Here is a way in modern browsers without looping:

var temp = document.createElement('template');
temp.innerHTML = '<div>x</div><span>y</span>';

var frag = temp.content;

or, as a re-usable

function fragmentFromString(strHTML) {
    var temp = document.createElement('template');
    temp.innerHTML = strHTML;
    return temp.content;
}

UPDATE: I found a simpler way to use Pete's main idea, which adds IE11 to the mix:

function fragmentFromString(strHTML) {
    return document.createRange().createContextualFragment(strHTML);
}

The coverage is better than the <template> method and tested ok in IE11, Ch, FF.

Live test/demo available http://pagedemos.com/str2fragment/

Solution 2 - Javascript

Currently, the only way to fill a document fragment using only a string is to create a temporary object, and loop through the children to append them to the fragment.

  • Since it's not appended to the document, nothing is rendered, so there's no performance hit.
  • You see a loop, but it's only looping through the first childs. Most documents have only a few semi-root elements, so that's not a big deal either.

If you want to create a whole document, use the DOMParser instead. Have a look at this answer.

Code:

var frag = document.createDocumentFragment(),
    tmp = document.createElement('body'), child;
tmp.innerHTML = '<div>x</div><span>y</span>';
while (child = tmp.firstElementChild) {
    frag.appendChild(child);
}

A one-liner (two lines for readability) (input: String html, output: DocumentFragment frag):

var frag =document.createDocumentFragment(), t=document.createElement('body'), c;
t.innerHTML = html; while(c=t.firstElementChild) frag.appendChild(c);

Solution 3 - Javascript

Use Range.createContextualFragment:

var html = '<div>x</div><span>y</span>';
var range = document.createRange();
// or whatever context the fragment is to be evaluated in.
var parseContext = document.body; 
range.selectNodeContents(parseContext);
var fragment = range.createContextualFragment(html);

Note that the primary differences between this approach and the <template> approach are:

  • Range.createContextualFragment is a bit more widely supported (IE11 just got it, Safari, Chrome and FF have had it for a while).

  • Custom elements within the HTML will be upgraded immediately with the range, but only when cloned into the real doc with template. The template approach is a bit more 'inert', which may be desirable.

Solution 4 - Javascript

No one ever provided the requested "easy one-liner".

Given the variables…

var html = '<div>x</div><span>y</span>';
var frag = document.createDocumentFragment();

… the following line will do the trick (in Firefox 67.0.4):

frag.append(...new DOMParser().parseFromString(html, "text/html").body.childNodes);

Solution 5 - Javascript

@PAEz pointed out that @RobW's approach does not include text between elements. That's because children only grabs Elements, and not Nodes. A more robust approach might be as follows:

var fragment = document.createDocumentFragment(),
    intermediateContainer = document.createElement('div');

intermediateContainer.innerHTML = "Wubba<div>Lubba</div>Dub<span>Dub</span>";

while (intermediateContainer.childNodes.length > 0) {
    fragment.appendChild(intermediateContainer.childNodes[0]);
}

Performance may suffer on larger chunks of HTML, however, it is compatible with many older browsers, and concise.

Solution 6 - Javascript

createDocumentFragment creates an empty DOM "container". innerHtml and other methods work only on DOM nodes (not the container) so you have to create your nodes first and then add them to the fragment. You can do it using a painful method of appendChild or you can create one node and modify it's innerHtml and add it to your fragment.

var frag = document.createDocumentFragment();
    var html = '<div>x</div><span>y</span>';
var holder = document.createElement("div")
holder.innerHTML = html
frag.appendChild(holder)

with jquery you simply keep and build your html as a string. If you want to convert it to a jquery object to perform jquery like operations on it simply do $(html) which creates a jquery object in memory. Once you are ready to append it you simply append it to an existing element on a page

Solution 7 - Javascript

Like @dandavis said, there is a standard way by using the template-tag.
But if you like to support IE11 and you need to parse table elements like '<td>test', you can use this function:

function createFragment(html){
    var tmpl = document.createElement('template');
    tmpl.innerHTML = html;
    if (tmpl.content == void 0){ // ie11
        var fragment = document.createDocumentFragment();
        var isTableEl = /^[^\S]*?<(t(?:head|body|foot|r|d|h))/i.test(html);
        tmpl.innerHTML = isTableEl ? '<table>'+html : html;
        var els        = isTableEl ? tmpl.querySelector(RegExp.$1).parentNode.childNodes : tmpl.childNodes;
        while(els[0]) fragment.appendChild(els[0]);
        return fragment;
    }
    return tmpl.content;
}

Solution 8 - Javascript

Here is a x-browser solution, tested on IE10, IE11, Edge, Chrome and FF.

> function HTML2DocumentFragment(markup: string) { > if (markup.toLowerCase().trim().indexOf(' let doc = document.implementation.createHTMLDocument(""); > doc.documentElement.innerHTML = markup; > return doc; > } else if ('content' in document.createElement('template')) { > // Template tag exists! > let el = document.createElement('template'); > el.innerHTML = markup; > return el.content; > } else { > // Template tag doesn't exist! > var docfrag = document.createDocumentFragment(); > let el = document.createElement('body'); > el.innerHTML = markup; > for (let i = 0; 0 < el.childNodes.length;) { > docfrag.appendChild(el.childNodes[i]); > } > return docfrag; > } > }

Solution 9 - Javascript

I would go with something like this..

function fragmentFromString(html) {
  const range = new Range();
  const template = range.createContextualFragment(html);
  range.selectNode(template.firstElementChild);
  return range;
}

// Append to body
// document.body.append(fragmentFromString(`<div>a</div>`).cloneContents())

This way you keep the content inside a Range object and you get all the needed methods for free.

You can find the list of all Range methods and properties here https://developer.mozilla.org/en-US/docs/Web/API/Range

Note: Remember to use detatch() method once you are done with it to avoid leaks and improve performance.

Solution 10 - Javascript

var html = '<div>x</div><span>y</span>';
var frag = document.createDocumentFragment();
var e = document.createElement('i');
frag.appendChild(e);
e.insertAdjacentHTML('afterend', html);
frag.removeChild(e);

Solution 11 - Javascript

To do this with as little lines as possible, you could wrap your content above in another div so you do not have to loop or call appendchild more than once. Using jQuery (as you mentioned is allowed) you can very quickly create an unattached dom node and place it in the fragment.

var html = '<div id="main"><div>x</div><span>y</span></div>';
var frag = document.createDocumentFragment();
frag.appendChild($​(html)[0]);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDomenicView Question on Stackoverflow
Solution 1 - JavascriptdandavisView Answer on Stackoverflow
Solution 2 - JavascriptRob WView Answer on Stackoverflow
Solution 3 - JavascriptPete BloisView Answer on Stackoverflow
Solution 4 - JavascriptPatrick DarkView Answer on Stackoverflow
Solution 5 - JavascriptMattView Answer on Stackoverflow
Solution 6 - JavascriptMichalView Answer on Stackoverflow
Solution 7 - JavascriptTobias BuschorView Answer on Stackoverflow
Solution 8 - JavascriptbnielandView Answer on Stackoverflow
Solution 9 - JavascriptBallpinView Answer on Stackoverflow
Solution 10 - JavascriptGuestView Answer on Stackoverflow
Solution 11 - JavascriptMorgan T.View Answer on Stackoverflow