load inserted script, making sure that libraries load first [duplicate] - javascript

I'm creating a jquery plugin and I want to verify an external script is loaded. This is for an internal web app and I can keep the script name/location consistent(mysscript.js). This is also an ajaxy plugin that can be called on many times on the page.
If I can verify the script is not loaded I'll load it using:
jQuery.getScript()
How can I verify the script is loaded because I don't want the same script loaded on the page more than once? Is this something that I shouldn't need to worry about due to caching of the script?
Update:
I may not have control over who uses this plugin in our organization and may not be able to enforce that the script is not already on the page with or without a specific ID, but the script name will always be in the same place with the same name. I'm hoping I can use the name of the script to verify it's actually loaded.

If the script creates any variables or functions in the global space you can check for their existance:
External JS (in global scope) --
var myCustomFlag = true;
And to check if this has run:
if (typeof window.myCustomFlag == 'undefined') {
//the flag was not found, so the code has not run
$.getScript('<external JS>');
}
Update
You can check for the existence of the <script> tag in question by selecting all of the <script> elements and checking their src attributes:
//get the number of `<script>` elements that have the correct `src` attribute
var len = $('script').filter(function () {
return ($(this).attr('src') == '<external JS>');
}).length;
//if there are no scripts that match, the load it
if (len === 0) {
$.getScript('<external JS>');
}
Or you can just bake this .filter() functionality right into the selector:
var len = $('script[src="<external JS>"]').length;

Few too many answers on this one, but I feel it's worth adding this solution. It combines a few different answers.
Key points for me were
add an #id tag, so it's easy to find, and not duplicate
Use .onload() to wait until the script has finished loading before using it
mounted() {
// First check if the script already exists on the dom
// by searching for an id
let id = 'googleMaps'
if(document.getElementById(id) === null) {
let script = document.createElement('script')
script.setAttribute('src', 'https://maps.googleapis.com/maps/api/js?key=' + apiKey)
script.setAttribute('id', id)
document.body.appendChild(script)
// now wait for it to load...
script.onload = () => {
// script has loaded, you can now use it safely
alert('thank me later')
// ... do something with the newly loaded script
}
}
}

#jasper's answer is totally correct but with modern browsers, a standard Javascript solution could be:
function isScriptLoaded(src)
{
return Boolean(document.querySelector('script[src="' + src + '"]'));
}
UPDATE July 2021:
The accepted solutions above have changed & improved much over time. The scope of my previous answer above was only to detect if the script was inserted in the document to load (and not whether the script has actually finished loading).
To detect if the script has already loaded, I use the following method (in general):
Create a common library function to dynamically load all scripts.
Before loading, it uses the isScriptLoaded(src) function above to check whether the script has already been added (say, by another module).
I use something like the following loadScript() function to load the script that uses callback functions to inform the calling modules if the script finished loading successfully.
I also use additional logic to retry when script loading fails (in case of temporary network issues).
Retry is done by removing the <script> tag from the body and adding it again.
If it still fails to load after configured number of retries, the <script> tag is removed from the body.
I have removed that logic from the following code for simplicity. It should be easy to add.
/**
* Mark/store the script as fully loaded in a global variable.
* #param src URL of the script
*/
function markScriptFullyLoaded(src) {
window.scriptLoadMap[src] = true;
}
/**
* Returns true if the script has been added to the page
* #param src URL of the script
*/
function isScriptAdded(src) {
return Boolean(document.querySelector('script[src="' + src + '"]'));
}
/**
* Returns true if the script has been fully loaded
* #param src URL of the script
*/
function isScriptFullyLoaded(src) {
return src in window.scriptLoadMap && window.scriptLoadMap[src];
}
/**
* Load a script.
* #param src URL of the script
* #param onLoadCallback Callback function when the script is fully loaded
* #param onLoadErrorCallback Callback function when the script fails to load
* #param retryCount How many times retry laoding the script? (Not implimented here. Logic goes into js.onerror function)
*/
function loadScript(src, onLoadCallback, onLoadErrorCallback, retryCount) {
if (!src) return;
// Check if the script is already loaded
if ( isScriptAdded(src) )
{
// If script already loaded successfully, trigger the callback function
if (isScriptFullyLoaded(src)) onLoadCallback();
console.warn("Script already loaded. Skipping: ", src);
return;
}
// Loading the script...
const js = document.createElement('script');
js.setAttribute("async", "");
js.src = src;
js.onload = () => {
markScriptFullyLoaded(src)
// Optional callback on script load
if (onLoadCallback) onLoadCallback();
};
js.onerror = () => {
// Remove the script node (to be able to try again later)
const js2 = document.querySelector('script[src="' + src +'"]');
js2.parentNode.removeChild(js2);
// Optional callback on script load failure
if (onLoadErrorCallback) onLoadErrorCallback();
};
document.head.appendChild(js);
}

