Reputation: 3520
I have a JS script that I am using to append a string to the head
tag, it works, but it doesn't execute what was appended.
HTML
file:
<html>
<head>
...
<script src="test.js"></script>
...
</head>
<body>
...
</body>
</html>
File: test.js
var str =
`
<script ... ></script>
<link...>
other stuff...
`
var html = document.getElementsByTagName('head')[0].innerHTML;
document.getElementsByTagName('head')[0].innerHTML=str+html;
With this, I understand that it would go into recursion if all of head
was executed again just to execute the appended string (unless there is a way to execute only the appended string), but I have a way to deal with this. First, I need it to actually execute.
I have seen other similar questions, but they ask about appending a single script
tag or something similar, I'm looking to add a whole chunk of HTML
and have the browser execute it.
I'm looking for a pure JavaScript solution.
Note that str
contains things like JQuery
and Bootstrap
and therefore the rest of the document heavily depends on this append & execution happening first.
Upvotes: 2
Views: 2728
Reputation: 1074178
Inserting scripts via innerHTML
, insertAdjacentHTML()
, etc. doesn't run them. To do that, we have to be a bit more creative; see inline comments:
setTimeout(() => {
const str =
`
<script>console.log("Hello from script")<\/script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
`;
// Create an element outside the document to parse the string with
const head = document.createElement("head");
// Parse the string
head.innerHTML = str;
// Copy those nodes to the real `head`, duplicating script elements so
// they get processed
let node = head.firstChild;
while (node) {
const next = node.nextSibling;
if (node.tagName === "SCRIPT") {
// Just appending this element wouldn't run it, we have to make a fresh copy
const newNode = document.createElement("script");
if (node.src) {
newNode.src = node.src;
}
while (node.firstChild) {
// Note we have to clone these nodes
newNode.appendChild(node.firstChild.cloneNode(true));
node.removeChild(node.firstChild);
}
node = newNode;
}
document.head.appendChild(node);
node = next;
}
}, 800);
The appearance of <input type="button" value="this button"> changes when Bootstrap's CSS is loaded
The Bootstrap CSS is just there to demonstrate that the link
is working. The timeout is just so you can see the link
take effect.
Also note the backslash in the <\/script>
tag in the string. We only need that because that string is within a <script>...</script>
tag. We wouldn't need it if this were in a .js
file.
Tested in current Chrome and Firefox. A version using a plain string instead of a template literal works in IE11, too.
We could do much the same with DOMParser
instead of a freestanding head
element. Instead of:
// Create an element outside the document to parse the string with
const head = document.createElement("head");
// Parse the string
head.innerHTML = str;
you'd use
// Parse the HTML and get the resulting head element.
// You could probably get away without the wrapper markup, but let's
// include it for completeness.
const parser = new DOMParser();
const doc = parser.parseFromString("<!doctype html><html><head>" + str + "</head></html>", "text/html");
const head = doc.head;
The rest is the same:
setTimeout(() => {
const str =
`
<script>console.log("Hello from script")<\/script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
`;
// Parse the HTML and get the resulting head element.
// You could probably get away without the wrapper markup, but let's
// include it for completeness.
const parser = new DOMParser();
const doc = parser.parseFromString("<!doctype html><html><head>" + str + "</head></html>", "text/html");
const head = doc.head;
// Copy those nodes to the real `head`, duplicating script elements so
// they get processed
let node = head.firstChild;
while (node) {
const next = node.nextSibling;
if (node.tagName === "SCRIPT") {
// Just appending this element wouldn't run it, we have to make a fresh copy
const newNode = document.createElement("script");
if (node.src) {
newNode.src = node.src;
}
while (node.firstChild) {
// Note we have to clone these nodes
newNode.appendChild(node.firstChild.cloneNode(true));
node.removeChild(node.firstChild);
}
node = newNode;
}
document.head.appendChild(node);
node = next;
}
}, 800);
The appearance of <input type="button" value="this button"> changes when Bootstrap's CSS is loaded
Upvotes: 3