Inserting arbitrary HTML into a DocumentFragment
Solution 1
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
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
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
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
@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.
Domenic
I'm a developer on the Google Chrome team, working to make the web platform more awesome. I work on web specs at the WHATWG, including editing the HTML Standard and Streams Standard. I help improve the JavaScript standard at Ecma TC39, representing Chrome's interest there and proposing some new features for future inclusion. Once upon a time I was elected to the W3C TAG to ponder issues of web architecture. In my spare time, my hobbyist programming centers around lots of Node.js and web apps. My favorite project is currently jsdom. I also used to present a lot at conferences. Check me out on GitHub and npm.
Updated on October 20, 2020Comments
-
Domenic over 3 years
I know that 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 thespan
inside offrag
, 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. -
gdoron is supporting Monica over 12 years@RobW. That's not a good reason to down vote if the OP wrote " jQuery is allowed"
-
Rob W over 12 years@gdoron The main reason that I downvoted the answer is that the textual explanation is 100% wrong. A
DocumentFragment
object can have an arbitrary number of child elements. My previous comment was referring to Morgan's comment at Michal's answer. -
Morgan T. over 12 years@RobW it was just a playful comment because we posted at the exact same time. I can see how others would see it as inappropriate though. I've removed it. I see what you mean about my logic too. During my testing I made a mistake and based my explanation off that. I've updated my answer. Thanks
-
Domenic over 12 yearsYeah, I don't want the extra container element.
-
Domenic over 12 yearsYeah, same problem as Michal's answer; this does not satisfy the problem statement, since it inserts an extra wrapper
div#main
. -
Domenic over 12 yearsBleh, I guess this is the only way to go. At least you actually answered the question and satisfied the problem statement :). Still, it's annoying to have to loop; ah well.
-
Michal over 12 yearsWhen you appending you always need an "extra" (or should I say a target) container - be a body or some other tag. If you don't want that it is loops for you
-
Domenic over 12 yearsI guess I was hoping there would be a way to copy all children of
holder
intofrag
in one go (without looping).appendChildren
or something. But it sounds like no such method exists. -
Christophe almost 11 years@Domenic such a method exists...that's the whole point of document fragments ;-) your real issue here is that you're dealing with a html string, not html elements.
-
B.F. over 10 years@Domenic frag = holder.cloneNode(true);
-
Marcello di Simone over 9 yearsNot supported by IE at all, so "modern browsers" is a bit misleading.
-
PAEz about 9 yearsMisses text between elements....
var elemQueried = document.createDocumentFragment(); var tmp = document.createElement('body'), child; tmp.innerHTML = '<div>x</div>Bleh<span>y</span>'; var children = tmp.childNodes; while(children.length){ elemQueried.appendChild(children[0]); }
-
Jonatas Walker almost 9 yearsAlmost upvoted, if it weren't that
<font>
tag in the demo. -
Munawwar over 8 yearsAn issue with createContextualFragment is that, html like '<td>test</td>' would ignore the td (and only create 'test' text node). template tag solution is the way to go. BTW Edge 13 supports template tag now.
-
tomalec over 8 yearsDo you have any idea for nice Safari alternative? or at least an issue in their tracker?
-
Matt about 8 years@PAEz Posted an answer below that takes care of this issue.
-
Rob W about 8 yearsEven conciser:
.firstChild
-while (intermediateContainer.firstChild) fragement.appendChild(intermediateContainer.firstChild);
-
Matt about 8 yearsGood suggestion. I suppose it's a question of style at this point. Thanks!
-
KevBot over 7 yearsThis does not work (at least in Chrome). When using insertAdjacentHTML inside of a DocumentFragment, you will get this error:
Uncaught DOMException: Failed to execute 'insertAdjacentHTML' on 'Element': The element has no parent.
Also,addChild
is not a method, you should be usingappendChild
-
jcubic over 5 yearsI've needed array off element nodes from Fragement and this works in Chrome and IE11
[].slice.call(temp.content ? temp.content.children : temp.childNodes)
-
jcubic over 5 yearsPlus for supporting td in IE.
-
Johan about 5 yearsThis should be the selected answer and deserves way more upvotes <3
-
Victoria over 4 yearsCareful! not standardized.
-
Bharata almost 4 yearsIt does not work with
td
,th
elements fortable
. -
Uchenna Ajah over 2 yearsNo doubt! This is the best answer! Both documentFragments and DOMParser are supported by older browsers, including IE9. Thumbs up