conditionally loading external javascript libraries in my plugin - javascript

I have written a plugin that depends on external libraries that I want to include conditionally, that is, the user can choose to not have them be included automatically in case the user's web site already has those libraries. Here is some pseudocode to illustrate the issue
<script type="text/javascript" src="path/to/plugin.js"></script>
<script type="text/javascript">
PLUGIN.init({
"param1": "foo",
"parma2": 33,
"include": {"jquery": 0, "googlemaps": 0}
});
</script>
In my plugin script
var PLUGIN = {
"init": function(obj) {
if (obj.include.googlemaps !== 0) {
document.write('<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true&v=3.6">\x3C/script>');
}
if (obj.include.jquery !== 0) {
document.write('<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js">\x3C/script>');
}
.. do more things ..
}
The problem is that when I am ready to "do more things," the libraries don't seem to be loaded yet. I get an error that jquery not found, or google maps not found. I can solve this by changing my code to
document.write('<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true&v=3.6">\x3C/script>');
document.write('<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js">\x3C/script>');
var PLUGIN = {
"init": function(obj) {
.. do more things ..
}
but now the user can't control loading the libraries or not loading them. Suggestions? Workarounds?
Update: Thanks for the suggestions, you all, but no joy so far. Here is what I am doing, and what is happening. Since I am potentially loading 0 or more scripts (the user can optionally decide which scripts need not be loaded), I have made my code like so
"importLib": function(libPath, callback) {
var newLib = document.createElement("script");
if (callback !== null) {
newLib.onload = callback;
}
newLib.src = libPath;
document.head.appendChild(newLib);
},
"init": function(obj) {
var scripts = [];
if (obj.include.googlemaps !== 0) {
scripts.push("http://maps.google.com/maps/api/js?sensor=true&v=3.6");
}
if (obj.include.jquery !== 0) {
scripts.push("http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js");
}
if (obj.include.anotherlib !== 0) {
scripts.push("http://path/to/another/lib.js");
}
var len_scripts = scripts.length,
callback = null;
if (len_scripts > 0) {
for (var i = 0; i < len_scripts; i++) {
// add callback only on the last lib to be loaded
if (i == len_scripts - 1) {
callback = function() { startApp(obj) };
}
importLib(scripts[i], callback);
}
}
// Start the app rightaway if no scripts need to be loaded
else {
startApp(obj);
}
},
"startApp": function(obj) {
}
What happens is that Firefox croaks with a attempt to run compile-and-go script on a cleared scope error, and Safari doesn't get that error, but doesn't load anything. Funnily, Safari error console shows no error at all. Seems like the Firefox error is caused by the line document.head.appendChild(newLib); which, if I comment, the error goes away, but of course, the web page doesn't load correctly.

You should add each script as a DOM node and use the onload attribute to take action when it has completed loading.
function importLib(libPath, callback) {
var newLib = document.createElement("script");
newLib.onload = callback;
newLib.src = libPath;
document.head.appendChild(newLib);
}
Above, the libPath argument is the URL of the library, and the callback argument is a function to call when loading is complete. You could use it as follows:
importLib("http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js", function() {
alert("jquery loaded!");
nowDoSomething(aboutIt);
});
By the way: in general, document.write is not a good solution for most problems (but I won't say never the right solution -- there are exceptions to every rule).

the above solution would work in modern browsers but for IE 7/8 u might wanna added little extra code, something like this:
function importLib(libPath, callback) {
var newLib = document.createElement("script");
if (navigator.userAgent.indexOf('MSIE') !== -1) {
newLib.onreadystatechange = function () {// this piece is for IE 7 and 8
if (this.readyState == 'complete') {
callback();
}
};
} else {
newLib.onload = callback;
}
newLib.src = libPath;
document.head.appendChild(newLib);
}

I encountered the same issue. One workaround if you are writing your site in .NET is by conditionally writing the script reference before the page loads from the code behind. My issue is when remote users access my app over VPN, it blocks access to the internet, thus google maps cannot be referenced. This prevents the rest of the page from loading within a reasonable timeframe. I tried controlling the script reference of the google maps library via jQuery's getScript() command, but as you found out, the subsequent google maps configuration code runs before the external library is referenced.
My solution was to conditionally reference google maps from code behind instead:
VB (code behind):
'if VPN mode is not enable, add the external google maps script reference (this speeds up the interface when using VPN significantly)
If Session("VPNMode") = False Then
Dim sb As System.Text.StringBuilder = New System.Text.StringBuilder()
sb.AppendLine("")
sb.AppendLine("<script type='text/javascript'")
sb.Append(" src='http://maps.google.com/maps/api/js?v=3&sensor=false'>")
sb.Append("</script>")
Dim header As LiteralControl = New LiteralControl
header.Text = sb.ToString()
Me.Page.Header.Controls.Add(header)
End If
Client side script (javascript):
<script type='text/javascript'>
$(function () {
if ($("input[name*='VPN']").is(":checked"))
{ }
else {
loadGoogleMap()
}
});
function loadGoogleMap() {
hazsite = new google.maps.LatLng(hazLat, hazLong);
map = new google.maps.Map(document.getElementById('map_canvas'), { zoom: 18, center: hazsite, mapTypeId: google.maps.MapTypeId.SATELLITE });
var marker = new google.maps.Marker({
position: hazsite,
map: map,
title: "Site Location"
});
}
</script>

Related

SharePoint 2016: how to force JS to execute on every site page despite Ajax and/or MDS?

I'm implemeting a very simple use case, and yet not only do I not find a solution, but I can't find any article that talks about it, as if I was the only one.
I want my custom Javascript to execute on every page of a given SharePoint site.
Easy, you'll say. Well, no. Far from it, like always with SharePoint.
Steps to reproduce :
Create a out-of-the-box publishing site
Include the custom javascript below using any of the means I describe below
Go to the site, to the home page. It's a publishing site, so by default you should have the left navigation pane with at least "Home" and "Documents" by default.
The first time you load the page, the javascript executes. Now, click on "documents". The page changes but the Javascript is not executed.
That's because SharePoint uses Ajax. Even if the MDS is disabled. It uses Ajax through the hash ( # ) in the URL.
For example, it transforms a very inocuous link like this one :
< a href src="/SitePages/Home.aspx">
into this URL when you click it:
https://your-url/sites/your-site/_layouts/15/start.aspx#/SitePages/Home.aspx
Here is my Javascript :
if (ExecuteOrDelayUntilScriptLoaded && _spBodyOnLoadFunctionNames) {
_spBodyOnLoadFunctionNames.push(ExecuteOrDelayUntilScriptLoaded(
function () {
alert("It's working!");
}, "sp.js"));
}
So, I've tried the following ways of including the Javascript :
Through a User Custom Action. I've used this very handy page to add it, but that's not relevant. The action is added to the site and I can see the JS in the DOM on first load. But then after I click on a link in the page and after SP uses Ajax, it does not execute it again.
By modifying the master page -- namely: seattle.html. at first I included it this way, simply under other native inclusions :
<head runat="server">
...
<!--SPM:<SharePoint:ScriptLink language="javascript" name="suitelinks.js" OnDemand="true" runat="server" Localizable="false"/>-->
<!--SPM:<SharePoint:ScriptLink language="javascript" Name="~sitecollection/SiteAssets/MYJAVASCRIPT.js" runat="server"/>-->
But then I read about AjaxDelta (here : https://msdn.microsoft.com/fr-fr/library/office/dn456543.aspx ) , and I moved my inclusion (still in the header) into < AjaxDelta >, like this :
<head runat="server">
...
<!--SPM:<SharePoint:AjaxDelta id="DeltaPlaceHolderAdditionalPageHead" Container="false" runat="server">-->
<!--SPM:<asp:ContentPlaceHolder id="PlaceHolderAdditionalPageHead" runat="server"/>-->
<!--SPM:<SharePoint:DelegateControl runat="server" ControlId="AdditionalPageHead" AllowMultipleControls="true"/>-->
<!--SPM:<SharePoint:ScriptLink language="javascript" Name="~sitecollection/SiteAssets/MYJAVASCRIPT.js" runat="server"/>-->
<!--SPM:</SharePoint:AjaxDelta>-->
...and yet nothing works. The Javascript is never executed when switching between pages of the same site by clicking on SharePoint's "managed" links.
I'm looking for a solution that handles elegantly SharePoint's Ajax, not something heavy and risky that hijacks every hyperlink on a page. For example I've tried to hook my code onto ajaxNavigate methods (for example : addNavigate) but I'm not sure I understand what's actualy going on there and if it could be of any help to me.
EDIT :
There seems to be a consensus (for example, here at the very bottom) that User Custom Actions get executed no matter what -- because SharePoint allegedly places their ScriptLink into the AjaxDelta for some reason. Well, that's not what I witnessed.
There's another consensus that this issue can be adressed by using "RegisterModuleInit". This doesn't work for me either.
I'm extermely puzzled. I think those two solutions do address navigation issues when the user clicks on a link and then clicks "back". But it does NOT address SharePoint's clever "managed", Ajax-riddled, hyperlinks.
I've finally found a solution that never seems to fail so far. That's a real relief.
Short answer: use asyncDeltaManager.add_endRequest
This MSDN discussion suggests a simple way to implement it:
https://social.msdn.microsoft.com/Forums/office/en-US/1ae292b4-3589-46f6-bedc-7bd9dc741f1b/javascript-code-to-execute-after-all-the-elements-and-css-are-loaded?forum=appsforsharepoint
$(function () {
ExecuteOrDelayUntilScriptLoaded(function () {
if (typeof asyncDeltaManager != "undefined")
asyncDeltaManager.add_endRequest(MYCUSTOMCODE); //execute it after any ajax event
else
MYCUSTOMCODE(); //execute it at first load
}, "start.js");
});
This shows how to include it properly in SharePoint's cycle (with ExecuteOrDelayUntilScriptLoaded )
https://sharepoint.stackexchange.com/questions/171490/javacript-only-executed-on-first-page-load
Full-blown solution (objet "LefeCycleHelper"), by Mx
https://sharepoint.stackexchange.com/questions/192974/where-to-place-a-js-script-with-whom-i-need-to-get-an-div-id/193009#193009
//use an IIFE to create a scope and dont dirty the global scope
(function (_) {
// use strict to ensure we dont code stupid
'use strict';
var initHandlers = [];
var initMDSHandlers = [];
var ensureSharePoint = function (handler) {
var sodLoaded = typeof (_v_dictSod) !== 'undefined' && _v_dictSod['sp.js'] != null && _v_dictSod['sp.js'].state === Sods.loaded;
if (sodLoaded) {
handler();
} else {
SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function () { });
SP.SOD.executeOrDelayUntilScriptLoaded(handler, 'sp.js');
}
};
var initMDS = function () {
for (var i = 0; i < initMDSHandlers.length; i++) {
initMDSHandlers[i]();
}
};
var init = function () {
// Register MDS handler
if ('undefined' != typeof g_MinimalDownload && g_MinimalDownload && (window.location.pathname.toLowerCase()).endsWith('/_layouts/15/start.aspx') && 'undefined' != typeof asyncDeltaManager) {
asyncDeltaManager.add_endRequest(initMDS);
} else {
for (var i = 0; i < initHandlers.length; i++) {
initHandlers[i]();
}
}
};
var registerInit = function (handler) {
initHandlers.push(handler);
};
var registerInitMDS = function (handler) {
initMDSHandlers.push(handler);
};
var domReady = (function (handler) {
var fns = [];
var listener;
var loaded = (document.documentElement.doScroll ? /^loaded|^c/ : /^loaded|^i|^c/).test(document.readyState);
if (!loaded) {
document.addEventListener('DOMContentLoaded', listener = function () {
document.removeEventListener('DOMContentLoaded', listener);
loaded = 1;
while (listener = fns.shift()) listener();
});
}
return function (fn) {
loaded ? setTimeout(fn, 0) : fns.push(fn);
};
})();
var attachToLoad = function (functionToAttach) {
registerInit(functionToAttach);
registerInitMDS(functionToAttach);
domReady(function () {
init();
});
};
_.AttachToLoad = attachToLoad;
// THIS WILL PROTECT YOUR GLOBAL VAR FROM THE GARBAGE COLLECTOR
window.LifeCycleHelper = _;
if (window.Function != 'undefined' && typeof (Function.registerNamespace) == 'function') {
Function.registerNamespace('LifeCycleHelper');
}
})({});
var theCodeYouWantToRun = function () {
alert('theCodeYouWantToRun');
};
window.LifeCycleHelper.AttachToLoad(theCodeYouWantToRun);

How to recognize that one js file loaded?

I try to display progress bar when page loading. I want change width of progress bar after js files loaded .At the final after load all documents set with of progress bar to 100% .Now I need to recognize js files loading with javascript.
Is this possible ? Please advice.
For internal js files loading recognition:
As functions and variables can be accessed from another file you can set the value of global progress variable and display it's value by calling the function
//on page or head js file
var progress = 0;
function displayProgress()
{
//show progress based on 'progress' variable
}
//file1.js
progress += 10;
displayProgress();
...
//file2.js
progress += 20;
displayProgress();
...
For external js files there is good article. The main idea is to periodically check existense of external functions (typeof fixpng =='function') and if it exist - stop checking and display progress.
Here's the JavaScript code to load the external library with a
callback passed in:
function loadExtScript(src, callback) { var s = document.createElement('script'); s.src = src; document.body.appendChild(s); // if loaded...call the callback }
Firefox allows you to listen for the onload event on the script
element:
s.onload = callback;
With Internet Explorer you can wait for a state change on the script
element:
s.onreadystatechange = function() { if ( this.readyState != "loaded"
) return; callback.call(); }
The problem comes with Safari - there's no event change for Safari, so
we can't tell when the script has loaded. This is the solution I came
up with (and this solution should also work with Opera):
function loadExtScript(src, test, callback) { var s =
document.createElement('script'); s.src = src;
document.body.appendChild(s);
var callbackTimer = setInterval(function() {
var call = false;
try {
call = test.call();
} catch (e) {}
if (call) {
clearInterval(callbackTimer);
callback.call();
} }, 100); }
The function takes a test as a parameter. Since you are the designer
of the app, you'll know what successful test is. Once this test is
true, it will execute the callback. A simple test could be to check
whether a function exists, for example:
loadExtScript('/fixpng.js', function() { return (typeof fixpng ==
'function'); }, myCallbackFunction);
If you know at least one defined namespace (almost all libraries and plugins have it: e.g. jQuery, jQuery.ui, jQuery.mobile, toastr, DataTable, etc.) or one global variable name introduced by the script files which are being loaded, then you can do this:
(function(undefined) {
var scriptFilesLoaded = false,
files = [],
timer = setInterval(function(){
try {
files = [
jQuery,
jQuery.mobile,
jQuery.ui,
someGlobalVariableName
];
if(files.indexOf(undefined)<0){
scriptFilesLoaded = true;
clearInterval(timer);
}
}
catch(e) {
console.warn('Preloader in action: Script files not loaded yet.');
}
},200);
})();
It doesn't matter if the script file is remote or local.

Load javascript sequentially from javascript code

I have a javascript widget that is included in a page by inserting a single script tag (as the application should be easiliy distributable):
<script type="text/javascript" src="loadMyWidget.js"></script>
loadMyWidget.js then needs to load multiple script files, which has to run in a certain sequence. I've tried to load them async by inserting script elements into the DOM, but that doesn't give me control of the sequence.
I also tried using head.js which is great for modern browsers, but I can't get it to work in IE7 and 8.
Minifying the scripts into one file is unfortunately difficult, as it is composed of a number of files from different projects and I wouldn't know when to update the script.
As simple as it seems, I need to load javascript files from javascript code in a certain sequence and get it to work in all browsers, including IE7 and 8.
If you need vanilla JS, something like this could work:
function loadScripts(scripts, complete) {
var loadScript = function( src ) {
var xmlhttp, next;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
} else {
try {
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
} catch(e) {
return;
}
}
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
eval(xmlhttp.responseText);
next = scripts.shift();
if ( next ) {
loadScript(next);
} else if ( typeof complete == 'function' ) {
complete();
}
}
}
xmlhttp.open("GET", src , true);
xmlhttp.send();
};
loadScript( scripts.shift() );
}
loadScripts(['jquery.js','jquery.plugin.js'], function() {
console.log('loaded');
});
Tested in Chrome, should work in IE too.
If you're using jQuery.getScript() you can use it as a $.when() to hold off execution until things have stopped loading.
If by "sequential execution" you mean that you need to load the requisites before execution the following will work
$(function(){
$.when(
$.getScript("/script1"),
$.getScript("/scirpt2"),
$.getScript("/script3")
}).done(function(){
// do stuff with the contents of my new script files
});
If by sequential execution you mean that you need to execute files one after the other try this:
$.Deferred()
.then(function () { return $.getScript("/script1"); })
.then(function () { return $.getScript("/scirpt2"); })
.then(function () { return $.getScript("/script3"); })
.resolve();
Of course, this requires jQuery, which after your edits, this may not work for you.
Suggested Reading
http://api.jquery.com/jQuery.getScript
http://api.jquery.com/category/deferred-object/
http://api.jquery.com/jQuery.when/
I have run into this exact same issue and handled it with:
document.write('<script type="text/javascript" src="other1.js"></script>');
document.write('<script type="text/javascript" src="other2.js"></script>');
runSomeCode();
The code will be loaded and run synchronously. Pros: simple, light, cross browser compliant, no deps. Cons: ugly.
More details: https://stackoverflow.com/a/3292763/235179
Have you tried require.js? http://requirejs.org/
function loadScript(arrayOfUrlStrings, callback) {
var numScripts = arrayOfUrlStrings.length;
var count = 0;
var headElement = document.getElementsByTagName('head')[0]
function onLoad() {
count += 1;
if (count === numScripts) {
callback();
} else {
addScript();
}
}
function addScript() {
var script = document.createElement('script');
script.src = arrayOfUrlStrings[count];
script.onload = onLoad;
headElement.appendChild(script);
}
addScript();
}

Windows 7 gadget not loading

I'm developing a simple gadget for Windows 7 as a learning exercise.
I read in this article (under the subtopic Gadgets and Script) that to initialize the gadget, you should use document.onreadystatechange instead of events such as onLOad. I've seen it in the example project code I've looked through as well. This is what I came up with for my project.
document.onreadystatechange = function()
{
if(document.readyState == "complete")
{
System.Gadget.settingsUI = "settings.html"; //this line enables the settings UI
System.Gadget.onSettingsClosed = settingsClosed;
}
}
However when I use this snippet in my work, it doesn't work. The Options button in the gadget doesn't show up. If I use onLoad, it works. I have installed 2 gadgets. Each of them use these 2 methods. One use onLoad and the other use document.onreadystatechange. And both of them works!
Now I'm confused why it doesn't work with my gadget. Is there any important part I'm overlooking?
try something along these lines,
move your onsettingsclosed to a different event and call the function with it
document.onreadystatechange = function()
{
if(document.readyState=="complete")
{
var searchTags = System.Gadget.Settings.read("searchTags");
if(searchTags != "")
{
searchBox.value = searchTags;
}
}
}
System.Gadget.onSettingsClosing = function(event)
{
if (event.closeAction == event.Action.commit)
{
var searchTags = searchBox.value;
if(searchTags != "")
{
System.Gadget.Settings.write("searchTags", searchTags);
}
event.cancel = false;
}
}

Verify External Script Is Loaded

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.

Categories