I'm having some concurrency issues with a webpage I'm building. Basically, I have three script files that I'm using to hold some data:
script1.js:
var myValue = 1;
script2.js:
var myValue = 2;
script3.js:
var myValue = 3;
And I have one page, mypage.html that basically looks like this:
<html>
<script>
function get_number()
{
var script_file = GetQueryStringValue( "run" );
e = document.createElement('script');
e.type='text/javascript';
e.src = script_file + ".js"
head.appendChild( e );
document.write( myValue );
}
</script>
<body onload="get_number()">
<div onclick="get_number()">Click me!</div>
</body>
</html>
The main idea with this page is that you would query it like this:
mypage.html?run=script1
which would tell the get_number() function to dynamically insert script1.js in to mypage.htm. Then, I call get_number() to display the value loaded from the script.
Now, I've stripped down the above to what I think are the relevant parts and I've left out a bunch of stuff, obviously. My actual code loads a large array of values and is more interesting... But, I'm hoping someone can help me out with this regardless.
What I'm finding is that in IE, the number displays correctly.
In Firefox, Chrome and Safari, I get an error that myValue is undefined. However, if I click the Click me div that I created, the number displays correctly. So, I know I'm correctly loading the javascript external file. But, it just isn't loaded in time for my get_number() function to work correctly onload.
Now, I hacked up my file a little so that the get_number function looks like this:
function get_number()
{
var script_file = GetQueryStringValue( "run" );
e = document.createElement('script');
e.type='text/javascript';
e.src = script_file + ".js"
head.appendChild( e );
setTimeout( function() { document.write( myValue ), 10 } );
}
The setTimeout delays enough for the DOM to be updated and the javascript to be loaded in most cases. But, this is a total hack. Firefox tends to load this 'improved' code correctly all the time while Chrome and Safari manage to get the value about 50% of the time.
So, I guess I'm wondering, is there a better way to accomplish what I'm trying to do here? It's very important that the value be driven externally (by the query string), but other than that requirement, I'm very flexible.
This method of dynamically loading script files using DOM methods like head.appendChild is asynchronous, meaning that the page does not wait for it to load before running any further code. If you did not want this asynchronous behaviour, you could load them with regular elements in your HTML, or you could mess around with a 'synchronous' XMLHttpRequest to grab it and eval() to run it.
You probably don't want to do that, though, because being asynchronous makes the entire page load faster, anyway. You will probably just need to add some logic that waits for the dynamically loaded script to have loaded before you go on with the next bit of code. This may, however, involve polling using setInterval() until you notice a variable from the included script has been defined. Or, you could add code in the included script that calls a method to whatever has been waiting for it.
Or, you could look at how jQuery does something similar, look for where the ajax method is defined, specifically these parts...
var script = document.createElement("script");
// then later ...
script.onload = script.onreadystatechange = function(){
if ( !done && (!this.readyState ||
this.readyState == "loaded" || this.readyState == "complete") ) {
// do processing
head.removeChild( script );
}
};
// ...
head.appendChild(script);
Could you possibly add a function call to your script files, so that when they're run, they call the function that does your
document.write( myValue );
So that script1.js would be
var myValue = 1;
scriptLoaded();
and your main file would have:
function scriptLoaded(){
document.write( myValue );
}
Related
Let's say I've got my-file.js or some CDN file in different server that has
for (var i = 0; i < 1000; i ++) {
//something really long and hard to execute
}
//after this long execution
window.myObj = {
//initialization of some global object that I need
}
(I cannot change my-file.js...)
I want to add my-file.js asynchronously to page, and then, after it is loaded and EXECUTED I want to call some event like:
//when my my-file.js is loaded I finally use window.myObj
window.myObj.somefunc(); //yaaay
Is it possible? (cross browser for IE9+ is important for me, but any not critical)
Note:
In case file I need is on CDN or somewhere on different server - I need to remember about cross-domain policy, so I think loading content of file in ajax is not possible in such case.
Note 2:
http://www.chromestatus.com/features/5711871906676736 there is exacly what I need, but I bet it'll be few years before you can easly use it globally.
var script = document.createElement('script');
script.onload = function(){
// loaded
}
document.head.appendChild(script);
script.src = "path/to/script";
That's about the simplest example. And yes, the entire thing is async, hence it needed the onload handler.
You could also wrap it up in a function:
function getScript(src,callback){
var script = document.createElement('script');
script.onload = callback;
document.head.appendChild(script);
script.src = src;
}
getScript('path/to/js',function(){
//loaded
});
However, thinking out of the box, it sounds like you need dependency management. Use RequireJS. Should fit your needs.
jQuery getScript has a callback you can use to execute events after a file is loaded, or you can use script.onload as in Joseph's example:
http://api.jquery.com/jquery.getscript/
If you want to wait until a certain property is available (ie after some work has finished), you can create an interval and then clear it as soon as the property is found in window, ie:
var waitForObj = setInterval(function(){
if(window.myObj && window.myObj.somefunc){
clearInterval(waitForObj);
//Do some stuff you need this object for
}
}, 100);
I'm trying to write a function which will append a javascript file to the DOM, but I am looking to have the rest of the code wait until the newly added JS file is completely loaded. Here is an example of what I am trying to accomplish, although this code doesn't work properly:
$(document).ready(function () {
var newScript = document.createElement("script");
newScript.setAttribute("type", "text/javascript");
newScript.src = "http://www.domain.com/script.js";
document.getElementsByTagName("body")[0].appendChild(newScript);
$(newScript).ready(function () { // This is the idea of what I'm trying to do, but this doesn't seem to actually wait until the new file is completely loaded.
foo.bar(); // foo is a new global variable which is declared in the newScript. This causes an error "foo is not defined".
// Here is where more code I wish to execute should continue.
});
});
As Musa mentioned in the comments above. Use jQuery's getScript and use the success callback function to trigger your other functions.
If you want more robust module loading functionality, then require.js works great in this capacity. Check out: http://requirejs.org/docs/why.html for an overview. I use require.js specifically for lazy-loading script modules.
Using jQuery (as you've tagged), it's extremely easy:
$.getScript('/script.js', function() {
foo.bar();
});
There's a few different ways to do this... via libraries or "by hand," so to speak, using only the browser APIs and straight JavaScript. For an answer on how to do this in JS only, look here for Stoyan's post to give you guidance. Basically, the gist of it is setting an event handler to both the script's unload and onreadystatechange properties and then check to see if the readyState is "loaded" or "complete" (if it exists at all). It would look something like this:
var done = false;
newScript.onload = newScript.onreadystatechange = function () {
if (!done && (!newScript.readyState || newScript.readyState === "loaded" || newScript.readyState === "complete)) {
done = true;
// run your actual code here
}
};
I have a small script
document.write("<html><head><script src='/js/jquery-1.4.2.min.js' type='text/javascript'></scr"
+ "ipt><script>alert($"+"().jquery);</scri" + "pt></head></html>");
But I get a $ is undefined in Internet Explorer. I think it tries to run the script before loading the library.
However this runs in Firefox. Please help.
Edit: I open a new window and write to that window's document.
What's wrong with creating the <script> tag the proper way? document.write is evil, end of discussion.
Try with this:
var load_script = function(options) {
options.owner_document = options.owner_document || document;
var script_tag = options.owner_document.createElement('script');
script_tag.setAttribute('type', 'text/javascript');
script_tag.setAttribute('src', options.src);
script_tag.onload = function() {
script_tag.onreadystatechange = null;
options.callback && options.callback();
};
script_tag.onreadystatechange = function() {
if (script_tag.readyState == 'loaded' || script_tag.readyState == 'complete') {
script_tag.onload = null;
options.callback && options.callback();
}
};
options.owner_document.getElementsByTagName('head')[0].appendChild(script_tag);
};
as you see, there's a simple API on that snippet:
src - source of the script
owner_document - document where the script will be inserted, defaults to the current document where the script is running from
callback - function to run after the script has loaded, anything that requires the src script is safe to be run inside this closure.
example usage:
// sample loading of jQuery
load_script({
src: '/js/jquery-1.4.2.min.js',
callback: function() {
// jQuery is available at this point, run your code.
}
});
alternatively, you can use loaders like requiere.js and LABjs
The thing is that you have unintentionally triggered IE to load your scripts in a non-blocking way. Read this for more information: Loading JavaScript without blocking.
The page suggests that you use this code to get notified when the script has finished loading:
//Internet Explorer only
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "/js/jquery-1.4.2.min.js";
script.onreadystatechange = function(){
if (script.readyState == "loaded" ||
script.readyState == "complete"){
script.onreadystatechange = null;
// Your code goes here.
alert("Script is ready!");
}
};
document.body.appendChild(script);
try running your second script on page load(window.onload) , or try inserting the script in the body, not in the head section.
try :
document.write("<html><head><script src='/js/jquery-1.4.2.min.js' type='text/javascript'></scr"
+ "ipt><script>window.onload = function(){alert($"+"().jquery);};</scri" + "pt></head></html>");
the browser won't load the library instantly (unless it has it in its cache) so your script might get called before the library is loaded.
See this solution:
JavaScript's document.write Inline Script Execution Order
(also note the comment, it's important)
You could write a recursive function that waits for the jquery library to load and then executes any code once the library is loaded. You probably want to add a conditional that breaks out of the recursion if it gets too deep.
document.write("<html><head><script src='/js/jquery-1.4.2.min.js' type='text/javascript'></scr"+ "ipt><script type='text/javascript'>var wait_for_jQuery = function() {if (typeof $ === 'undefined') {setTimeout('wait_for_jQuery()', 1000);} else {alert($"+"().jquery);}}; wait_for_jQuery(); </scri" + "pt></head></html>");
Working example here: http://www.jsfiddle.net/YqTgM/36/
Have you tried moving the script tag running your code to the end of the body tag? According to Yahoo's best practices (creators of YSlow):
The problem caused by scripts is that
they block parallel downloads. The
HTTP/1.1 specification suggests that
browsers download no more than two
components in parallel per hostname.
If you serve your images from multiple
hostnames, you can get more than two
downloads to occur in parallel. While
a script is downloading, however, the
browser won't start any other
downloads, even on different
hostnames.
So the result would look like...
document.write("<html><head><script src='/js/jquery-1.4.2.min.js' type='text/javascript'></scr"
+ "ipt></head><body><script>alert($"+"().jquery);</scri" + "pt></body></html>");
Although I would also advocate not using document.write().
Give this a try?
http://jsfiddle.net/MP75r/
If you want simplified conditional loading of jQuery, there are many options out there already; no need to reinvent the wheel. Already mentioned are RequireJS and LabJS. I am using Head.js and it couldn't be easier to accomplish the goal of non-blocking loading while respecting execution order.
you are just creating a tag to reference jQuery, and as soon as its created next statement is an alert. There is no way specified by you that alert statement should run after after file load.
A simple timer should do it.
(function(){
function wait() {
if (typeof jQuery == 'undefined') {
setTimeout(wait, 50);
} else {
run(); // your code to execute
}
}
wait();
})();
Example on jsfiddle: http://jsfiddle.net/madr/Gtafq/
Inside the <script></script> tag wrap your code in a document ready function so the libraries are loaded before running.
$(document).ready(function() {
// put all your jQuery code here
});
When using an importjs() type of function (see below for an example), jQuery doesn't seem to be loading before the code following it.
Here's a sample html file:
<html>
<head></head>
<body>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
function importjs(jsFile) {
var body = document.getElementsByTagName('head').item(0);
var scpt = document.createElement('script');
scpt.src = jsFile;
scpt.type = 'text/javascript';
body.appendChild(scpt);
}
var f1="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js";
//importjs(f1);
var $j=jQuery;
alert("hello stackoverflow!");
</script>
</body>
</html>
With the above code, the alert should successfully fire.
Next, comment out the first script block, i.e. the one explicitly loading jQuery, and uncomment the importjs(f1) line in the second script block. This time, the alert does not fire, at least in firefox and safari.
Now, put in an extra alert before the line "var $j=jQuery". For me, it works in both browsers, regardless of how long or short I wait. A setTimeout would probably also do the trick, but it's also not an ideal way to program something like this.
If javascript is single-threaded, why does the importjs fail? Is it because the new element created by importjs doesn't get 'executed' until the first block finishes, or should the new element be executed as soon as it is created?
There are several problems here:
you have jQuery duplicated, one in the html, one in the js
dynamically added javascript won't be available immediately
if you load scripts this way the dependant code should be in a callback function
function importjs(jsFile, callback) {
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.src = jsFile;
script.type = 'text/javascript';
script.onload = script.onreadystatechange = function() {
// execute callback
if (callback) callback();
// prevent memory leak in IE
script.onload = null;
head.removeChild(script);
};
head.appendChild(script);
}
then you should use it as:
importjs("jquery.js", function(){
// all jQuery dependant code
// goes here...
});
UPDATE
There is a more robust solution for including javascript files which allows you to:
include multiple files that are related
ensure they are executed in order
load them in a non-blocking way (parallel with other resources)
I'm still working on this script, but pretty much works right now. Be sure to check it out.
It combines the advantages of different techniques to give a huge benefit on page load time. Here is a related article: Loading Scripts Without Blocking
The syntax is:
include(['jquery.js','jquery-ui.js'], myjQueryCode); // executed in order
I have a script that knows to load dynamiclly scripts that contains javascript classes.
i'm loading the class script using the following code:
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "myscript.js";
head.appendChild(script);
i'm then trying to create the new class using eval:
var classObj = eval(" new MyClass()" );
the problem is that the code of the eval is being executed bofre the script has been loaded into memory and i get an error that the MyClass is undefined.
Is there a way to synch these events? i need to make sure the script is fully being loaded into memory before i can start allocating classes from it.
You need to attach an event handler to either the onload method, in browsers compliant with Web standards, or the onreadystatechange, checking for the script.readyState property getting equal to "loaded" or "complete", in Internet Explorer.
Before you get notified that the script was loaded, you are probably trying to access objects, functions and properties that have not been declared or created yet.
Here is an example function, extracted from the module bezen.dom.js in my Javascript library, bezen.org:
var appendScript = function(parent, scriptElt, listener) {
// append a script element as last child in parent and configure
// provided listener function for the script load event
//
// params:
// parent - (DOM element) (!nil) the parent node to append the script to
// scriptElt - (DOM element) (!nil) a new script element
// listener - (function) (!nil) listener function for script load event
//
// Notes:
// - in IE, the load event is simulated by setting an intermediate
// listener to onreadystate which filters events and fires the
// callback just once when the state is "loaded" or "complete"
//
// - Opera supports both readyState and onload, but does not behave in
// the exact same way as IE for readyState, e.g. "loaded" may be
// reached before the script runs.
var safelistener = catchError(listener,'script.onload');
// Opera has readyState too, but does not behave in a consistent way
if (scriptElt.readyState && scriptElt.onload!==null) {
// IE only (onload===undefined) not Opera (onload===null)
scriptElt.onreadystatechange = function() {
if ( scriptElt.readyState === "loaded" ||
scriptElt.readyState === "complete" ) {
// Avoid memory leaks (and duplicate call to callback) in IE
scriptElt.onreadystatechange = null;
safelistener();
}
};
} else {
// other browsers (DOM Level 0)
scriptElt.onload = safelistener;
}
parent.appendChild( scriptElt );
};
To adapt it to your needs, you may replace the call to catchError, which wraps the listener to catch and log errors, and use the modified function:
var appendScript = function(parent, scriptElt, listener) {
// append a script element as last child in parent and configure
// provided listener function for the script load event
//
// params:
// parent - (DOM element) (!nil) the parent node to append the script to
// scriptElt - (DOM element) (!nil) a new script element
// listener - (function) (!nil) listener function for script load event
//
// Notes:
// - in IE, the load event is simulated by setting an intermediate
// listener to onreadystate which filters events and fires the
// callback just once when the state is "loaded" or "complete"
//
// - Opera supports both readyState and onload, but does not behave in
// the exact same way as IE for readyState, e.g. "loaded" may be
// reached before the script runs.
var safelistener = function(){
try {
listener();
} catch(e) {
// do something with the error
}
};
// Opera has readyState too, but does not behave in a consistent way
if (scriptElt.readyState && scriptElt.onload!==null) {
// IE only (onload===undefined) not Opera (onload===null)
scriptElt.onreadystatechange = function() {
if ( scriptElt.readyState === "loaded" ||
scriptElt.readyState === "complete" ) {
// Avoid memory leaks (and duplicate call to callback) in IE
scriptElt.onreadystatechange = null;
safelistener();
}
};
} else {
// other browsers (DOM Level 0)
scriptElt.onload = safelistener;
}
parent.appendChild( scriptElt );
};
Since you seem to be able to edit the external script (since you tested it with an alert), why not just put this code in that script?
If you can't do that (maybe the extra code is generated or the first file is shared perhaps), just add a function call at the end of the script you're loading like this:
load_complete();
and then put your extra code in that function:
function load_complete() {
var classObj = eval(" new MyClass()" );
}
It's a lot simpler and foolproof than any kind of onload trigger. Also, if the js file is shared, then you can have different load_complete functions on every page that uses it (just be sure to always define a load_complete, even if it is empty).
I believe that this can actually be remedied by making sure you put the code loading the external script and the code using the external script separate script blocks like this:
<script>
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "myscript.js";
head.appendChild(script);
//this is in the same script block so it wouldn't work
//var classObj = eval(" new MyClass()" );
</script>
<script>
//this is in a separate script block so it will work
var classObj = eval(" new MyClass()" );
</script>
Use the jQuery (a JavaScript library) getScript function to load your script async. Use the callback function to create your objects. Example:
$.getScript("script.js", function () {
var classObj = new MyClass();
});
Are you sure that adding a <script> element to the DOM will cause the browser to actually evaluate the script at all? I have a vague memory of reading somewhere that it doesn't, but perhaps I inhaled a bit too much oven cleaner yesterday.
Amir, it looks to me as if the script is still sitting on the server, that is to say, has not been loaded by the browser. So your head.appendChild(script);is appending null. You can't fetch a script from the server just by saying its name, you need to request it and inject it into the page, using ajax, or by loading it in using <script> tag.