Adding a <script> node on top of <body> from javascript in <head> - javascript

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>?

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 on body, because at that point document.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>

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.
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.

Related

Object.defineProperty not changing property of element

I am trying to override the src property of all iframes in my application so their src property always gets set to "redirect.html" regardless of what value the HTML tag defines for it.
So far, I have come up with the following, but it doesn't seem to be applying to the DOM element:
<!doctype html>
<html>
<head>
<script>
var propertyDescriptorSrc = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, "src");
Object.defineProperty(HTMLIFrameElement.prototype, "src", {
get: function get_src() {
var val = propertyDescriptorSrc.get.call(this);
return "redirect.html";
},
set: function (val) {
alert('setting: ' + val);
propertyDescriptorSrc.set.call(this, val);
}
});
</script>
</head>
<body>
<iframe src="page.html"></iframe>
</body>
</html>
I expected the iframe element in the body to load redirect.html instead of page.html, since I overrided its "getter", but it still loaded page.html.
Is there a way to force this behavior where all iframes by default go to redirect.html instead of whatever is defined in their src attribute?
(This is just an experimental project)
Before it starts javascript, the DOM tree is already parsed and contains all iframes together with its src, according to the Critical Rendering Path.
The only way to do this is by using javascript to redefine the src attributes of the individual iframe node. For example, as below.
all iframes are set to redirect.html:
<!doctype html>
<html>
<head>
</head>
<body>
<iframe src="page.html"></iframe>
<iframe src="page2.html"></iframe>
<script>
( function(){
var lng = document.getElementsByTagName('iframe').length;
for (var i=0; i<lng;i++){
document.getElementsByTagName('iframe')[i].src="redirect.html";
}
})();
</script>
</body>
</html>
According to the suggestion, #Aaron Digulla gives a more readable form of function.
It seems that the search algorithms of the DOM tree are so efficient today that the argument is the readability of the record, not the efficiency.
(function(){
var frames = document.getElementsByTagName('iframe');
for (var i=0; i<frames.length;i++){
frames[i].src="redirect.html";
}
})();

Javascript can't find certain html elements

I'm putting together some offline technical documentation, and so have written some javascript for very basic syntax highlighting, and now for convenience I'm using it to replace < and > characters to save me time having to manually escape them.
The problem is this works great for a lot of html tags, except for <html>, <head> and <body> blocks.
The HTML within the <code> blocks are present in the DOM, but JS doesn't seem to find them.
I understand the HTML in question is not valid, but given it is present when I view the page source, shouldn't it still be found?
function stringReplace(str,from,to) {
if (str) return str.replace(from,to)
}
var htmlChars = [
["<", "<"],
[">", ">"]
];
function escapeHtmlChars(elementTagName, chars) {
var codeSections = document.getElementsByTagName(elementTagName);
for (var i = 0; i < codeSections.length; i++) {
var codeContent = codeSections[i].innerHTML;
for (var j = 0; j < chars.length; j++) {
codeContent = stringReplace(codeContent, chars[j][0], chars[j][1])
codeSections[i].innerHTML = codeContent;
}
}
}
window.addEventListener("load", function() {
console.log(
escapeHtmlChars("code", htmlChars)
)
});
<code class="code-snippet"><!doctype html>
<html>
<head>
<style type="text/css"></style>
</head>
<body>
</body>
</html>
</code>
I understand the HTML in question is not valid, but given it is present when I view the page source, shouldn't it still be found?
No, because your JavaScript isn't interacting with the source code.
The browser reads the source code. It constructs a DOM from it (which involves a lot of error recovery rules). You then read the innerHTML which generates HTML from the DOM.
The original data isn't available because the error recovery has already been applied.
now for convenience I'm using it to replace < and > characters to save me time having to manually escape them
I suggest generating your HTML from Markdown files to save on the effort there. Alternatively, set up a Find & Replace in selection macro in your editor.
Since these tags are stripped when rendered, you should use AJAX to get at the documents and convert them when you receive them.
Alternatively: Although XMP is obsolete this still works in my browser
var html = document.querySelector("xmp").textContent
console.log(html)
document.querySelector("code").innerHTML = html.replace(/<(\/)?(\w+)/g,"<br/><$1$2")
xmp { display: none }
code { white-space: pre; }
<xmp class="code-snippet">
<!doctype html>
<html>
<head>
<style type="text/css"></style>
</head>
<body>
</body>
</html>
</xmp>
<code></code>

Catch 22: Load Script if Element That Depends on Script Exists

