I have looked up and tested different approaches and will list them here, you can then decide what works best for you.
The Problem
But first of all, why does it not work?
I'm assuming you add the HTML string either with container.innerHTML = string or container.insertAdjacentHTML(string). The problem here, is that HTML inserted with innerHTML will not execute scripts.
As W3C describes (source):
Note: script elements inserted using innerHTML do not execute when they are inserted.
And this also goes for insertAdjacentHTML() as that function uses innerHTML.
But there are ways to execute javascript this way though, which is considered a cross-site scripting attack.
MDN writes (source):
However, there are ways to execute JavaScript without using elements, so there is still a security risk whenever you use innerHTML to set strings over which you have no control. For example:
const name = "<img src='x' onerror='alert(1)'>";
el.innerHTML = name; // shows the alert
So it is generally recommended to use textContent instead of innerHTML to prevent security risks, unless you 100% know the HTML is not malicious or can be sanitized beforehand.
Solutions
1. Appending DocumentFragments (source)
This is probably one of the best and straight forward approaches, since you can simply take the whole string, in contrast to other solutions, and insert it with 2 simple lines of code:
const fragment = document.createRange().createContextualFragment(string);
document.querySelector('.webPageCont').appendchild(fragment);
I'm not sure what the differences to innerHTML are, as they both utilize the HTMLparser. But I can assume that the one innerHTML uses has a limitation on parsing scripts. Also the fourth step of the fragment parsing algorithm that createContextualFragment() utilizes says:
- Append each node in new children to fragment (in tree order)
which might also play a role in this.
2. Seperately insert script element
This approach is very simple. Put the javascript into a seperate file (e.g. user.js) and manually add a script with the file as src. So it would look something like this:
// get the HTML string by fetch or other means and add to page
document.querySelector('.webPageCont').insertAdjacentHTML('beforeend', HTMLstring_without_script);
// create script element with javascript as source
let script_el = document.createElement('script');
script_el.src = "/user.js"; // adjust depending on file hierachy
// either append to the HEAD
document.querySelector('head').appendChild(script_el);
// or anywhere in the BODY
document.body.appendChild(script_el);
If interested, one can read more on dynamic script loading here.
3. Extract script tags from string
This approach, just like the one before, is pretty simple. When receiving the HTML string on the client-side, extract any script tags from the string and insert them as individual element:
while(/<script>((?:.*?\n*\s*)+?)<\/script>/.test(text)) {
let reg_match = text.match(/<script>((?:.*?\n*\s*)+?)<\/script>/);
let script = reg_match[1];
text = text.replace(reg_match[1], '');
let s = document.createElement('script');
s.textContent = script;
document.querySelector('head').appendChild(s);
}
document.querySelector('.container-content').insertAdjacentHTML('beforeend', text);
But, depending on how the javascript is written, you might not want to load the scripts before the HTML. Then you can just safe each string representing a script tag in an array and add them all after adding the HTML.
4. Script replacer (source)
You could say that this approach is the more advanced version of the one before. You define a method that recursively goes through all the children of a provided element and replaces all not-parsed scripts with functional ones.
The method is defined as:
function nodeScriptReplace(node) {
if ( nodeScriptIs(node) === true ) {
node.parentNode.replaceChild( nodeScriptClone(node) , node );
}
else {
var i = -1, children = node.childNodes;
while ( ++i < children.length ) {
nodeScriptReplace( children[i] );
}
}
return node;
}
function nodeScriptClone(node){
var script = document.createElement("script");
script.text = node.innerHTML;
var i = -1, attrs = node.attributes, attr;
while ( ++i < attrs.length ) {
script.setAttribute( (attr = attrs[i]).name, attr.value );
}
return script;
}
function nodeScriptIs(node) {
return node.tagName === 'SCRIPT';
}
Which then can be utlized as:
document.querySelector('.webPageCont').insertAdjacentHTML('beforeend', string);
nodeScriptReplace(document.querySelector('.webPageCont'));
user.ejs? With a request to the server with server-side rendering or on the client side?document.querySelector('.webPageCont').innerHTML = renderedUser?