<script> dynamic script load issue - weird behaviour - javascript

I am having a weird issue that is rather difficult to recreate, but I will try to explain to the best of my abilities.
I have a setup.js which is a file that includes parameters for the scripts to be added to the document by the main script, like so:
{
script1: true,
script2: true,
script3: false
}
I have a main script file, lets call it main.js which basically checks the setup file and then for every "true" does the following:
var element = document.createElement('script');
element.src = "path/to/file.js"
document.getElementsByTagName('body').appendChild(element);
Now I've checked using console.log, and the main.js file is the one that has to execute all the way to the end until any of the files added to the body begin loading, however they are then loaded one-by-one and should become ready for usage.
Now here comes the tricky part
I need to know when ALL of them are finished loading in order to be able to use their functions in the rest of the main.js file, so I need some sort of "onload" handler. I've tried to attach a addEventListener('load', function(){}) to the last element that was added, and it worked.
However.....
Once I changed the event listener function to something such as:
addEventListener('load' function(){
script1Function(); //Which is defined in script1
script2Function(); //Which is defined in script2
}
It started throwing errors such as "script1Function is not defined".
I know that both scripts are loaded at this point, because I did a console.log("I have loaded") as the last line of each script, and those appeared in the console, but it is not possible to use the function definitions that were defined in these files.
What is even more.... if I instead do the following event handler:
addEventListener('load' function(){
alert("Last script loaded"); //This line was ADDED
script1Function(); //Which is defined in script1
script2Function(); //Which is defined in script2
}
It all suddenly works.
Adding "alert("whatever");" allows me to use the functions defined in the files.... I would assume it has something to do with "time needed for me to click "OK" on the alert dialog... but the entire thing doesn't make sense, because I know functions are defined by the time I am calling them.

/*
Example usage:
importAsync(["script1.js", "script2.js", "script3.js"]);
waitUntil(function() {
return ((typeof(script1Function) !== "undefined")
&& (typeof(script2Function) !== "undefined")
&& (typeof(script3Function) !== "undefined"));
});
*/
function waitUntil(testFunc) {
var start = Date.now();
var timeout = 3000;
do {
if((Date.now() - start) > timeout) {
console.log("waitUntil() timed out");
return false;
}
} while(!testFunc());
}
This is what I meant in the comments.
I direct your attention to the example above the function:
importAsync(["script1.js", "script2.js", "script3.js"]);
waitUntil(function() {
return ((typeof(script1Function) !== "undefined")
&& (typeof(script2Function) !== "undefined")
&& (typeof(script3Function) !== "undefined"));
});
importAsync is whatever function/method you have for loading the .js files - it's not relevant.
What is relevant, is waitUntil and the function you pass it. In the example above, I check whether functions from all three scripts exist (ergo: are not equal to undefined) and only then will execution of the script continue, due to the nature of the do..while loop. As I also mentioned in the comments, you're going to want a timeout on the do..while loop to prevent an infinite loop. You could add a parameter to waitUntil for the timeout as well if you'd like.
I hope this gives you a basic idea of what I meant in the comments and if you have any further questions, fire away!

Related

Best way to solve this "ReferenceError: Function is not defined"?

I have an AEM page with multiple components, these components have a .js file with a function that encloses all the client side logic. We then call that function inside the component's HTML:
<script>
window.bootstrap_component(function() {
init_component_name();
});
</script>
As stated before init_component_name is the name of a function that encompasses all the logic we need:
function init_component_name() {
//DO STUFF
}
The wrapper bootstrap_component fuction is defined in the shared head.html of all our pages as:
<script>
window.bootstrap_component = function (handler) {
if (typeof handler === 'function') {
if (document.readyState === "complete" || document.readyState === "loaded" || document.readyState === "interactive") {
handler();
} else {
document.addEventListener("DOMContentLoaded", function() {
handler();
});
}
}
}
</script>
This works okay and we don't have any actual issues but we recently started using Bugsnag for error monitoring and reporting and we're getting reports for almost every component saying ReferenceError on page so/and/so init_component_name() is not defined.
The reason I think this is happening is because the init_component_name() function is not declared within the script tag and because this, function(init_component_name) has been attached to the window object it is executing fine and you don't see any console errors.
If I am correct would modifying those scripts tags to be like this work?
<script>
window.bootstrap_component(function() {
window.init_component_name();
})
</script>
A colleague of mine wants to add a timeout to the init_component_name functions of like 1ms but it rubs me the wrong way. Is there a more sensible approach?
If I am correct would modifying those scripts tags to be like this work?
window.bootstrap_component(function() {
window.init_component_name();
})
Yes, but you then have the problem that you're writing multiple data to the global namespace, window, which isn't ideal. What if another third-party script decides to override it?
Ideally you'd have a single namespace and put everything on there, and write only that namespace to window.
window.something = {};
something.bootstrap_component = { //...
and
something.init_component_name = () => {
//DO STUFF
}
Or better still, use modules (though that will need some light code refactoring).
Don't do the timeout hack. It's really, really horrible; what if scripts take longer than a second to load for some reason? You're also forcing your UI to wait a second, often unnecessarily. This hack tends to feature where chronology and scope has not been thought out properly.

How can I execute a JavaScript function on the first page load?

I am wondering if there is a way to execute a JavaScript function once only on the first ever page load and then not execute on any subsequent reloads.
Is there a way I can go about doing this?
The code below will execute once the onload event fires. The statement checks if the onetime function has NOT been executed before by making use of a flag (hasCodeRunBefore), which is then stored in localStorage.
window.onload = function () {
if (localStorage.getItem("hasCodeRunBefore") === null) {
/** Your code here. **/
localStorage.setItem("hasCodeRunBefore", true);
}
}
Note: If the user clears their browsers' localStorage by any means, then the function will run again because the flag (hasCodeRunBefore) will have been removed.
Good news...
Using localStorage can be tedious because of operators and long winded function names. I created a basic module to simplify this, so the above code would be replaced with:
window.onload = function () {
if (!ls.exists('has_code_run_before')) {
/** Your code here... **/
ls.set.single('has_code_run_before', true);
/** or... you can use a callback. **/
ls.set.single('has_code_run_before', true, function () {
/** Your code here... **/
});
}
};
Update #1
Per #PatrickRoberts comment, you can use the in operator to see if a variable key exists in localStorage, so
if (localStorage.getItem('hasCodeRunBefore') === null)
becomes
if (!('hasCodeRunBefore' in localStorage))
and is less verbose and a lot cleaner.
Secondly, you can set values as you would an object (localStorage.hasCodeRunBefore = true) though it will be stored as a string, not as boolean (or whatever data type your value is).
function toBeExecutedOnFirstLoad(){
// ...
}
if(localStorage.getItem('first') === null){
toBeExecutedOnFirstLoad();
localStorage.setItem('first','nope!');
}
All JavaScript must execute every time a page loads. If the script is on the page, it will execute.
The logic that is executed within the JavaScript included on the page may execute in a different manner depending on the page state, input provided, and any other signals it receives, be it from the server or the client.
If you're using a server side language, you might choose to render a script conditionally, such as the first time a user logs in.
If you need to include the javascript irrespective of context, then you need to listen to other signals.
The simple modern solution is to make use of localStorage. localStorage can be used to store custom string values on custom key values for any given domain.
The code to make use of this would look like:
if (localStorage['...my key here...'] === '...my expected value here...') {
// The page has been visited before
} else {
// The page has not been visited before
// OR
// The user or script has cleared the localStorage value
}
localStorage['...my key here...'] = '...my expected value here...';
That's all well and good if you just need things to work on the client alone. Sometimes you might need the server to know whether or not the page has been visited before.
The (less)simple solution is to use document.cookie:
if (/(?:^|;\s*)...my key here...=...my expected value here...(?:;|$)/.test(document.cookie)) {
// the page has been visited before
} else {
// The page has not been visited before
// OR
// The user or script has cleared the cookie
}
document.cookie = '...my key here...=...my expected value here...';
If you need to defer the execution until the page has finished loading, then simply include the script in an onload callback, either by assigning the event to the window:
window.onload = function () {
// do stuff when the page has loaded
//this will override any other scripts that may have been assigned to .onload
};
or by binding the event handler to the window:
window.addEventListener('load', function () {
// do stuff when the page has loaded
}, false);
It depends on what first page load means to you. It's subjective.
If you want the function to fire once the DOM has been parsed, but only the HTML and no other external resources, bind it to the DOMContentLoaded event.
document.addEventListener('DOMContentLoaded', fn);
Otherwise, if you want to wait for external resources to be loaded and then fire the event, you should bind it to the window object's load event like so:
window.addEventListener('load', fn);
Here are some links from the Mozilla Developer Network that explain the what I just said in more detail:
https://developer.mozilla.org/en-US/docs/Web/Events/load
https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded
Good luck!
I was facing something similar, the difference in my case was, I wanted to run a code whenever a new instance was being created I needed a certain code to execute, and then later for the rest of the reloads that code should not execute.
For that similar to localStorage solutions above use session storage instead:
fun_RunOnlyOnFirstPageLoad(){}
if(!$window.sessionStorage.getItem(hasRunBefore)){
fun_RunOnlyOnFirstPageLoad();
$window.sessionStorage.setItem(hasRunBefore, true);
}
using the window.sessionStorage instead stores the value only for that session.
this way once the tab is closed (session is over, that value is wiped out) and on every new instantiation, the code is executed.

See whether object is undefined before executing script

I'm currently loading a custom.js file on my site and it calls various functions. However, to keep the size down I only load the libraries needed on certain pages.
Because of this, since the custom.js file is loaded on every page and it calls functions that the particular page may not have, I get undefined is not a function errors on my site on certain pages.
What I would like to be able to do is determine if something is defined before executing the code to keep the errors from popping up.
For an example, I'm using Jarallax (http://www.jarallax.com/) on my front page only with the following:
var jarallax = new Jarallax();
jarallax.addAnimation('div#bigSlider',[{progress:'0%',marginTop:'0px'},{progress:'100%', marginTop:'-200px'}]);
Since Jarallax is only loaded on the homepage and no others I get the undefined function error on all pages but the hompeage. How could I first confirm Jarallax is loaded before attempting to execute the code?
Since referring to undefined variables raises a ReferenceError exception, you could use a try/catch block to handle the exception.
try {
var jarallax = new Jarallax();
}
catch (e) {
// desired behavior for this situation.
}
More on try/catch blocks.
However, to keep the size down I only load the libraries needed on
certain pages. Because of this I get "undefined is not a function"
errors on my site on certain pages.
So this means you're not doing it properly on every page?
You could solve this by using a wrapper object or class:
(function($){
var wrapper = {
init: function(){
var jarallax;
if (typeof Jarallax == 'function'){
jarallax = new Jarallax();
jarallax.addAnimation('div#bigSlider',[{progress:'0%',marginTop:'0px'},{progress:'100%', marginTop:'-200px'}]);
}
}
};
// once the DOM is read
$(function(){
wrapper.init();
});
}(window.jQuery));
By stalling the init function on the DOM ready, you can be certain the script is loaded if you make sure the script tag for Jarallax is added before the wrapper in the HTML. In any other case the init function won't do a thing.
if (typeof jarallax === "undefined") {
var jarallax = {
obj: {},
return {
obj;
};

Loading jquery after inserting the jquery tag

I insert the jquery tag
var newScript = document.createElement("script");
newScript.id = "grazit_jq";
newScript.type = "text/javascript"
newScript.src = "https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js";
script.parentNode.appendChild(newScript);
And after that I want to check if jquery exists.
I do so in a few ways.One way is like this:
var checkJquery=function(){
if(typeof(jQuery) === 'undefined')
{
console.log('jQuery doesnt exist');
window.setTimeout(arguments.callee,1000);
return;
}
else{
sprk_lib=new sprk();
sprk_lib.load(jQuery);
}
};
sprk.prototype.load=function($){
console.log($);
};
UPDATE
Now i updated the code. It will call the function as soon as the jQuery object is availeable.
In that function I will execute my jquery code. Do you think that there will be problems? is it okay to do it that way?
original
I wait there. problem is that in the console log , it returns null and the prints jQuery exists. How can I stop the script till the jquery is loaded,, and then continue executing the script?
Consider using an established JavaScript script loader such as LABjs or LazyLoad which offer reliable callbacks that don't fire until the script has fully loaded.
Check out this summary of 5 JavaScript script loaders.
For example:
LazyLoad.js('https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js', function () {
console.log('jQuery exists!');
console.log('Document title: ' + $("title").text());
});
One point of note - if you want to hold off all further script execution until after jQuery has loaded, you could put the rest of your script inside this callback function. However, that would break a whole load of best practices (separation of concerns, loose coupling etc) so your best bet is to pass an existing function as your callback, which can in turn call out to other functions etc. For example:
function doSomething() {
console.log('I am doing something!')
doSomethingElse();
}
function doSomethingElse() {
console.log('I am doing something else!');
}
LazyLoad.js('https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js', doSomething);
You could further improve your application architecture (e.g. by introducing PubSub) but that's getting outside the scope of this question so I'll leave it there.

How can I force JavaScript to wait until after a dynamically added script file has completed loading?

I'm trying to write a function which will append a javascript file to the DOM, but I am looking to have the rest of the code wait until the newly added JS file is completely loaded. Here is an example of what I am trying to accomplish, although this code doesn't work properly:
$(document).ready(function () {
var newScript = document.createElement("script");
newScript.setAttribute("type", "text/javascript");
newScript.src = "http://www.domain.com/script.js";
document.getElementsByTagName("body")[0].appendChild(newScript);
$(newScript).ready(function () { // This is the idea of what I'm trying to do, but this doesn't seem to actually wait until the new file is completely loaded.
foo.bar(); // foo is a new global variable which is declared in the newScript. This causes an error "foo is not defined".
// Here is where more code I wish to execute should continue.
});
});
As Musa mentioned in the comments above. Use jQuery's getScript and use the success callback function to trigger your other functions.
If you want more robust module loading functionality, then require.js works great in this capacity. Check out: http://requirejs.org/docs/why.html for an overview. I use require.js specifically for lazy-loading script modules.
Using jQuery (as you've tagged), it's extremely easy:
$.getScript('/script.js', function() {
foo.bar();
});
There's a few different ways to do this... via libraries or "by hand," so to speak, using only the browser APIs and straight JavaScript. For an answer on how to do this in JS only, look here for Stoyan's post to give you guidance. Basically, the gist of it is setting an event handler to both the script's unload and onreadystatechange properties and then check to see if the readyState is "loaded" or "complete" (if it exists at all). It would look something like this:
var done = false;
newScript.onload = newScript.onreadystatechange = function () {
if (!done && (!newScript.readyState || newScript.readyState === "loaded" || newScript.readyState === "complete)) {
done = true;
// run your actual code here
}
};

Categories

Resources