Html string to dom element

JavaScript Inject/Add or Append Raw Plain HTML Strings into DOM Nodes

So you want to either inject (add) or append raw HTML strings into the DOM directly or convert the HTML string into DOM nodes and then flush them ? Let’s see all the different ways to do them. We’ll assume that the following HTML string needs to be parsed and and added to the following node:

// HTML String const htmlString = '

The Sky is Blue!

'; // DOM Element where the injection or append/prepend will happen const root = document.querySelector('div#root');

Element.innerHTML

This is the most widely known approach I guess. The innerHTML property on a DOM element allows getting and setting the entire HTML or XML markup (string) contained within. Setting it multiple times overrides the previous value and removes all the attached event listeners on the children.

root.innerHTML = htmlString;

Element.insertAdjacentHTML

The el.insertAdjacentHTML(pos, text) method on a DOM element parses the input text as HTML or XML nodes and inserts the resulting nodes into the element at the specified pos that can have the following values:

  • beforebegin – Before the element, only valid if the element has a parent element.
  • afterend – After the element, only valid if the element has a parent element.
  • afterbegin – Inside the element, before its first child.
  • beforeend – Inside the element, after its last child.
root.insertAdjacentHTML('beforeend', htmlString);

insertAdjacentHTML is a slightly faster alternative to innerHTML with the benefit of inserting/appending/prepending the HTML/XML markup instead of replacing/overriding the entire contents of the DOM element.

DOMParser

With the DOMParser.parseFromString(string, mimeType) method we can convert a raw HTML string into DOM nodes first and then append or prepend the nodes into the target DOM element. Passing text/html as the mimeType invokes the HTML parser that’ll return an HTMLDocument (with html and body tags). Let’s see the usage:

const xmlDoc = (new DOMParser()).parseFromString(htmlString, 'text/html') // 1. The parsed nodes are in xmlDoc.body // 2. The . (spread) operator helps pass all children as arguments root.append(. xmlDoc.body.children);

Feel free to use methods other than Element.append() depending upon your needs:

Range

Using the Range API, we can create a DocumentFragment out of an HTML (or XML) string containing DOM nodes for all the tags and texts passed into the string.

const range = document.createRange(); const fragment = range.createContextualFragment(htmlString); root.append(fragment);

Again, instead of append() you could use the other options/methods listed in the DOMParser section above.

Note: Except this method ( createContextualFragment() ), none of the other approaches in this article will execute any script tags that are passed in the HTML string. Although with the other methods, there are still ways to execute JavaScript code passed within the string (more info here).

Читайте также:  Apache server for python

Template or DocumentFragment

We can create a template element in memory and set its innerHTML with the HTML string to create DOM nodes inside it. Then we can use the template.content which is a DocumentFragment to append or prepend into any element in our DOM.

const template = document.createElement('template'); template.innerHTML = htmlString; root.append(template.content);

TBH, we could do this by creating any other DOM element in memory and settings its innerHTML , then pass the el.children to the target node (like we did in the DOMParser section above). Also this approach doesn’t seem much different from the innerHTML section we saw above. But there’s one slight difference which could be important for some people – no context content restriction. Let me example.

const div = document.createElement('div'); div.innerHTML = 'Hello World';

The td will get stripped and only the text will get stored in the div . It doesn’t matter whether you do it in memory ( createElement ) or on a live DOM node. This is because a td doesn’t belong to a div contextually. You’ll face this contextual restriction with all the methods above. But if you want to get around it (for whatever reason), then template will help you:

const t = document.createElement('template'); const d = document.createElement('div'); t.innerHTML = 'Hello World'; d.append(t.content); // 
Hello World

Источник

Create a DOM node from an HTML string

There are many different ways to convert a string of HTML to a DOM node/element. Here’s a comparison of common methods, including caveats and things to consider.

Methods

We’re asuming that the used html contains a string with valid HTML.

innerHTML