This was very simple now that I realize how to do it, thanks to all the answers for leading me to the solution. I had to abandon $.getScript() in order to specify the source of the script...sometimes doing things manually is best.
Solution
//great suggestion #Jasper
var len = $('script[src*="Javascript/MyScript.js"]').length;
if (len === 0) {
alert('script not loaded');
loadScript('Javascript/MyScript.js');
if ($('script[src*="Javascript/MyScript.js"]').length === 0) {
alert('still not loaded');
}
else {
alert('loaded now');
}
}
else {
alert('script loaded');
}
function loadScript(scriptLocationAndName) {
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = scriptLocationAndName;
head.appendChild(script);
}

Create the script tag with a specific ID and then check if that ID exists?
Alternatively, loop through script tags checking for the script 'src' and make sure those are not already loaded with the same value as the one you want to avoid ?
Edit: following feedback that a code example would be useful:
(function(){
var desiredSource = 'https://sitename.com/js/script.js';
var scripts = document.getElementsByTagName('script');
var alreadyLoaded = false;
if(scripts.length){
for(var scriptIndex in scripts) {
if(!alreadyLoaded && desiredSource === scripts[scriptIndex].src) {
alreadyLoaded = true;
}
}
}
if(!alreadyLoaded){
// Run your code in this block?
}
})();
As mentioned in the comments (https://stackoverflow.com/users/1358777/alwin-kesler), this may be an alternative (not benchmarked):
(function(){
var desiredSource = 'https://sitename.com/js/script.js';
var scripts = document.getElementsByTagName('script');
var alreadyLoaded = false;
for(var scriptIndex in document.scripts) {
if(!alreadyLoaded && desiredSource === scripts[scriptIndex].src) {
alreadyLoaded = true;
}
}
if(!alreadyLoaded){
// Run your code in this block?
}
})();

Simply check if the global variable is available, if not check again. In order to prevent the maximum callstack being exceeded set a 100ms timeout on the check:
function check_script_loaded(glob_var) {
if(typeof(glob_var) !== 'undefined') {
// do your thing
} else {
setTimeout(function() {
check_script_loaded(glob_var)
}, 100)
}
}

Another way to check an external script is loaded or not, you can use data function of jquery and store a validation flag. Example as :
if(!$("body").data("google-map"))
{
console.log("no js");
$.getScript("https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&callback=initilize",function(){
$("body").data("google-map",true);
},function(){
alert("error while loading script");
});
}
}
else
{
console.log("js already loaded");
}

I think it's better to use window.addEventListener('error') to capture the script load error and try to load it again.
It's useful when we load scripts from a CDN server. If we can't load script from the CDN, we can load it from our server.
window.addEventListener('error', function(e) {
if (e.target.nodeName === 'SCRIPT') {
var scriptTag = document.createElement('script');
scriptTag.src = e.target.src.replace('https://static.cdn.com/', '/our-server/static/');
document.head.appendChild(scriptTag);
}
}, true);

Merging several answers from above into an easy to use function
function GetScriptIfNotLoaded(scriptLocationAndName)
{
var len = $('script[src*="' + scriptLocationAndName +'"]').length;
//script already loaded!
if (len > 0)
return;
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = scriptLocationAndName;
head.appendChild(script);
}

My idead is to listen the error log if there is an error on script loading.
const checkSegmentBlocked = (e) => {
if (e.target.nodeName === 'SCRIPT' && e.target.src.includes('analytics.min.js')) {
window.isSegmentBlocked = true;
e.target.removeEventListener(e.type, checkSegmentBlocked);
}
};
window.addEventListener('error', checkSegmentBlocked, true);

