Use global variables declared in a javascript file in another javascript file - javascript

I'm trying to get TestService.Server.WWW_SERVER_URL, but TestService.Server is undefined.
When I call test1(), it works well. But I cannot access the object literal TestServer.
Is there a different way?
test.html
<script type="text/javascript" language="javascript" src="TestService.js"></script>
<script type="text/javascript" language="javascript">
function test() {
alert("TestService.Server.WWW_SERVER_URL[" + TestService.Server.WWW_SERVER_URL + "]");
//test1();
}
</script>
TestService.js
document.write("<scr" + "ipt type='text/javascript' src='TestServer.js'><" + "/scr" + "ipt>");
var TestService = {
Server: TestServer,
Delimiter: ""
};
function test1() {
test2();
}
TestServer.js
var TestServer = {
WWW_SERVER_URL: "http://www.test.com"
};
function test2() {
alert("test2 has been called!");
}

You have this in your TestService.js
document.write("<scr" + "ipt type='text/javascript' src='TestServer.js'><" + "/scr" + "ipt>");
var TestService = {
Server: TestServer,
Delimiter: ""
};
you are trying to set a property in TestService with TestServer which hasnt loaded yet as you do not give time for the newly added script to load
TestService.Server will evaluate to undefined since TestServer does not exist yet
Setup an onload function that will add your script and then set your TestService.Server variable when its loaded
var TestService = {
Server: null,
Delimiter: ""
};
function test1() {
test2();
}
window.onload = function() {
var head = document.querySelector("head");
var script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("src", "TestServer.js");
head.addEventListener("load", function(event) {
if (event.target.nodeName === "SCRIPT"){
TestService.Server = TestServer;
}
}, true);
head.appendChild(script);
}

If you attach scripts dynamically, IE, Firefox, and Chrome will all
download the scripts in an asynchronous manner.
Firefox and Chrome will wait till all of the async requests return and
then will execute the scripts in the order that they are attached in
the DOM but IE executes the scripts in the order that they are
returned over the wire.
source
In your case, you can't gurantee TestServer.js get executed before TestService.js. So I will recommend you change the way you access global variable cross-file.
You can add TestServer.js to your html right before TestService.js, so they can execute one by one.
Anyhow, it is NOT recommended to do stuff like this, you can wrap them in your own namespace. Plus you'd better check the variable you want to use cross-file whether it's undefined before you use it.

Related

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

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.

The function should be callable

I have two js files. In the first I use this code:
var rightsRef = db.collection("users").doc(uid).collection("rights");
if(createDocumentWithoutId(rightsRef, "readGrades", "false", null, null, null, null) === true) {
window.location.href = "../public/main/main_index.html";
}
else {
}
In the second js file, I use this code:
function createDocumentWithoutId(var databaseRef, var titleValue1, var contentValue1, var titleValue2, var contentValue2, var titleValue3, var contentValue3) {
databaseRef.set({
titleValue1: contentValue1,
titleValue2: contentValue2,
titleValue3: contentValue3,
}).then(function() {
return true;
}).catch(function(error) {
console.error("Error adding document: ", error);
return false;
});
}
That I can call the function of the second js file I "import" both of them in the HTML file, by this way:
<script type="text/javascript" src="javascript1.js"></script>
<script type="text/javascript" src="../javascript2_folder/javascript2.js"></script>
But I am getting this Error:
ReferenceError: createDocumentWithoutId is not defined
You first JS file is being fully executed before the second one. That's what's causing your error - the function in the second file hasn't been loaded yet. You could reverse the order that they're define in your HTML so that the function is defined before it's called.
JS files execute when they're loaded, and in this case execution includes defining the functions. Try reversing the load order of your files, or putting some sort of check on the first code, such as this:
function amIDependentOnAnotherFile(){
if (typeof functionInAnotherFile == 'undefined')
return setTimeout(amIDependentOnAnotherFile, 5);
doSomething();
}

How can I inject a function into a script tag without including the wrapper syntax?