My goal is to load javascript in the <head> only if a certain element exists in the <body>.
However, I have a problem: I am loading a Web Component, which is just a <script> hosted elsewhere and is pulled in as:
<script src="https://someurl.com/some-web-component.min.js"></script>
This web component file is huge, so I don't want to pull it in unless it is inserted into body by our Content Management System (CMS).
The constraints I am working under are:
• The <head> is shared between pages, so I need conditional logic
• I have no control over the <body> inserted by the CMS, which will potentially contain the <my-web-component> tag
• I NEED the script to load the web component, so I can't use jQuery's $(document).ready, at least I think I can't - an error will be thrown because the browser won't know the element exists
This plunker partially shows my problem, minus all the web component complexity:
https://plnkr.co/edit/GGif2RNHX1iLAvSk1nUw?utm_source=next&utm_medium=banner&utm_campaign=next&p=preview
Any way around this?
You can use DOMContentLoaded event.
The DOMContentLoaded event is fired when the initial HTML document has
been completely loaded and parsed, without waiting for stylesheets,
images, and subframes to finish loading.
In this case you can look for the Component and add the script with something like the following
document.addEventListener("DOMContentLoaded", function(event) {
if(document.querySelector('Component')){
var script = document.createElement('script');
script.src = 'https://someurl.com/some-web-component.min.js';
document.head.appendChild(script)
}
});
Probably a better approach though would be to add the script in the head with async attribute and later remove it if the component is not found.
Something like this
<script async src = "https://someurl.com/some-web-component.min.js"> </script>
<script >
document.addEventListener("DOMContentLoaded", function(event) {
if (document.querySelector('Component') == null) {
var script = document.querySelector('script[src="https://someurl.com/some-web-component.min.js"]')
document.head.removeChild(script)
}
});
</script>
More about DOM lifecycle events
More about async script loading
I am using $(document).ready and inside this method checking if element exists or not. It is working completely fine for me. Below is code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>jQuery Test Element Exists or Not</title>
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var elem = document.querySelector('h1');
var isElemPresent = !!elem;
console.log('Is the element present: ', isElemPresent);
});
</script>
</head>
<body>
<h1>Hello Plunker!</h1>
<script>
var elem = document.querySelector('h1');
var isElemPresent = !!elem;
console.log('Oh NOW it works...: ', isElemPresent);
</script>
</body>
</html>
I am not sure where you are facing issue while using jQuery. There might be some other issue. Above approach is good enough to load script after checking if element is present.
Plunker link:
https://run.plnkr.co/preview/cjgczwlzt000knneldv5d52ea/

Calling a javascript function in a document created with document.write

I have a web page with a button. The click code is:
var html = ...html string containing visual and script elements...
var view = window.open();
view.document.write(html);
view.init(<parameters>); // see next code block
the html content is:
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style type="text/css">
html, body {
height: 100%;
margin: 0;
}
</style>
</head>
<body>
<div id="id1"></div>
<script>
function init(<parameters>) {
...work...
}
</script>
</body>
</html>
The problem is with the init function call in chrome: all good if I am in IE, but in chrome I get "init function not defined" exception.
How should I do to get this working in all browsers? Of course I am looking for a solution that doesn't require a server round trip.
IM a noob so idk if this is exaclty true but i have read that ie allows you to do alot more then chrome or firefox. It might be one of those example where ie will let you do something.
using document.write does in fact work when it comes to create the page I want. Problem is when I want to call a function defined in a javascript block inside that page. Different browsers give different results so I guess this is a matter not completely standardized yet. There are posts in the internet about this, but I couldn't find a clear and common answer.
I then solved my impasse with a workaround. The initial markup contains now placeholders for the parameters:
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style type="text/css">
html, body {
height: 100%;
margin: 0;
}
</style>
</head>
<body>
<div id="id1"></div>
<script>
(init = function () {
var parameter1 = ${{placeholder1}}
var parameter2 = ${{placeholder2}}
...
...work...
})();
</script>
</body>
</html>
The creating code, then, replaces the placeholders with actual values:
var html = ...html string containing placeholders...
html = html.replace("${{placeholder1}}", actual1);
html = html.replace("${{placeholder2}}", actual2);
...
var view = window.open();
view.document.write(html);
Now the init function is called in the same page context, and this works in Chrome as well.
It is not possible to write to a new window if its not on the same domain. What I suggest is that you can open an iframe an work inside that.
How to write to iframe
How to write to page on same domain

Script only runs in some browsers when head tags are removed

I'm using a tumblr theme which is structured like this:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<style>
</style>
<body>
</body>
</html>
There are no head tags which I thought was strange but it works fine; because when you run the site it places head tags automatically. However, since I'm using plugins I added the head tags myself to give me better control as some required me to place it in <head>. This worked fine in chrome and safari but it disabled one of my scripts in firefox and IE:
$(document).ready(function () {
var speed = 25,
timer;
$("#hoverscroll").hover(function () {
$(this).css({cursor: 'none'});
$(this).css({opacity: '0'});
var div = $('body');
(function startscrolling(){
timer = setTimeout(function () {
var pos = div.scrollTop();
div.scrollTop(pos + 1);
startscrolling();
}, speed);
})();
},
function () {
$("#hoverscroll").css({opacity: '1'});
clearTimeout(timer);
speed = 25;
})
.click(function(){
speed = 1;
});
});
It's a simple script where it scrolls the page when an element is hovered.
I reproduced the problem in these 2 demo-sites:
http://testmycode.tumblr.com/ This site has head tag, scrolls in chrome, does not scroll in Firefox.
http://testmycode2.tumblr.com/ This site has no head tag, works perfectly and scrolls in all browsers.
you can add code to your head tag using JQuery like below
$("head").append("<script src=\"mypluginscript.js\"></script>");
this will let you add any new code to it without having to write the head tag
an example!
'However, since I'm using plugins I added the head tags myself to give
me better control'
What you mean by that?
Most of the times there is an alternative solution.
A proper html needs a html tag a head tag and a body tag
inside the head tag you put meta,script and style tags
minimal structure is
<html>
<head>
</head>
<body>
</body>
</html>
Else it's not a html file.
Almost TEN years html5 was launched....
and as it is similar to the basic structure it works on all browsers.
<!doctype html> // html5
<html lang="en"> // language set to english
<head>
<meta charset="utf-8"> // can decode almost al charachters éçàò..
<style></style>
<script></script>
<title>Title</title>
</head>
<body>
</body>
</html>
now reguarding your code.
<script>
goes inside head (or at the end of the body [or just after each element])
if you put the code in the head..
to handle elements inside the body
you need to add a window.onload // DOMContentLoaded
else you have no access to the dom elements.
Now looking at your code from Chrome the errors starts on the html tag..
Why the html has a webkit animation?
Then your talking about the doctype tag and not the head tag.
So if you tell us why you need that we can probably find a solution.

Categories

Resources