Creating a new DOM element from an HTML string using built-in DOM methods or Prototype

757,156

Solution 1

Note: most current browsers support HTML <template> elements, which provide a more reliable way of turning creating elements from strings. See Mark Amery's answer below for details.

For older browsers, and node/jsdom: (which doesn't yet support <template> elements at the time of writing), use the following method. It's the same thing the libraries use to do to get DOM elements from an HTML string (with some extra work for IE to work around bugs with its implementation of innerHTML):

function createElementFromHTML(htmlString) {
  var div = document.createElement('div');
  div.innerHTML = htmlString.trim();

  // Change this to div.childNodes to support multiple top-level nodes.
  return div.firstChild;
}

Note that unlike HTML templates this won't work for some elements that cannot legally be children of a <div>, such as <td>s.

If you're already using a library, I would recommend you stick to the library-approved method of creating elements from HTML strings:

Solution 2

HTML 5 introduced the <template> element which can be used for this purpose (as now described in the WhatWG spec and MDN docs).

A <template> element is used to declare fragments of HTML that can be utilized in scripts. The element is represented in the DOM as a HTMLTemplateElement which has a .content property of DocumentFragment type, to provide access to the template's contents. This means that you can convert an HTML string to DOM elements by setting the innerHTML of a <template> element, then reaching into the template's .content property.

Examples:

/**
 * @param {String} HTML representing a single element
 * @return {Element}
 */
function htmlToElement(html) {
    var template = document.createElement('template');
    html = html.trim(); // Never return a text node of whitespace as the result
    template.innerHTML = html;
    return template.content.firstChild;
}

var td = htmlToElement('<td>foo</td>'),
    div = htmlToElement('<div><span>nested</span> <span>stuff</span></div>');

/**
 * @param {String} HTML representing any number of sibling elements
 * @return {NodeList} 
 */
function htmlToElements(html) {
    var template = document.createElement('template');
    template.innerHTML = html;
    return template.content.childNodes;
}

var rows = htmlToElements('<tr><td>foo</td></tr><tr><td>bar</td></tr>');

Note that similar approaches that use a different container element such as a div don't quite work. HTML has restrictions on what element types are allowed to exist inside which other element types; for instance, you can't put a td as a direct child of a div. This causes these elements to vanish if you try to set the innerHTML of a div to contain them. Since <template>s have no such restrictions on their content, this shortcoming doesn't apply when using a template.

However, template is not supported in some old browsers. As of April 2021, Can I use... estimates 96% of users globally are using a browser that supports templates. In particular, no version of Internet Explorer supports them; Microsoft did not implement template support until the release of Edge.

If you're lucky enough to be writing code that's only targeted at users on modern browsers, go ahead and use them right now. Otherwise, you may have to wait a while for users to catch up.

Solution 3

Use insertAdjacentHTML(). It works with all current browsers, even with IE11.

var mylist = document.getElementById('mylist');
mylist.insertAdjacentHTML('beforeend', '<li>third</li>');
<ul id="mylist">
 <li>first</li>
 <li>second</li>
</ul>

Solution 4

No need for any tweak, you got a native API:

const toNodes = html =>
    new DOMParser().parseFromString(html, 'text/html').body.childNodes[0]

Solution 5

For certain html fragments like <td>test</td>, div.innerHTML, DOMParser.parseFromString and range.createContextualFragment (without the right context) solutions mentioned in other answers here, won't create the <td> element.

jQuery.parseHTML() handles them properly (I extracted jQuery 2's parseHTML function into an independent function that can be used in non-jquery codebases).

If you are only supporting Edge 13+, it is simpler to just use the HTML5 template tag:

function parseHTML(html) {
    var t = document.createElement('template');
    t.innerHTML = html;
    return t.content;
}

var documentFragment = parseHTML('<td>Test</td>');
Share:
757,156
Omer Bokhari
Author by

Omer Bokhari

Updated on July 08, 2022

Comments

  • Omer Bokhari
    Omer Bokhari almost 2 years

    I have an HTML string representing an element: '<li>text</li>'. I'd like to append it to an element in the DOM (a ul in my case). How can I do this with Prototype or with DOM methods?

    (I know i could do this easily in jQuery, but unfortunately we're not using jQuery.)

  • Tim Ferrell
    Tim Ferrell about 9 years
    Does it feel like jQuery because it is jQuery?
  • kumarharsh
    kumarharsh almost 9 years
    That last line is just hilarious!
  • Mark Amery
    Mark Amery over 8 years
    Note that this has similar drawbacks to setting the innerHTML of a div; certain elements, like tds, will be ignored and not appear in the resulting fragment.
  • Mark Amery
    Mark Amery over 8 years
    Fails if the element to be created is itself a table.
  • Koen.
    Koen. over 8 years
    @MarkAmery the difference here is that he uses a fragment to allow multiple root element be appended in the DOM, which is an added benefit. If only William did mention that is his answer...
  • shanef22
    shanef22 about 8 years
    This is an effective approach and very clean; however, (at least in Chrome 50) this breaks script tag handling. In other words, using this method to create a script tag and then appending it to the document (body or head) doesn't result in the tag being evaluated and hence prevents the script from being executed. (This may be by design if evaluation happens on attach; I couldn't say for sure.)
  • Roger Gajraj
    Roger Gajraj about 8 years
    LOVELY! you can even query for elements by doing something like: template.content.querySelector("img");
  • akauppi
    akauppi almost 8 years
    "There are reports that desktop Safari did at one point support Range.createContextualFragment(), but it is not supported at least in Safari 9.0 and 9.1." (MDN link in the answer)
  • Dai
    Dai over 7 years
    I don't see innerHTML defined as a property of DOM fragment objects (from <template>.content) on MDN: developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
  • Mark Amery
    Mark Amery almost 7 years
    @Dai indeed not, since there is no such property. You've misread my answer; I set the innerHTML of the template itself (which is an Element), not its .content (which is a DocumentFragment).
  • Mark Amery
    Mark Amery over 6 years
    This suffers from the same major drawback as the accepted answer - it will mangle HTML like <td>text</td>. This is because DOMParser is trying to parse a full HTML document, and not all elements are valid as root elements of a document.
  • Mark Amery
    Mark Amery over 6 years
    Even though this creates a DocumentFragment object, it still - to my great surprise - suffers from the same defect as the accepted answer: if you do document.createRange().createContextualFragment('<td>bla</td‌​>'), you get a fragment that just contains the text 'bla' without the <td> element. Or at least, that's what I observe in Chrome 63; I haven't delved into the spec to figure out whether it's the correct behavior or not.
  • Mark Amery
    Mark Amery over 6 years
    -1 because this is a duplicate of an earlier answer that explicitly pointed out the drawback mentioned in my comment above.
  • Mark Amery
    Mark Amery over 6 years
    Note that - as pointed out elsewhere on this page - this won't work for tds.
  • Mark Amery
    Mark Amery over 6 years
    -1; this is the same technique as proposed in the accepted answer and has the same drawbacks - notably, not working for tds.
  • Shivanshu Goyal
    Shivanshu Goyal over 6 years
    How to set innerHTML to the created div using jQuery without using this unsafe innerHTML assignment (div.innerHTML = "some value")
  • Semmel
    Semmel over 6 years
    The function name createElementFromHTML is misleading since div.firstChild returns a Node which is not a HTMLElement e.g. cannot node.setAttribute. To create an Element return div.firstElementChild from the function instead.
  • Jonas Äppelgran
    Jonas Äppelgran about 6 years
    First, insertAdjacentHTML works with all browsers since IE 4.0.
  • Jonas Äppelgran
    Jonas Äppelgran about 6 years
    Second, it's great! A big plus compared to innerHTML += ... is that references to previous elements is still intact using this method.
  • Jonas Äppelgran
    Jonas Äppelgran about 6 years
    Third, possible values for the first argument is: beforebegin, afterbegin, beforeend, afterend. See the MDN article.
  • e-motiv
    e-motiv about 6 years
    There is one problem with it. If you have a tag in there with spaces inside(!) at start or end, they will be removed! Don't get me wrong, this is not about the spaces removed by html.trim but by inner parsing of innerHTML setting. In my case it removes important spaces being part of a textNode. :-(
  • Ivan
    Ivan about 6 years
    Thank you, the <div> wrapping the HTML I added with .innerHTML was annoying me. I never thought of using .firstChild.
  • ed1nh0
    ed1nh0 about 6 years
    I'm trying to parse a SVG inside the created div and and the output is [object SVGSVGElement] while the console log gives me the correct DOM element. What am I doing wrong?
  • ethan.roday
    ethan.roday about 6 years
    Note, by the way, that this does not work for script tags. Script tags added to the DOM using innerHTML will not be executed. For those cases, better to go with var script = document.createElement('script'), and then use script.src or script.textContent depending on whether the script is inline. Then, add the script with document.body.appendChild(script).
  • Chris Panayotoff
    Chris Panayotoff over 5 years
    I would use firstElementChild instead of firstChild ( see w3schools.com/jsref/prop_element_firstelementchild.asp ) , because if there is space in front or end of template, the firstChild would return empty textNode
  • Dwhitz
    Dwhitz about 5 years
    Thank you for this code snippet, which might provide some limited, immediate help. A proper explanation would greatly improve its long-term value by showing why this is a good solution to the problem and would make it more useful to future readers with other, similar questions. Please edit your answer to add some explanation, including the assumptions you’ve made.
  • Mojimi
    Mojimi about 5 years
    Too bad it doesn't return the inserted HTML as an element
  • Tintin81
    Tintin81 almost 5 years
    What is wwww?
  • DylanReile
    DylanReile over 4 years
    This has fantastic browser support and is entirely straightforward. There's no odd creating a dummy element just to get its child Node.
  • Jeya Suriya Muthumari
    Jeya Suriya Muthumari over 4 years
    Is this jQuery? or JS?
  • Pablo Borowicz
    Pablo Borowicz over 4 years
    @JuanHurtado this is using PrototypeJs as OP requested almost 11 years ago ;)
  • Manngo
    Manngo almost 4 years
    Don’t browsers which support template literals also support the <template> element? That would be much more flexible.
  • NVRM
    NVRM almost 4 years
    Right, template is now largely supported. But for fresh html elems (coming from dynamic datas), each time the DOM is modified, the whole DOM is recalculated, with a lot of elements this can hang the browser responsivness. Thus it is more appropriate to build the DOM beforehand, just as using document. createElement. About that it's great to test our apps on slow and old machines or smartphones, the difference is more noticable. Many ways to achieve the same thing, that one is just a hack, appears to be efficient with a lot of data, allow to simply build a page from pure js only.
  • zardilior
    zardilior over 3 years
    Great answer by far
  • mLstudent33
    mLstudent33 over 3 years
    What does div.firstChild look like for the example in the question?
  • vdegenne
    vdegenne over 3 years
    @MarkAmery it doesn't matter. this answer and syntax is shaped as what we are used to see today. It's never a bad duplicate if it shows a more modern way to write something.
  • bot19
    bot19 over 3 years
    amazing!! Then one can easily use node.appendChild(e)
  • Muhammad Ali
    Muhammad Ali over 3 years
    That moment when you realize that the native solution is the simplest one-->priceless ^_^
  • Jonathan
    Jonathan over 3 years
    Can't this be modified to work with fragments by wrapping in an HTML root element and plucking the firstChild?
  • asynts
    asynts about 3 years
    template.content.firstElementChild can be used instead of stripping the html.
  • Juan Hurtado
    Juan Hurtado about 3 years
    @PabloBorowicz Oops you're correct. My mind automatically went to the "built in dom methods" and I missed the prototype part.
  • Arturokin12
    Arturokin12 almost 3 years
    This answer should be the one, it worked perfect!
  • walbury
    walbury almost 3 years
    For the record I gave this code a go - IT IS BLOODY SUPERB. I think is going to become my go-to way of dynamically creating html from json - huge thanks mate!!
  • Christoffer Bubach
    Christoffer Bubach over 2 years
    Parsing “<table><tr>” + tdRowArr.join(“”) + “</tr></table” and then moving the TD nodes shouldn’t be very hard, similar to how he extracts the actual nodes from the body tag above
  • Christoffer Bubach
    Christoffer Bubach over 2 years
    Just me that finds it a bit funny to init a domparser, a domdocument and so on.. just to generate a single node where most options are just set on a createElement(iframe)..? Not the most compelling use case.. ;)
  • vatavale
    vatavale about 2 years
    How to combine this two functions to work with any string type (single element / number of siblings)?
  • Mark Amery
    Mark Amery about 2 years
    @vatavale I guess you could use htmlToElements but change the final line to return template.content.childNodes.length == 1 ? template.content.firstChild : template.content.childNodes;, but I wouldn't recommend it; it seems weird to me to have a function that usually returns a collection but sometimes returns a single item.
  • Mecanik
    Mecanik about 2 years
    Kudos for this answer, I have been testing and searching like crazy to replace the jQuery function.
  • Max
    Max about 2 years
    Unfortunately in Safari (iOS and Desktop v15.5) this stops videos fromautoplaying, and makes a.origin for any relative URLs the string "null" (!!!). The createElement('div') approach doesn't have those issues.