Some answers on this page are wrong. They check for the existence of the <script> tag - but that is not enough. That tells you that the tag was inserted into the DOM, not that the script is finished loading.
I assume from the question that there are two parts: the code that inserts the script, and the code that checks whether the script has loaded.
The code that dynamically inserts the script:
let tag = document.createElement('script');
tag.type = 'text/javascript';
tag.id = 'foo';
tag.src = 'https://cdn.example.com/foo.min.js';
tag.onload = () => tag.setAttribute('data-loaded', true); // magic sauce
document.body.appendChild(tag);
Some other code, that checks whether the script has loaded:
let script = document.getElementById('foo');
let isLoaded = script && script.getAttribute('data-loaded') === 'true';
console.log(isLoaded); // true
If the both of those things (inserting and checking) are in the same code block, then you could simplify the above:
tag.onload = () => console.log('loaded');

I found a quick tip before you start diving into code that might save a bit of time. Check devtools on the webpage and click on the network tab. The js scripts are shown if they are loaded as a 200 response from the server.

Related

Execute Script tag using JS instead of PHP

I have a case in php, where I execute <script> tag of Adsense, if the userAgent is not BOT, but for some good reason I want to execute it using JS.
Helper Function:
function detectBottypes() {
$userAgent = strtolower($_SERVER['HTTP_USER_AGENT']);
if(!empty($userAgent) and preg_match('~(bot|crawl|google|lighthouse|spider|feedparser|crawler|pinterest)~i', $userAgent)) {
return true;
}
return false;
}
in View:
#if( Request::is('photo/*') && detectBottypes()==false )
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js" crossorigin="anonymous">
</script>
#endif
Above, if request is photo/* and not bot then it is rendered in view, but I want it to be rendered in either of cases but only executed for the specific case.
I have the case of JS
window.onload = function () {
var agent = navigator.userAgent.toLowerCase();
if (agent.indexOf('bot') != -1) {
// ******* Execute here ********
}
else {
}
}
Reason why I want: I cache the view file to skip the load on server, so if the page is first crawled by Bot(Google) it is cached without the above case of Adsense Script ( Ad is not loaded to Bot) but since it is cached if later it is viewed by real user, the cached version without Ads is shown which I do not want, so preferred to be with JS
You can dynamically load a script with something like:
window.onload = function () {
var agent = navigator.userAgent.toLowerCase();
if (agent.indexOf('bot') != -1) {
var scriptTag = document.createElement('script');
scriptTag.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';
scriptTag.async = true;
scriptTag.type = 'text/javascript';
scriptTag.crossorigin = 'anonymous';
document.head.prepend(scriptTag);
} else {
}
}
This should cause the browser to download and run the script. However there's a broader question on your use of caching. It may be simpler if you cache two versions of the content and serve each one based on the UA, if that is an option.

Javascript file load fallback. Alternative to document.write()

You might be familiar with the good old Jquery load fallback:
<script>window.jQuery || document.write('<script src="https://example.com/jquery.js"></script>')</script>
But I read here and there: don’t use document.write, is bad for your health, it does not work on Chrome (It’s working for me, Chrome 78).
So I’m trying to replace it, but I’m not able to find a solution that will load synchronously the new js file, before DOM loaded is triggered.
And what ends happening with a DOM manipulation alternative is that the browser consideres the DOM is loaded and all $(document).ready() fail with “$ is not defined”.
function Jqfallback() {
var j = document.createElement('script');
j.src = 'https://example.com/jquery.js';
document.getElementsByTagName('head')[0].appendChild(j);
}
(window.jQuery || Jqfallback() );
No matter where I put this script, or the new JS file, which in this case ('head')[0] is already before all other JS which are in the body, it loads it “asyncronically”.
Is there another option or I continue rocking document.write() in late 2019?
It takes a bit of time to load and parse JQuery. So use a (small) timeout after appending the script.
This snippet wraps conditional loading in a immediately executed anonymous function:
(myScripting => {
if (!window.$) {
let j = document.createElement('script');
j.src = '//code.jquery.com/jquery-3.4.1.slim.min.js';
document.querySelector('head').appendChild(j);
setTimeout( myScripting, 200 );
} else {
myScripting();
}
})(JqIsLoadedSoMyScriptingCanStart);
// put your main scripting in here
function JqIsLoadedSoMyScriptingCanStart() {
// extra check
if (!window.$) {
alert("Sorry, JQuery is not loaded, can't continue");
return;
}
console.log("JQuery in place?");
console.log($("head script")[1]);
}
<script src="cantLoadThis"></script>
Place the code that uses jQuery in the onload() function.
var jQuery1 = document.createElement('script');
jQuery1.src = "https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js";
jQuery1.onload = function () {
var $ = window.jQuery;
$.when(
$.getScript("https://someOtherScript.js"), //if you need
$.Deferred(function (deferred) {
$(deferred.resolve);
})
).done(function () {
console.log("all scripts loaded!!");
doNextTask(); //some other code which uses jQuery
});
};
Append jQuery to your document in onreadystatechange
document.onreadystatechange = function () {
if (document.readyState == "complete") {
// document is ready.
document.head.appendChild(jQuery1);
}
}

Run the next line in javascript after the previous function from 3rd library is done (not jquery)

In our website, there is a call tracking script which directly runs after the user select their countries in the contact page.
And the process is as follows:
1.Customers click into the list and select the country that they are in, and in this stage we start to track once they click into the element
2.The script from 3rd part runs, changes the telephone number in the website by replace the previous class with a new one with new number
3.Then we want to get the new number in the next line.
The planned script looked like this:
orgCallNo = document.querySelector(".oldTel").innerHTML;
var x = setInterval(function (){
telephone.change();
var newCallNo = document.querySelector(".tel").innerHTML;
if (newCallNo !== orgCallNo) {
console.log(newCallNo);
clearInterval(x);
}
}, 20);
The problem is, when I called the external script i.e telephone change script, it takes a while and the next line takes the old input and continue the process, before the telephone script has been completed.
To solve it, I have already tried the setTimeOut function to a kind of pause the next step:
orgCallNo = document.querySelector(".oldTel").innerHTML;
var x = setInterval(function (){
telephone.change();
var newCallNo = setTimeout(function(){return
document.querySelector(".tel").innerHTML;}, 300);
if (newCallNo !== orgCallNo) {
console.log(newCallNo);
clearInterval(x);
}
}, 20);
However it returns undefined. At the same time, I want to try to use the call back function, but first, most of the solutions are in jQuery, which I cannot use in this time. And the second point is, the script uses the library from 3rd party and did not directly return some input in the script, therefore I am not sure when to set the call back function.
In this case, how can I make sure that the next line runs after the script from 3rd party completed?
Thanks for you guys help =)
You can use readyState for loading this external library, like this you detect when this 3rd library is loaded (better than setTimeout)
Sample code :
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 );
}
// call the function...
loadScript(pathtoscript, function() {
console.log('script ready! i can now execute my code...');
});

Can this be simplified to load multiple scripts in order

The following works but I need to distribute it to clients that may be uncomfortable of pasting all this script into their home page. Just wondering if it can be simplified? I need to load Jquery 1.71, then the UI and then my own script and then call the function in my own script. Even minimized its rather long.
Hope some javascript guru can help. Thanks!
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js';
script.type = 'text/javascript';
head.appendChild(script);
if (script.onreadystatechange) script.onreadystatechange = function () {
if (script.readyState == "complete" || script.readyState == "loaded") {
script.onreadystatechange = false;
//alert("complete");
load_script();
}
} else {
script.onload = function () {
//alert("complete");
load_script();
}
}
//setup array of scripts and an index to keep track of where we are in the process
var scripts = ['script/jquery-ui-1.8.7.custom.min.js', 'script/wfo171.js'],
index = 0;
//setup a function that loads a single script
function load_script() {
//make sure the current index is still a part of the array
if (index < scripts.length) {
//get the script at the current index
$.getScript('http://mydomainn.com/script/' + scripts[index], function () {
//once the script is loaded, increase the index and attempt to load the next script
//alert('Loaded: ' + scripts[index] + "," + index);
if (index != 0) {
LoadEdge();
}
index++;
load_script();
});
}
}
function LoadEdge() {
Edge('f08430fa2a');
}
As soon as you have jQuery you can use its power:
$.when.apply($, $.map(scripts, $.getScript)).then(LoadEdge);
This relies on its deferred functionality - each URL is replaced with a getScript deferred (this will fetch the script), and these deferreds are then passed to $.when so that you can add a callback using .then to be called when all scripts have finished loaded.
Why don;t you just use an onload event to make sure everything is loaded before trying to execute?
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="http://mydomainn.com/script/jquery-ui-1.8.7.custom.min.js"></script>
<script src="http://mydomainn.com/script/wfo171.js"></script>
<script>
$(function() { // this executes when the page is ready
Edge('f08430fa2a');
});
</script>
(check the paths on the scripts, you seem to be loading from /script/script, wasn't sure if that was correct so I removed it.

Firefox extension: Add javascript to webpage

I'm working on a FireFox extension that listens to onStateChange. When the current document has been loaded it should insert a script to the page and it should be able to call the script on a button event.
Now I am able to add a button to all webpages by using:
nsCOMPtr<nsIDOMElement> NewInputElementTest;
rv = htmlDoc->CreateElement(NS_LITERAL_STRING("input"),getter_AddRefs(NewInputElementTest));
rv = NewInputElementTest->SetAttribute(NS_LITERAL_STRING("type"),NS_LITERAL_STRING("button"));
rv = NewInputElementTest->SetAttribute(NS_LITERAL_STRING("value"),NS_LITERAL_STRING("hummer"));
rv = body->AppendChild(NewInputElementTest,getter_AddRefs(AddedNewInputElement2));
The button is displayed correctly.
I wish to use the same procedure to add a SCRIPT to the page, like so:
rv = htmlDoc->CreateElement(NS_LITERAL_STRING("script"),getter_AddRefs(NewInputElement));
rv = NewInputElement->SetAttribute(NS_LITERAL_STRING("type"),NS_LITERAL_STRING("text/javascript"));
rv = NewInputElement->SetAttribute(NS_LITERAL_STRING("text"),NS_LITERAL_STRING("alert('hello world!')"));
rv = body->AppendChild(NewInputElement,getter_AddRefs(AddedNewInputElement));
All functions return success, but no script is added to the page. No alert is displayed, and if i insert a function and call it from the button.onclick then the FireFox log displayes that the function is not available.
If I use the exact same procedure from a javascript inside the html page, then it works find and the alert pops up.
Do I need to do anything to enable the script from my extension or why is the script not available from the button or anywhere else?
I hate to say it after you created a bunch of code, but check out Greasemonkey: https://addons.mozilla.org/en-US/firefox/addon/748
It'll probably handle a lot of your work for you.
Yes, sounds like you're tryin to re-invent the wheel. Use Greasemonkey as Oren suggested.
Here is a Greasemonkey script that I use to load external JS framework (Prototype and Scriptaculous in this case) load any number of external files (js and css) into a page.
// ==UserScript==
// #name External Loader
// #namespace http://ifelse.org
// #description Loads external JS and CSS
// #include http://*.yoursitedomainetc.com/*
// ==/UserScript==
var hasPrototype = ('Prototype' in unsafeWindow);
var hasEffects = ('Effect' in unsafeWindow);
function _require(url, isCSS) {
if (isCSS) {
var script = document.createElement('link');
script.setAttribute('type', 'text/css');
script.setAttribute('rel', 'stylesheet');
script.setAttribute('href', url);
} else {
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('charset', 'UTF-8');
script.src = url;
}
document.getElementsByTagName('head')[0].appendChild(script);
}
// Load prototype; shouldn't get here because it is already on the page
if ( !hasPrototype ) {
_require('http://path.com/to/prototype/1.6.0.2/prototype.js');
}
// Load scriptaculous effects if it's not already loaded
if ( !hasEffects ) {
_require('http://path.com/to/scriptaculous/1.8.1/effects.js');
}
// Add greasemonkey ajax object
// Copies format of Prototype Ajax.Request to
// Allow to easily swap out at a later point (i.e. no longer FF plugin)
unsafeWindow.Remote = new Object;
unsafeWindow.Remote.Ajax = function(url, options) {
if (options.onCreate) {
options["onCreate"]();
}
var request = {
method: options.method || 'get',
url: url + ('?' + unsafeWindow.Object.toQueryString(options.parameters) || ''),
onload: function(response) {
if (response.status == 200)
options["onComplete"](response);
options["onSuccess"]();
},
onerror: options.onFailure || null
};
window.setTimeout(GM_xmlhttpRequest, 0, request);
};
// Load these External files
_require('http://path/to/anything/and/dont/cache/it.js' + '?cache=' + (new Date()).getTime());
_require('http://paht/to/something/else.css', true);
}

Categories

Resources