Convert String to DOM Nodes
My original post featured DOMParser
, a JavaScript API for converting HTML strings
into DOM nodes. While DOMParser
works well in most cases, that API does have some
rough edges and isn't as performant as another API: ContextualFragment
. I've rewritten
this post to highlight ContextualFragment
, but if you still care to learn about
DOMParser
, please see the original text at the bottom of this post.
It wasn't too long ago that browsers were mostly stagnant when it came to implementing new APIs and features, leading to the rise of MooTools (FTW), jQuery, Dojo Toolkit, Prototype, and likewise JavaScript toolkits. Then we started doing more client side rendering and were forced to use a variety of tricks to handle templates, including massive HTML strings in our JavaScript and even abusing <script>
tags to hold our templates.
Of course after you've placed your content into the template, you then need to turn that string into DOM nodes, and that process had a few of its own tricks, like creating an offscreen, dummy <div>
, setting its innerHTML
to the string value, grabbing the firstChild
, and moving the node to its desired node. Each JavaScript toolkit would use its own strategy for converting string to DOM, highlighting the need for a standard method to accomplish this task.
Today there's a little known (but standard) way for converting string to DOM with JavaScript: ContextualFragment
.
I've touched on DocumentFragment
to create
and store DOM nodes for performance in the past, but that post illustrated element creation via
document.createElement
:
// Use a DocumentFragment to store and then mass inject a list of DOM nodes var frag = document.createDocumentFragment(); for(var x = 0; x < 10; x++) { var li = document.createElement("li"); li.innerHTML = "List item " + x; frag.appendChild(li); }
To create DOM nodes from a string of HTML we'll use
document.createRange().createContextualFragment
:
let frag = document.createRange().createContextualFragment('OneTwo'); console.log(frag); /* #document-fragmentOneTwo*/
DocumentFragment
objects share most of the methods that NodeList
objects
have, so you can use typical DOM methods like querySelector
and querySelectorAll
as well DOM traversal properties like firstChild
with the resulting DocumentFragment
:
let firstChild = frag.firstChild; let firstDiv = frag.querySelector('div'); let allDivs = frag.querySelectorAll('div');
When you're ready to inject all of the created DOM nodes, you can simply execute:
// "placementNode" will be the parent of the nodes within the DocumentFragment placementNode.appendChild(frag);
You can also inject nodes one at a time:
placementNode.appendChild(frag.firstChild);
The document.createRange().createContextualFragment
function is an awesome, sane method for converting strings to DOM nodes within JavaScript. Ditch your old shims and switch to this performant, simple API!
The Original Post: DOMParser
Today we have a standard way for converting string to DOM with JavaScript:
DOMParser
.The JavaScript
All you need to do is create a
DOMParser
instance and use itsparseFromString
method:let doc = new DOMParser().parseFromString('<div><b>Hello!</b></div>', 'text/html');Returned is a
document
containing the nodes generated from your string. With saiddocument
you can use standard node traversal methods to retrieve the nodes we specified in our string:let doc = new DOMParser().parseFromString('<div><b>Hello!</b></div>', 'text/html'); let div = doc.body.firstChild; let divs = doc.body.querySelectorAll('div');You don't need a single wrapping element like JSX components -- you can have sibling elements:
let doc = new DOMParser().parseFromString('<div>1</div><div>2</div>', 'text/html'); let firstDiv = doc.body.firstChild; let secondDiv = firstDiv.nextSibling;Here's a simple wrapping function for
DOMParser
to retrieve the nodes:let getNodes = str => new DOMParser().parseFromString(str, 'text/html').body.childNodes; let nodes = getNodes('<div>1</div><div>2</div>'); // [div, div]The
DOMParser
object is an awesome, sane method for converting strings to DOM nodes within JavaScript. Ditch your old shims and switch to this efficient, simple API!
Can’t you do this like 100 times faster and easier with jQuery using
with jQuery <- exactly the problem with your argument. not to mention pretty big difference in performance.
Thats the whole point of this article — present a native, free of libraries way of doing it.
Some old technique:
Thank you for sharing this technique! Definitely learn something new.
https://jsperf.com/domparser-vs-jquery
and
https://jsperf.com/html-parsing-performance
Show DOMParser is the fastest of all methods
I tried running those performance tests and DOMParser came out the slowest for both. Am I missing something?
Link to test results: http://imgur.com/a/scy0Q
How do I get the HTML back out (for example, what if the html that went in has a style-sheet)? Using
doc.body.innerHTML
doesn’t have that or any header or other information in it… I want all the marked-up text of the whole document…I was able to get all of the html by wrapping the string with body tags. Sort of a kludge, but it works for what I need…
createContextualFragment()
only works if the fragment is a valid child of theelement.
The following, for example, *doesn’t* work:
I’m getting weird results when trying to append a fragment to a table – the fragment is appended as text.
https://codepen.io/andfinally/pen/vWqwxB
Whoops, I’ve just discovered why my table row example didn’t work. The HTML string has to be valid if inserted into a document – so if you have a you must have the tag. The parsing process acts as though you’re creating a new HTML document, which of course you are.
Mr. Walsh, your post safeguarded my sanity this evening and kept me from dashing my poor MacBook against the pavement. Thank you! =)
Was sad to see Scriptaculous missing from your list of early libraries.
Today I discovered that in MS Edge, if you try to use querySelector on the fragment, to addEventListeners or things of the like, you get an error message saying that “object does not support querySelector”. Spent a good chunk of the day trying to work around it until I came across your older method, which seems to work just fine.
I expect this won’t be an issue once “Chredge” is released.
https://gist.github.com/krivaten/90558c7786a97826a741dfb8533f532c
You’re actually getting a Text node when you use firstChild, this depends on the string you pass on to your createFragment function. Use firstElementChild instead.
Thank you for the response!
It should be noted that both old-style
innerHTML
andcreateContextualFragment
are usually susceptible to security issues because event handler attributes will be executed.The exception to innerHTML if the element is a
template
element.DOMParser is also safe.
Theoretically I think it should be possible to use
createContextualFragment
safely if you get the “context node” to be a template, but I couldn’t figure out how to do it (the API is very confusing)ack, the sanitizer ate my html string:
createContextualFragment
may be safely used (Chrome, Firefox 4+, MSIE/EdgeHTML 11+, Opera 12.1+, and Safari 9+) with the following approachObviously, you’d want to sanitize the
docFrag
before further use.