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.
Related
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 have script that I would like visitors on my website to run when they load a web page. It looks like this:
window.onload = function(){
var pxl=document.createElement('img');
pxl.setAttribute('src', 'http://localhost:8080/getTrackingPixel')
document.body.appendChild(pxl);
}
Most of the times the source returns an image and it works fine. However, sometimes it returns this:
<html><body style="background-color:transparent"></body></html>
And I can't really change the fact that it might sometimes not return an image. How do I change the javascript so that it can handle the html response without any errors? It might be possible for me to predict when it happens though - but I haven't managed to find a good way to request the source and return the html either.
You can achieve it by using the javascript Image object which, unlike the createElement approach, allows you to fetch the src url before inserting the img in the DOM.
The onload event of the Image object won't fire if the loaded content isn't an img.
Here it is :
window.onload = function(){
var pxl = new Image();
pxl.onload = function(){
// is IMG
document.body.appendChild(pxl);
}
pxl.onerror = function(){
// is not IMG
// Meaning in your case : <html><body style="background-color:transparent"></body></html>
}
pxl.src = 'http://localhost:8080/getTrackingPixel';
}
(Note that your code also missed the semicolon ";" line 4)
I am making a live editor for my website. I have the CSS and HTML parts down, only issue is the JS part now. Here is a snippet of the code
var frame = $('#preview_content'),
contents = frame.contents(),
body = contents.find('body');
csstag = contents.find('head').append('<style></style>').children('style');
java = contents.find('head').append('<script><\/script>').children('script');//Issues here
$('.area_content_box').focus(function() {
var $this = $(this);
var check = $this.attr('id');
$this.keyup(function() {
if (check === "html_process"){
body.html($this.val());
} else if(check === "css_process") {
csstag.text($this.val());
} else if (check === "java_process"){
java.text( $this.val() );
}
});
});
Problem is it is not injecting script tags in the iframes body nor the head when ever I try this. I've read up on injecting and some issues containing the ending script tag. I need to figure out how to inject script tags, really want them in the head but if it is easier in the body that is fine.
jfriend00 - I will be focusing on making this vanilla, if you think I should honestly.
So any words of advice on how to go about making my editor work correctly with the injecting JS?
These two lines of code look like they could have problems:
csstag = contents.find('head').append('<style></style>').children('style');
java = contents.find('head').append('<script><\/script>').children('script');//Issues here
It seems like it would be much better to create the style tag and remember that DOM element.
var iframe = document.getElementById("preview_content");
var iframewindow = iframe.contentWindow || iframe.contentDocument.defaultView;
var doc = iframewindow.document;
var csstag = doc.createElement("style");
var scripttag = doc.createElement("script");
var head = doc.getElementsByTagName("head")[0];
head.appendChild.cssTag;
head.appendChild.scriptTag;
$('.area_content_box').focus(function() {
var $this = $(this);
var check = this.id;
$this.keyup(function() {
if (check === "html_process") {\
// I would expect this to work
doc.body.html($this.val());
} else if(check === "css_process") {
// I don't know if you can just replace CSS text like this
// or if you have to physically remove the previous style tag
// and then insert a new one
csstag.text($this.val());
} else if (check === "java_process"){
// this is unlikely to work
// you probably have to create and insert a new script tag each time
java.text( $this.val() );
}
});
});
This should work for the body HTML and it may work for the CSS text into the style tag.
I do not believe it will work for the javascript as you can't change a script by assigning text to the tag the way you are. Once a script has been parsed, it's in the javascript namespace.
There is no public API I'm aware of for removing previously defined and interpreted scripts. You can redefine global symbols with subsequent scripts, but not remove previous scripts or their effects.
If this were my code, I'd remove the previous style tag, create a new one, set the text on it before it was inserted in the DOM and then insert it in the DOM.
If you're not going to do it that way, then you'll have to test to see if this concept of setting .text() on an already inserted (and parsed) style tag works or not in all relevant browsers.
For the script tag, I'm pretty sure you'll have to create a new script tag and reinsert it, but there's no way to get rid of older code that has already been parsed other than redefining global symbols. If you really wanted to start fresh on the code, you'd have to create a new iframe object from scratch.
There are other issues with this too. You're installing a .keyup() event handler every time the .area_content_box gets focus which could easily end up with many of the event handlers installed, all getting called and all trying to do the same work.
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])}};
I want to use javascript in the url bar to manipulate the rendered html of a given page. Please note that I'm not trying to do something illegal here. Long story short, my university generates a weekly schedule based on your courses. I'd like to use javascript to add a button on the generated schedule page that will allow you to push the schedule to a google calendar. Unfortunately, I can't just go and edit the source itself (obviously), so I figured I would use javascript to edit the page once it has been rendered by my browser. I'm having some trouble calling an external javascript file to parse the rendered html.
As it is, this is what I have:
javascript:{{var e=document.createElement('script');
e.src = http://www.url.of/external/js/file.js';
e.type='text/javascript';
document.getElementsByTagName('head')[0].appendChild(e);}
functionToCall(document.body.innerHTML);}
Which, when pasted into the URL bar, SHOULD add my javascript file to the head and then call my function.
Any help would be greatly appreciated, thanks!
EDIT: Here's a working example if you're interested, thanks everyone!
javascript:(function(){var e=document.createElement('script');
e.src = 'http://www.somewebsite.net/file.js';
e.type='text/javascript';e.onload =function(){functiontocall();};
document.getElementsByTagName('head')[0].appendChild(e);})();
If you need the code to execute once it is loaded you can do one of two things:
Execute functionToCall(document.body.innerHTML); at the bottom of your script (http://www.url.of/external/js/file.js) rather than at the end of your bookmarklet.
Use e.onload = function(){ functionToCall(document.body.innerHTML); }; after e.type='text/javascript' near the end of your JavaScript snippet / bookmarklet, rather than calling functionToCall right after appending e to the document head (since e will most likely not have been loaded and parsed right after appendChild(e) is called.
I see that you've accepted an answer, and that's perfectly valid and great, but I would like to provide a useful tool I made for myself.
It's a bookmarklet generator called zbooks.
(Yes it's my website, no I'm not trying to spam you, there are no ads on that page, I gain nothing from you using it)
It's jQuery enabled and I think it's simple to use (but I built it, so who knows). If you need an extensive explanation of how to use it, let me know so I can make it better. You can even browse over the source if you'd like.
The important part is the business logic that gets jQuery on the page:
//s used for the Script element
var s = document.createElement('script');
//r used for the Ready state
var r = false;
//set the script to the latest version of jQuery
s.setAttribute('src', 'http://code.jquery.com/jquery-latest.min.js');
//set the load/readystate events
s.onload = s.onreadystatechange = function()
{
/**
* LOAD/READYSTATE LOGIC
* execute if the script hasn't been ready yet and:
* - the ready state isn't set
* - the ready state is complete
* - note: readyState == 'loaded' executes before the script gets called so
* we skip this event because it wouldn't have loaded the init event yet.
*/
if ( !r && (!this.readyState || this.readyState == 'complete' ) )
{
//set the ready flag to true to keep the event from initializing again
r = true;
//prevent jQuery conflicts by placing jQuery in the zbooks object
window.zbooks = {'jQuery':jQuery.noConflict()};
//make a new zbook
window.zbooks[n] = new zbooks(c);
}
};
//append the jQuery script to the body
b.appendChild(s);
Can't you use a proper tool like Greasemonkey?