Loading javascript dynamically - wait for execution? - javascript

I'm loading multiple javascript references dynamically with appending script tag to DOM.
I would appreciate your help with explaining script loading behavior.
What i'm assuming is that when i append script element to DOM, script is downloaded and then executed. But when .onload event is fired? As soon as script starts execution or when it finishes execution? If second is true, how to await for script to be executed/initialized (i would like to execute callback that will append additional html with scripts that depends dynamically loaded references )?
To load scripts i use function of below:
function recursiveInclude(scriptPathArray, callback) {
if (scriptPathArray.length > 0) {
var scriptPath = scriptPathArray[0];
// check if script is already loaded if not load
// this_loadedScriptList is one scope level up variable
if (this_loadedScriptList.indexOf(scriptPath) > 0 == false) {
var body = document.getElementsByTagName('body')[0];
var scriptElement = document.createElement('script');
scriptElement.type = 'text/javascript';
scriptElement.src = scriptPath;
// first script from the array will loaded as soon as body.appendChild function will be called
// when script loads 'onload' event will fire next script loading
// to this work properly first script from scriptPathArray has to be removed:
scriptPathArray.shift();
// if there are any other scripts to load, load them sequentially
if (scriptPathArray.length > 0) {
// then bind the event to the callback function
// there are several events for cross browser compatibility
// script.onreadystatechange = callback;
scriptElement.onload = recursiveInclude(scriptPathArray, callback);
} else {
// if no other scripts to load - fire base callback;
scriptElement.onload = callback;
}
// fire the loading
body.appendChild(scriptElement);
// add script to loaded array.
this_loadedScriptList.push(scriptPath);
}
}
}

there is a big issue you must know about. Doing that implies that you remotely load the code. Modern web browsers will load the file and keep executing your current script because they load everything asynchronously to improve performance. (This applies to both the jQuery method and the manual dynamic script loading method.)
It means that if you use these tricks directly, you won't be able to use your newly loaded code the next line after you asked it to be loaded, because it will be still loading.
Practically all you can do is to use an event to run a callback function when the script is loaded.
function loadScript(url, callback)
{
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
script.onreadystatechange = callback;
script.onload = callback;
// Fire the loading
head.appendChild(script);
}
Then write your code like this:
var myCode = function() {
...
};
At last you can run it:
loadScript("the-remote-script.js", myCode);

JavaScript is neither threaded nor event-interrupted. The hole script is executed before anything else can happen. Events are captured until the browser gets back the control. So onload or any other event can only be fired before or after the script execution is done. Actually onload is fired after the execution.
There is an event beforescriptexecute still supported by firefox, however, it has been remove from HTML5.1 specs.
You can try it out yourself:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
console.log('base script start');
var script=document.createElement('script');
script.onload = function() { console.log('onload fired'); }
// MDN says:
// This event was a proposal in an early version of the specification. Do not rely on it.
//
script.addEventListener('beforescriptexecute', function () { console.log('beforescriptexecute fired'); });
script.src = 'external.js';
document.head.appendChild(script);
console.log('waiting 3 seconds');
timebase = Date.now();
while((Date.now() - timebase) < 3000)
;
console.log("base script end");
</script>
</body>
</html>
external.js:
console.log('external start... waiting 3 seconds');
timeext = Date.now();
while((Date.now() - timeext) < 3000)
;
console.log('external end');
base script start
waiting 3 seconds
base script end
beforescriptexecute fired
external start... waiting 3 seconds
external end
onload fired
MDN - beforescriptexecute

Related

async onload not working in this case

