Reputation: 6531
I want to insert some javascript code,
That should be run before other javascript codes in the <body>
run.
As they manipulate html in <body>
, that are inside <body>
.
Normally i would put this javascript in a <script>
tag right after the opening <body>
tag. But I am not writing the html directly. Its generated for me by a program. (react-storybook). Its API allows to inject html inside <head>
but not <body>
.
<head>
<script></script> <-- I can inject a script tag here
</head>
<body>
<script></script> <-- I can't directly add this script tag but I need one here
<script>other js</script>
</body>
I tried putting my js in a document
load
event handler, but they run after body is completely loaded so other js has already run.
I tried putting my js directly in the head then my js can't use appendChild
on body, because at that point document.body
is null.
Is there a way to insert a script tag satisfying both above requirements with accessing only the <head>
?
Upvotes: 0
Views: 2493
Reputation: 23830
I tried putting my js directly in the head then my js can't use
appendChild
on body, because at that pointdocument.body
is null.
MutationObserver to the rescue!
You can simply wait for the <body>
tag to be parsed:
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<script>
new MutationObserver(function(records, self)
{
for(var i = 0; i < records.length; ++i)
{
for(var j = 0; j < records[i].addedNodes.length; ++j)
{
if(records[i].addedNodes[j].nodeName == 'BODY')
{
self.disconnect();
console.log('herp');
/*
At this point, the body exists, but nothing inside it has been parsed yet.
document.body might be available, but to be safe, you can use:
var body = records[i].addedNodes[j];
*/
}
}
}
}).observe(document.documentElement,
{
childList: true,
});
</script>
</head>
<body>
<script>console.log('derp');</script>
</body>
</html>
Save this to an HTML file, open it in your browser, and you should see this in the console (indicating that the "herp" part runs before the "derp" one (note: Firefox seems to discard message order if the console is opened after the page loads, but the "herp" part is actually still running before the "derp" one)):
herp
derp
(Note: The above code won't work as a stack snippet, because everything is placed in the <body>
tag there.)
Now just to be safe, I'd add a check to see if document.body
is already set, and only set up the MutationObserver if that isn't the case:
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<script>
function onBodyLoaded(body)
{
console.log('herp');
/* Do whatever you want with "body" here. */
}
if(document.body)
{
onBodyLoaded(document.body)
}
else
{
new MutationObserver(function(records, self)
{
for(var i = 0; i < records.length; ++i)
{
for(var j = 0; j < records[i].addedNodes.length; ++j)
{
if(records[i].addedNodes[j].nodeName == 'BODY')
{
self.disconnect();
onBodyLoaded(records[i].addedNodes[j]);
}
}
}
}).observe(document.documentElement,
{
childList: true,
});
}
</script>
</head>
<body>
<script>console.log('derp');</script>
</body>
</html>
This way you might not have to add a <script>
tag to your body at all, but just place the code you want to run there inside the onBodyLoaded
function.
If you do need to add a script tag though, you can do so with:
function onBodyLoaded(body)
{
body.appendChild(document.createElement('script')).src = 'https://example.com/my.js';
}
or
function onBodyLoaded(body)
{
body.appendChild(document.createElement('script')).innerHTML = 'document.write("hi there")';
}
Note that IE 10 and earlier don't support MutationObserver
. IE 11 and any other browser from this decade should work though.
Upvotes: 2
Reputation: 1074038
I don't see any way to do this without probably breaking the tool you're trying to work within and doing some fairly nasty things. I suspect there's a better way to solve your underlying problem.
I tried putting my js directly in the head then my js can't use
appendChild
onbody
, because at that pointdocument.body
is null...
It's nasty and probably a bad idea, but you could force the body to be started:
document.write("<body>");
At that point, the parser will create the body
element. You can then use appendChild
(or just continue using the evil document.write
). When the body is started again later in the normal course fo things, that second opening tag will be ignored.
It's not a good idea. react-storybook is open source. If there isn't a way to achieve your actual goal with it already, I suggest modifying it so you can rather than doing something like the above.
Here's an example, tested and working in Chrome, Firefox, IE11, IE8, and iOS Safari.
(Live copy on JSBin):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Example</title>
<script>
(function() {
var d = document.createElement("div");
d.innerHTML =
"This is the element appended to the body element " +
"that we forced to be created using the evil " +
"<code>document.write</code>";
document.write("<body>");
document.body.appendChild(d);
})();
</script>
</head>
<body>
<div>
This is the first element inside the body element in the markup.
</div>
</body>
</html>
Upvotes: 4