I'm currently loading the Google Maps API using JavaScript like so:
// Private methods
function loadApi() {
var script = document.createElement('script');
script.onerror = function(e) {
exit('MarvMap: Unable to load Google Map API, please check the URL.');
};
script.onload = function () {
if (this.options.debug) console.log('MarvMap (Debug): Google Map API loaded, using: ' + this.options.api.key);
}.call(this);
script.src = this.options.api.url;
document.head.appendChild(script);
}
This loads the script and ads it before the closing </head> tag. However the script it loads appears to load additional scripts so it's not ready to use straight away.
Here you can see that i'm calling the above function first, and then trying to use the Google Maps API:
// Public methods
MarvMap.prototype.init = function() {
loadApi.call(this);
// Setup map options and map reference
initMapOptions.call(this);
// Build the map
build.call(this);
};
However im getting an error saying google is not defined on the line marked below:
function initMapOptions() {
this.mapReference = new google.maps; // Error here
this.map_options = {
map_type: function(i) {
if (this.mapReference.MapTypeId[(i-1)] !== undefined) {
return this.mapReference.MapTypeId[(i-1)];
} else { exit(this.errors.map_type(i)); }
}.bind(this)
};
}
to delay api usage while i wait for some file (or multiple files) to load i would use something like:
loadApi(this);
ensureApiIsLoaded();
function ensureApiIsLoaded(){
if(typeof google == 'undefined'){
setTimeout(ensureApiIsLoaded, 125);
return;
} else {
callbackGoesHere(); //initMapOptions?
}
};
regarding your while loop:
while (typeof google != 'undefined') {
setTimeout(function() { }, 1000);
}
google;
you initiate the script loading -> api isn't ready, obviously
the while condition will evaluate to 'false' -> body of while loop is dismissed
google is still undefined because there wasn't any time to actually load the lib in between ordering that the script file be acquired and the attempted usage
-> error.
Related
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.
I'd like some insight into a little problem I'm encountering with a custom JS plugin that I've made. It's reasonably simple, I'm using a plugin JS template to write out plugins, which I'll attach. I'm then initialising the plugin, and ideally need to figure out if there's a way I can check if the plugin JS file is loaded before doing anything:
plugin template
(function() {
this.MyPlugin = function() {
// default settings
const INBOUND_CONFIG = {
isEnabled: false
}
// Create options by extending defaults with the passed in arugments
if (arguments[0] && typeof arguments[0] === "object") {
this.options = extendDefaults(INBOUND_CONFIG, arguments[0]);
}
// custom public method
HoneycombInbound.prototype.getSettings = function() {
}
// Utility method to extend defaults with user options
function extendDefaults(source, properties) {
var property;
for (property in properties) {
if (properties.hasOwnProperty(property)) {
source[property] = properties[property];
}
}
return source;
}
}
}());
With the above, I'd initialise the plugin in HTML as follows:
<script src="./my-plugin.js"></script>
<script>
const plugin = new MyPlugin()
// this doesn't work if the script file isn't linked:
if (plugin) {
// do something
}
</script>
Unfortunately, a simple check of plugin doesn't actually work if the JS file isn't loaded for instance, I've even tried something like: if (new MyPlugin()) {} with little hope.
I essentially need a way of not throwing an error in the browser. Any solution I can use to check correctly if it's loaded before executing?
A solution that has worked for me is to work with the WebAPI postMessage
I've used to communicate with the client, that the plugin is ready.
So on the plugin you will need something like this:
window.postMessage("The plugin is ready to go" );
And on your client you will need to add a new listener that can catch the message:
window.addEventListener("message", myMessage, false);
const myMessage = (event) => {
if (event.data === "The plugin is ready to go")
// This is your message so do your stuff here
...
}
EDIT
So to achieve this behavio, you will need to enable on the client sidethe initialization:
const myPlugin = {
init: () => {
// Initilize the plugin if you are using the DOM you can add a parameter with an id to mount it whenever you need it
//Otherwise just configure or initilize the variables that you will use
// And here you need to pass the messsage to tell the client that everyting is ready
// Here you need to configure the origin correctly depending on your needs check the documentation for more details
window.addEventListener("message", myMessage, false);
},
someFunctionality: () => {
//This pattern is to expose the funtionalities to the client so you can achieve any
}
};
So now on your client you just need to add your script and tell the plugin to initialize:
<script type='text/javascript' src='/myPlugin.js'></script>
<script>
window.addEventListener("message", myMessage, false);
const myMessage = (event) => {
if (event.data === "The plugin is ready to go")
// This is your message so do your stuff here
...
}
myPlugin.init();
</script>
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);
}
}
I only load Google maps When needed. Before I refactored my code, it worked and looked like this:
function initialize_map_brand() {
(...)
}
jQuery(document).ready(function ($) {
load_google_map('initialize_map_brand');
});
After refactoring, my code looks like this:
lib = {
loadGoogleMapScript: function(_callback) {
(...)
script.src = 'http://maps.google.com/maps/api/js?sensor=false&callback=' +_callback;
}
}
Page = {
viewPage: {
init: function() {
lib.loadGoogleMapScript(self.initialize_map);
},
initialize_map: function() {
var locations = [ (...)
}
}
}
jQuery(document).ready(function ($) {
Page.viewBPage.init();
});
And now I'm getting this error:
"NetworkError: 400 Bad Request -
http://maps.google.com/maps/api/js?sensor=false&callback=function%20()%20{var%20locations%20=%20[[%....
It seems it takes the entire function code and passes it in the url. So how can I load the Google Maps script correctly with my new code?
Se my fiddle here.
According to the documentation You need to provide a string for the url callback parameter with the name of a function in the global scope.
You instead passed the function as parameter, so the function body gets appended to the url.
var Page = {
viewPage: {
init: function() {
lib.loadGoogleMapScript('Page.viewPage.initialize_map');
},
initialize_map: function() {
}
}
};
window.Page = Page;
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);
}