How do you execute a dynamically loaded JavaScript block? - javascript

I'm working on a web page where I'm making an AJAX call that returns a chunk of HTML like:
<div>
<!-- some html -->
<script type="text/javascript">
/** some javascript */
</script>
</div>
I'm inserting the whole thing into the DOM, but the JavaScript isn't being run. Is there a way to run it?
Some details: I can't control what's in the script block (so I can't change it to a function that could be called), I just need the whole block to be executed. I can't call eval on the response because the JavaScript is within a larger block of HTML. I could do some kind of regex to separate out the JavaScript and then call eval on it, but that's pretty yucky. Anyone know a better way?

Script added by setting the innerHTML property of an element doesn't get executed. Try creating a new div, setting its innerHTML, then adding this new div to the DOM. For example:
<html>
<head>
<script type='text/javascript'>
function addScript()
{
var str = "<script>alert('i am here');<\/script>";
var newdiv = document.createElement('div');
newdiv.innerHTML = str;
document.getElementById('target').appendChild(newdiv);
}
</script>
</head>
<body>
<input type="button" value="add script" onclick="addScript()"/>
<div>hello world</div>
<div id="target"></div>
</body>
</html>

You don't have to use regex if you are using the response to fill a div or something. You can use getElementsByTagName.
div.innerHTML = response;
var scripts = div.getElementsByTagName('script');
for (var ix = 0; ix < scripts.length; ix++) {
eval(scripts[ix].text);
}

While the accepted answer from #Ed. does not work on current versions of Firefox, Google Chrome or Safari browsers I managed to adept his example in order to invoke dynamically added scripts.
The necessary changes are only in the way scripts are added to DOM. Instead of adding it as innerHTML the trick was to create a new script element and add the actual script content as innerHTML to the created element and then append the script element to the actual target.
<html>
<head>
<script type='text/javascript'>
function addScript()
{
var newdiv = document.createElement('div');
var p = document.createElement('p');
p.innerHTML = "Dynamically added text";
newdiv.appendChild(p);
var script = document.createElement('script');
script.innerHTML = "alert('i am here');";
newdiv.appendChild(script);
document.getElementById('target').appendChild(newdiv);
}
</script>
</head>
<body>
<input type="button" value="add script" onclick="addScript()"/>
<div>hello world</div>
<div id="target"></div>
</body>
</html>
This works for me on Firefox 42, Google Chrome 48 and Safari 9.0.3

An alternative is to not just dump the return from the Ajax call into the DOM using InnerHTML.
You can insert each node dynamically, and then the script will run.
Otherwise, the browser just assumes you are inserting a text node, and ignores the scripts.
Using Eval is rather evil, because it requires another instance of the Javascript VM to be fired up and JIT the passed string.

The best method would probably be to identify and eval the contents of the script block directly via the DOM.
I would be careful though.. if you are implementing this to overcome a limitation of some off site call you are opening up a security hole.
Whatever you implement could be exploited for XSS.

You can use one of the popular Ajax libraries that do this for you natively. I like Prototype. You can just add evalScripts:true as part of your Ajax call and it happens automagically.

For those who like to live dangerously:
// This is the HTML with script element(s) we want to inject
var newHtml = '<b>After!</b>\r\n<' +
'script>\r\nchangeColorEverySecond();\r\n</' +
'script>';
// Here, we separate the script tags from the non-script HTML
var parts = separateScriptElementsFromHtml(newHtml);
function separateScriptElementsFromHtml(fullHtmlString) {
var inner = [], outer = [], m;
while (m = /<script>([^<]*)<\/script>/gi.exec(fullHtmlString)) {
outer.push(fullHtmlString.substr(0, m.index));
inner.push(m[1]);
fullHtmlString = fullHtmlString.substr(m.index + m[0].length);
}
outer.push(fullHtmlString);
return {
html: outer.join('\r\n'),
js: inner.join('\r\n')
};
}
// In 2 seconds, inject the new HTML, and run the JS
setTimeout(function(){
document.getElementsByTagName('P')[0].innerHTML = parts.html;
eval(parts.js);
}, 2000);
// This is the function inside the script tag
function changeColorEverySecond() {
document.getElementsByTagName('p')[0].style.color = getRandomColor();
setTimeout(changeColorEverySecond, 1000);
}
// Here is a fun fun function copied from:
// https://stackoverflow.com/a/1484514/2413712
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
<p>Before</p>

Related

How to get a list of Javascript functions in a specific <script> tag

Using the HTML below, how can I get a list of the functions in the <script> tag that is IN the #yesplease div. I don't want any other functions from other script tags. I don't want any global functions or native functions. What I'd like is an array with "wantthis1" and "wantthis2" only in it. I'm hoping not to have to use regex.
I am going to be emptying and filling the #yesplease div with different strings of html (including new script tags with new functions), and I need to make sure that I delete or "wantthis1 = undefined" each function in the script tag before filling it, programmatically, since I won't know every function name. (I don't want them to remain in memory)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script>
function dontCare() {
// don't care
}
</script>
</head>
<body>
<div id="notthisone">
<p>
Hello
</p>
<script>
function dontwantthis () {
// nope
}
</script>
</div>
<div id="yesplease">
<p>
Hello again
</p>
<script>
function wantthis1 () {
// yes
}
function wantthis2 () {
// yes
}
</script>
</div>
<script>
// this function can be called by whatever, but this will look to
// see if functions exist, then call them, otherwise do something
// else
function onSaveOrWhatever () {
if (wantThis1 !== "undefined") {
wantThis1();
}
else {
// do something else (won't get called with above example)
}
if (wantThis3 !== "undefined") {
wantThis3();
}
else {
// do something else (will get called with above example)
}
}
</script>
</body>
</html>
Take innerHTML of all script tags you need
Create an iframe
Get a list of built-in functions of iframe.contentWindow object
Write the content of the script to the iframe created
Get a new list of the functions of iframe.contentWindow object
Find new functions added to the new list
Somehow it doesn't work in stack snippets but it works in Codepen link
var code = document.querySelector("#target script").innerHTML;
var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
var builtInFunctions = getFunctionsOfWindowObject();
var html = `<html><head><script>${code}</script></head><body /></html>`;
iframe.srcdoc = html;
var allFunctions = getFunctionsOfWindowObject();
var result = allFunctions.filter(function(n) {
return builtInFunctions.indexOf(n) < 0;
});
console.log(result);
function getFunctionsOfWindowObject() {
var functions = [];
var targetWindow = iframe.contentWindow;
for (var key in targetWindow) {
if (
targetWindow.hasOwnProperty(key) &&
typeof targetWindow[key] === "function"
) {
functions.push(key);
}
}
return functions;
}
iframe {
display: none;
}
<div id="target">
<script>
function wantthis1() {}
function wantthis2() {}
</script>
</div>
The are a few ways to solve this problem
Architect your application. Use react or vue (or even jquery) to add more control to your code/dom
AST parsing, which would be overkill
Hack it
If you hack it, the problem that you will face is that you are adding functions to global scope. This is shared by everyone, so you can't really monitor it in a nice way.
You can however take advantage of javascript singlethreadedness, and know that things won't happen in the background while you are doing monitoring tasks.
<script>
// take a cache of what is present before your script
window.itemsPresentBeforeScript = {};
foreach (var item in window) {
window.itemsPresentBeforeScript[item] = window[item];
}
</script>
<script> .... your other script </script>
<script>
// use your first cache to see what changed during the script execution
window.whatWasAddedInTheLastScript = {};
foreach (var item in window) {
if (!window.itemsPresentBeforeScript[item]) {
window.whatWasAddedInTheLastScript[item] = window[item];
}
}
delete window.itemsPresentBeforeScript;
// not you have a global list of what was added and you can clear it down when you need to
</script>

Use inline javascript to set some values [duplicate]

I'm wondering if there is a way to get a handle on the DOM element that contains the script inside it. So if I had:
<script type="text/javascript> var x = ?; </script>
Is there a way that I can assign "x" a reference to the script element that contains "x"?
There isn't a truly safe way.
The closest you can come is to use getElementsByTagName to get all the scripts, get its length, get the last script element, then work from there.
This can fail if the script is deferred or if the script has been dynamically added to the page before another script element.
You could include some marker text in the script element, and then (similar to what David said), you can loop through all the script elements in the DOM (using getElementsByTagName or whatever features your library, if you're using one, may have). That should find the element reliably. That would look something like this (live example):
<body>
<script id='first' type='text/javascript'>
(function() {
var x = "MARKER:first";
})();
</script>
<script id='second' type='text/javascript'>
(function() {
var x = "MARKER:second";
})();
</script>
<script id='third' type='text/javascript'>
(function() {
var x = "MARKER:third";
})();
</script>
<script id='last' type='text/javascript'>
(function() {
var scripts, index, script;
scripts = document.getElementsByTagName("script");
for (index = 0; index < scripts.length; ++index) {
script = scripts[index];
if (script.innerHTML.indexOf("MARKER:second") >= 0
&& script.id !== "last") {
display("Found MARKER:second in script tag #" + script.id);
}
}
function display(msg) {
var p = document.createElement('p');
p.innerHTML = msg;
document.body.appendChild(p);
}
})();
</script>
</body>
Note that, like the script above, if you're looking for a script tag marker from within a different script tag, you'll need to handle that. Above it's handled by checking the ID of the script tag, but you can also just break it up in the one you don't want to find, like this (live example):
if (script.innerHTML.indexOf("MARKER:" + "second") >= 0) {
display("Found MARKER:" + "second in script tag #" + script.id);
}

javascript document.write script

Edited qestion
I'm trying to create two separate HTML documents: main.html and sufler.html . Idea is to control sufler.html page from main.html . To do that I found a solution
to write sufler's.html code like string element on main.html page, change what I need in that string element and write it with document.write function from main.html . Everything
works fine except <script> function...
main.html
<script type="text/javascript">
var HTMLstringPage1 = '<!DOCTYPE html><html><head><link href="stilius.css" rel="stylesheet" type="text/css" />',
HTMLstringPage2 = '</body></html>',
HTMLstringStyle = '\x3Cscript type="text/javascript">function styluss(){document.getElementById("flip").style.font="normal normal 30px sans-serif";}\x3C/script>',
HTMLstringDiv1 = '</head><body onload="styluss()"><div id="sufler"><div id="mov"><p id="flip">',
HTMLstringDiv2 = '</p></div></div>';
var newWindow = window.open('sufler.html','_blank','toolbar=no, scrollbars=no, resizable=no, height=615,width=815');
newWindow.document.body.innerHTML = '';
newWindow.document.write(HTMLstringPage1,HTMLstringDiv1+"Text"+HTMLstringDiv2,HTMLstringPage2); //works fine
// newWindow.document.write(HTMLstringPage1,HTMLstringStyle,HTMLstringDiv1+"Text"+HTMLstringDiv2,HTMLstringPage2);//works except script function
</script>
Can someone help on this?
To dynamically add a script tag, it is much better to do this:
var newDoc = newWindow.document;
var script = newDoc.createElement("script");
script.type = "text/javascript";
var text = newDoc.createTextNode('function styluss(){document.getElementById("flip").style.font="normal normal 30px sans-serif";}');
script.appendChild(text);
newDoc.getElementsByTagName("head")[0].appendChild(script);
Working demo: http://jsfiddle.net/jfriend00/JqW5F/
But, creating code on the fly like this is almost never needed. Since you must, by definition, generally already know what you want the code to do, you can just have a pre-created function that takes some arguments and then call that existing function with the right arguments to accomplish what you wanted to rather than creating a custom function on the fly:
function styleIt(id, fontStyle) {
document.getElementById(id).style.font = fontStyle;
}
styleIt("flip", "normal normal 30px sans-serif");
styleIt("flip2", "normal normal 12px sans-serif");
You have to use normal opening brackets for <script> tags:
var HTMLstringStyle = '<script type="text/javascript">function styluss(){document.getElementById("flip").style.font="normal normal 30px sans-serif";}<\/script>';
Although I can't see why you would ever use this... attaching a <script> tag to the <head> or the <body> is almost always a superior solution.

Defining a for loop in Javascript

I can't seem to define a for loop function, what am I doing wrong?
My HTML code:
<body onload="generate()">
My Javascript code:
function generate(){
for(i = 0; i < 150; i++) {
document.write("<div></div>");
}
};
Your loop is fine (other than that you don't declare i, and so you fall prey to the Horror of Implicit Globals), it's document.write that's the problem. You can only use document.write in an inline script, not after the page has been loaded (e.g., not in the body load event). If you use document.write after the page is loaded, it tears down the page and replaces it with what you output (because there's an implicit document.open call). So in your case, your page disappears and 150 blank divs are there instead.
To manipulate the page after load, you'll want to use the DOM, references:
DOM2 Core - Widely supported by browsers
DOM HTML bindings - Widely suppored by browsers
DOM3 Core - Fairly well supported, some gaps
For instance, here's how you'd write your generate function to append 150 blank divs to the page:
function generate() {
var i;
for (i = 0; i < 150; i++){
document.body.appendChild(document.createElement('div'));
}
}
Or more usefully, 150 divs with their numbers in:
function generate() {
var i, div;
for (i = 0; i < 150; i++){
div = document.createElement('div');
div.innerHTML = "I'm div #" + i;
document.body.appendChild(div);
}
}
Live copy
Separately, if you're going to do any significant DOM manipulation, it's well worth using a good JavaScript browser library like jQuery, Prototype, YUI, Closure, or any of several others. These smooth over browser differences (and outright bugs), provide useful utility functions, and generally let you concentrate on what you're actually trying to do rather than fiddling about with inconsistencies between IE and Chrome, Opera and Safari...
The document.write mentioned in https://stackoverflow.com/q/8257414/295783 is the reason it does not work. The first document.write wipes the page including the script that is executing.
A better way is
<html>
<head>
<script type="text/javascript">
var max = 150;
window.onload=function() {
var div, container = document.createElement('div');
for (var i=0;i<max;i++) {
div = document.createElement('div');
container.appendChild(div);
}
document.body.appendChind(container);
}
</script>
</head>
<body>
</body>
</html>
window.addEventListener("DOMContentLoaded", function() {
for(var i = 0; i < 150; i++) {
document.body.appendChild( document.createElement("div") );
}
}, false);
This should work, didn't test it though.
It's important to wait for the 'DOMContenLoaded' event, because else some elements might not exist at the time your script was executed.
do the folowing;
function generate(){
var echo="";
for(i = 0; i < 150; i++){
echo+="<div></div>";
}
document.getElementById("someID").innerHTML=echo;
//document.write(echo); //only do this, if you want to replace the ENTIRE DOM structure
};

Reference script container element?

I'm wondering if there is a way to get a handle on the DOM element that contains the script inside it. So if I had:
<script type="text/javascript> var x = ?; </script>
Is there a way that I can assign "x" a reference to the script element that contains "x"?
There isn't a truly safe way.
The closest you can come is to use getElementsByTagName to get all the scripts, get its length, get the last script element, then work from there.
This can fail if the script is deferred or if the script has been dynamically added to the page before another script element.
You could include some marker text in the script element, and then (similar to what David said), you can loop through all the script elements in the DOM (using getElementsByTagName or whatever features your library, if you're using one, may have). That should find the element reliably. That would look something like this (live example):
<body>
<script id='first' type='text/javascript'>
(function() {
var x = "MARKER:first";
})();
</script>
<script id='second' type='text/javascript'>
(function() {
var x = "MARKER:second";
})();
</script>
<script id='third' type='text/javascript'>
(function() {
var x = "MARKER:third";
})();
</script>
<script id='last' type='text/javascript'>
(function() {
var scripts, index, script;
scripts = document.getElementsByTagName("script");
for (index = 0; index < scripts.length; ++index) {
script = scripts[index];
if (script.innerHTML.indexOf("MARKER:second") >= 0
&& script.id !== "last") {
display("Found MARKER:second in script tag #" + script.id);
}
}
function display(msg) {
var p = document.createElement('p');
p.innerHTML = msg;
document.body.appendChild(p);
}
})();
</script>
</body>
Note that, like the script above, if you're looking for a script tag marker from within a different script tag, you'll need to handle that. Above it's handled by checking the ID of the script tag, but you can also just break it up in the one you don't want to find, like this (live example):
if (script.innerHTML.indexOf("MARKER:" + "second") >= 0) {
display("Found MARKER:" + "second in script tag #" + script.id);
}

Categories