I have some code to asynchronously load an Ad on my site which looks like this:
<div id="ad-div"></div>
<script>function onAdReady() {
debug_log('Method onAdReady called');
new Ad(document.getElementById("ad-div"))
}
</script>
<script src="http://content.xxxxx.com/ads/ads.min.js" async onload="onAdReady()"></script>
The problem is that the onAdReady function is never beeing called. The reason for this might be that the html code which contains this snippet is beeing loaded via javascript in the first place like this:
// Initiate out_window_view
$.ajax({
url: loadPagePath("main.html"),
success: function (result) {
debug_log("Going in main.html view");
$("#content").html(result);
},
error: function (result) {
debug_log("Error, failed to load out_window_main view");
}
});
This code is beeing executed in the $(document).ready(function () {] in case that might matter.
Can anyone expalain to me why this is not working and provide me with a workaround or alternative way for solving this issue?
I don't understand why you need to add async to an ajax-generated-content. The purpose of async script is to allow the browser to keep on parsing the document without waiting for the script to fully load. The onload event is called immediately after the script has finished loading and before DOMReady. In other words, async's onload will not fire after DOMReady.
When you add the snippet to your page, the page has already finished parsing, so the async onload event won't fire.
The solution IMO is to remove the async part and just call the function after the <script> synchronously.
You want to load an ad when the page is loaded?
What do you see in the network tab from the debug tools (firebug or F12 in chrome/Firefox)?
Where do you call $( document ).ready() ?
https://learn.jquery.com/using-jquery-core/document-ready/
Can you tell me when you want to send the HTTP request?
If you are able to add the script programmatically, it will be async by default (reference here), and you can listen to the onload or onreadystatechange event (as you can read here, IE11 doesn't support onreadystatechange). Here is an example:
var setup = function(){
console.log('the script has been parsed');
};
var script = document.createElement("script");
script.src = "http://content.xxxxx.com/ads/ads.min.js";
document.getElementsByTagName("head")[0].appendChild(script);
// standard browser
script.onreadystatechange = function (){
if (this.readyState == 'complete'){
setup();
}
}
// IE
script.onload = setup;

document.attachEvent() not working in IE8

I am aware that in Internet Explorer (Pre-IE9) you cannot use document.addEventListener(), instead you must use document.attachEvent. The problem I'm having is that document.attachEvent('onload', AddExternals); does nothing, at all. In the console, the output should be as follows:
- document.attachEvent
- Adding Externals...
- jQuery loaded!
However in IE8, the console output is:
Is there any obvious reason why this would occur in the below code?
if (document.addEventListener) {
console.log("document.addEventListener")
document.addEventListener("DOMContentLoaded", AddExternals);
} else if (document.attachEvent) {
console.log("document.attachEvent")
document.attachEvent("onload", AddExternals);
}
function AddExternals(){
console.log("Adding Externals...");
var jq = document.createElement("script");
jq.type = "text/javascript";
document.getElementsByTagName("head")[0].appendChild(jq);
jq.onload = function(){console.log("jQuery loaded!")};
jq.src = "https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js";
}
Edit
I have changed document.attachEvent("onload", AddExternals) and now the console is outputting both document.attachEvent and Adding Externals... but the function never completes?
As far as I know, there is no document.onload event. Instead, you would use window.onload as your fallback. You may also have to test the document state to make sure that it is not already loaded (e.g. the events have already fired).
For a completely tested function to know when the document is ready in any level of browser see the code in this prior question/answer: pure JavaScript equivalent to jQuery's $.ready() how to call a function when the page/dom is ready for it
Keep in mind that older versions of IE do not have a .onload for your script tag so you will not necessarily see that console message, but your script should still load. There is a more complicated scheme that will get you notified of when it is loaded for older versions of IE here: javascript notify when script is loaded dynamically in IE
I would suggest you change your script to this:
function AddExternals(){
var doneLoad = false;
function onload() {
if (!doneLoad) {
doneLoad = true;
console.log("jQuery loaded!")
}
}
console.log("Adding Externals...");
var jq = document.createElement("script");
jq.type = "text/javascript";
jq.onload = doneLoad;
jq.onreadystatechange= function () {
if (script.readyState == "loaded" || script.readyState == "complete"){
doneLoad();
}
};
jq.src = "https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js";
document.getElementsByTagName("head")[0].appendChild(jq);
}
Relevant changes:
Added support for older method of knowing when the script has loaded.
Made sure there is no duplicate load notification since listening for multiple mechanisms
Set .src before inserting the script tag.

how to know if the javascript, which has been loaded by a javascript, has been loaded [duplicate]

This question already has answers here:
'onload' handler for 'script' tag in internet explorer
(2 answers)
Closed 7 years ago.
I know my subject is quite tricky but i dont know how to much more ellaborate it on the subject alone.
so here how it goes.
i have a button
Load IT!
on the script tag:
function loadTheFile() {
var script = $("<script><\/script>");
script.attr("type", "text/javascript");
script.attr('src','http://www.thisismyexternalloadingjsfile"');
$('body').append(script);
alert("done! the file has been loaded");
}
the script well, when loaded will automatically have a modal box.
but the problem is, my alert seems to fire first than what is one the script
so how will i know if i have finished to load the script?
update for the first attempt to answer:
function loadTheFile() {
var script = $("<script><\/script>");
script.attr("type", "text/javascript");
script.attr('src','http://www.thisismyexternalloadingjsfile"');
$('body').append(script);
$(document).ready(function() {
alert("done! the file has been loaded")};
}
same problem
alert does indeed run before the script has been loaded. All that appending the script tag to the page does is append the script tag to the page. Then the browser has to download the script and, once received, run it. That will be after your loadTheFile function has exited.
So you need to get a callback when the script has actually be loaded and run. This is more standard than it used to be, but still has some cross-browser hassles. Fortunately for you, jQuery's already solved this problem for you (since you're using jQuery already):
function loadTheFile() {
$.getScript('http://www.thisismyexternalloadingjsfile"')
.then(function() {
alert("done! the file has been loaded");
});
}
Re your comment:
but my script file has data-* attributes
Assuming you're talking about data-* attributes on the script tag, then you'll have to do a bit more work, but it's still fairly straightfoward:
function loadTheFile() {
var load = $.Deferred();
var script = document.createElement('script');
script.src = 'http://www.thisismyexternalloadingjsfile"';
// No need for `type`, JavaScript is the default
script.setAttribute("data-foo", "bar");
script.onreadystatechange = function() {
if (script.readyState === "loaded") {
load.resolve();
}
};
script.onload = function() {
load.resolve();
};
load.then(function() {
alert("done! the file has been loaded");
});
document.body.appendChild(script); ;// Or wherever you want to put it
}
The onreadystatechange bit is to handle older versions of IE.
Rather than forge the script with text and jQuery, just use native Javascript:
var s = document.createElement('script');
s.onload = scriptLoaded;
s.src = '/path/to/my.js';
document.body.appendChild(s);
function scriptLoaded() {
console.log('Script is loaded');
}
Try something along these lines:
Your main page:
function whenScriptIsReady(){
alert('This function is called when the other script is loaded in!')
}
function loadTheFile() {
var script = $("<script><\/script>");
script.attr("type", "text/javascript");
script.attr('src','myotherjs.js');
$('body').append(script);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Load IT!
myotherjs.js:
alert('This will automatically run when the JS is loaded in!');
whenScriptIsReady();
JavaScript is executed asynchronously, so you alert will be executed before the browser can load the new script. If you want to execute logic after the script has been loaded, you could add an event listener to your script that will call the function 'loadFunc` once the script load is completed:
var loadFunc = function() {
alert("External Javascript File has been loaded");
};
//Other browsers trigger this one
if (script.addEventListener)
script.addEventListener('load', loadFunc, false);

JavaScript dynamic loading

I have senerio, where I need to render my HTML page by using dynamic JavaScript.
I am using loadScript function to load external JavaScript and passing callback funtion. In my HTML page , I am loading this script for my header.
My header section is working perfectly after the script is loaded and I my head section I can see my new script.
However , when I am trying to use the variables from this script its undefined.
function loadScript(url,callback){
var script = document.createElement("script");
script.type = "text/javascript";
script.id="acvDataRequest";
document.getElementsByTagName("head")[0].appendChild(script);
if (script.readyState){ //IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" ||
script.readyState == "complete"){
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function(){
alert(dataHeader) // I CAN SEE MY OBJECT FROM LOADED SCRIPT
callback();
};
}
script.src = url;
alert(dataHeader); // IT SAYS UNDEFINED
}
calling a script using :
var actionName ="JSONdata/json.js";
loadScript(actionName,mergeTemplateJSONScript);
Please advice , why i can't see my variables even if my script is there.
Inside script.onload , I am able to see my variable but not outside
The line script.src = url; triggers the loading of the script file. If you call alert immediately after it your external script has not been loaded yet. You can only access variables from the json once the onreadystatechange or onload functions have been called.
What you should do is using it like this:
var actionName ="JSONdata/json.js";
loadScript(actionName,function(){
alert(dataHeader);
});
What you assign:
script.src = url;
that just starts the loading of the dynamic script. When you call the second alert() on the very next line your script has not yet loaded (it is loading in the background at that point). You can only reliably access the variables from the newly loaded script from within the onload handler or in some function called from the onload handler.
Keep in mind that the dynamic loading of scripts is asynchronous. That means it happens in the background while other scripts keep running (thus your second alert() runs before the script has finished). And, then the script finishes loading some time later and when it does the onload handler is called.
So, when you dynamically load scripts, all code that uses those scripts needs to either be in the onload handler, in some function called from the onload handler or guarenteed not to execute until some time later (such as in an event handler that you're sure won't happen before the script finishes loading).
To explain further, I've added some annotations to your code:
function loadScript(url,callback){
var script = document.createElement("script");
script.type = "text/javascript";
script.id="acvDataRequest";
document.getElementsByTagName("head")[0].appendChild(script);
if (script.readyState){ //IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" ||
script.readyState == "complete"){
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function(){
// ** your script is now loaded here **
alert(dataHeader) // I CAN SEE MY OBJECT FROM LOADED SCRIPT
callback();
};
}
script.src = url;
// ** your script is in the process of loading here and has likely not completed **
alert(dataHeader); // IT SAYS UNDEFINED
}

Load javascript in consecutive order after browser load event

What am I trying to do? In an attempt to speed up my website I am loading non-essential javascript after the browser load event. (So the JS files are not render blocking) This is currently functioning correctly.
What is the problem? The problem is sometimes the non-essential javascript depends on other libraries and plus those libraries need to load first.
What have I tried to do to fix the problem? In an attempt to fix the problem I have added a delay event to library dependent javascript. While this works sometimes, the load times of a JS file varies between refreshes and at times can load before the library even with a delay.
QUESTION: Does anyone know of a better way for me the load JS files only after the first JS file has loaded? (See code below)
<script type="text/javascript">
function downloadJSAtOnload() {
var element = document.createElement("script");
var element2 = document.createElement("script");
var delay=40;
element.src = "http://119.9.25.149/sites/all/themes/bootstrap/bootstrap_nova/js/highcharts.js";
element2.src = "http://119.9.25.149/sites/all/themes/bootstrap/bootstrap_nova/js/future-plastic.js";
document.body.appendChild(element);
setTimeout(function(){
document.body.appendChild(element2);
},delay);
}
if (window.addEventListener)
window.addEventListener("load", downloadJSAtOnload, false);
else if (window.attachEvent)
window.attachEvent("onload", downloadJSAtOnload);
else window.onload = downloadJSAtOnload;
</script>
As you can see from the above, I am trying to load the highcharts js file before I load the future-plastic file.
You're not the first to have this problem, thankfully. There's a lot of difficult solutions around this problem, including using a module loader as suggested in the comment (which I agree is the best long term solution, because they account for more browsers and flexibility, but it's a lot to learn to solve one small problem).
The place to start learning about this problem and the ways to tackle it are all over the web. This is a pretty good resource: http://www.html5rocks.com/en/tutorials/speed/script-loading/
You may want to try defer if you don't have to support Opera Mini or IE9. Or, you can load sync and execute as it loads- their examples is this:
[
'//other-domain.com/1.js',
'2.js'
].forEach(function(src) {
var script = document.createElement('script');
script.src = src;
script.async = false;
document.head.appendChild(script);
});
The reason why this might work (different browser implement this differently) is because the default is to load dynamically generated script tags are set to async by default, if you set it to false: "they’re executed outside of document parsing, so rendering isn’t blocked while they’re downloaded"
You should use ScriptElement.onload:
var pre = onload;
onload = function(){
if(pre)pre();
var doc = document, bod = doc.body;
function C(t){
return doc.createElement(t);
}
function downloadJSAtOnload(){
var s = C('script'), ns = C('script'), h = doc.getElementsByTagName('head')[0];
var u = 'http://119.9.25.149/sites/all/themes/bootstrap/bootstrap_nova/js/';
s.type = ns.type = 'text/javascript'; s.src = u+'highcharts.js'; h.appendChild(s);
s.onload = function(){
ns.src = u+'future-plastic.js'; h.appendChild(ns);
}
}
downloadJSAtOnload();
}
Note: The first onload is window.onload, since window is implicit.

Categories

Resources