const placeholder = document.createElement("div"); placeholder.innerHTML = html; const node = placeholder.firstElementChild; 
  • Safety: no script execution (see caveats)
  • Allowed nodes: only valid nodes (see HTML restrictions)
  • Support: 👍👍👍
  • MDN: Element.innerHTML

insertAdjacentHTML

const placeholder = document.createElement("div"); placeholder.insertAdjacentHTML("afterbegin", html); const node = placeholder.firstElementChild; 
  • Safety: no script execution (see caveats)
  • Allowed nodes: only valid nodes (see HTML restrictions)
  • Support: 👍👍👍 (but below IE 10 there’s no support for table -related nodes)
  • MDN: Element.insertAdjacentHTML()

DOMParser

const node = new DOMParser().parseFromString(html, "text/html").body  .firstElementChild; 
  • Safety: no script execution (see caveats)
  • Allowed nodes: only valid nodes (see HTML restrictions)
  • Support: 👍👍 (IE 10+, Safari 9.1+)
  • MDN: DOMParser

Range

const node = document.createRange().createContextualFragment(html); 
  • Safety: executes scripts, sanitize first 🚨
  • Allowed nodes: you can set context (see HTML restrictions)
  • Support: 👍 (IE 10+, Safari 9+)
  • MDN: Range.createContextualFragment()

Note: in most examples we’re using firstElementChild, since this will prevent you having to trim any whitespace (as opposed to firstChild ). Note that this is not supported in IE and Safari when a DocumentFragment is returned. In our case that’s not a problem since the fragment itself is the node we want.

Caveats

There are a few things to consider when choosing a method. Will it handle user generated content? Do we need to support table -related nodes?

HTML restrictions

There are a few restrictions in HTML which will prevent adding certain types of nodes to a node like div , think of thead , tbody , tr and td .

Читайте также:  And operator string python

Most methods will return null when you try to create one of these nodes:

const placeholder = document.createElement("div"); placeholder.innerHTML = `Foo`; const node = placeholder.firstElementChild; //=> null 

createContextualFragment

With createContextualFragment you can circumvent this by setting the context, as Jake Archibald pointed out:

const table = document.createElement(`table`); const tbody = document.createElement(`tbody`); table.appendChild(tbody);  const range = document.createRange(); range.selectNodeContents(tbody); const node = range.createContextualFragment(`Foo`); //=> tr 

template

Another way is by using a template tag as the placeholder, which doesn’t have any content restrictions:

const template = document.createElement("template"); template.innerHTML = `Foo`; const node = template.content.firstElementChild; //=> tr 

Note that template is not supported in any IE version.

Alternatives

You could also opt for a solution using DocumentFragment, or make the temporary placeholder you’re appending to a table . The latter will return a tbody as well.

Script execution

All methods except createContextualFragment will prevent ‘regular script execution’:

const placeholder = document.createElement("div"); placeholder.innerHTML = `
`
;
const node = placeholder.firstElementChild; document.body.appendChild(node); //=> will not show an alert

There are, however, ways to execute scripts without script tags (see MDN):

const placeholder = document.createElement("div"); placeholder.innerHTML = ``; const node = placeholder.firstElementChild;  document.body.appendChild(node); //=> will show an alert (!) 

Note that the above won’t throw an alert in Firefox, but it does so in Chrome.

Sanitizing

You could strip all offending attributes of child nodes before appending the actual node to the DOM, although there are probably other issues that you should be aware of.

[. placeholder.querySelectorAll("*")].forEach((node) =>  node.removeAttribute("onerror") ); 

Key takeaway: if you’re parsing user-generated content, make sure to sanitize properly.

Performance

Unless you’re adding a huge amount of nodes to your page, performance shouldn’t be a big problem with any of these methods. Here are the results of multiple runs of a jsPerf benchmark, which ran in the latest versions of Chrome and Firefox:

  1. Range.createContextualFragment() — winner (fastest in Firefox)
  2. Element.insertAdjacentHTML() — winner
  3. Element.innerHTML — winner
  4. DOMParser.parseFromString() — 90% slower

