Javascript can't find certain html elements - javascript

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>

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";
}
})();

Properly declaring a variable

I don't know how to declare a variable here in javascript. I have an example situation that if the paragraph is equals to a, the alert will popup.
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<p id="sample">a</p>
</body>
</html>
<script type="text/javascript">
var sample = getElementById('sample');
if (sample == "a") {
alert("Correct")
};
</script>
You're declaring your variable just fine, however if you want the text within the element, you also need to use the innerHTML property. And when you use the getElementById method, you need to use it on the document object like document.getElementById:
var sample = document.getElementById('sample');
if (sample.innerHTML == "a") {
alert("Correct")
};
<p id="sample">a</p>
sample is a variable and you are correct but it is storing a reference to a DOM Element with id sample. To get the inner html of that you need
var sample = getElementById('sample').innerHTML;
Also, use === over == for no casting etc. Refer here
I will recommend you to have a quick look at JS from w3schools and then move to MDN. Nobody will report you here if you show your efforts, so relax :).
Your declaration is fine, but the assignment part is missing document as the object which has the .getElementById method. Then, once you have the reference to the element, you then need to access its content with .textContent (you can't compare the entire element to a value that the element might contain). As a side note on this, when the string you wish to set/get doesn't contain any HTML, you should use .textContent so that the browser doesn't parse the string for HTML unnecessarily. Often, people will suggest that the content of an element should be gotten/set using .innerHTML and, while that will work, it's wasteful if the string doesn't contain any HTML.
Also, the <script> must be located within the head or the body, not outside of them. I would suggest placing it just prior to the closing body tag so that by the time the processing reaches the script, all of the HTML elements have been parsed into memory and are available.
Lastly (and this is really just a side point), an HTML page also needs the title element to have something in it, otherwise it won't be valid. While browsers don't actually do HTML validation, it's important to strive for valid HTML so that you can be sure that your pages will work consistently across all devices. You can validate your HTML at: http://validator.w3.org.
<!DOCTYPE html>
<html>
<head>
<title>Something Here</title>
</head>
<body>
<p id="sample">a</p>
<script type="text/javascript">
var sample = document.getElementById('sample');
if (sample.textContent == "a") {
alert("Correct")
};
</script>
</body>
</html>

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

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.

Why is my browser forcing the script tag inside the body tag?

I'm trying to learn JavaScript from the very beginning to understand it.
What I'm trying to do here is to output the nodeTypes of each element found in the <body> tag. I understand that there are invisible texts between the <body>'s child elements for some unknown reason, which makes the output
3 1 3 1
I put the <script> tag outside the <body> tag, but it's still being counted in the for loop, which resulted the last digit of 1 in the 3 1 3 1 loop sequence. Why? Why is the <script> tag being forced inside the <body> tag by the browser?
<html>
<body id = "bodyTest">
<p>Some Text</p>
</body>
<script type="text/javascript">
var c = document.body.childNodes;
var txt = "";
for(var k = 0; k < c.length; k++) {
txt += c[k].nodeType + " ";
console.log(txt);
console.log(c[k].nodeName);
}
alert(txt);
</script>
</html>
Here is the code I am using.
<html>
<body id = "bodyTest">
<p>Some Text</p>
</body>
<script type="text/javascript">
// Code above
</script>
</html>
That's not valid HTML. The <html> tag can only contain <head> and <body> tags, not <script>.
See the specification:
Permitted contents
One head element, followed by one body element
When your browser encounters broken HTML, it tries to fix it. In this case, that means treating your <script> tag as though it was in the <body>.
The html specification has some restriction about which elements can be contained by the root html element. Script is not one of them, so the htmls you showed us are indeed invalid. However browsers will do their best to still process most of the source. For example, you can have a html file without html, head and body, and the browser will still show the page by wrapping the input in body and html tags.
About the invisible nodes: they are text nodes with a single space. By the specification, any whitespace sequence should be treated as a single space, and the whitespace between your elements (spaces, newlines) still count as text. Try this and see what happens:
<html>
<body id="bodyTest">
<p>Some Text</p><script type="text/javascript">
var c = document.body.childNodes;
var txt = "";
for(var k = 0; k < c.length; k++) {
txt += c[k].nodeType + " ";
console.log(txt);
console.log(c[k].nodeName);
}
alert(txt);
</script>
</body>
</html>
Your browser is correcting your poorly formed HTML by putting the <script> in the closest element that is valid within an <html> element, the <body>.

Javascript: How do fix W3 Validation Error caused by &

Is there any way to fix the error that a JavaScript & causes in w3 validation? The problem is that i have to use && in a if statement and these two &&'s causes errors in w3 validation.
EDIT:
Same problem with "<" and ">".
There are a few things you can do.
You can enclose it within HTML comments:
<script type="text/javascript">
<!--
if (foo && bar) ...
//-->
</script>
You can enclose it in a CDATA section:
<script type="text/javascript">
// <![CDATA[
if (foo && bar) ...
// ]]>
</script>
You can include in in a file instead:
<script src="foobar.js" type="text/javascript"></script>
The primary answer is: Use JavaScript files for JavaScript, not HTML files, and use the src attribute of script tags. (Combine all your JS into one file, minimize, gzip, etc. for performance.)
But, you can embed JavaScript in HTML if absolutely necessary. Use a valid, modern DOCTYPE and you don't have to resort to comment tags and CDATA sections.
Valid HTML5 example:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<title>Example</title>
<script type='text/javascript'>
function foo() {
var a = 1, b = 2;
if (a && b) {
alert("Both");
}
if (a < b) {
alert("a < b");
}
if (a > b) {
alert("a > b");
}
}
</script>
</head>
<body>
<p>Hi there</p>
</body>
</html>
That will also validate as HTML4 strict if you change the doctype to
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
Note that in both cases, you need to be careful about end tags in your script --
This causes the problem:
<script type='text/javascript'>
alert("</td>");
</script>
This solves the problem by prefacing the slash with a backslash (or you can break the end tag up into separate string literals):
<script type='text/javascript'>
alert("<\/td>");
// -or-
alert("<" + "/td>");
</script>
But again, the basic answer is: Don't embed JavaScript within HTML files when you can avoid it, use JavaScript files for that.
Based on your description, I suspect that you're talking about script that's inside an event property in an HTML tag (such as onclick). In that event, the script code needs to be HTML encoded. Elijah hit the nail on the head there.
For example:
<input type="submit" onclick="if(somevar && othervar) callFunc("clicked");">
You do not need to do that inside a <script></script> block.
Escape & with &, < with <, and > with >.

Categories

Resources