Detect whether external script has loaded - javascript

Using JavaScript, is there a way to detect whether or not an external script (from a third-party vendor) has completely loaded?
The script in question is used to pull in and embed the markup for a list of jobs and, unfortunately, doesn't make use of any variables or functions. It uses document.write to output all of the content that gets embedded in my page.
Ideally, I'd like to display some kind of loading message while I'm waiting for the external script to load, and if it fails to load, display a "We're sorry, check back later..." message.
I'm using jQuery on the site, but this external script is called before I make the jQuery call.
Here's what the document.write stuff from the external script looks like:
document.write('<div class="jt_job_list">');
document.write("
<div class=\"jt_job jt_row2\">
<div class=\"jt_job_position\">
Position Title
</div>
<div class=\"jt_job_location\">City, State</div>
<div class=\"jt_job_company\">Job Company Name</div>
</div>
");

Attach an function to the load event:
<script type="text/javascript" src="whatever.js" onload ="SomeFunction()" />
As far as your loading... problem goes, try displaying a div for loading and then just display:none-ing it in your onload function. Make sure to handle cases where your script fails to load too, though.

Script tags block downloads, so as long as the content dependent on your script is below where your script it loaded, you should be fine. This is true even if the script is in-line in the body of your page.
This website has a great example of how this works.
This obviously does not work if you're loading the scripts asynchronously.
Scripts without async or defer attributes are fetched and executed immediately, before the browser continues to parse the page.
Source: MDN

You could put a script block after it on the page:
<script src="external_script.js"></script>
<script type="text/javascript">
ExternalScriptHasLoaded();
</script>

Thanks for the assistance above, especially ngmiceli for the Steve Souders link!
I decided to take what's probably a "lazy" approach, and also forego the "loading" message:
$(document).ready(function(){
if ($('.jt_job_list').length === 0){
$('#job-board').html("<p>We're sorry, but the Job Board isn't currently available. Please try again in a few minutes.</p>");
};
});
Pretty simple, but I'm looking to see if an element with the .jt_job_list class is in the dom. If it isn't, I display an error message.

This worked for me: it does however, rely on the newer querySelector interface which most modern browsers support. But if you're using really old browsers, you can use getElement... and run a for loop.
function loadJS(file, callback, error, type) {
var _file = file ;
var loaded = document.querySelector('script[src="'+file+'"]') ;
if (loaded) {
loaded.onload = callback ;
loaded.onreadystatechange = callback;
return
}
var script = document.createElement("script");
script.type = (typeof type ==="string" ? type : "application/javascript") ;
script.src = file;
script.async = false ;
script.defer = false ;
script.onload = callback ;
if (error) {
script.onerror = error ;
}
else {
script.onerror = function(e) {
console.error("Script File '" + _file + "' not found :-(");
};
}
script.onreadystatechange = callback;
document.body.appendChild(script);
}

You could give what ever your looking for an ID
and check whether not the ID has been loaded using document.getElementById("ID");
Is that what your looking for not sure I fully understand?

Related

Is there any advantage to add 'defer' to a new script tag after $(document).ready()?

I have some javascript that is not required for my initial page load. I need to load it based on some condition that will be evaluated client-side.
$(document).ready(function() {
let someCondition = true; // someCondition is dynamic
if (someCondition) {
var element = document.createElement('script');
element.src = 'https://raw.githubusercontent.com/Useless-Garbage-Institute/useless-garbage/master/index.js';
element.defer = true; // does this make a difference?
element.onload = function() {
// do some library dependent stuff here
document.getElementById("loading").textContent = "Loaded";
};
document.body.appendChild(element);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1 id="loading">Loading...</h1>
Does it make a difference (in terms of how browser will treat the script tag), if a new tag created using javascript, after document is ready, has 'defer' attribute or not? I think there is no difference, but how can I say for sure?
I believe I understand how deferred scripts behave when script tag is part of the initial html (as described here). Also, this question is not about whether element.defer=true can be used or not (subject of this question).
No that doesn't make any difference, the defer attribute is ignored in case of "non-parser-inserted" scripts:
<script defer src="data:text/javascript,console.log('inline defer')"></script>
<script>
const script = document.createElement("script");
script.src = "data:text/javascript,console.log('dynamic defer')";
script.defer = true;
document.body.append(script);
</script>
<!-- force delaying of parsing -->
<script src="https://deelay.me/5000/https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Look at your browser's console or pay attention to the logs timestamps to see that the dynamically inserted script actually did execute while we were waiting for the delayed script to be fetched.
There's a difference between adding them to the function and adding directly the CDN ( especially in your case ).
Let's look at the code execution of the above-mentioned code first,
You have added the jquery CDN first ( without defer ) so that loads first.
$(document).ready will be fired once after the complete load of jquery.
There'll be the creation and insertion of a new script tag to the dom.
Download the https://raw.githubusercontent.com/Useless-Garbage-Institute/useless-garbage/master/index.js asynchronously.
Let's look at another approach: adding CDN to the code:
Your DOM will have 2 script tags.
Both will start loading based on the type of load parallelly ( defer async etc ).
Notice you are not waiting for the dom ready event to load the second script.
I suggest adding only the main JS part in a js file and adding it to the CDN. Others can wait load with the delay.
In case you are really needed with a js src, then don't load it the first way since it waits for the complete page load.
I suggest you read and look at web-vitals and SEO for this.
and for your other question, yes you can add defer attribute with element.defer=true to the elements while creating and loading to DOM.
Hope this answer helps you!
Feel free to comment if you get any errors or doubts.
I think the JQuery Arrive lib will solve your case.

How to remove HTML script tag with open type [duplicate]

How can I remove script elements before they are being executed?
I thought about using the DOMNodeInserted event, but apparently it doesn't catch script elements. I've also tried using the jQuery livequery plugin like that:
$("script").livequery(function () {
$(this).remove();
});
It did remove the script element, but after it was executed.
I'm looking for a cross-browser solution, but I'm not even sure if that's possible. I read about Mutation Observers which seems close enough but I'm not sure if it can solve my problem.
It would be even better if there was a way to modify the script content before it is being executed without removing and recreating it.
Removing a script element does not do anything. If you can somehow access a script element, it was executed a long time ago and removing it will have no effect.
So we need to work around it. If your script element is at the top of the page like this:
<head>
<script src="yourscript.js"></script>
You could make a synchronous ajax request to the same page, so you can parse its content into a new document, modify all script tags and then replace
the current document with the modified document.
var xhr = new XMLHttpRequest,
content,
doc,
scripts;
xhr.open( "GET", document.URL, false );
xhr.send(null);
content = xhr.responseText;
doc = document.implementation.createHTMLDocument(""+(document.title || ""));
doc.open();
doc.write(content);
doc.close();
scripts = doc.getElementsByTagName("script");
//Modify scripts as you please
[].forEach.call( scripts, function( script ) {
script.removeAttribute("src");
script.innerHTML = 'alert("hello world");';
});
//Doing this will activate all the modified scripts and the "old page" will be gone as the document is replaced
document.replaceChild( document.importNode(doc.documentElement, true), document.documentElement);
Unfortunately this cannot be set up in jsfiddle or jsbin. But you should be able to copy paste this code exactly as it is into this
page's console in google chrome. You should see the alerts and when you inspect the live dom, each script was modified.
The difference is that we are running this after scripts have been executed on the page, so the old scripts should still have a working effect on the page.
That's why, for this to work, you need to be the very first script on the page to do it.
Tested to work in google chrome. Firefox is completely ignoring the doc.write call for some reason.
i donot know what you are trying to do. But it is better to load them on request rather than delete on some conditions.
$.getScript('helloworld.js', function() {
$("#content").html('
Javascript is loaded successful!
');
});
If you wants to remove scripts before there execution, its not possible.
But what you can do is, remove script programatically on a condition & if have an issue with memory-leaks, then you can call below code before remove script.
var var1 = 'hello';
var cleanAll = function () {
delete window.var1;
delete window.cleanAll;
};
// unload all resources
cleanAll();

Problems with "document.write" using RequireJS modules

I have a module that needs to execute a document.write action in order to print a banner once the page is loaded.
If I do this using the old-school way, I get no problems.
The banner is printed inside the div.
<div id="banner">
<script> addAdvert() </script>
</div>
<script>
function addAdvert(){
srcScript = 'http://www.urltoscript.js';
document.write('<script type=\"text/javascript\" src=\"'+srcScript+'" ><\/script>');
}
</script>
But If I try this using a require js module (kind of this):
addAvert: function() {
var srcScript = options.urltoScript
document.write('<script type=\"text/javascript\" src=\"'+srcScript+'" ><\/script>');
}
It executes the document.write and render all the document printing only the banner on the entire document...
I have try this alternative:
addAvert: function() {
var srcScript = options.urltoScript
var bsa = document.createElement('script');
bsa.type = 'text/javascript';
bsa.async = true;
bsa.src = srcScript;
$("#banner").append(bsa);
}
Judging by your comment, the script which you are trying to load from the imaginary URL http://www.urltoscript.js also uses document.write. Either you should change this script to not use document.write or you should abandon the idea of loading it asynchronously, because document.write does not work reliably when invoked asynchronously.
You've already discovered when you tried require([bsa.src]) (as you mentioned in a comment) that you cannot call document.write from a script that is loaded asynchronously. (Also note that just doing require([bsa.src]) is not going to work unless the source code at the other end is an AMD-module or you defined a shim for it.)
Your first attempt at loading through RequireJS did not produce an error message about document.write being loaded asynchronously because the script element that loads it is not itself asynchronous. However, it completely blanked your page. This is because if you call document.write after the page has finished loading, the browser may implicitly call document.open and this may blank your page.
The upshot here is that you cannot reliably use document.write with asynchronous code.

Struggling with Javascript and jQuery logic

Please help me by answering with a short theory behind how it works as I would like to understand the logic rather than just get an answer. If you know of any material that explains it for beginners then please reference it. I have spent quite a lot of time researching and have come up blank and find the name or anything that explains this behavior.
My question is and want to understand, I thought the way browsers parse the html is line by line. When it encounters a <script> tag everything else comes to a halt (this basic example) whilst it passes it off to the js interpreter to also complete line by line. Once it has finished it then passes it back to the html parser to continue with the rest of the page
So my question is from the short example below, why is "find" being loaded before backup.js has completed, when I remove backup.js and have jQuery instead the code in console.log(find); works as expected, but when jQuery is removed from html and asked to be added via backup.js, which is still the first tag encountered before the console.log(find); at the bottom, it does not work? I get the following error message:
ReferenceError: $ is not defined
var find = $('.link');
Makes me believe that var find = $('.link'); is being attempted to be accessed before backup.js and jQuery have finished loading, but why is this when "find" comes long after backup.js? Or am I doing something wrong with the Javascript code in backup.js that adds it after rather than earlier?
I have this short piece of html:
<html>
<head>
<!--<script src="https://code.jquery.com/jquery-1.11.1.js"></script>-->
<script type="text/javascript" src="backup.js"></script>
<meta charset="UTF-8">
<title>My Web Page</title>
</head>
<body>
<p>hello</p>
<div class="link">test</div>
<script>
var find = $('.link');
console.log(find);
</script>
</body>
</html>
In backup.js I have this:
if(typeof jQuery=='undefined') {
var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.src= 'https://code.jquery.com/jquery-1.11.1.js';
head.appendChild(script);
console.log('jquery not found');
}
else{
console.log('jquery found');
}
In your backup.js file you load jquery from the server if it does not yet exist. Loading something from the server through an Asynchronous call means it won't stop the page rendering, so what happens is:
The page starts rendering, it comes to backup.js, starts loading jquery from the server, keeps rendering the page while loading jquery, goes all the way to the bottom and then finds $(".link");. In this line of code the symbol "$" means jQuery (I want to use jquery for this part of code), but it might happen that jquery is not yet completely loaded, and so the program breaks and tells you ReferenceError: $ is not defined (or in human: you're trying to use $ but it doesn't exist).
To fix this, you can create a function that gets called when jquery has loaded:
if(typeof jQuery=='undefined') {
var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.src= 'https://code.jquery.com/jquery-1.11.1.js';
script.onload = function() {
runAfterjQueryLoad();
}
head.appendChild(script);
console.log('jquery not found');
} else {
console.log('jquery found');
}
function runAfterjQueryLoad() {
var find = $('.link');
console.log(find);
}
Working fiddle: http://jsfiddle.net/aj803z7u/
You're right about the page rendering top-down, but always be careful for asynchronous calls (also know as ajax, in case you want to search for more information on the web).
The best way of learning is by doing. Think of a page you want to create and try to do it, searching for tutorials for each single small step (you can always come here on SO and ask questions) .

How do I add an external js file with a parameter

I want to give a minimal js code to random websites so that they can add a widget.
The code needs to run after the main page loads and include a parameter with the domain name. ( I don't want to hardcode it)
One option is to add this code just before the </body> (so It will run after the page loads):
<script type="text/javascript" id="widget_script">
document.getElementById('widget_script').src=location.protocol.toLowerCase()+"//mywebsite.com/?u="+encodeURIComponent(window.location.host);
</script>
This works in IE but not in Firefox. I see with Firebug that the src property is created correctly but the script from my site is not loaded.
My question is : what is the best way to do that ? (preferably by putting minimal lines on the header part.)
To further clarify the question: If I put a script on the header part, how do I make it run after it is loaded and the main page is loaded? If I use onload event in my script I may miss it because it may load after the onload event was fired.
You can try to statically include the script with document.write (is an older technique and not recommended to use as it can cause problems with more modern libraries):
var url = location.protocol.toLowerCase() +
"//mywebsite.com/?u="+encodeURIComponent(window.location.host);
document.write('<script src="', url, '" type="text/javascript"><\/script>');
Or with dynamically created DOM element:
var dynamicInclude = document.createElement("script");
dynamicInclude.src = url;
dynamicInclude.type = "text/javascript";
document.getElementsByTagName("head")[0].appendChild(dynamicInclude);
Later edit:
To ensure the script is run after onload this can be used:
var oldWindowOnload = window.onload;
window.onload = function() {
oldWindowOnload();
var url = location.protocol.toLowerCase() +
"//mywebsite.com/?u="+encodeURIComponent(window.location.host);
var dynamicInclude = document.createElement("script");
dynamicInclude.src = url;
dynamicInclude.type = "text/javascript";
document.getElementsByTagName("head")[0].appendChild(dynamicInclude);
}
I do not believe it can be shorter than this, apart from shorter variable names :)
Why not use the getScript method of jQuery to do the loading? If you don't want to be dependant on jQuery, you can trace through the source to learn how they tackled it.
Viewing a widely used library is always going to show you solutions to problems you didn't know you had. For example, you can see how jQuery manages to generate a callback when the script is loaded, and how it avoids a purported memory leak in IE.
You probably want to be implementing the non-blocking script technique. Essentially instead of modifying the src of a script, you're going to create and append a whole new script element.
Good write up here and there are standard ways to do this in YUI and jQuery. It's quite straightforward with only one gotcha which is well understood (and documented at that link).
Oh and this:
One option is to add this code just
before the </body> (so It will run
after the page loads):
...is not technically true: you're just making your script the last thing in the loading order.

Categories

Resources