Note that results differ from test to test. However, the clear ’loser’ appears to be DOMParser .

Further improvements

When adding multiple nodes at once, it is recommended to use a DocumentFragment as placeholder and append all nodes at once:

const htmlToElement = (html) => (  /* . */ >); const fragment = document.createDocumentFragment();  items.forEach((item) =>   const node = htmlToElement(` $item.name> `);  fragment.appendChild(node); >);  document.body.appendChild(fragment); 

This will cause only one reflow:

“Since the document fragment is in memory and not part of the main DOM tree, appending children to it does not cause page reflow (computation of element’s position and geometry).”

MDN on DocumentFragment

Conclusion

There are different ways to get the desired outcome. Maybe we’ve missed some. There’s no ideal way or ‘best solution’, so choose what’s working for you.

For us: we’ve been using the innerHTML option in our own @grrr/utils library (see the htmlToElement function). This has been largely due to its browser support. We’d probably move to a template version in the future when IE-support isn’t an issue anymore.

Updated on June 14, 2019: Increased IE support for Range after a conversation on Twitter.

We welcome your feedback

We enjoy compliments, but you can totally shout at us for doing it wrong on our Twitter account 👋

Interested in our work? Visit our regular website or check us out on GitHub and Twitter.

Источник

Convert HTML string to DOM element

I’m working on a chrome extension which accepts user text, work with it and output the result in a div. I used jQuery in the beginning, for several tasks, but later I decided to remove it and deal with pure javascript/DOM manipulations. The output div was filled by using innerHTML property and everything was ok. But, at some point, I sent few buttons to the user and he has to interact with them. However, once I update the div’s content the event listeners are detached and the buttons became non-functional.

Here is a small jsfiddle, which illustrates the problem.

I.e. once you add a new markup, an event is attached to the link. It does its job till a new HTML is inserted. Because the whole content of the output div is replaced only the latest button has a valid listener.

index = 0; addMarkup = function() < var output = document.getElementById("js-output"); var htmlButton = 'click here please'; output.innerHTML = htmlButton + output.innerHTML; document.getElementById("button" + (index++)).addEventListener("click", function() < alert("button clicked"); >); > 

So, the solution was to avoid the usage of innerHTML and insert the markup with appendChild or insertBefore methods. The problem is that those functions accept DOM elements not string.

Try #1 (document.createElement)

Something like this may work:

var link = document.createElement("A"); link.setAttribute("href", "#"); link.addEventListener("click", function() < alert("it works"); >); output.appendChild(link); 

But of course it didn’t work in my case, because I had complex markup which involves several nested elements. It wasn’t possible to create everything with document.createElement.

Try #2 (DOMParser)

There is an object DOMParser, which seems perfect for the situation:

var markup = 'text here'; var parser = new DOMParser() var el = parser.parseFromString(markup, "text/xml"); 

However it works a little bit strange. The created el object is not exactly a DOM element. I had to use el.firstChild or el.childNodes[0] to get the actual thing. The styling of the elements inside didn’t work and sometimes some of the them weren’t rendered at all.

Try #3 (little dirty hack)

As normally happen I ended with a little dirty hack.

var str2DOMElement = function(html) < var frame = document.createElement('iframe'); frame.style.display = 'none'; document.body.appendChild(frame); frame.contentDocument.open(); frame.contentDocument.write(html); frame.contentDocument.close(); var el = frame.contentDocument.body.firstChild; document.body.removeChild(frame); return el; >var markup = 'text here'; var el = str2DOMElement(markup); 

I.e. I created an iframe, adding the markup inside the iframe produced a valid DOM element. I got the needed element and at the end removed the iframe from the DOM.

If you have any other workarounds for the problem please feel free to comment below. Have in mind that I don’t want to use jQuery-liked libraries.

Just recently, I released my first online video course on advanced JavaScript called «Do you speak JavaScript?». Check it out here. v8.0.20 | Driven by Lumina CMS

Источник

Оцените статью