I am working on a javascript that sequentially loads a list of other external javascript.
The code I have so far:
function loadJavascript(url){
var js = document.createElement("script");
js.setAttribute("type", "text/javascript");
js.setAttribute("src", url);
if(typeof js!="undefined"){
document.getElementsByTagName("head")[0].appendChild(js)
}
}
loadJavascript("Jquery.js");
loadJavascript("second.js");
loadJavascript("third.js");
The problem I ran into is that sometimes the other js files loads before the Jquery file completes its loading. This gives me some errors.
Is it possible to make it so that the next JS file is only initiated when the previous file is finished loading.
Thanks in advance
Sure there is, but there's entire libraries written around doing this. Stop reinventing the wheel and use something that already works. Try out yepnope.js or if you're using Modernizr it's already available as Modernizr.load
loadJavascript("Jquery.js");
$(function(){
$.getScript('second.js', function(data, textStatus){
$.getScript('third.js', function(data, textStatus){
console.log("loaded");
});
});
}
Also, consider using the Google or Microsoft CDN for the jQuery, it will save you bandwidth and hopefully your visitors will already have it cached.
Actually, it's not necessary to load jquery within a js function. But if you insist, you can callback to make sure other js loaded after jquery.
Still, I recommend you load jquery just before </body> then use $.getScript to load other .js
You could do a check to see if jQuery is loaded, not the best way to do it, but if you really have to wait until jQuery is loaded before loading the other scripts, this is how I would do it, by checking for $ :
loadJavascript("Jquery.js");
T=0;
CheckIfLoaded();
function CheckIfLoaded() {
if (typeof $ == 'undefined') {
if (T <= 3000) {
alert("jQuery not loaded within 3 sec");
} else {
T=T+200;
setTimeout(CheckIfLoaded, 200);
} else {
loadJavascript("second.js");
loadJavascript("third.js");
}
}
In technical terms: Browsers have a funny way of deciding I which order to execute/eval dynamically loaded JS, so after suffering the same pain and checking a lot of posts, libraries, plugins, etc. I came up with this solution, self contained, small, no jquery needed, IE friendly, etc. The code is extensively commented:
lazyLoader = {
load: function (scripts) {
// The queue for the scripts to be loaded
lazyLoader.queue = scripts;
lazyLoader.pendingScripts = [];
// There will always be a script in the document, at least this very same script...
// ...this script will be used to identify available properties, thus assess correct way to proceed
var firstScript = document.scripts[0];
// We will loop thru the scripts on the queue
for (i = 0; i < lazyLoader.queue.length; ++i) {
// Evaluates if the async property is used by the browser
if ('async' in firstScript ) {
// Since src has to be defined after onreadystate change for IE, we organize all "element" steps together...
var element = document.createElement("script");
element.type = "text/javascript"
//... two more line of code than necessary but we add order and clarity
// Define async as false, thus the scripts order will be respected
element.async = false;
element.src = lazyLoader.queue[i];
document.head.appendChild(element);
}
// Somebody who hates developers invented IE, so we deal with it as follows:
// ... In IE<11 script objects (and other objects) have a property called readyState...
// ... check the script object has said property (readyState) ...
// ... if true, Bingo! We have and IE!
else if (firstScript.readyState) {
// How it works: IE will load the script even if not injected to the DOM...
// ... we create an event listener, we then inject the scripts in sequential order
// Create an script element
var element = document.createElement("script");
element.type = "text/javascript"
// Add the scripts from the queue to the pending list in order
lazyLoader.pendingScripts.push(element)
// Set an event listener for the script element
element.onreadystatechange = function() {
var pending;
// When the next script on the pending list has loaded proceed
if (lazyLoader.pendingScripts[0].readyState == "loaded" || lazyLoader.pendingScripts[0].readyState == "complete" ) {
// Remove the script we just loaded from the pending list
pending = lazyLoader.pendingScripts.shift()
// Clear the listener
element.onreadystatechange = null;
// Inject the script to the DOM, we don't use appendChild as it might break on IE
firstScript.parentNode.insertBefore(pending, firstScript);
}
}
// Once we have set the listener we set the script object's src
element.src = lazyLoader.queue[i];
}
}
}
}
Of course you can also use the minified version:
smallLoader={load:function(d){smallLoader.b=d;smallLoader.a=[];var b=document.scripts[0];for(i=0;i<smallLoader.b.length;++i)if("async"in b){var a=document.createElement("script");a.type="text/javascript";a.async=!1;a.src=smallLoader.b[i];document.head.appendChild(a)}else b.readyState&&(a=document.createElement("script"),a.type="text/javascript",smallLoader.a.push(a),a.onreadystatechange=function(){var c;if("loaded"==smallLoader.a[0].readyState||"complete"==smallLoader.a[0].readyState)c=smallLoader.a.shift(),
a.onreadystatechange=null,b.parentNode.insertBefore(c,b)},a.src=smallLoader.b[i])}};
Related
You are developing two JavaScript widget, say first_widget.js and second_widget.js, both relaying on shared_library.js, which is creating the global window.MyLibrary.
Normally, you would use the two widgets like this, where shared_library.js is imported only once:
<!-- The first widget -->
<div id="first_widget_root"></div>
<script src="https://example.com/first_widget.js"></script>
<!-- The second widget -->
<div id="second_widget_root"></div>
<script src="https://example.com/second_widget.js"></script>
<!-- The shared library -->
<script src="https://example.com/shared_library.js"></script>
Then you decide to simplify things, embedding the loading of shared_library.js into each script:
// first_widget and second_widget skeleton
var loadCallback = function () {
// Use window.MyLibrary
};
(function(document, tag) {
var script = document.createElement(tag),
el = document.getElementsByTagName(tag)[0];
script.src = 'https://example.com/shared_library.js';
script.defer = true; // Execute after HTML parsing ends
script.onload = function () {
loadCallback();
};
el.parentNode.insertBefore(script, el);
}(document, 'script'));
Nice, now one can use the widget more easily:
<!-- The first widget -->
<div id="first_widget_root"></div>
<script src="https://example.com/first_widget.js"></script>
When using both widgets, the problem is shared_library.js imported twice: how would you check and inject the script only one?
I was thinking about adding a check for window.MyLibrary but I think isn't reliable: shared_library.js is imported twice depending on the loading speed anyways.
In your case:
if(! document.querySelector('script[src*=shared_library]'){
// insert
}
!
That is a common practice to check of a script containing smth in its attribute (need of some browser support probably to write such comfortable code) and, if not existent, it's to be quietly inserted there into the document.
Alongside with real site examples, SO is often talking about this topic of finding a script to whether it's to be inserted or the script is already loading like in this, for example:
//given url argument, in a call, e.g url='http://example.com/shared_library.js'
var scripts = document.getElementsByTagName('script');
for (var i = scripts.length; i--;) {
if (scripts[i].src == url) return true;
}
return false;
}
This is actually a merge of three differents questions already all well answered here on stack overflow !
1 - How to Dynamic Load a JavaScript file from inside a Js Script :
2 - How to Dynamic Load Jquery
3 - SetTimeout inside a JS Class using this
Basically, I am building a class that will inject some pages inside my clients's website.
To do so, the client just need to add my script src on the page.
<script src="<my_pub_http_address>/MyClass.js">
Once the script is invoked, I will need jquery to continue the execution !
But, I cannot know if the website that invoked the scripts has jquery already loaded.
So, I need to check if jquery is loaded, if not, I will have to load it, append to head and only then when jquery is loaded and working, I will proceed with the script's execution .
PS: this is a kind of legacy answer ! I already had the solution beforehand !
So, any improvement will be appreciated !
That's the solution I've found:
// MyClass.js
var counterLoopLoad = 0;
class MyClass {
constructor(){
// do the code that does not need jQuery
return this.Init()
}
JqueryLoader() {
// Loop Breaker
counterLoopLoad ++;
if (counterLoopLoad == 100) {
throw 'I need jQuery in order to do what I am suppose to do!';
}
var __jquery = document.createElement('script');
__jquery.src = "http://code.jquery.com/jquery-latest.min.js";
__jquery.type = 'text/javascript';
// if needed ....
// __jquery.onload = function() {
// some code here
//
// };
// must be prepend !!! append won't work !!!!
document.head.prepend(__jquery);
// here is the point that makes all work !!!!
// without setTimeOut, the script will get in a loop !
var that = this;
setTimeout(function () {
that.Init();
}, 500);
}
Init() {
if (typeof jQuery == 'undefined') {
return this.JqueryLoader();
}
jQuery.ajax(...);
}
}
I need to dynamically load several JavaScript file assets in a very specific order after a page has loaded. I'm trying to use onload, but this seems to fire before the asset has fully loaded. How should I adjust the below script to fire a proper callback to load the next script?
Note: Only needs to work in the latest Chrome, Firefox, Safari, and IE9.
function loadAssets() {
// Setup script
var scriptJS = document.createElement('script');
scriptJS.type = 'text/javascript';
scriptJS.src = objectUrl;
scriptJS.onload = loadAssetsNext();
// Begin insertion
var headerJS = document.getElementsByTagName('HEAD');
headerJS[0].appendChild(scriptJS);
},
function loadAssetsNext() {
// Increment object counter
objectsCount++;
// Test to see if you should call another item
if ((objectsCount) < objects.length) {
// Setup script
var scriptJS = document.createElement('script');
scriptJS.type = 'text/javascript';
scriptJS.src = nextObjectUrl;
// Declare callback to fire after script has fully loaded
scriptJS.onload = loadAssetsNext();
// Begin insertion
var headerJS = document.getElementsByTagName('HEAD');
headerJS[0].appendChild(scriptJS);
}
}
What I need is something like scriptJS.fullyLoaded = doStuff. Have no clue where to go from here though.
PS: jQuery is not an option or another library. You should be able to do this by slightly modifying the above script.
The reason your onload event is firing immediately is that you are calling it, not assigning it.
scriptJS.onload = loadAssetsNext();
This just assigns the returned value from the call to loadAssetsNext to the property onload of the scriptJS object. What you are intending to do is:
scriptJS.onload = loadAssetsNext;
That sets the onload handler to be the loadAssests function. This should take care of your issues.
I think the problem is that your scriptJS.onload = loadAssetsNext(); is placed before headerJS[0].appendChild(scriptJS);
That means that your scripts would load and get appended to the page like this:
load script 1
load script 2
load script 3
...
append script 3
append script 2
append script 1
So I think you should just reorder your script a little bit.
Background
I'm developing a bookmarklet which has dependencies on 3rd party libraries to be loaded into the DOM before my bookmarklet script can run. The heart of the problem I'm having is this dependency chain that I need to wait for before I can react, and I'm having issues getting it to fly in IE.
I cannot be sure JQuery is even on the page at this stage. It's something I check, and if it isn't there, then I have to create and inject this into the DOM.
The flow of control is something like this:
Load main library (jquery) into the DOM, I've attached an event listener to pick up on when it's actually in the DOM.
var e = document.createElement('script');
e.type = 'text/javascript';
e.src = http:// ... jquerylibrary.js
e.addEventListener('load', callback);
Setup the callback function
var callback = function() {
//my stuff that wants to use jquery
}
This works, and it works well. I can chain jquery, some jquery plugins, then my code, and then I can be sure when my code runs (in real browser land) that it'll run.
The Crux of the problem
When IE > 9 (Which we must support) has a similar function ... attachEvent('onload',callback);
Thing that's similar, but doesn't appear to fire correctly when DOM elements are updated. Looking through MSDN's long list of events, it's not clear if any of those events are what I'm looking for.
What event does IE 7+ fire when the DOM is updated?
I've figured it out after reading some of the source of head.js. This calls for a bit of creative javascript magic.
The events we're interested in are onreadystatechange, and onload. You can attach a custom function to these to trigger update notifications when they insert into the page.
I've abstracted this into a function, where, if you pass it an element and a callback statement, it'll dump the element at the end of the page body and run the callback.
function loadElement(e, callback) {
if ( typeof(callback) === 'function' ) {
e.onreadystatechange = e.onload = function() {
var state = e.readyState;
if ( !callback.done && (!state || /loaded|complete/.test(state)) ) {
callback.done = true;
callback();
}
}
}
document.body.appendChild(e);
}
Though I've posted this answer to my problem, I'm interested in alternative approaches to this :).
Is there a simple and reliable way to determine the URL of the currently-executing JavaScript file (inside a web page)?
My only thought on this is to scan the DOM for all the script src attributes to find how the current file was referenced and then figure out the absolute URL by applying it to document.location. Anyone have other ideas, is there some super-easy method I completely overlooked?
UPDATE: Script elements accessed via the DOM already have a src property which contains the full URL. I don't know how ubiquitous/standard that is, but alternatively you can use getAttribute("src") which will return whatever raw attribute value is in the [X]HTML.
Put this in the js file that needs to know it's own url.
Fully Qualified (eg http://www.example.com/js/main.js):
var scriptSource = (function(scripts) {
var scripts = document.getElementsByTagName('script'),
script = scripts[scripts.length - 1];
if (script.getAttribute.length !== undefined) {
return script.src
}
return script.getAttribute('src', -1)
}());
Or
As it appears in source (eg /js/main.js):
var scriptSource = (function() {
var scripts = document.getElementsByTagName('script'),
script = scripts[scripts.length - 1];
if (script.getAttribute.length !== undefined) {
return script.getAttribute('src')
}
return script.getAttribute('src', 2)
}());
See http://www.glennjones.net/Post/809/getAttributehrefbug.htm for explanation of the getAttribute parameter being used (it's an IE bug).
For recent browsers, you can use document.currentScript to get this information.
var mySource = document.currentScript.src;
The upside is that it's more reliable for scripts that are loaded asynchronously. The downside is that it's not, as best I know, universally supported. It should work on Chrome >= 29, FireFox >= 4, Opera >= 16. Like many useful things, it doesn't seem to work in IE.
When I need to get a script path, I check to see if document.currentScript is defined, and, if not, use the method described in the accepted answer.
if (document.currentScript) {
mySource = document.currentScript.src;
} else {
// code omitted for brevity
}
https://developer.mozilla.org/en-US/docs/Web/API/document.currentScript
As it appears in source (e.g. /js/main.js), this is cross-browser:
var scriptSource = (function()
{
var scripts = document.getElementsByTagName('script'),
script = scripts[scripts.length - 1];
//No need to perform the same test we do for the Fully Qualified
return script.getAttribute('src', 2); //this works in all browser even in FF/Chrome/Safari
}());
Fully Qualified (e.g. http://www.example.com/js/main.js):
After some tests it seems hard to get the fully qualified one in a cross-browser way. The solution suggested by Crescent Fresh does not work in IE8 to get the fully qualified, even if it works in IE7
This method work with defer, async and lazy loading
Since you know the filename of your script, and if it will be unique
/* see
* http://stackoverflow.com/questions/984510/what-is-my-script-src-url/984656#984656
* http://www.glennjones.net/Post/809/getAttributehrefbug.htm
*
* iterate all script to find script with right filename
* this work with async and defer (but your script MUST have a unique filemane)
* mozilla support document.currentScript and we use it, if is set
*
* this will not work with local script loaded by jQuery.getScript(),
* since there is no script tag added into the dom. the script is only evaluated in global space.
* http://api.jquery.com/jQuery.getScript/
*
* to fix this odd, you can add a reference in meta ( meta[name=srcipt][content=url] )
* when you load the script
*/
var scriptFilename = 'jquery.plugins.template.js'; // don't forget to set the filename
var scriptUrl = (function() {
if (document.currentScript) { // support defer & async (mozilla only)
return document.currentScript.src;
} else {
var ls,s;
var getSrc = function (ls, attr) {
var i, l = ls.length, nf, s;
for (i = 0; i < l; i++) {
s = null;
if (ls[i].getAttribute.length !== undefined) {
s = ls[i].getAttribute(attr, 2);
}
if (!s) continue; // tag with no src
nf = s;
nf = nf.split('?')[0].split('/').pop(); // get script filename
if (nf === scriptFilename) {
return s;
}
}
};
ls = document.getElementsByTagName('script');
s = getSrc(ls, 'src');
if ( !s ) { // search reference of script loaded by jQuery.getScript() in meta[name=srcipt][content=url]
ls = document.getElementsByTagName('meta');
s = getSrc(ls, 'content');
}
if ( s ) return s;
}
return '';
})();
var scriptPath = scriptUrl.substring(0, scriptUrl.lastIndexOf('/'))+"/";
a jquery plugin template with it:
https://github.com/mkdgs/mkdgs-snippet/blob/master/javascript/jquery.plugins.template.js
note: this will not work with local script loaded by jQuery.getScript(), since there is no script tag added into the dom. the script is only evaluated in global space.
http://api.jquery.com/jQuery.getScript/
to fix it you can do something like:
function loadScript(url,callback) {
if ( $('[src="'+url+'"]').length ) return true; // is already loaded
// make a reference of the loaded script
if ( $('meta[content="'+url+'"]', $("head")).length ) return true; // is already loaded
var meta = document.createElement('meta');
meta.content = url;
meta.name = 'script';
$("head").append(meta);
return $.ajax({
cache: true,
url: u,
dataType: 'script',
async: false,
success : function (script) {
try {
if ( typeof callback == 'function' ) callback();
} catch (error) {
//console.log(error);
}
}
});
}
If this is a strictly client solution, yours sounds pretty good.
If you are writing code on the server, you could probably just populate a div/hidden field/(insert your fave HTML element here) with the fully resolved URL to the script, and pick that up with your javascript on the clientside.
You may want to have a look at https://addons.mozilla.org/en-US/firefox/addon/10345 if you're interested in learning which functions (and thus which file) are executing on a page you don't control.
If you're interested in figuring out which of your scripts is executing, then there are a number of ways. With Firebug you could console.log() the information. Even just putting alert statements in your code (while annoying) can help debug in a low-tech way. You could also raise errors and catch them, then process using properties of the error (see: https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error)
However, why would this be important? If the script is causing errors already then it's easy enough to determine where the error is occurring. If it's not about errors at all, then what's the advantage in knowing which file it comes from?