How do you execute a dynamically loaded JavaScript block?
Asked Answered
K

7

41

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?

Kielce answered 16/9, 2008 at 19:20 Comment(1)
Best answer I found so far. I believe it applies to most cases.Rosana
M
18

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>
Maestoso answered 16/9, 2008 at 19:38 Comment(1)
This script does not work in IE 7. IE 7 seems to have a bug with the appendChild function.Wingover
S
15

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);
}
Superposition answered 16/9, 2008 at 19:27 Comment(0)
M
10

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

Mablemabry answered 17/2, 2016 at 16:37 Comment(0)
A
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.

Azucenaazure answered 16/9, 2008 at 19:36 Comment(1)
It would be very nice if you explain how to insert each node dynamically.Libbie
D
1

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.

Dulcedulcea answered 16/9, 2008 at 19:33 Comment(1)
Correct me if I'm wrong, but the way I understand XSS vulnerabilities in this type of situations, it's only an issue if you're worried about someone being able to gain access to someone else's online account, and thus be able to steal sensitive info, but if it's not possible for users to login to the site then this isn't a worry.Sepsis
C
0

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.

China answered 16/9, 2008 at 20:9 Comment(0)
F
0

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://mcmap.net/q/73238/-random-color-generator
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>
Fortify answered 31/8, 2020 at 20:4 Comment(1)
"live dangerously"...due to the use of exec.Bouse

© 2022 - 2024 — McMap. All rights reserved.