I'm wanting to wait for all of my requireJS modules to finish loading before firing an event.
Is there an event, or a way to listen for all of my requireJS modules to finish loading?
The details:
I'm trying to add custom dimensions to my Google Analytics, and then send a pageview. This is easy to do on all pages if I set the same dimensions but in some of my requireJS modules I want to change the dimensions BEFORE the page view.
I've got my template javascript which is run on every single page, I want it to do:
LISTEN_FOR_MODULES_TO_LOAD(function() {
$(document).trigger('set-your-analytics-dimensions-yall');
ga('send', 'pageview');
});
Then on some of my subpages I've got modules doing:
$(document).on('set-your-analytics-dimensions-yall', function() {
ga('set', 'dimension');
});
I want the flow to be:
Listen for all my modules to finish loading
Ask other modules to set their dimensions and other values
Set pageview
The problem is that I have no way to listen for all my requirejs modules to load.
Things I've considered:
DOM Ready
This doesn't work because DOM Ready can fire before or after all JavaScript modules are loaded.
window.onload
This isn't reliable and can fire multiple times. If an image is added to the page 10 minutes later it can fire again. If an external resource never loads this event will never fire.
Is there an options I have missed? Or something I have missed entirely from requireJS?
This question is over two years old, but it was the only relevant question I found on SO when I had the same problem. I eventually found an answer that works for me. Here's some background: I have a dynamically generated page, with multiple and varied numbers of page sections that may include modules loaded by require.js. Picture a page with a variable number tiles for example, and each tile may load scripts using require. Now when all tiles are loaded, I want to run some other javascript. But how do you tell when require.js is done loading everything, including modules that may be loaded as sub modules?
Looking at the require.js code, I see that it contains a registry, which appears to be a list of pending loads and their callbacks. So when the registry is empty, require.js is done loading and done running callbacks. At least it appears that way, and this works for me. Basically at the bottom of the page (below any other calls to require()), I add this javascript:
function run_final_action($) {
if ($.isEmptyObject(require.s.contexts._.registry)) {
do_final_action();
}
else {
setTimeout(run_final_action, 100);
}
}
require( ['jquery'], function ($) { run_final_action($) } );
Here I am using jquery because I already have it loaded in the page, but it isn't really necessary. The basic idea is just to check the registry for the default context in requirejs (named "_") and when it is empty, that means requirejs has no pending scripts to load or callbacks to run. This works for my use case anyway.
Related
Lots of pages on my site have their own javascript files that are included in their respective pages.
SmoothState provides the onAfter handler to re-initialize plugins, and some of these pages' javascript files have their own "init function" that initializes whatever plugins they are using. So obviously, the goal would be to invoke these initialization functions in the onAfter handler.
It appears however that SmoothState is keeping each pages javascript file loaded. What this results in, is that some of these scripts have functions of the same name (but obviously different function) and this is causing some of them to be "replaced."
For example, the init function is never invoked correctly because the last loaded page overrides it.
I toyed around with this a bit.
You need to place the <script> tags that are unique per page inside the container you provide to smoothstate during its initialization. I.e, inside the "main" div. Maybe this is in the documentation somewhere, but I did not see it.
Everything outside of the main div will not be reloaded.
I had a javascript file(initial.js) on the page inserted through the script tag like so:
<script src="initial.js"></script>
This file creates dom elements(let say two links) and also loads another jQuery plugin(plugin.js) asynchronously via jQuery ajax method. Clicking on those two links brings up a module from the jQuery plugin(plugin.js).
The javascript file(initial.js) was then modified to load asynchronously on the page via jQuery ajax instead of via script tag. This has resulted in some events not getting attached to the links intermittently and this results in the plugin not being called.
I believe the browser is loading the async scripts in its own order and hence the links fail to launch the plugin intermittently. Any pointers to resolve this issue with this new set up?
At a high-level, I think you need to look into something like require.js. Alternatively, you could look into some jQuery event handling code which allows you to listen on load events of calls which may help you determine when one script loaded before loading the next one.
You have probably tried something like this in the past:
var output;
$.get('data.php',function(data){
output=data;
});
alert(output);
You will get an undefined error because Javascript doesn't wait around for the AJAX call to be returned before moving onto the next code.
Same thing goes for scripts. If you place multiple calls to multiple scripts, you will probably get the smallest one returned the quickest, and that script executed. If you load a script that is 10kb and then one that is 1kb, the 1kb script will probably return the quickest and then be executed even though it was called after the 10kb script.
To correct this, you could make a queue system and then only load each script after the previous has loaded:
var scripts=['script1.js','script2.js','script3.js'];
$(document).ready(function(){
loadScript();
});
function loadScript(){
if(sendQueue.length==0)
return;
$.getScript(scripts[0],function(){
scripts=scripts.slice(1);
loadScript();
});
}
But if you are loading scripts from within scripts from within scripts... very Inception like, then this still may not work.
I'm experiencing a similar problem to this guy except my problem is related to scripts and not images.
I'm using a combination of $.getScript() and $(window).load() to load JavaScript dynamically and use functions that are being loaded once the script has completely finished loading.
It works well in Chrome but Safari seems to always fire the onload event too early. I was wondering if there was some way to force Safari to wait for all scripts to load similarly to the document.body.offsetWidth block. From what I understood (and from the results I saw when I tested it), document.body.offsetWidth blocks until styles and images are loaded but apparently not scripts.
Edit: It might be worth mentioning that it does work in Safari sometimes but I don't see a pattern. Sometimes the event is fired before the script is fully loaded and executed and the handler passed $(window).load() fails because the functions are missing.
Based on the comments you have made, specifically those surrounding dependencies, I would use a library like RequireJS. This will give you what you need to import scripts and their dependencies, and also hooks to call functions and code when dependencies are met.
Have you tried the good old document ready?
$(document).ready(function() {
//code here
})
I've never had any issues when using that. Worst case scenario, you can drop a script tag after all your other script tags that include the code you require.
In this example, I tell loaded scripts that I need to do something to report loaded to a function. Once all that I need are loaded, I run whatever code I need to.
// Simplified callback to check for all scrips loaded.
var numDynScriptsReady = 0,
numDynScriptsNeeded = 4;
function dynScriptsReady()
{
numDynScriptsReady++;
if( numDynScriptsLoaded == numDynScriptsNeeded )
{
// Run functions you need after loaded....
}
}
// Example script get
$.getScript( "http://example.com/script.js", dynScriptsReady );
// etc....
Im experiencing strange behavior with Firefox and Dojo. I have a html page with these lines in the <head> section:
...
<script type="text/javascript" src="dojo.js" djconfig="parseOnLoad: true, locale: 'de'"></script>
<script type="text/javascript">
dojo.require("dojo.number");
</script>
...
Sometimes the page loads normally. But sometimes it won't. Firefox will fetch the whole html page but not render it. I see only a gray window.
After some experiments I figured out that the rendering problem has something to do with the load time of the html. Firefox starts evaluating the html page while loading it. If the page takes too long to load the above javascript will be executed BEFORE the html finishes loading.
If this happens I'll get the gray window. Advising Firefox to show me the source code of the page will display the correct complete html code. BUT: if I save the page to disk (File->Save Page As...) the html code will be truncated and the above part will look like this:
...
<script type="text/javascript" src="dojo.js" djconfig="parseOnLoad: true, locale: 'de'"></script>
<script type="text/javascript">
dojo.require("dojo.number");
</script></head><body></body></html>
This explains why I get to see a gray area. But why does this code appear there? I assume the require() method of Dojo does something "evil". But I can't figure out what. There is no write.document("</head><body></body></html>"); in the Dojo code. I checked for it.
The problem would be fixed, if I'd place the dojo.require("dojo.number"); statement in the window.load event:
<script type="text/javascript">
window.load=function() {
dojo.require("dojo.number");
}
</script>
But I'm curious why this happens. Is there a Javasctript function which forces Firefox to stop evaluating the page? Does Dojo do somethig "bad"? Can anyone explain this behavior to me?
EDIT: Dojo 1.3.1, no JS errors or warnings.
What does the rest of the page look like? What elements should be rendering that aren't? What other Javascript do you have?
What you have looks fine, but you will not be able to use methods in dojo.number or anything else loaded via dojo.require until after the page loads -- you must wait for window.onload to fire, or use the dojo.addOnLoad() method to trigger a callback. The latter is actually a bit quicker than onload.
dojo.require uses synch xhr to load which does block the browser, so if the load is unusually slow, you will notice a delay in the rendering of the page.
I think this is a rendering bug in Firefox that I've seen in a number of contexts where the one common factor is the amount of time the browser takes to load all the resources loaded in the of the page. The more scripts you have in the head that take a long time to request over the network or eval, the higher your chances are of running into this. Hitting the page with a warm cache notably reduces the possibility of running into the paint bug as well. Another way to mitigate it is to put the javascript at the end of the which is also a best practice since it doesn't block the browser from previewing markup immediately as it gets it.
Regarding the specifics of using dojo, common use cases include running things onload like creating and starting up widgets. If you have code in an onload handler that uses a dojo module like a widget, then stick the dojo.require statement inside the onload handler as well instead of before the onload handler. There's no point in suffering the performance penalty or blocking the initial UI rendering if you don't need it until later. Then build custom dojo layers to include the minimal core (possibly a custom base to make it even smaller) and the other 90% of what you need in a separate layer. Load the minimal core layer in the head (to get dojo.addOnLoad, etc) and then the other layer at the end of the body. If you live in a modular application framework where apps come and go in the page content area depending on the page you're on, each app should put the dojo.require statements for the respective dojo module it uses immediately before the module is actually referenced.
This won't work obviously if you need a module immediately in an inline script, but if that's the case then a custom dojo build will also help mitigate that case also.
I'm unaware of a reported issue with Mozilla, but I have also seen this much less often on other browsers some time ago.
I want to have the addthis widget available for my users, but I want to lazy load it so that my page loads as quickly as possible. However, after trying it via a script tag and then via my lazy loading method, it appears to only work via the script tag. In the obfuscated code, I see something that looks like it's dependent on the DOMContentLoaded event (at least for Firefox).
Since the DOMContentLoaded event has already fired, the widget doesn't render properly. What to do?
I could just use a script tag (slower)... or could I fire (in a cross browser way) the DOMContentLoaded (or equivalent) event? I have a feeling this may not be possible because I believe that (like jQuery) there are multiple tests of the content ready event, and so multiple simulated events would have to occur.
Nonetheless, this is an interesting problem because I have seen a couple widgets now assume that you are including their stuff via static script tags. It would be nice if they wrote code that was more useful to developers concerned about speed, but until then, is there a work around? And/or are any of my assumptions wrong?
Edit:
Because the 1st answer to the question seemed to miss the point of my problem, I wanted to clarify the situation.
This is about a specific problem. I'm not looking for yet another lazy load script or check if some dependencies are loaded script. Specifically this problem deals with
external widgets that you do not
have control over and may or may not
be obfuscated
delaying the load of the
external widgets until they
are needed or at least, til
substantially after everything else
has been loaded including other deferred elements
b/c of the how
the widget was written, precludes
existing, typical lazy loading
paradigms
While it's esoteric, I have seen it happen with a couple widgets - where the widget developers assume that you're just willing to throw in another script tag at the bottom of the page. I'm looking to save those 500-1000 ms** though as numerous studies by Yahoo, Google, and Amazon show it to be important to your user's experience.
**My testing with hammerhead and personal experience indicates that this will be my savings in this case.
The simplest solution is to set parameter domready to 1 when embedding addthis script into your page. Here is an example:
<script type="text/javascript"
src="http://s7.addthis.com/js/250/addthis_widget.js#username=addthis&domready=1">
</script>
I have tested it on IE, Firefox, Chrome, and Safari, and all worked fine. More information on addthis configuration parameters is available here.
This code solves the problem and saves the loading time that I was looking for.
After reading this post about how most current js libraries implement tests for a dom loaded event. I spent some time with the obfuscated code, and I was able to determine that addthis uses a combination of the mentioned doscroll method, timers, and the DOMContentLoaded event for various browsers. Since only those browsers dependent on the DOMContentloaded event would need the following code anyway:
if( document.createEvent ) {
var evt = document.createEvent("MutationEvents");
evt.initMutationEvent("DOMContentLoaded", true, true, document, "", "", "", 0);
document.dispatchEvent(evt);
}
and the rest depend on timers testing for existence of certain properties, I only had to accommodate this one case to be able to lazy load this external JS content rather than using the static script tags, thus saving the time that I was hoping for. :)
Edit: If the goal is simply to have your other contetn load first, try putting the <script> tags near the bottom of your page. It will still be able to catch the DOMContentLoaded and the content that comes before will be loaded before the script.
Original:
in addition to loading on DOMContentLoaded, you could have it load if a certain var is set true. e.g.
var isDOMContentLoaded = false;
document.addEventListener("DOMContentLoaded",function() { isDOMContentLoaded = true; }, false);
then add to the other script file
if (isDOMContentLoaded) loadThisScript();
Edit in response to comments:
Load the script, and run the function that the DOMContentLoaded listener fires. (read the script if you're not sure what function is being called ).
e.g.
var timerID;
var iteration=0;
function checkAndLoad() {
if (typeof loadThisScript != "undefined") {
clearInterval(timerID);
loadThisScript();
}
iteration++;
if (iteration > 59) clearInterval(timerID);
}
var extScript = document.createElement("script");
extScript.setAttribute("src",scriptSrcHere);
document.head.appendChild(extScript);
timerID = setInterval(checkAndLoad,1000);
The above will try once a second for 60 seconds to check if the function you need is available, and, if so, run it
AddThis has a section on how to load their tools asynchronously.
Current 'best' solution:
<script type="text/javascript" src="//s7.addthis.com/js/300/addthis_widget.js#pubid=[YOUR PROFILE ID]" async="async"></script>