Kosmetika
Kosmetika

Reputation: 21304

How to know that dynamically created script tag was executed?

I'm creating a script tag dynamically:

var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.charset = 'utf-8';
script.defer = true;
script.async = true;
script.text = 'some my javascript content here';
head.appendChild(script);

script.onload = function () {
    // this never get fired..
    debugger;
}

How to get notified when script was executed inside other code block? Maybe some event?

Upvotes: 14

Views: 15015

Answers (7)

mindplay.dk
mindplay.dk

Reputation: 7350

From what I found, you can simply do this:

var script = document.createElement("script");

script.innerHTML = "console.log('(1) hello from inline script');"

document.head.appendChild(script);

setTimeout(
  function() {
    console.log("(2) we're done!");
  },
  0 // 👈 zero timeout
);

Why this works: The zero timeout means basically "next event cycle" - and since it's an inline <script> node, there's no delay from any network activity when you append it to <head>, which means the browser will have executed the inline script immediately after the next event-cycle.

Just don't use the load event, and you're good to go.

Tested and worked in IE 10 + 11 and current versions of Chrome, Firefox, Edge, Opera and Safari.

JsFiddle

If your script needs to support both inline and external scripts, just use a condition like if (script.src) to test if the script is external - then conditionally use a load event listener or the zero timeout.

Upvotes: 0

EugenSunic
EugenSunic

Reputation: 13703

The code checks every 100ms if the script got appended to the DOM. You can use it anywhere across your app without the need of event listeners and dispatch events. Likewise, you can see a time interval for which the code will throw an error if the script doesn't append in the time interval that you've set.

const waitForScriptToLoad = (scriptName, checkTimeMs, timeOutMs) => {
  let elapsedTime = 0;
  return new Promise((resolve, reject) => {
    setTimeout(x => reject('script: ' + scriptName + ' Timed out!')
      , timeOutMs)
    const time = setInterval(() => {
      elapsedTime += checkTimeMs;
      if (document.body.innerHTML.indexOf(scriptName) > -1) {
        resolve(
          {
            response: 'script: ' + scriptName + ' found!',
            time: (elapsedTime / 1000).toFixed(2) + 's'
          });
        clearInterval(time);
      }
    }, checkTimeMs)
  })
}

waitForScriptToLoad('script_name.js', 100, 20000)
  .then(res => console.log(res))
  .catch(err => console.log(err))

Upvotes: 0

pherris
pherris

Reputation: 17703

I was able to get this to work by adding an ID to the script, then in the JS, manually firing the load event on that DOM element. Tested only in Chrome, will have issues in older IE according to MDN).

var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.charset = 'utf-8';
script.id = 'testing';
script.defer = true;
script.async = true;
script.onload = function () {
    console.log('The script is loaded');
}
script.text = ["console.log('This is from the script');",
               "var script = document.getElementById('testing');",
               "var event = new UIEvent('load');",
               "script.dispatchEvent(event);"].join('');
head.appendChild(script);

Fiddle

Upvotes: 10

vernonner3voltazim
vernonner3voltazim

Reputation: 786

For testing purposes you can always add a call to the alert() function. However, after looking at your code I'm not sure I see anything in it to actually do the calling of the function. That "onload" event handler probably should be added to the dynamic creation section, before you do the appendChild().

I wrote some code a while back to do some dynamic script creation, and it works fine. My code has two main functions, one that creates the scripts (a bunch of them), loading data from ".js" files, and another that calls the functions in those scripts, in a sequence determined by their names (using the "eval()" function). Your function doesn't have any name that I can see... I know that a lot of folks frown on using the eval() function, but so long as the only thing being called by it is something YOU wrote in its entirety, it should be fine.

This code lets the BROWSER, not the Web Server, create a menu of clickable-link items dynamically (each clickable link looks like an ordinary hyperlink, but is actually a JavaScript construct, and the browser has to have JavaScript enabled for the link to work --but then it had to have JavaScript enabled to create the menu, so no problem!):

 var F00, F01, F02, F03, F04, F05, F06, F07, F08, F09,
     F10, F11, F12, F13, F14, F15, F16, F17, F18, F19;
 var dat = new Array();
 var form, script, str, st2, tmp, tmp2, dtno, indx, unde;

 function initialize()
 { window.name="MyMenu";
   form = document.getElementById('MENU');
   for(indx=0; indx<20; indx++)
   { str = "0" + indx;
     tmp = str.length - 2;
     str = str.substr(tmp);
     script = document.createElement('script');
     script.type = 'text/javascript';
     script.src = str + ".js";
     form.appendChild(script);
   }
   window.setTimeout("BuildMenu();", 1000); //delay is necessary;
      // scripts are actually only loaded after the function ends,
      // and you need to allow time for it to finish
      // before calling the functions in those scripts.
   return;
 }