In this weird situation, I need to convert a JS function into a string, and then then that string into a dynamically generated <script> tag (which uses type javascript/worker so as to not be executed).
I'm working on a user script, injecting code to create a shared web worker between two third party sites. The thing about user scripts is you usually just want to use one file for the script's functionality, and web workers like to use a separate JS file for the worker. Well there's a way around that using a Blob. However, remember I'm only working with a script file, not an HTML file for the user script, so I want to store this part (the shared worker code) as a function in my script:
var worker = function() {
self.addEventListener('connect', function(e) {
var port = e.ports[0];
port.addEventListener('message', function(e) {
var message = e.data;
port.postMessage(message);
});
port.start();
});
};
then use the toString() on it so I can inject it into a dynamically generated script tag in the third party pages, so it ends up like this without the script calling on any extra files:
<script id="worker1" type="javascript/worker">
self.addEventListener('connect', function(e) {
var port = e.ports[0];
port.addEventListener('message', function(e) {
var message = e.data;
port.postMessage(message);
});
port.start();
});
</script>
However using the toString() method results in the word function being included, as shown in this example:
Remember I was going to append the function to a script tag with type
javascript/worker instead of text/javascript so the function
wouldn't be executed until it was converted to a blob and used as a
file source for the worker, but here I'm going to use
text/javascript so you can see the error when it executes.
var worker = function() {
self.addEventListener('connect', function(e) {
var port = e.ports[0];
port.addEventListener('message', function(e) {
var message = e.data;
port.postMessage(message);
});
port.start();
});
};
var func = worker.toString();
var script = document.createElement("script");
script.type = "text/javascript";
script.id = "worker1";
$("head").append(script);
$('body').append(script);
$('#worker1').append(func);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Just so you can understand what I'm doing, because it might be confusing,
a function like this would then convert the above
non-executed script tag into a Blob and use that instead of an
external file to create the shared worker:
var blob = new Blob([document.querySelector('#worker1').textContent],
{ type: "text/javascript" });
var sharedWorker = new SharedWorker(window.URL.createObjectURL(blob));
So, how can I inject the contents of the function into a script tag without including the function wrapper syntax(function (){)?
You can use string's methods to do this:
I think this can help you or something like this, to remove the function name.
function test(){
console.log('TEST TEST');
}
var fBody = test.toString()
var index = fBody.indexOf('{');
var functionBodyAsArray = fBody.substring(index + 1);
functionBodyAsArray = functionBodyAsArray.substring(0, functionBodyAsArray.length - 1);
console.log(functionBodyAsArray );

Dynamically loading Javascript files and load completion events

today I've been working on loading dynamic javascript code (files). The solution I use is :
function loadScript(scriptUrl) {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement('script');
script.id = 'uploadScript';
script.type = 'text/javascript';
script.src = scriptUrl;
head.appendChild(script);
}
the problem with this is that I don't know how to make sure WHEN the script contents are executed. Since the script contains classes (well JS classes), I wrote down a function that is called through setTimeout and checks if those objects are defined. This is not flexible as it is not automatical. So? Are there ways to load those scripts and have a reliable notification on when they have been executed?
You can use jquery's getScript and pass a callback function.
$.getScript("test.js", function(){
alert("Script loaded and executed.");
});
See: jquery.
The easiest way short of a JS library is to make an XMLHttpRequest and eval() the return. Your "onload" would be when the data is returned, and "oninit" would be right after you've eval'd.
EDIT: If you want to sequence this, you can create an AssetLoader that takes an array of scripts and won't eval() a script until the one preceding it has been fetched and initialized.
EDIT 2: You can also use the script.onload stuff in the post referenced in the comments. The eval method has a slight advantage in that you can separate and control the load and execution portions of the script import.
EDIT 3: Here's an example. Consider a file called foo.js that contains the following:
function foo () {
alert('bar');
}
Then execute the following from another page in your browser:
function initScript (scriptString) {
window.eval(scriptString);
}
function getScript (url, loadCallback, initCallback, callbackScope) {
var req = new XMLHttpRequest();
req.open('GET', url);
req.onreadystatechange = function (e) {
if (req.readyState == 4) {
if (loadCallback) loadCallback.apply(callbackScope);
initScript.call(null, req.responseText);
if (initCallback) initCallback.apply(callbackScope);
}
}
req.send();
}
function fooScriptLoaded () {
alert('script loaded');
}
function fooScriptInitialized () {
alert('script initialized');
foo();
}
window.onload = function () {
getScript('foo.js', fooScriptLoaded, fooScriptInitialized, null);
}
You will see the alerts "script loaded", "script initialized", and "bar". Obviously the implementation of XMLHttpRequest here isn't complete and there are all sorts of things you can do for whatever scope you want to execute the script in, but this is the core of it.
You could use a counter variable, and a kind of callback:
var scriptsToLoad = 10;
var loadedScripts = 0;
//...
function increaseLoadCount() {
loadedScripts++;
if(loadedScripts == scriptsToLoad) {
alert("start here");
}
}
//script1-10.js
increaseLoadCount();
Maybe a bit nicer than with a timeout..

How to use jQuery in Firefox Extension

I want to use jQuery inside a firefox extension,
I imported the library in the xul file like this:
<script type="application/x-javascript" src="chrome://myExtension/content/jquery.js"> </script>
but the $() function is not recognized in the xul file neither do the jQuery().
I googled about the problem and found some solutions but no one did work with me:
http://gluei.com/blog/view/using-jquery-inside-your-firefox-extension
http://forums.mozillazine.org/viewtopic.php?f=19&t=989465
I've also tried to pass the 'content.document' object(which refrences the 'document' object) as the context parameter to the jQuery function like this:
$('img',content.document);
but still not working,
does any one came across this problem before?
I use the following example.xul:
<?xml version="1.0"?>
<overlay id="example" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<head></head>
<script type="application/x-javascript" src="jquery.js"></script>
<script type="application/x-javascript" src="example.js"></script>
</overlay>
And here is an example.js
(function() {
jQuery.noConflict();
$ = function(selector,context) {
return new jQuery.fn.init(selector,context||example.doc);
};
$.fn = $.prototype = jQuery.fn;
example = new function(){};
example.log = function() {
Firebug.Console.logFormatted(arguments,null,"log");
};
example.run = function(doc,aEvent) {
// Check for website
if (!doc.location.href.match(/^http:\/\/(.*\.)?stackoverflow\.com(\/.*)?$/i))
return;
// Check if already loaded
if (doc.getElementById("plugin-example")) return;
// Setup
this.win = aEvent.target.defaultView.wrappedJSObject;
this.doc = doc;
// Hello World
this.main = main = $('<div id="plugin-example">').appendTo(doc.body).html('Example Loaded!');
main.css({
background:'#FFF',color:'#000',position:'absolute',top:0,left:0,padding:8
});
main.html(main.html() + ' - jQuery <b>' + $.fn.jquery + '</b>');
};
// Bind Plugin
var delay = function(aEvent) {
var doc = aEvent.originalTarget; setTimeout(function() {
example.run(doc,aEvent);
}, 1);
};
var load = function() {
gBrowser.addEventListener("DOMContentLoaded", delay, true);
};
window.addEventListener("pageshow", load, false);
})();
The following solution makes it possibile to use jQuery in contentScriptFile
(Targetting 1.5 Addon-sdk)
In your main.js:
exports.main = function() {
var pageMod = require("page-mod");
pageMod.PageMod({
include: "*",
contentScriptWhen: 'end',
contentScriptFile: [data.url("jquery-1.7.1-min.js") , data.url("notifier.js") , data.url("message.js")],
onAttach: function onAttach(worker) {
//show the message
worker.postMessage("Hello World");
}
});
};
In your message.js :
self.on("message", function(message){
if(message !== "undefined"){
Notifier.info(message);
}
});
Some pitfalls you need to watchs out for:
The order of the contentScriptFile array. if message.js would be placed first: jQuery won't be reconized.
Do not place a http:// url in the data.url (this does not work)!
All your javascript files should be in the data folder. (only main.js should be in lib folder)
There is an excellent article in the mozillaZine forums that describes this step-by-step: http://forums.mozillazine.org/viewtopic.php?f=19&t=2105087
I haven't tried it yet, though so I hesitate to duplicate the info here.
Turns out the current top-answer by #sunsean does not work as expected when it comes to handling multiple loads. The function should properly close over the document and avoid global state.
Also, you have to call jQuery.noConflict(true) to really avoid conflicts with other add-ons!
This is who I would write it (then again, I would avoid jquery (in add-ons) like the plague...).
First the overlay XUL
<?xml version="1.0"?>
<overlay id="test-addon-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="text/javascript" src="jquery.js"/>
<script type="text/javascript" src="overlay.js"/>
</overlay>
And then the overlay script:
// Use strict mode in particular to avoid implicitly var declarations
(function() {
"use strict";
// Main runner function for each content window.
// Similar to SDK page-mod, but without the security boundaries.
function run(window, document) {
// jquery setup. per https://stackoverflow.com/a/496970/484441
$ = function(selector,context) {
return new jq.fn.init(selector,context || document);
};
$.fn = $.prototype = jq.fn;
if (document.getElementById("my-example-addon-container")) {
return;
}
let main = $('<div id="my-example-addon-container">');
main.appendTo(document.body).text('Example Loaded!');
main.click(function() { //<--- added this function
main.text(document.location.href);
});
main.css({
background:'#FFF',color:'#000',position:'absolute',top:0,left:0,padding:8
});
};
const log = Components.utils.reportError.bind(Components.utils);
// Do not conflict with other add-ons using jquery.
const jq = jQuery.noConflict(true);
gBrowser.addEventListener("DOMContentLoaded", function load(evt) {
try {
// Call run with this == window ;)
let doc = evt.target.ownerDocument || evt.target;
if (!doc.location.href.startsWith("http")) {
// Do not even attempt to interact with non-http(s)? sites.
return;
}
run.call(doc.defaultView, doc.defaultView, doc);
}
catch (ex) {
log(ex);
}
}, true);
})();
Here is a complete add-on as a gist. Just drop in a copy of jquery and it should be good to go.
I think this is what Eric was saying, but you can load Javascript from the URL directly.
javascript:var%20s=document.createElement('script');s.setAttribute('src','http://YOURJAVASCRIPTFILE.js');document.getElementsByTagName('body')[0].appendChild(s);void(s);
Im assuming you want your extension to load JQuery so you can manipulate the page elements easily? My company's labs has something that does this using Javascript directly here: http://parkerfox.co.uk/labs/pixelperfect
It may be bad practice, but have you considered including it inline?
Instead of
$('img',content.document);
you can try
$('img',window.content.document);
In my case it works.

Categories

Resources