I am using JQuery to inject dynamically script tags in the body tab of a webpage. I got something like :
function addJS(url) {
$("body").append('<script type="text/javascript" src='+url+'></script>');
}
I add several scripts this way, and try to use them right after. E.G :
lib.js
function core() {...}
alert("I'am here !");
init.js
addJS("lib.js");
c = new core();
test.html
<html>
<head>
<title>test</title>
<script type="text/javascript" src="init.js"></script>
</head>
<body>
Hello
</body>
</html>
Loading test.html pops up "I'm here" and then ends up with an error "core is not defined". Of course merging both of the JS files will make them work perfectly.
I just don't get it o_O.
EDIT
I simplified this example, but Jeff answer made me understand that it was a mistake. So here are some details :
init.js is not in the head of test.html when it reload because I inject it with a code exectuted on a bookmarklet.
So the real execution process is the following :
reload test.html > run the bookmarklet > jquery and init.js are inserted > lib.js is inserted
Sorry for the confusion.
EDIT 2
Now I have the solution to my problem (that was quick :-)) but I am still interested to the answer to my question. Why does this go wrong ?
jQuery has this functionality built in with getScript.
You get the "core is not defined" error because the scripts are loaded asynchronous. Which means that your browser will start loading lib.js in the background, and continue executing init.js, and then encounter "new core()" before the lib.js has finished loading.
The getScript function has a callback that will be triggered after the script is finished loading:
$.getScript('lib.js', function() {
var c = new core();
});
Notice your addJS function is appending to the end of the body element.
Since browsers will run scripts as they appear in the HTML source,
c = new core()
will run before your lib.js script is loaded (at the end of the body element).
I would recommend moving c = new core(); into the $(document).ready(function() {...}); or into a script element AFTER the body tag.
IMO, appending the script tag to the end of the document to load a script is rather unsightly. Reason:
you are trusting the browser to automatically fetch the script and load it.
You have no way of finding out whether the script is loading, has loaded, or if it encountered an error (maybe 404?)
The appropriate way would be to either use $.getScript(), or for a finer-grained control, fetch the script file with $.ajax() and use eval().
However, the second method has some issues: if you invoked eval() inside a function, then the script won't be available outside it! This mandates workarounds...
but why bother! use $.getScript() and get over with it :)
cheers, jrh.
In response to why your code is failing: Adding a script tag to the body does not block further script execution. Your code adds it, which starts the browser download process. Meanwhile, your script tries to call core(), which doesn't exist because lib.js hasn't finished downloading. jQuery works because it waits till the script finishes downloading before executing your callback function.
Related
I have some javascript that is not required for my initial page load. I need to load it based on some condition that will be evaluated client-side.
$(document).ready(function() {
let someCondition = true; // someCondition is dynamic
if (someCondition) {
var element = document.createElement('script');
element.src = 'https://raw.githubusercontent.com/Useless-Garbage-Institute/useless-garbage/master/index.js';
element.defer = true; // does this make a difference?
element.onload = function() {
// do some library dependent stuff here
document.getElementById("loading").textContent = "Loaded";
};
document.body.appendChild(element);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1 id="loading">Loading...</h1>
Does it make a difference (in terms of how browser will treat the script tag), if a new tag created using javascript, after document is ready, has 'defer' attribute or not? I think there is no difference, but how can I say for sure?
I believe I understand how deferred scripts behave when script tag is part of the initial html (as described here). Also, this question is not about whether element.defer=true can be used or not (subject of this question).
No that doesn't make any difference, the defer attribute is ignored in case of "non-parser-inserted" scripts:
<script defer src="data:text/javascript,console.log('inline defer')"></script>
<script>
const script = document.createElement("script");
script.src = "data:text/javascript,console.log('dynamic defer')";
script.defer = true;
document.body.append(script);
</script>
<!-- force delaying of parsing -->
<script src="https://deelay.me/5000/https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Look at your browser's console or pay attention to the logs timestamps to see that the dynamically inserted script actually did execute while we were waiting for the delayed script to be fetched.
There's a difference between adding them to the function and adding directly the CDN ( especially in your case ).
Let's look at the code execution of the above-mentioned code first,
You have added the jquery CDN first ( without defer ) so that loads first.
$(document).ready will be fired once after the complete load of jquery.
There'll be the creation and insertion of a new script tag to the dom.
Download the https://raw.githubusercontent.com/Useless-Garbage-Institute/useless-garbage/master/index.js asynchronously.
Let's look at another approach: adding CDN to the code:
Your DOM will have 2 script tags.
Both will start loading based on the type of load parallelly ( defer async etc ).
Notice you are not waiting for the dom ready event to load the second script.
I suggest adding only the main JS part in a js file and adding it to the CDN. Others can wait load with the delay.
In case you are really needed with a js src, then don't load it the first way since it waits for the complete page load.
I suggest you read and look at web-vitals and SEO for this.
and for your other question, yes you can add defer attribute with element.defer=true to the elements while creating and loading to DOM.
Hope this answer helps you!
Feel free to comment if you get any errors or doubts.
I think the JQuery Arrive lib will solve your case.
I am including an external JS file, which has some code that I need to run after a particular thing happens (after contact form is submitted).
The external file has a function but also calls the function. This isn't much of a pain if it was OK to run that function on page load, but I need to run its function after a form is submitted. My form is submitted via AJAX, so I need to run the external file's function after the form has submitted, only.
How can I do this?
Here's an illustration to help...
MY SITE'S FOOTER:
<script type="text/javascript">
var a_variable = 12345;
</script>
<script src="//domain.com/external.js" type="text/javascript"></script>
</body>
</html>
CONTENTS OF EXTERNAL.JS:
function doThis(){
alert(window.a_variable);
}
doThis();
But I only want to run doThis() at a time that I want, not on page load.
Can I stop doThis(); from executing until I explicitly tell it to?
NOTE: I have already tried including external.js by creating a script tag (in javascript) and loading it that way, but the function in external.js needs to write to my users' browser, to which the users' browser says Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.. After that, I looked into the solutions in this answer, but it did not solve the problem. So, I am looking for a way to stop doThis() from executing.
Can you edit the external.js file? If so you should just remove the doThis() line and add a listener to call the function on window load like so:
function doThis(){
alert(window.a_variable);
}
window.onload = doThis;
If you cannot edit the JavaScript file you are a bit stuck. You could try to rewrite it and just include it locally, following the above pattern (since you can pull the file I assume you have access to the source and could easily download the file and edit it). I would suggest this course of action.
If you really need it to be pulled from that specific source and cannot edit the file, you can look into something like require.js. This will allow you to load a JavaScript file from within other JavaScript code, so you can wait for the document to load before including it. So your html could be:
<html>
<body>
<script type="text/javascript">
var a_variable = 12345;
window.onload = function() {
require(["//domain.com/external.js"], function() {
console.log("loaded!");
});
}
</script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.3/require.js"></script>
</body>
</html>
I am having a error if I used async in script tag like below
<script async src="main.js"></script>
The error shows only on chrome saying
Uncaught ReferenceError: $ is not defined
If I removed the async from the script tag there is no more error in my console and everything works fine.
Do you have any idea why am having this problem ?
EDIT
Below script are placed inside the head tag
<!-- JS -->
<script async src="../js/jquery/jquery-1.10.1.min.js"> </script>
<script async src="../js/vendor/modernizr-2.8.2.min.js"></script>
<script async src="../js/asynchronous-resources/2014-06-03-asynchronous-resources.js"></script>
<!-- IE JS -->
<!--[if !IE]><!--><script async src="../js/ie10.js"></script><!--<![endif]-->
main.js is added to the footer.
<script async src="../js/main.js"></script>
I have found a similar question on stackoverflow.
Load jquery asynchronously before other scripts
I had to change async to defer there is no more issue now in firefox, chrome and IE9.
Byt it breaks in IE8 and IE7 completely. jQuery stopped working if I use defer.
Okay.....
So Basically...
WITHOUT ASYNC:
JS files are downloaded and executed sequentially IN ORDER ... i.e., The first encountered JS file gets downloaded and executed first, then the next and so on, while at this time the HTML parser is blocked which means no further progress in HTML rendering.
WITH ASYNC:
JS files[all] are put to asynchronous download as they are encountered, and are executed as soon as they get fully downloaded. HTML parser is not blocked for the time they are downloaded. So the HTML rendering is more progressive.
DISADVANTAGE:
However, the problem with asynchronous download and execution is that the JS files are executed as soon as they are downloaded... i.e., NO ORDER is maintained..for example, a smaller JS file(main.js) that gets downloaded before a larger file(jQuery.js) gets executed before the larger file.
So, if my smaller file has reference to some variable / code ($ of jQuery) initialized in the larger file, alas, the code has not been initialized yet and therefore an error is thrown. And that is what is happening here.
WHAT SHOULD YOU DO:
1> Remove async attribute and live with a lower performance.
2> Use dynamic loading of scripts which maintains the order of execution. Dynamic scripts are downloaded asynchronously by default but are executed seperately from the DOM parsing, therefore not blocking the HTML rendering. In this, by writing
script.async = false;
we force these to get downloaded and executed IN ORDER.
<script type="text/javascript">
[
'jQuery.js',
'main.js'
].forEach(function(src) {
var script = document.createElement('script');
script.src = src;
script.async = false;
document.head.appendChild(script);
});
</script>
Had the same problem but wanted to keep the async for better performance. I've fixed the problem by moving all code from main.js into an onload function. Like so:
window.onload = function () {
// main.js code
};
This way the main.js code only runs after the page is loaded (including the jQuery).
UPDATE: Here's another (better?) way:
var myScript = document.createElement('script');
myScript.src = 'http://code.jquery.com/jquery-1.9.1.min.js';
myScript.onload = function() {
console.log('jQuery loaded.');
};
document.body.appendChild(myScript);
Your main.js is getting executed asynchronously even before jquery is loading synchronously.
Either add async attribute to jquery as well or remove async attribute from main.js
MDN
Set this Boolean attribute to indicate that the browser should, if
possible, execute the script asynchronously. It has no effect on
inline scripts (i.e., scripts that don't have the src attribute).
I just finished writing jsinit.js just for this purpose.
It enables you to use async even with jQuery. It works by delaying execution of your module until jquery has finished loading. (And it can be loaded "async" by itself!)
Have a look: https://github.com/ScheintodX/jqinit.js
This way work fine on ggle chrome for me :
Replace
<script async src="JS/jquery-3.1.1.min.js"></script>
<script async src="JS/my.js"></script>
by
<script defer src="JS/jquery-3.1.1.min.js"></script>
<script defer src="JS/my.js"></script>
it charge first jquery and after my.js like the order
My guess is main.js has some jQuery in it. The async tag will force the browser to parse the JavaScript as soon as it is available, which may be before the jQuery script is ready.
From W3schools:
When present, it specifies that the script will be executed
asynchronously as soon as it is available.
To solve:
Add the async attribute to your jQuery in addition to main.js
Remove the async attribute from main.js
Remove the async tag altogether
If you provide some more information on what you're using the async tag for I can offer some more suggestions.
To solve this problem you can simply write all of the inline code as a function. Then add the onload attribute to the script tag for the async jQuery like so:
<script src="../scripts/jquery.min.js" async onload="myFunction()"></script>
Make sure that in the head section of your html myFunction is placed before the async script tag.
This is a bit of an oddball use case, but I have my reasons:
I'd like to be able to write
<script type="text/javascript" src="first.js"></script>
<script type="text/javascript" src="second.js"></script>
in my markup and, using the code in first.js, prevent or delay the execution of second.js. Is this possible, in any browser? What if the contents of first.js are inlined? (If it helps, assume that the second script tag has an id attribute.)
Since I've gotten a couple of answers that missed what I'm getting at, I should clarify:
The solution must be entirely within first.js. Anything that require changes to the original HTML of the page, or to second.js, is not acceptable.
It is acceptable to load second.js via Ajax and execute it using eval. That's the easy part. The hard part is preventing the immediate execution of second.js.
Assume that you don't know what's in second.js. So, you can't just replace each global function called by second.js with a no-op function. (Plus, this would almost certainly lead to errors.)
If you know of a solution that works in some browsers but not in others, I'd love to hear it.
Example: To make this a little more concrete, let's say that the code
<script type="text/javascript">
function func() {
window.meaningOfLife = 42;
window.loadSecond();
};
setTimeout(func, 10);
</script>
precedes the two script includes, and that second.js contains the line
if (window.meaningOfLife !== 42) {throw new Error();}
first.js should be able to prevent this error by delaying second.js from executing until window.loadSecond is run. (Assume the implementation of window.loadSecond is also in first.js.) It is not allowed to touch window.meaningOfLife.
Update: Alohci's answer meets these requirements, but only on the condition that the second script tag comes immediately after the first, with nothing but whitespace in between. If someone could extend his hack to avoid that requirement, without introducing other unwanted consequences, that would be amazing...
Given your specific requirements set, this is actually quite simple and should work completely cross-browser. It does require however, that first.js immediately precedes second.js without anything between them except white space.
First, let's assume that the HTML looks like this:
<!DOCTYPE html>
<html>
<head>
<title>Test Case</title>
<meta charset="UTF-8" />
<script type="text/javascript">
function func() {
window.meaningOfLife = 42;
window.loadSecond();
};
</script>
<script type="text/javascript" src="first.js"></script>
<script type="text/javascript" src="second.js"></script>
</head>
<body>
<p>Lorem ipsum dolor sit amet ...</p>
Run Func()
</body>
</html>
I've removed the setTimeout because that can cause func() to run before start.js runs causing a "loadSecond is not defined" error. Instead, I've provided an anchor to be clicked on to run func().
Second, let's assume that second.js looks like this:
document.body.appendChild(document.createTextNode("second.js has run. "));
if (window.meaningOfLife !== 42) {throw new Error();}
Here, I've just added a line to append some text to the document body, so that it is easier to see when second.js actually runs.
Then the solution for first.js is this:
function loadSecond()
{
var runSecond = document.createElement("script");
runSecond.setAttribute("src", "second.js");
document.body.appendChild(runSecond);
}
document.write("<script type='application/x-suppress'>");
The loadSecond function is just there to run second.js when func() runs.
The key to the solution is the document.write line. It will inject into the HTML <script type='application/x-suppress'> between the close script tag of first.js and the open script tag of second.js.
The parser will see this and start a new script element. Because the type attribute has a value which is not one that the browser recognises as being JavaScript, it will not attempt to run its content. (So there are an infinite number of possible type attribute values you could use here, but you must include a type attribute, as in its absence, the browser will assume that the script's content is JavaScript.)
The second.js script's opening tag will then be parsed as text content of the new script element and not executed. Finally the second.js script's closing tag will be re-purposed to close the new script element instead, which means that the remainder of the HTML is parsed correctly.
You can see a working version at http://www.alohci.net/static/jsprevent/jsprevent.htm
In first.js, set var shouldILoad = true
Then, load second.js this way:
<script>
if (shouldILoad) {
(function() {
var myscript = document.createElement('script');
myscript.type = 'text/javascript';
myscript.src = ('second.js');
var s = document.getElementById('myscript');
s.parentNode.insertBefore(myscript, s);
})();
}
</script>
(where 'myscript' is the ID of some element before which you'd like to insert the new Script element)
As far as I know, you can't. If the markup looks like
<script type="text/javascript" src="first.js"></script>
<script type="text/javascript" src="second.js"></script>
you can't access the second script element from within first.js, as it hasn't been added to the DOM at the moment the first script runs (even not if you assign an id to the second element). It doesn't matter whether the code of second.js is put inline or in an external file.
The only thing I don't understand is your second point. First you say that you can't control the markup of the document, but then you state it is possible to load second.js dynamically (using AJAX).
Following article describes the way you could block (3-rd party) scripts loading/execution from your script (including the both tag in the page head and dynamically added tags).
To handle existing tags on a page:
Use a MutationObserver to observe script elements insertion and inside the MutationObserver callback backup the script (to enable/insert it later) and change the script type to "javascript/blocked" (not works in IE, Edge, Firefox). Also you could handle deprecated (but working) beforescriptexecute event in Firefox to prevent script load.
Manually set type "javascript/blocked" (works everywhere including IE and Edge) like
<script type="text/javascript" type="javascript/blocked" src="second.js"></script>, then backup it in MutationObserver callback and re-add it later.
To handle dynamically added tags
Monkey-patch the document.createElement.
Override ‘src’ and ‘type’ descriptors on the HTMLScriptElement prototype.
Also this guys provide a yett library with the approach described in the article.
All <script> tags have their own execution context, which makes it nearly impossible to interfere with each other. Of course you've got the (infamous) global object (referenced by window in browsers).
Preventing the execution of second.js is rather simple: break it!
Assuming that second.js tries to call document.getElementById for example:
Working example of breaking jQuery, then loading later (with dependecies).
Tested on: IE 6+, FF 3.6+, Chrome
end of first.js
var execute;
// saving our position
var scripts = document.getElementsByTagName("script");
var i = scripts.length;
// breaking getElementById
var byId = document.getElementById;
document.getElementById = null;
var interval = setInterval(function () {
if (i != scripts.length) {
var second = scripts[i];
// stop polling
clearInterval(interval);
// fix getElementById
document.getElementById = byId;
// set the delayed callback
execute = function (onload) {
var script = document.createElement("script");
script.src = second.src;
script.onload = script.onreadystatechange = onload;
document.getElementsByTagName("head")[0].appendChild(script);
};
}
}, 100);
anytime you wanna execute second.js
execute(function(){
// second.js dependant code goes here...
});
Note: the onload parameter for execute is optional.
Just to see if this was possible, I had first.js send a synchronous XHR to a PHP file, and had the PHP file delete second.js. When the readyState reached '4', I had the JS alert something, to stop the thread. Then I went and checked the server... Yeah, second.js was deleted. And yet, it wouldn't work. I'd close the alert box, and the code that was in second.js would still be executed, despite the fact that the file was gone.
I don't really know what this means, but the answer to your question is probably, "No, it's not possible."
you may use setTimeout() to delay the execution of some code
I'm attempting to call a javascript function (in our code) from a silverlight control. I'm attempting to call the function via:
HtmlPage.Window.Invoke("showPopup", new string[] { "http://www.example.com" });
and I get the error "Failed to Invoke: showPopup"
I can call HtmlPage.Window.Invoke("alert", new string[]{"test"}); without issue, but not my own function.
I can also open up the page in question in the IE developer tools and manually call showPopup("http://www.example.com") and it works as expected.
So the js function works, and the Silverlight binary can find other js functions. What am I missing here?
Additional Notes:
The function call is in a button click event handler, so it happens after the page (and the script) have been loaded)
Aha! I figured it out. Our app uses an iframe, so the rendered html looks something like this
<html>
<head></head>
<body>
Stuff
<iframe>
<html>
<head></head>
<body>Other Stuff</body>
</html>
</iframe>
<body>
</html>
And the Silverlight control in question is in the iframe. The problem was that the file that contained the showPopup function was referenced in the outer <head> (why I could call the function with the IE toolbar) but not the inner <head>. Adding a reference to the file in the in-the-iframe <head> solved the problem.
Sort of anticlimactic, but thanks for all the help.
Actually referencing the script again from the iframe is not the most efficient way to reference code contained in the parent. If your function is called "showPopup", you can insert this in your iframe:
<script type="text/javascript">
var showPopup = parent.showPopup;
</script>
And voilà. The explanation for this is that all "global" functions and objects are part of this "global namespace"... which is the "window" object. So if you're trying to access "global" functions from a child, you need to either call the function on the parent (e.g parent.showPopup('....')) or declare a local alias for it (which is what we do in the above example).
Cheers!
Is the showPopup javascript function on the same html or aspx page as the Silverlight control? You will normally get the "Failed to Invoke ..." error if the javascript function does not exist:
HtmlPage.Window.Invoke("functionThatDoesNotExist", new [] { "Testing" });
What browser are you using when you are getting this problem?
Are you using the latest version of Silverlight?
Are you using the ScriptableType attrbiute anywhere?
Is it possible to list the code for a short but complete program that causes this problem to happen on your machine...
Make sure your script is fully loaded before trying to invoke functions from it.
Here's how I do it. But I'm creating silverlight without visual studio. I just have raw html, xaml, and js (javascript). Notice MouseLeftButtonUp and it's value "LandOnSpace"
<Canvas x:Name="btnLandOnSpace" Background="LightGreen" MouseLeftButtonUp="LandOnSpace"
Cursor="Hand" Canvas.Top ="0" Width="70" Height="50">
<TextBlock Text="LandOnSpace" />
</Canvas>
function LandOnSpace(sender, e) { //on server
if (!ShipAnimateActive && !blnWaitingOnServer) {
blnWaitingOnServer = true;
RunServerFunction("/sqgame/getJSLLandOnSpace");
ShowWaitingBox();
};
else {
alert('Waiting on server.');
};
}
I had the same problem in VS 2010 with SL 4. I had created a few methods and put them into one single JS file. However this file had not been added to the head section of the ASPX file. Adding it solved the problem. The difference is that though I did not have a separate head section in the iframe, I had the problem and it got solved.