Note this code is PREPARED to handle 20 menu items, even if you only have 5 items currently ready to be on the menu. The above function doesn't crash if some of the 20 maximum ".js" files don't exist.

 function BuildMenu()
 { dtno = 0;
   for(indx=0; indx<20; indx++)
   { str = "0" + indx;
     tmp = str.length - 2;
     str = "F" + str.substr(tmp);
     tmp = eval(str);
     if(tmp != unde)  //no value is assigned to 'unde'; it is undefined;
                      //this is a valid way to find out
                      //whether or not a ".js" script existed/was-loaded.
       dat[dtno++] = eval(str + "()");
   }
   dat.sort();
   for(indx=0; indx<dtno; indx++)
   { str = "0" + indx;
     tmp = str.length - 2;
     str = "W" + str.substr(tmp);
     tmp = document.getElementById(str);
     tmp.innerHTML = "<a onclick=\"window.open('" + dat[indx][1] + "', 'MyMenu');\" style=\"color:#0000ff;text-decoration:underline;cursor:pointer;\">" + dat[indx][0] + "</a> " + dat[indx][2] + "<br />";
   }
   return;
 }

In the HTML code for the web page, there is this:

 <body onload="initialize();>
 <br />
 <form id="MENU" action="" onsubmit="return false;">
 <span id="W00">&nbsp;</span>
 <span id="W01">&nbsp;</span>
 <span id="W02">&nbsp;</span>
 <span id="W03">&nbsp;</span>
 <span id="W04">&nbsp;</span>
 <span id="W05">&nbsp;</span>
 <span id="W06">&nbsp;</span>
 <span id="W07">&nbsp;</span>
 <span id="W08">&nbsp;</span>
 <span id="W09">&nbsp;</span>
 <span id="W10">&nbsp;</span>
 <span id="W11">&nbsp;</span>
 <span id="W12">&nbsp;</span>
 <span id="W13">&nbsp;</span>
 <span id="W14">&nbsp;</span>
 <span id="W15">&nbsp;</span>
 <span id="W16">&nbsp;</span>
 <span id="W17">&nbsp;</span>
 <span id="W18">&nbsp;</span>
 <span id="W19">&nbsp;</span>
 </form>

Here is some sample code for the "00.js" file:

 <!--
 function F00()
 { return ["Menu Item Alpha", "./Alpha.htm", "Select Web Page Alpha"];
 }
 -->

Note the function simply returns 3 array-elements (strings). The BuildMenu() function uses those strings in constructing the menu on the web page, partly by modifying the innerHTML of the span elements.

Upvotes: -2

Teemu
Teemu

Reputation: 23396

In modern browsers you could use Mutation Observer to detect changes in an element – head in this case. Something like this:

observer = new MutationObserver(function (m) {
    // This will be fired
});
observer.observe(document.head, {childList: true});

Unfortenately this doesn't work in IE < 11, but it seems that onload is fired in IE, so you can use it for IEs.

A live demo at jsFiddle.

Upvotes: 3

Sterling Archer
Sterling Archer

Reputation: 22395

You need to define the onload function before you set the script source. However, as Teemu has told me, since you are writing the javascript via the text property, the onload event never fires. Your best option would be to have an external js file and load it via the src attribute.

The order should be:

  • Append script element to DOM
  • Define onload function
  • define src
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.charset = 'utf-8';
script.defer = true;
script.async = true;
head.appendChild(script);
script.onload = function () {
    // this never get fired..
    debugger;
}
script.src = 'scriptName.js';

Then your onload event should fire, and you can insert a console.log("script has loaded!"); statement into the function.

Upvotes: -1

Grim
Grim

Reputation: 1976

Extract the code to a .js-file.

Add script.src = 'yourjs.js'; to the Script.

The .js-file is immerdently executed when the script is added to the DOM.

Add a if(console){console.debug('helloworld.');} ontop of the yourjs.js and you will see the message.

Upvotes: -1

Related Questions