Convert String to DOM Nodes

By  on  

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('
One
Two
'); console.log(frag); /* #document-fragment
One
Two
*/

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 its parseFromString 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 said document 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!

Recent Features

  • By
    Serving Fonts from CDN

    For maximum performance, we all know we must put our assets on CDN (another domain).  Along with those assets are custom web fonts.  Unfortunately custom web fonts via CDN (or any cross-domain font request) don't work in Firefox or Internet Explorer (correctly so, by spec) though...

  • By
    Regular Expressions for the Rest of Us

    Sooner or later you'll run across a regular expression. With their cryptic syntax, confusing documentation and massive learning curve, most developers settle for copying and pasting them from StackOverflow and hoping they work. But what if you could decode regular expressions and harness their power? In...

Incredible Demos

  • By
    Fullscreen API

    As we move toward more true web applications, our JavaScript APIs are doing their best to keep up.  One very simple but useful new JavaScript API is the Fullscreen API.  The Fullscreen API provides a programmatic way to request fullscreen display from the user, and exit...

  • By
    TextboxList for MooTools and jQuery by Guillermo Rauch

    I'll be honest with you: I still haven't figured out if I like my MooTools teammate Guillermo Rauch. He's got a lot stacked up against him. He's from Argentina so I get IM'ed about 10 times a day about how great Lionel...

Discussion

  1. parser

    Can’t you do this like 100 times faster and easier with jQuery using

    $('Hello!')
    • _xxx

      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.

  2. Hristo Chakarov

    Some old technique:

    var tmp = document.createElement( 'div' );
    tmp.innerHTML = 'Hello!';
    var elements = tmp.childNodes;
    
    • Bobby

      Thank you for sharing this technique! Definitely learn something new.

  3. Danny Engelman
    • David Baker

      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

  4. Owen

    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…

  5. Owen

    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…

  6. Stephen Thomas

    createContextualFragment() only works if the fragment is a valid child of the element.

    The following, for example, *doesn’t* work:

    document.createRange().createContextualFragment('table cell');
    
  7. Andfinally

    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

  8. Andfinally

    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.

  9. Mr. Walsh, your post safeguarded my sanity this evening and kept me from dashing my poor MacBook against the pavement. Thank you! =)

  10. Was sad to see Scriptaculous missing from your list of early libraries.

  11. Kris

    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

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!