Inside a script file I have to dynamicaly import another script and use functions and variables defined inside of it.
Right now, I'm adding it to the HEAD section of the Page, but just after adding it, functions and variables defined inside the outer script are not loaded and ready for use yet. How can I do that and be sure that the script was fully loaded?
I've tried using script.onreadystatechange and script.onload callbacks but I'm having some browser compatibility issues.
How do I do that, as safely as possible, with pure JS and decent browser compatibility?
Sample:
uno.js:
var script = document.createElement("script");
script.src = "dos.js";
script.type = "text/javascript";
document.getElementsByTagName("head")[0].appendChild(script);
alert(outerVariable); // undefined
dos.js:
var outerVariable = 'Done!';
sample.html
<html>
<head>
<script type="text/javascript" src="uno.js"></script>
</head>
...
</html>
If you are concerned with non-IE browsers I would try using DOMContentLoaded to see if the script is fully loaded.
You can see more about it here
In fact this is sort of how JQuery works with document ready. This is the snippit from the jquery source:
if ( document.addEventListener ) {
// Use the handy event callback
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
// A fallback to window.onload, that will always work
window.addEventListener( "load", jQuery.ready, false );
// If IE event model is used
} else if ( document.attachEvent ) {
// ensure firing before onload,
// maybe late but safe also for iframes
document.attachEvent( "onreadystatechange", DOMContentLoaded );
// A fallback to window.onload, that will always work
window.attachEvent( "onload", jQuery.ready );
// If IE and not a frame
// continually check to see if the document is ready
var toplevel = false;
try {
toplevel = window.frameElement == null;
} catch(e) {}
if ( document.documentElement.doScroll && toplevel ) {
doScrollCheck();
}
}
If you were to look at the jquery code to copy what they do it's in the bind ready function of the full source code.
So far, this seems to be the better working approach. Tested on IE9, Firefox and Chrome.
Any concerns?
uno.js:
function loadScript(url, callback) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
if (navigator.appName=="Microsoft Internet Explorer") {
script.onreadystatechange = function(){
if (script.readyState == 'loaded') {
document.getElementsByTagName('head')[0].appendChild(script);
callback();
}
};
} else {
document.getElementsByTagName('head')[0].appendChild(script);
script.onload = function(){
callback();
};
}
}
loadScript('dos.js', function(){
alert(outerVariable); // "Done!"
});
use the onload (IE9-11,Chrome,FF) or onreadystatechange(IE6-9).
var oScript = document.getElementById("script")
oScript.src = "dos.js"
if (oScript.onload !== undefined) {
oScript.onload = function() {
alert('load')
}
} else if (oScript.onreadystatechange !== undefined) {
oScript.onreadystatechange = function() {
alert('load2')
}
}
Related
I'm loading a javascript external file from another javascript file present in the document and since its loaded, I want to call a function from the loaded js file.
Here is the load function:
function loadScript(url) {
var head = window.top.document.getElementsByTagName('head')[0];
var script = window.top.document.createElement('script');
script.src = url;
script.type= "text/javascript";
head.appendChild(script);
if(script.readyState) { //IE
script.onreadystatechange = function() {
if ( script.readyState === "loaded" || script.readyState === "complete" ) {
script.onreadystatechange = null;
console.log("[BANDEAU] script loaded");
testAlert();
}
};
} else { //Others
script.onload = function() {
console.log("[BANDEAU] script loaded");
testAlert();
};
}
}
So it works nice because the javascript file is succesfuly loaded but I cannot access the testAlert() method from the loaded javascript file, as I try in the code above, right after printing that the script is loaded. When I try to get the type of the function with typeOf on window[testAlert], I get an undefined. But when I try to execute the testAlert() method in the developer console, it works perfectly. Does anyone see what I'm doing wrong ?
Does the position in the DOM between the caller javascript file and the loaded javascript file might be the reason ?
You need to assign the load handlers BEFORE changing the src
function loadScript(url) {
var head = document.getElementsByTagName('head')[0]; // window.top in frames/iFrames
var script = document.createElement('script');
script.type = "text/javascript";
if (script.readyState) { //IE
script.onreadystatechange = function() {
if (script.readyState === "loaded" || script.readyState === "complete") {
script.onreadystatechange = null;
console.log("[BANDEAU] script loaded");
testAlert(); // window.top.testAlert() if needed
}
};
}
else {
script.onload = function() {
console.log("[BANDEAU] script loaded");
testAlert(); // window.top.testAlert() if needed
};
}
script.src = url;
head.appendChild(script);
}
In addition to what mplungjan said, I'm pretty sure you'd have to do an eval() on the loaded script in order to have a legitimate address for the call to testAlert().
Also, check out this link for more info.
I need to add jquery and then another script that relies on jquery.
I then need to have code that uses both assets but my problem is that i don't want my code to run until i know that both assets are loaded.
I think the process would be to load jquery and then wait until jquery is loaded by waiting for window.onload, then load the jquery plugin, then detect that the plugin has loaded, then load my own code that uses functions from the jquery plugin.
code so far:
// load jquery if it is not allready loaded and put it into no conflict mode so the $ is available for other librarys that might be allready on the page.
if(!window.jQuery) {
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js";
document.getElementsByTagName('head')[0].appendChild(script);
jQuery.noConflict(); // stop jquery eating the $
console.log("added jquery");
}
window.onload = function(e) {
// we know that jquery should be available now as the window has loaded
if ( !jQuery.isFunction(jQuery.fn.serializeObject) ) { // use jquery to ask if the plugins function is allready on the page (don't do this if the website already had the plugin)
// website didn't have the plugin so add it to the page.
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "https://cdnjs.cloudflare.com/ajax/libs/jquery-serialize-object/2.5.0/jquery.serialize-object.min.js";
document.getElementsByTagName('head')[0].appendChild(script);
}
if ( !jQuery.isFunction(jQuery.fn.serializeObject) ) {
// console.log("serializeObject is undefined");
// its going to be undefined here because Its still loading in the script
} else {
// console.log("we have serializeObject");
}
// I now dont know when to call my code that uses .serializeObject() because it could still be loading
// my code
var form_data_object = jQuery('form#mc-embedded-subscribe-form').serializeObject();
};
You have to do like
Include
<script type="text/javascript" id="AssetJS"></script>
Script
$("#AssetJS").attr("src", "Asset.js");
$("#AssetJS").load(function () {
//after loaded jquery asset do your code here
})
OK i managed to find another way that is working for my specific needs so I am answering my own question.
Using this function from http://www.sitepoint.com/dynamically-load-jquery-library-javascript/
function loadScript(url, callback) {
var script = document.createElement("script")
script.type = "text/javascript";
if (script.readyState) { //IE
script.onreadystatechange = function () {
if (script.readyState == "loaded" || script.readyState == "complete") {
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function () {
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
and usage in my case:
if(!window.jQuery) {
loadScript("https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js", function () {
jQuery.noConflict(); // stop jquery eating the $
console.log('jquery loaded');
if ( !jQuery.isFunction(jQuery.fn.serializeObject) ) { // use jquery to ask if the plugins function is already on the page
loadScript("https://cdnjs.cloudflare.com/ajax/libs/jquery-serialize-object/2.5.0/jquery.serialize-object.min.js", function () {
console.log('serialize loaded');
SURGE_start(); // both scrips where not on the website but have now been added so lets run my code now.
});
}
});
} else {
if ( !jQuery.isFunction(jQuery.fn.serializeObject) ) { // use jquery to ask if the plugins function is already on the page
loadScript("https://cdnjs.cloudflare.com/ajax/libs/jquery-serialize-object/2.5.0/jquery.serialize-object.min.js", function () {
console.log('serialize loaded');
SURGE_start(); // jquery was on the web page but the plugin was not included. now we have both scripts lets run my code.
});
} else {
SURGE_start(); // web page already had both scripts so just run my code.
}
}
An easy way is using headjs. It's working fine on several projects.
I've been working on a bookmarklet project that loads external jQuery.js file like this:
jq_script = document.createElement('SCRIPT');
jq_script.type = 'text/javascript';
jq_script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.js';
document.getElementsByTagName('head')[0].appendChild(jq_script);
But when I try use jQuery right after this, I receive:
Uncaught ReferenceError: jQuery is not defined
on the chrome JS console.
Is there any "event" that is called when a single DOM instance is loaded?
(or any event that gets triggered when an external JS file is loaded like this?)
<script> elements have an onload event.
You can do like this
function loadScript()
{
jq_script = document.createElement('SCRIPT');
jq_script.type = 'text/javascript';
jq_script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.js';
document.getElementsByTagName('head')[0].appendChild(jq_script);
}
window.onload = loadScript;
Update : Using latest jquery code below.
jQuery's getScript handles it in the below way. You can place your functionality inside if(!isAbort).
script = document.createElement( "script" );
script.async = "async";
if ( s.scriptCharset ) {
script.charset = s.scriptCharset;
}
script.src = s.url;
// Attach handlers for all browsers
script.onload = script.onreadystatechange = function( _, isAbort ) {
if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;
// Remove the script
if ( head && script.parentNode ) {
head.removeChild( script );
}
// Dereference the script
script = undefined;
// Callback if not abort
if ( !isAbort ) {
//**Do your stuff here**
}
}
};
// Use insertBefore instead of appendChild to circumvent an IE6 bug.
// This arises when a base node is used (#2709 and #4378).
head.insertBefore( script, head.firstChild );
UPDATE:
I have the following code:
<script type="text/javascript">
function addScript(url) {
var script = document.createElement('script');
script.src = url;
document.getElementsByTagName('head')[0].appendChild(script);
}
addScript('http://google.com/google-maps.js');
addScript('http://jquery.com/jquery.js');
...
// run code below this point once both google-maps.js & jquery.js has been downloaded and excuted
</script>
How can I prevent code from executing until all required JS have been downloaded and executed? In my example above, those required files being google-maps.js and jquery.js.
You can use the onload event of the script element for most browsers, and use a callback argument:
Edit: You can't really stop the execution of the code when you load scripts in this way (and making synchronous Ajax requests is a bad idea most of the times).
But you can chain callbacks, so if you have some code that depends on both, Two.js and Three.js, you can chain the loading actions, for example:
loadScript('http://example.com/Two.js', function () {
// Two.js is already loaded here, get Three.js...
loadScript('http://example.com/Three.js', function () {
// Both, Two.js and Three.js loaded...
// you can place dependent code here...
});
});
Implementation:
function loadScript(url, callback) {
var head = document.getElementsByTagName("head")[0],
script = document.createElement("script"),
done = false;
script.src = url;
// Attach event handlers for all browsers
script.onload = script.onreadystatechange = function(){
if ( !done && (!this.readyState || // IE stuff...
this.readyState == "loaded" || this.readyState == "complete") ) {
done = true;
callback(); // execute callback function
// Prevent memory leaks in IE
script.onload = script.onreadystatechange = null;
head.removeChild( script );
}
};
head.appendChild(script);
}
For IE, the onreadystatechange event has to be bound.
I just read CMS's answer, and decided that from his "most browsers" comment, I might have a crack at getting it work for ones that do not have this functionality natively.
Basically, it's an interval that polls for a variable.
var poll = window.setInterval(function() {
if (typeof myVar !== 'undefined') {
clearInterval(poll);
doSomething();
};
}, 100);
I've got a bookmarklet which loads jQuery and some other js libraries.
How do I:
Wait until the javascript library I'm using is available/loaded. If I try to use the script before it has finished loading, like using the $ function with jQuery before it's loaded, an undefined exception is thrown.
Insure that the bookmarklet I load won't be cached (without using a server header, or obviously, being that this is a javascript file: a metatag)
Is anyone aware if onload for dynamically added javascript works in IE? (to contradict this post)
What's the simplest solution, cleanest resolution to these issues?
It depends on how you are actually loading jQuery. If you are appending a script element to the page, you can use the same technique that jQuery uses to dynamically load a script.
EDIT: I did my homework and actually extracted a loadScript function from the jQuery code to use in your bookmarklet. It might actually be useful to many (including me).
function loadScript(url, callback)
{
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.src = url;
// Attach handlers for all browsers
var done = false;
script.onload = script.onreadystatechange = function()
{
if( !done && ( !this.readyState
|| this.readyState == "loaded"
|| this.readyState == "complete") )
{
done = true;
// Continue your code
callback();
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;
head.removeChild( script );
}
};
head.appendChild(script);
}
// Usage:
// This code loads jQuery and executes some code when jQuery is loaded
loadScript("https://code.jquery.com/jquery-latest.js", function()
{
$('my_element').hide();
});
To answer your first question: Javascript is interpreted sequentially, so any following bookmarklet code will not execute until the library is loaded (assuming the library was interpreted successfully - no syntax errors).
To prevent the files from being cached, you can append a meaningless query string...
url = 'jquery.js?x=' + new Date().getTime();
I've paid an attention that in Chrome the order of scripts that are loaded is undetermined, when using #Vincent Robert's technique. In this case a little modification helps:
(function() {
var callback = function() {
// Do you work
};
// check for our library existence
if (typeof (MyLib) == 'undefined') {
var sources = [
'http://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js',
'https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js',
'https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js',
'http://myhost.com/javascripts/mylib.min.js'];
var loadNextScript = function() {
if (sources.length > 0) {
var script = document.createElement('script');
script.src = sources.shift();
document.body.appendChild(script);
var done = false;
script.onload = script.onreadystatechange = function() {
if (!done
&& (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {
done = true;
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;
loadNextScript();
}
}
} else {
callback();
}
}
loadNextScript();
} else {
callback();
}
})();
I got a little closer with this, but not completely. It would be nice to have a discrete, example of a bookmarklet that demonstrated how to avoided caching.