Picking up after @dandavis' updated answer using the createContextualFragment(), a few people pointed out the small limitation (see here) that certain context-sensitive elements require their parent to be present otherwise this function will discard them (i.e. tr, td, thead, caption, etc).
Most realistic alternative solutions revolve around doing this through the <template> element in some fashion.
Given let htmlStr = '<td></td>'
<template>let temp = document.createElement("template");
temp.innerHTML = htmlStr;
// temp.content = htmlStr; // don't use for setting! has same bug as default `createContextualFragment`, but fine for retrieval
let frag = temp.content;
Interesting thing about this one was that if setting HTML string via temp.content directly, then it has the same bug as the default usage of createContextualFragment(), but setting via temp.innerHTML produces the expected results.
html-fragments packagelet frag = HtmlFragment(htmlStr);
This seems to be a library someone created for this exact problem (see author's comment here), likely due to the need to support browsers that don't directly support <template> I suspect. Seems to work fine, but a bit overkill for me (to pull in a separate package just for this that is).
createContextualFragment w/ wrapped templatelet tempFrag = document.createRange()
.createContextualFragment(`<template>${htmlStr}</template>`);
let frag = tempFrag.firstChild.content;
Kinda surprised no one found this one (so perhaps there are some limitations to it), but per my testing if you wrap the html string within a <template> tag, then use createContextualFragment(), then the browser seems to process the <td> element just fine. It's really no different that Option #1, and therefore still dependent on <template>, but I kinda prefer this option. However, if you're browser still doesn't support templates (IE), then neither option will really work reliably.
Here's a code snippet showing the issue and comparing the relevant options:
let htmlStrings = [
'<table></table>',
'<tr></tr>',
'<td></td>',
'<table><tr><td></td></tr></table>'
]
for (let htmlStr of htmlStrings) {
// default solution
let frag = document.createRange().createContextualFragment(htmlStr);
let defaultResults = fragmentToHTML(frag);
// Option 1: use <tempalte> directly
let tmp = document.createElement("template");
tmp.innerHTML = htmlStr;
// tmp.content = htmlStr; // don't use for setting! has same bug as default `createContextualFragment`, but fine for retrieval
frag = tmp.content;
let tempResults = fragmentToHTML(frag);
// Option 2: html-fragment package option
frag = HtmlFragment(htmlStr);
let hfResults = fragmentToHTML(frag);
// Option 3: wrapped <template> option
let tempFrag = document.createRange().createContextualFragment(`<template>${htmlStr}</template>`);
frag = tempFrag.firstChild.content
let wrappedResults = fragmentToHTML(frag);
console.log(htmlStr);
console.log("\t0-createContextualFragment():\t\t\t", defaultResults);
console.log("\t1-createElement('template'):\t\t\t", tempResults);
console.log("\t2-html-fragment:\t\t\t\t", hfResults);
console.log("\t3-createContextualFragment() w/ wrapped template:", wrappedResults);
}
function fragmentToHTML(frag) {
let div = document.createElement("div");
div.appendChild(frag);
return div.innerHTML;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/html-fragment.min.js"></script>
->