This is a very common question in here related to AJAX, but I can't seem to find one for my case, because most of them used jQuery, and other reasons. Probably mine isn't efficient/recommended, but either way, here we go.
I have a button [let's say we have a reference to it for the sake of it called btn].
It listens for a clicking event. When the user clicks the button, it makes an AJAX request to a .txt file present in the same directory the main HTML/CSS/JS file is. Let's call it test.txt.
Now, it changes the <html>'s innerHTML(not the head/body, the html), and the innerHTML is the response from the AJAX request.
test.txt holds HTML code. And within that HTML code there's <script src="another-js-file.js">.
This doesn't get executed, this is the problem.
Before you scream at me INNERHTML DOESN'T EXECUTE ANY SCRIPTS I know that. I've seen other answers saying to create a <script> tag within a div that's within yet another div, but it doesn't seem to work with external js files, and the solutions indeed used innerHTML.
Okay, here's a sketch:
btn.onclick = function(){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState = XMLHttpRequest.DONE){
document.documentElement.innerHTML = this.responseText;
}
}
xhr.open("GET", "./test.txt");
xhr.send();
}
Where test.txt holds:
<head>
<title>Document</title>
<link rel="stylesheet" href="test.css">
<script src="another-js-file.js"></script>
</head>
<body>
<h1>Hello World!</h1>
</body>
No jQuery.
I found that by creating a script element and setting its source to "another-file.js" like this worked:
var script = document.createElement("script");
script.src = "test.js";
document.body.appendChild(script);
THIS GOES AFTER document.documentElement.innerHTML = this.responseText;
I noticed in your initial XHR request you wrote:
if(xhr.readyState = XMLHttpRequest.DONE)
This should actually be triple === not the single one.
Add this function to your initial JS file where you make the XHR request,
function setInnerHtml(el, html) {
el.innerHTML = html;
// Get all the scripts from the new HTML
const scripts = el.querySelectorAll('script');
// Loop through all the scripts
for (let i = 0; i < scripts.length; i++)
{
// Create a new script
const s = document.createElement('script');
// Go through all the attributes on the script
for (let j = 0; j < scripts[i].attributes.length; j++) {
const a = scripts[i].attributes[j];
// Add each attribute to the new script
s.setAttribute(a.name, a.value);
}
// Incase there is code inside the script tag
// Example: <script>alert</script>
s.innerHTML = scripts[i].innerHTML;
// Append the new script to the head (you could change this to the end of the body as well)
document.head.appendChild(s);
}
}
Then when you go to set the innerHTML of the root document object, use the above function instead.
if(xhr.readyState === XMLHttpRequest.DONE){
setInnerHtml(document.documentElement, this.responseText);
}
Related
Including javascript into an html file is easy, but is it possible to go the other way? If I have a test.html file
<html>
<head>
<script>
function helloWorld() { console.log("Hello World!"); }
</script>
</head>
<body>
<h1 id="test">TEST</h1>
</body>
</html>
I want to be able to include this html in javascript so that I can reference all of the DOM elements and javascript of my html file, i.e.
require("test.html");
var header = document.getElementById("test");
helloWorld();
This code obviously does not work. But I'd really like to find a way to include an html file in javascript as if it were the document object. Is this possible?
I would do something like this.
https://codesandbox.io/s/happy-frog-pmidw?file=/src/index.js
const url = "test.html";
fetch(url)
.then((response) => response.text())
.then((text) => new DOMParser().parseFromString(text, "text/html"))
.then((dom) => dom.getElementById("test"))
.then((test) => {
console.log(test);
//Do something with test.
});
You can perform an AJAX call to test.html in your javascript code.
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
//xhttp.responseText will have all the HTML content
}
};
xhttp.open("GET", "test.html", true);
xhttp.send();
I don't have the context on why you want to do this but I don't recommend it. If you include your script into your HTML code, that script will be able to get any HTML node in that file.
Example:
<html>
<body>
<h1 id="test">TEST</h1>
</body>
<script>
//document.getElementById/getElementByClassName, etc will work with any node inside this file.
</script>
This is the same code from your question, except that the script is at the bottom and will work for you to get any elements.
With jQuery:
jQuery.get('https://example.caom/test.html', function(data) {
alert(data);
});
With vanilla js:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://example.caom/test.html');
xhr.onreadystatechange = function() {
xhr.responseText;
}
xhr.send();
When you include a tag in your html, the browser understands that to be instructions to make a GET request to whatever at src. Prior to the introduction of esm to browsers, Javascript didn't have anything like that. Require was added into nodejs pretty early on, but it still didn't work in browsers. A lot of projects have attempted to solve the same problem in browsers, with the most successful being bundlers like webpack. They resolve the file that is being required, then include it in the same file, or in more advanced cases, add a request to go get that file to the code.
As all the other answers have indicated, this is what you need to do to get html (or really any other files) into your javascript.
I’m trying to make a bookmarklet that swaps out the DOM on the current page for a new one, and then injects and runs some javascript.
It works fine when the original page is HTML (so I don’t think this is a CORS problem). However, when the original page is XML, the injected javascript doesn’t run :( Why not? How can I get it working?
Here’s some example bookmarklet code:
(function () {
var jsHref = 'https://rawgit.com/andylolz/b2e894fa5ccdecacd901c05769fa97fe/raw/289719871f859a1b19e06f8b8ce3769f0002ce55/js.js';
var htmlHref = 'https://rawgit.com/andylolz/b2e894fa5ccdecacd901c05769fa97fe/raw/289719871f859a1b19e06f8b8ce3769f0002ce55/html.html';
// fetch some HTML
var xhr = new XMLHttpRequest();
xhr.open('GET', htmlHref, false);
xhr.send();
var htmlString = xhr.response;
var parser = new DOMParser();
var result = parser.parseFromString(htmlString, 'text/html');
// swap the DOM for the fetched HTML
document.replaceChild(document.adoptNode(result.documentElement), document.documentElement);
// inject some javascript
var sc = document.createElement('script');
sc.setAttribute('src', jsHref);
document.documentElement.appendChild(sc);
})();
Here it is working on codepen (on an HTML page):
https://codepen.io/anon/pen/wEWemL?editors=1010
Again – If I run the above on an XML page, it mostly works, but the injected javascript doesn’t execute.
Thanks!
I'm trying to implement dynatrace in my react app, and this works fine if I just add the required script tag manually in my index.html header.
However I want to make use of dynatraces api which returns the whole script tag element (so I can use for different environments).
How can I add the script tag to my index.html after calling the api? Creating a script element from code won't work because the response of the api call is a script tag itself (which is returned as string).
I tried creating a div element and adding the script as innerHtml, then append it to the document. But scripts don't get executed in innerHtml text.
const wrapperDiv = document.createElement("div");
wrapperDiv.innerHTML = "<script>alert('simple test')</script>";
document.head.appendChild(wrapperDiv.firstElementChild);
Can this be done?
I found a roundabout way of doing this:
const wrapperDiv = document.createElement("div");
const scriptElement = document.createElement("script");
wrapperDiv.innerHTML = "<script src=... type=...></script>";
for(let i = 0; i < wrapperDiv.firstElementChild.attributes.length; i++){
const attr = wrapperDiv.firstElementChild.attributes[i];
scriptElement.setAttribute(attr.name, attr.value);
}
document.head.appendChild(scriptElement);
in this example of the script i'm only using a src but this can be done with the value as well. If there is any better way for doing this pls let me know
This can be achieved without use of eval() :
const source = "alert('simple test')";
const wrapperScript = document.createElement("script");
wrapperScript.src = URL.createObjectURL(new Blob([source], { type: 'text/javascript' }));
document.head.appendChild(wrapperScript);
In the code above you basically create Blob, containing your script, in order to create Object URL (representation of File or Blob object in browser memory).
This solution is based on idea that dynamically added <script> is evaluated by browser in case it has src property.
Update:
Since endpoint returns you <script> tag with some useful attributes, the best solution would be to clone attributes (including src) - your current approach is pretty good.
I found a roundabout way of doing this:
const wrapperDiv = document.createElement("div");
const scriptElement = document.createElement("script");
wrapperDiv.innerHTML = "<script src=... type=...></script>";
for(let i = 0; i < wrapperDiv.firstElementChild.attributes.length; i++){
const attr = wrapperDiv.firstElementChild.attributes[i];
scriptElement.setAttribute(attr.name, attr.value);
}
document.head.appendChild(scriptElement);
in this example of the script i'm only using a src but this can be done with the value as well
I'm running this function to open a new window.
function htmlNewWindow(id) {
var html = $(id).html();
var newWindow = window.open('');
newWindow.document.body.innerHTML = '<html><head><title>Hi</title> <script src="js/myScript.js"></script> </head>' + html;
}
This successfully creates a new window with the HTML in it. I have a bunch of HTML tags which when clicked run a function called Foo1. I've tried printing the entire function of Foo1 to the new HTML document, and tried putting Foo1 inside myScript.js. I see both Foo1 inside a script tag in the new window, and but neither are loaded since they are just written to the new page as HTML.
Scripts added with .innerHTML aren't executed. You need to create a script node and append it to the window's DOM.
$("#button").click(newWindow);
function newWindow(id) {
var html = $(id).html();
var win = window.open('');
win.document.head.innerHTML = '<title>Hi</title></head>';
win.document.body.innerHTML = '<body>' + html + '</body>';
var script = document.createElement('script');
script.src = 'js/myScript.js';
win.document.head.appendChild(script);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="button">Click me</button>
This doesn't run in Stack Snippet's sandbox, here's a working jsfiddle.
Try this:
var newWindow = window.open('');
newWindow.document.createElement('script');
script.src = 'js/myScript.js';
newWindow.document.head.appendChild(script);
Just in case someone has this to be done in a link. Do the following:
Link
This opens a new window with that URL, it set the focus to that windows, and as soon as the 'load' event is triggered, it executes the code in the function. It only works with a page in the same domain.
Hope this helps ⬆✌.
Cheers 👍
Here's how you create, and then append a script file within a new window:
var fileref = document.createElement('script');
//creates script in current document
fileref.setAttribute("type", "text/javascript")
//set it to JS by "type"
fileref.setAttribute("src", filename)
//set your "src=yourFile_href_Here.js"
//Then create your newWindow as you did above, but slightly updated
//Create your function which will consume the "fileref" argument
function htmlNewWindow(fileref) {
var newWindow = window.open('');
newWindow.document.getElementsByTagName("head")[0].appendChild(fileref);
}; //right now the function is made but you still have to execute it
//Execute your function, and pass it the variable "fileref" that you set above.
htmlNewWindow(fileref);
//Within this edit you will append the head element
//with your newly created script(or any other parameterized argument)
/* Replace your filename to pass any other script */
NOTE - Opening a page residing on a different domain, if not specifically allowed, will reject instances of this due to CORS(https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS)
It's not a safe practice to be sending your scripts into other people's pages or allowing them in your own if your domain hasn't sent them. Also, depending on your server/technology stack you may need to configure your *-origin settings within your backend stack. See here: (https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)
I am currently contracted to a place that cannot use a CMS or PHP, but they want me to build something like a CMS using HTML and JavaScript.
I know it sounds ridiculous but I do not want to be searching for another job these days and they are the nicest people that I have ever worked for - EVER - and I old.
One of the concepts of a CMS is to have global files that you can include at any given time.
As a result, I tried the $.ajax, $.get, etc..., but I was running into issues of Access URI denied and those kind of things for trying to load a file which is one directory level the current directory.
I was able to get the javascript file to load by using the old XMLHttpRequest/ActiveXObject.
However, the script within the div that has been loaded cannot be called. I receive an error of "Can't find variable: mFunc" which is the name of the function that has been loaded into the div.
Here's the code for my html:
<!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">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>retrieve local file one level up</title>
<script type="text/javascript">
<!--
var createRequestObject = function(){
var req;
if(window.XMLHttpRequest){
// Firefox, Safari, Opera...
req = new XMLHttpRequest();
}else if(window.ActiveXObject){
// Internet Explorer 5+
req = new ActiveXObject("Microsoft.XMLHTTP");
}else{
alert('There was a problem creating the XMLHttpRequest object');
}
return req;
}
// Make the XMLHttpRequest object
var http = createRequestObject();
var sendRequestPost = function(){
var jscript = '../test.js';
// Open PHP script for requests
http.open('GET', jscript);
http.setRequestHeader('Content-Type', 'text/javascript');
http.onreadystatechange = handleResponsePost;
http.send(null);
var mT = setTimeout("mFunc()", 2000);
}
var handleResponsePost = function(){
if(http.readyState == 1){
document.getElementById('mDiv').innerHTML = "Please wait, loading... " ;
}else if(http.readyState == 4 && http.status == 200){
// Text returned from PHP script
var response = http.responseText;
document.getElementById('mDiv').innerHTML = response;
if(response){
// Update ajaxTest2 content
document.getElementById('mDiv').innerHTML = response;
}
}else if(http.readyState == 2){
document.getElementById('mDiv').innerHTML = http.responseText;
}else if(http.readyState == 3){
document.getElementById('mDiv').innerHTML = http.responseText;
}
}
-->
</script>
</head>
<body onload="javascript:sendRequestPost();">
<div id="mDiv"></div>
</body>
</html>
Here is the javascript that loads just fine into mDiv:
<script type="text/javascript">
<!--
var mFunc = function(){
var mScript = document.createElement("script");
mScript.setAttribute("type","text/javascript");
var data = 'alert("gets here");'
mScript.text = data;
var head = document.getElementsByTagName("head")[0];
head.appendChild(mScript);
}
-->
</script>
Yet, after the two seconds have passed, I receive the error.
I am sure that it is probably because the browser just sees this as text within the div, so how do I make it recognize that it is javascript.
I have tried using eval, which I do not want to use, but even returns a parse error.
Thanks in advance
../ has meaning to the local filesystem (on most platforms), but not to HTML or to most webservers. Remember that the URL is just a query string for the server.
Generally speaking, you need to parse the URL to remove the undesired few elements. If you just want scripts that are common across the website, though, they should be referenced from the root, so the relative URL would begin with /.
A quick hack would be /(.*)\/.*/.exec( '/foo/bar/baz.html' )[1]. This doesn't handle the query string following ? or anchor following # but you won't have a query on a static website, and won't have anchors until you get into more advanced techniques. jQuery has a better utility for parsing URLs, also based on regexps.
It's offtopic for this site, but you will have to be very familiar with XHR to implement a JavaScript CMS.
OK, another programmer that I work with, has found a simple solution.
Instead trying to use ajax to load a JavaScript file from a higher directory level and then run a document.writeln or document.getElementById("someDiv").innerHTML -- reverse the steps.
Include the JS file as you would normally:
<script type="text/javascript" src="../../common/header.js"></script>
Within this JS file
function CommonHeader(mPath) {
document.writeln('<header>');
document.writeln(' <div class="PageWidth">');
document.writeln(' <h1>Something<sup>®</sup> <em>Learn about us</em></h1>');
document.writeln(' <nav>');
document.writeln(' <ul>');
document.writeln(' <li id="loginOut"></li>');
The order needs to be for you to call document.writeln at the beginning of the process.
We can now load header.js, footer.js, and whatever other file that we wish to load, along with having an array at the top of each page denoting the path to those files, for lower directory level htmls
dynamicPathArr[0] = "../../";
Then within whatever file, you can call the function to write the date into the page
<script type="text/javascript">CommonHeader(dynamicPathArr[0])</script>
I cannot believe that I did not think of this completely simple solution.
Although this is not SEO friendly, it is good for only updating header, footer, nav, etc... in one location, until everything is finalized.
And thanks you for the response