Are scripts added dynamically with appendChild() executed sequentially or in parallel? - javascript

I'm adding scripts to a page dynamically using code similar to this:
var s = document.createElement('script');
s.src = 'script.js';
(document.head||document.documentElement).appendChild(s);
The first script I add is jQuery bundled with a few plugins after that (all in one JS file), then the second script uses jQuery and some of the other objects created in the first JS file added.
The problem is that errors are being thrown in the second script saying jQuery, along with several other things are undefined.
I assumed scripts added to the DOM in this manner would be parsed/executed sequentially, but these errors would indicate they are executed in parallel.
Which is it?

Basically you'll be advised to use a library to handle this, but I'm not a big fan of libraries since they often times offer a lot more than you need, which is not necessarily a bad thing though.
To load scripts in order you need to bind the second to load when the first one has finished loading.
var script1 = document.createElement('script');
script1.onload = function() {
// script 1 has loaded
var script2 = document.createElement('script');
document.head.appendChild(script2);
script2.src = "...";
}
document.head.appendChild(script1);
script1.src = "...";

Related

Including dynamic script in jQuery [duplicate]

Any idea why the piece of code below does not add the script element to the DOM?
var code = "<script></script>";
$("#someElement").append(code);
The Good News is:
It's 100% working.
Just add something inside the script tag such as alert('voila!');. The right question you might want to ask perhaps, "Why didn't I see it in the DOM?".
Karl Swedberg has made a nice explanation to visitor's comment in jQuery API site. I don't want to repeat all his words, you can read directly there here (I found it hard to navigate through the comments there).
All of jQuery's insertion methods use
a domManip function internally to
clean/process elements before and
after they are inserted into the DOM.
One of the things the domManip
function does is pull out any script
elements about to be inserted and run
them through an "evalScript routine"
rather than inject them with the rest
of the DOM fragment. It inserts the
scripts separately, evaluates them,
and then removes them from the DOM.
I believe that one of the reasons jQuery
does this is to avoid "Permission
Denied" errors that can occur in
Internet Explorer when inserting
scripts under certain circumstances.
It also avoids repeatedly
inserting/evaluating the same script
(which could potentially cause
problems) if it is within a containing
element that you are inserting and
then moving around the DOM.
The next thing is, I'll summarize what's the bad news by using .append() function to add a script.
And The Bad News is..
You can't debug your code.
I'm not joking, even if you add debugger; keyword between the line you want to set as breakpoint, you'll be end up getting only the call stack of the object without seeing the breakpoint on the source code, (not to mention that this keyword only works in webkit browser, all other major browsers seems to omit this keyword).
If you fully understand what your code does, than this will be a minor drawback. But if you don't, you will end up adding a debugger; keyword all over the place just to find out what's wrong with your (or my) code. Anyway, there's an alternative, don't forget that javascript can natively manipulate HTML DOM.
Workaround.
Use javascript (not jQuery) to manipulate HTML DOM
If you don't want to lose debugging capability, than you can use javascript native HTML DOM manipulation. Consider this example:
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "path/to/your/javascript.js"; // use this for linked script
script.text = "alert('voila!');" // use this for inline script
document.body.appendChild(script);
There it is, just like the old days isn't it. And don't forget to clean things up whether in the DOM or in the memory for all object that's referenced and not needed anymore to prevent memory leaks. You can consider this code to clean things up:
document.body.removechild(document.body.lastChild);
delete UnusedReferencedObjects; // replace UnusedReferencedObject with any object you created in the script you load.
The drawback from this workaround is that you may accidentally add a duplicate script, and that's bad. From here you can slightly mimic .append() function by adding an object verification before adding, and removing the script from the DOM right after it was added. Consider this example:
function AddScript(url, object){
if (object != null){
// add script
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "path/to/your/javascript.js";
document.body.appendChild(script);
// remove from the dom
document.body.removeChild(document.body.lastChild);
return true;
} else {
return false;
};
};
function DeleteObject(UnusedReferencedObjects) {
delete UnusedReferencedObjects;
}
This way, you can add script with debugging capability while safe from script duplicity. This is just a prototype, you can expand for whatever you want it to be. I have been using this approach and quite satisfied with this. Sure enough I will never use jQuery .append() to add a script.
I've seen issues where some browsers don't respect some changes when you do them directly (by which I mean creating the HTML from text like you're trying with the script tag), but when you do them with built-in commands things go better. Try this:
var script = document.createElement( 'script' );
script.type = 'text/javascript';
script.src = url;
$("#someElement").append( script );
From: JSON for jQuery
It is possible to dynamically load a JavaScript file using the jQuery function getScript
$.getScript('http://www.whatever.com/shareprice/shareprice.js', function() {
Display.sharePrice();
});
Now the external script will be called, and if it cannot be loaded it will gracefully degrade.
What do you mean "not working"?
jQuery detects that you're trying to create a SCRIPT element and will automatically run the contents of the element within the global context. Are you telling me that this doesn't work for you? -
$('#someElement').append('<script>alert("WORKING");</script>');
Edit: If you're not seeing the SCRIPT element in the DOM (in Firebug for example) after you run the command that's because jQuery, like I said, will run the code and then will delete the SCRIPT element - I believe that SCRIPT elements are always appended to the body... but anyway - placement has absolutely no bearing on code execution in this situation.
This works:
$('body').append($("<script>alert('Hi!');<\/script>")[0]);
It seems like jQuery is doing something clever with scripts so you need to append the html element rather than jQuery object.
Try this may be helpful:
var fileref=document.createElement('script');
fileref.setAttribute("type","text/javascript");
fileref.setAttribute("src","scriptAnalytics.js");
document.getElementsByTagName("head")[0].appendChild(fileref);
I want to do the same thing but to append a script tag in other frame!
var url = 'library.js';
var script = window.parent.frames[1].document.createElement('script' );
script.type = 'text/javascript';
script.src = url;
$('head',window.parent.frames[1].document).append(script);
<script>
...
...jQuery("<script></script>")...
...
</script>
The </script> within the string literal terminates the entire script, to avoid that "</scr" + "ipt>" can be used instead.
Adding the sourceURL in the script file helped as mentioned in this page:
https://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/
In the script file, add a statement with sourceURL like "//# sourceURL=foo.js"
Load the script using jQuery $.getScript() and the script will be available in "sources" tab in chrome dev tools
Your script is executing , you just can't use document.write from it. Use an alert to test it and avoid using document.write. The statements of your js file with document.write will not be executed and the rest of the function will be executed.
This is what I think is the best solution. Google Analytics is injected this way.
var (function(){
var p="https:" == document.location.protocol ? "https://" : "http://";
d=document,
g=d.createElement('script'),
s=d.getElementsByTagName('script')[0];
g.type='text/javascript';
g.src=p+'url-to-your-script.js';
s.parentNode.insertBefore(g,s); })();
You don't need jQuery to create a Script DOM Element. It can be done with vanilla ES6 like so:
const script = "console.log('Did it work?')"
new Promise((resolve, reject) => {
(function(i,s,o,g,r,a,m){
a=s.createElement(o),m=s.getElementsByTagName(o)[0];
a.innerText=g;
a.onload=r;m.parentNode.insertBefore(a,m)}
)(window,document,'script',script, resolve())
}).then(() => console.log('Sure did!'))
It doesn't need to be wrapped in a Promise, but doing so allows you to resolve the promise when the script loads, helping prevent race conditions for long-running scripts.
Append script to body:
$(document).ready(function() {
$("<script>", { src : "bootstrap.min.js", type : "text/javascript" }).appendTo("body");
});
Another way you can do it if you want to append code is using the document.createElement method but then using .innerHTML instead of .src.
var script = document.createElement( 'script' );
script.type = 'text/javascript';
script.innerHTML = 'alert("Hey there... you just appended this script to the body");';
$("body").append( script );
I tried this one and works fine. Just replace the < symbol with that \x3C.
// With Variable
var code = "\x3Cscript>SomeCode\x3C/script>";
$("#someElement").append(code);
or
//Without Variable
$("#someElement").append("\x3Cscript>SomeCode\x3C/script>");
You can test the code here.
Can try like this
var code = "<script></" + "script>";
$("#someElement").append(code);
The only reason you can't do "<script></script>" is because the string isn't allowed inside javascript because the DOM layer can't parse what's js and what's HTML.
I wrote an npm package that lets you take an HTML string, including script tags and append it to a container while executing the scripts
Example:
import appendHtml from 'appendhtml';
const html = '<p>Hello</p><script src="some_js_file.js"></script>';
const container = document.getElementById('some-div');
await appendHtml(html, container);
// appendHtml returns a Promise, some_js_file.js is now loaded and executed (note the await)
Find it here: https://www.npmjs.com/package/appendhtml
Just create an element by parsing it with jQuery.
<div id="someElement"></div>
<script>
var code = "<script>alert(123);<\/script>";
$("#someElement").append($(code));
</script>
Working example: https://plnkr.co/edit/V2FE28Q2eBrJoJ6PUEBz

Injected Javascript seems to run asynchronously

I'm making a bookmarklet version of a Greasemonkey extension that appends two <script> tags to the page body: the first one is a largish library, the second calls a function from that library.
Generally, it takes two or more attempts (never one, sometimes more than two) to get the script to properly fire. Running the function directly from the console after injecting the library also works. If I check my error logs, I get the message that my injected method doesn't exist yet - hence the title: despite the fact that JavaScript ought to be single threaded, somehow the second method tag is being run first, before the library is done loading.
The bookmarklet code, before minifying, looks like this:
document.body.appendChild(document.createElement('script')).src = "https://my-site-address";
var scrN = document.createElement('script');
var txtN = document.createTextNode("main.Main().main(document.location.href)");
scrN.appendChild(txtN);
document.body.appendChild(scrN);
and the generated html is simply
<script src="https://my-site-address"></script>
<script>main.Main().main(document.location.href)</script>
Use the load event of the script element to execute the function in it:
var script = document.createElement("script");
script.onload = function() { main.Main().main(document.location.href); };
script.src = "https://my-site-address";
document.body.appendChild(script);

Warning: A call to document.write() from an asynchronously-loaded external script was ignored. How is this fixed?

In my Ruby on Rails application I am using the Facebox plugin for an Ajax pop up window. I have 2 pages called add_retail_stores/new.html.erb and add_retail_stores/new.js. The new.js page inherits all elements from the new.html.erb page so it looks exactly alike. I have a Google map script on the HTML page that works as it should. But the new.js page that pops up on my different page called add_store_prices.html.erb page(<%= link_to add_retail_store_path, :remote => true %>)
I get the error:
Warning: A call to document.write() from an asynchronously-loaded external script was ignored.
Source File: http://localhost:3000/add_store_prices
Line: 0
I believe because it's trying to go through 2 functions/scripts. The first one for the Facebox and then the Google script. Anyone know how to handle this error?
EDIT:
I believe the Facebox plugin is using document.write but I am not sure where, perhaps in one of these 2 lines on my page?
new.js:
$.facebox('<%= escape_javascript(render :template => 'business_retail_stores/new.html') %>')
$('#facebox form').data('remote','true');
Don't use document.write. The script is being loaded asynchronously, which means it's detached from the document parsing state. There is quite literally NO WAY for the JS engine to know WHERE the document.write should be executed in the page.
The external script could load instantaneously and the document.write executes where the <script src="..."> tag is, or it could hit a net.burp and load an hour later, which means the document.write gets tagged at the end of the page. It's quite literally a race condition, so JS engines will ignore document.writes from scripts loaded asynchronously.
Convert the document.write to use regular DOM operations, guarded by a document.onload type handler.
If you have access to the .js file in question, your best solution is going to be to modify the "document.write()" method and replace it with whatever makes sense in order to distribute the content contained within.
The reasons for this are very well described above.
If you are using document.write to write html tags to the page:
document.write("<script src=...></script>");
or
document.write("<img href=... />");
Consider using the same sort of asynchronous format you've already been using:
// Add/Remove/Sugar these components to taste
script = document.createElement("script");
script.onload = function () { namespaced.func.init(); };
script.src = "http://...";
document.getElementsByTagName("script")[0].parentNode.appendChild(script);
If you're looking to append DOM elements that are for the user to see and interact with, then you're better off either:
a) Grabbing a specific containter (section/div) by id, and appending your content:
document.getElementById("price").innerHTML = "<span>$39.95</span>";
b) Building content off-DOM and injecting it into your container:
var frag = document.createDocumentFragment(),
span = document.createElement("span");
span.innerText = "39.95";
frag.appendChild(span);
document.getElementById("price").appendChild(frag);
Again, Sugar to your liking.
If you DON'T have access to mod this second .js file, I'd suggest taking it up with them.
I had the same problem loading google maps with the places library. I temporarily override the write function to create a new script element in the head.
(function() {
var docWrite = document.write;
document.write = function(text) {
var res = /^<script[^>]*src="([^"]*)"[^>]*><\/script>$/.exec(text);
if (res) {
console.log("Adding script " + res[1]);
var head = document.getElementsByTagName('head')[0];
var script = document.createElement("script");
script.src = res[1];
head.appendChild(script);
} else {
docWrite(text);
}
}
})();
Now all I have to do to load a script asynchronously is
document.write('<script src="http://maps.googleapis.com/maps/api/js?libraries=places"></script>');

JavaScript: How to download JS asynchronously?

On my web site, I'm trying to accomplishes the fastest page load as possible.
I've noticed that it appears my JavaScript are not loading asynchronously. Picture linked below.
alt text http://img249.imageshack.us/img249/2452/jsasynch2.png
How my web site works is that it needs to load two external JavaScript files:
Google Maps v3 JavaScript, and
JQuery JavaScript
Then, I have inline JavaScript within the HTML that cannot be executed until those two files above are downloaded.
Once it loads these external javascript files, it then, and only then, can dynamically render the page. The reason why my page can't load until both Google Maps and JQuery are loaded is that - my page, based on the geolocation (using Gmaps) of the user will then display the page based on where they are located (e.g. New York, San Francisco, etc). Meaning, two people in different cities viewing my site will see different frontpages.
Question: How can I get my JavaScript files to download asynchronously so that my overall page load time is quickest?
UPDATE:
If I were to download, somehow, Google-maps and JQuery asynchronously, how would I create an event that would be fired once both Google-maps and JQuery have downloaded since my page has a hard dependency on those files to execute.
UPDATE 2
Even though there are 3 answers below, none still actually answer the problem I have. Any help would be greatly appreciated.
HTTP downloads are generally limited by browsers to two simultaneous downloads per domain. This is why some sites have the dynamic content on www.domain.tla and the images and javascript on static.domain.tla.
But browsers act slightly differently with scripts, while a script is downloading, however, the browser won't start any other downloads, even on different hostnames.
The standard solution is to move scripts to the bottom of the page, but there is a workaround that might or might not work for you: Insert the script DOM element using Javascript.
You could use something like this, which works pretty well in most browsers. It has some issues in IE6 at least, but I don't really have the time to investigate them.
var require = function (scripts, loadCallback) {
var length = scripts.length;
var first = document.getElementsByTagName("script")[0];
var parentNode = first.parentNode;
var loadedScripts = 0;
var script;
for (var i=0; i<length; i++) {
script = document.createElement("script");
script.async = true;
script.type = "text/javascript";
script.src = scripts[i];
script.onload = function () {
loadedScripts++;
if (loadedScripts === length) {
loadCallback();
}
};
script.onreadystatechange = function () {
if (script.readyState === "complete") {
loadedScripts++;
if (loadedScripts === length) {
loadCallback();
}
}
};
parentNode.insertBefore(script, first);
}
};
require([
"http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js",
"http://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js",
"http://ajax.googleapis.com/ajax/libs/yui/2.7.0/build/yuiloader/yuiloader-min.js"
], function () {
console.log(jQuery);
console.log($);
console.log(YAHOO);
});
Someone asked me to comment on this thread, but that was before #lonut posted a response. #lonut's code is a very good solution, but I have some comments (critical and not so critical):
First, #lonut's code assumes that the scripts do NOT have load dependencies on the other scripts. This is a little hard to explain, so let's work with the simple example of jquery.min.js and prototype.js. Suppose we have a simple page that just loads these two scripts like this:
<script src="jquery.min.js"></script>
<script src="prototype.js"></script>
Remember - there's nothing else in the page - no other JavaScript code. If you load that page the two scripts get downloaded and everything's fine. Now, what happens if you remove the jquery.min.js script? If you get errors from prototype.js because it's trying to reference symbols defined in jquery.min.js, then prototype.js has a load dependency on jquery.min.js - you cannot load prototype.js unless jquery.min.js has already been loaded. If, however, you don't get any errors, then the two scripts can be loaded in any order you wish. Assuming you have no load dependencies between your external scripts, #lonut's code is great. If you do have load dependencies - it gets very hard and you should read Chapter 4 in Even Faster Web Sites.
Second, one problem with #lonut's code is some versions of Opera will call loadCallback twice (once from the onload handler and a second time from the onreadystatechange handler). Just add a flag to make sure loadCallback is only called once.
Third, most browsers today open more than 2 connections per hostname. See Roundup on Parallel Connections.
The LABjs dynamic script loader is designed specifically for this type of case. For instance, you might do:
$LAB
.script("googlemaps.js")
.script("jquery.js")
.wait(function(){
// yay, both googlemaps and jquery have been loaded, so do something!
});
If the situation was a little more complex, and you had some scripts that had dependencies on each other, as Steve Souders has mentioned, then you might do:
$LAB
.script("jquery.js")
.wait() // make sure jquery is executed first
.script("plugin.jquery.js")
.script("googlemaps.js")
.wait(function(){
// all scripts are ready to go!
});
In either case, LABjs will download all of the scripts ("jquery.js", "googlemaps.js", and "plugin.jquery.js") in parallel, as least up to the point the browser will allow. But by judicious use of the .wait() in the chain, LABjs will make sure they execute in the proper order. That is, if there's no .wait() in between the two scripts in the chain, they will each execute ASAP (meaning indeterminate order between tehm). If there's a .wait() in between two scripts in the chain, then the first script will execute before the second script, even though they loaded in parallel.
Here is how I've managed to load gmaps asynchronously on a jquery mobile:
First, you can load jquery (i.e. with the require function posted above by IonuČ› G. Stan)
Then you can make use of the callback param in gmaps to do the following:
<!DOCTYPE html>
<html>
<body>
<script type="text/javascript">
var require = function (scripts, loadCallback) {
var length = scripts.length;
var first = document.getElementsByTagName("script")[0];
var parentNode = first.parentNode;
var loadedScripts = 0;
var script;
for (var i=0; i<length; i++) {
script = document.createElement("script");
script.async = true;
script.type = "text/javascript";
script.src = scripts[i];
script.onload = function () {
loadedScripts++;
if (loadedScripts === length) {
loadCallback();
}
};
script.onreadystatechange = function () {
if (script.readyState === "complete") {
loadedScripts++;
if (loadedScripts === length) {
loadCallback();
}
}
};
parentNode.insertBefore(script, first);
}
};
require([
"http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js",], function () {
$.ajax({
type: "GET",
url: 'http://maps.googleapis.com/maps/api/js?v=3&sensor=false&callback=setMyMap',
dataType: "script"
});
});
function setMyMap() {
console.log('your actions here');
var coords = new google.maps.LatLng(40.5439532,-3.6441775);
var mOptions = {
zoom: 8,
center: coords,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
var map = new google.maps.Map(document.getElementById("gmap"), mOptions);
}
</script>
<div id="gmap" style="width:299px; height:299px"></div>
</body>
The point is load jquery async (whathever method you choose) and on that callback place a new async call to gmaps with your starting method in the callback param of the gmaps script string.
Hope it helps
Regardless what order they download in, the scripts should be parsed/executed in the order in which they occur on the page (unless you use DEFER).
So, you can put both Google Maps first in the head, THEN JQuery. Then, in the body of your page somewhere:
<script language="Javascript">
function InitPage() {
// Do stuff that relies on JQuery and Google, since this script should
// not execute until both have already loaded.
}
$(InitPage); // this won't execute until JQuery is ready
</script>
But this does have the disadvantage of blocking your other connections while loading the beginning of the page, which isn't so awesome for page performance.
Instead, you can keep JQuery in the HEAD, but load the Google scripts from the InitPage() function, using JQuery's Javascript-loading functionality rather than the Google JSAPI. Then start your rendering when that call-back function executes. Same as the above, but with this InitPage() function instead:
function InitPage() {
$.getScript('Google Maps Javascript URL', function() {
// Safe to start rendering now
});
Move your javascript includes (<script src="...) from the HEAD element to the end of your BODY element. Generally whatever is placed in the HEAD is loaded synchronously, whatever is placed in the BODY is loaded asynchronously. This is more or less true for script includes, however most browsers these days block everything below the script until it is loaded - hence why having scripts included at the bottom of the body is best practice.
Here is the YUI guildline for this which explains it in further detail:
http://developer.yahoo.net/blog/archives/2007/07/high_performanc_5.html
This is also the reason why stylesheets should be in the head, and javascript should be in the body. As we do not want to see our page turn from spaghetti to niceness while the styles load asynchronously, and we don't want to wait on our javascript while our page loads.
The objective you have in mind would be served by using requireJS. RequireJS downloads the js resources asynchronously. Its a very simple and useful library to implement. Please read more here. http://requirejs.org/

dynamic script tag loading is not working as expected

We have an application that uses both the google closure and dojo libraries. We have the following in our index page which works as expected:
<script type="text/javascript" src="runtime/src/lib/google-closure-rev26/closure/goog/base.js"></script>
<script type="text/javascript" src="runtime/src/lib/dojo_release_132_src/dojo/dojo.js"></script>
<script type="text/javascript" src="runtime/src/core/loader.js"></script>
We would like to use only one script tag in the actual html source. So we tried to do the following:
<head>
<script type="text/javascript" src="runtime/src-bootstrap.js"></script>
</head>
and then in src-bootstrap.js:
var head = document.getElementsByTagName("head")[0];
var s1 = document.createElement("script");
s1.type = "text/javascript";
s1.src = "runtime/src/lib/google-closure-rev26/closure/goog/base.js";
var s2 = document.createElement("script");
s2.type = "text/javascript";
s2.src = "runtime/src/lib/dojo_release_132_src/dojo/dojo.js";
var s3 = document.createElement("script");
s3.type = "text/javascript";
s3.src = "runtime/src/core/loader.js";
head.appendChild(s1);
head.appendChild(s2);
head.appendChild(s3);
However, this doesn't work in FF. core/loader.js runs before dojo is loaded completely. Any ideas why this doesn't work?
For this type of mechanism, you'd be better off using document.write() to include your scripts. The technique you're currently using is suited to lazy-loading scripts, and it downloads and executes the scripts asynchronously: http://www.nczonline.net/blog/2009/06/23/loading-javascript-without-blocking/
...or you could have a build process that actually concatenates these files, and just request the one script, which would save on the number of requests too, as what you've actually done is increased the number of requests.
My guess would be that because you are creating the elements through the DOM, instead of having them as markup, the browser doesn't wait for one script to be finished before executing the next (as would be the case in a straight <script></script><script></script>setup).
How about appending the scripts in a cascaded form (Google closure appends s2 at its end, Dojo s3) or, as Lee Kowalkowski suggests, writing <script> commands using document.write()?
Generally Speaking - add a namespace under window, and edit your external resources-
leave one action.js or main.js file locally, that will be added a method, preferably under global scope (meaning under window..).
edit your external resource, adding 1 extra line at the end, calling for a method on action.js or main.js, when the loading will be done, the "callback like" will execute that method you've been adding to the DOM previously. it works very much like JSONProtocol.
it works wonders even with with the most complex combination of dynamically loaded resources.
see the example for this very similar solution provided for dynamically loading the Google-Closure-Library on another thread (https://stackoverflow.com/a/17226714/257319)

Categories