$.getScript() and $(window).load() in Safari 5 not always working - javascript

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....

Related

Run JavaScript function when the DOM is "ready"?

I'm using a JavaScript upload script that says to run the initialize function as soon as the DOM is ready. I currently have it working just fine with either a call to the function with body.onload or directly after the function is defined. The function builds some HTML in a placeholder div that acts as the file uploader tool.
My question is what is the best practice here? Since it works for now, why would the instructions say to run the init function as soon as the DOM is ready? Should I be adding a <script> tag directly after the placeholder DIV for example?
<script>
window.addEventListener("DOMContentLoaded", function() {
// do stuff
}, false);
</script>
You do that so you know all the parsed elements are available in the DOM etc.
The easiest solution is using jQuery and its $(document).ready(function() { .... }); function. Instead of .... you put your own code.
Note that it basically does the same thing #Shadow2531 suggested, but also works in old browsers not supporting that event.
The DOM is usually ready before onLoad runs. onLoad only runs after everything loads - external scripts, images, stylesheets, etc.
But the DOM, i.e. the HTML structure is ready before that. If you run the code at the bottom of the page (or after the parts of the page the script works with) that will work fine as well.
In 2015 you have two options with modern browsers:
document.onload
this fires when the document is loaded, but other resources (most notably images) have not necessarily finished loading.
window.onload
this fires when the document is loaded, AND all other resources (again, most notably images) are loaded.
Both of the above events would be better utilized with window.addEventListener() of course, as multiple listeners would be allowed.
You could also just move the <script> to the bottom of your page like this:
<html>
<body>
<main></main>
<script>
// code
</script>
</body>
</html>
As you probably know you should not run init functions before the DOM is fully loaded.
The reason you must run the init function as soon as the DOM is ready, is that once the page has loaded the user starts hitting buttons etc. You have to minimize the small inavoidable gap where the page is loaded and the init-functions haven't run yet. If this gap gets too big (ie. too long time) your user might experience inappropiate behaviour... (ie. your upload will not work).
Other users have provided fine examples of how to call the init function, so I will not repeat it here... ;)
Get jQuery and use the following code.
$(document).ready(function(){
// Do stuff
});
var Tette =
{
init: function()
{
/* Your HTML code */
}
};
Core.start(Tette);
You can try in this code, registering "Core.start(your object)" on the last line of the script. This is a way to load in safety your functions after the DOM loading.

Is $(document).ready() also CSS ready?

I've got a script executing on $(document).ready() that's supposed to vertically align block element in my layout. 90% of the time, it works without issue. However, for that extra 10% one of two things happens:
There's an obvious lag in the time it takes to do the centering, and the block elements jump into position. This could simply be performance related - as the page size is often large and there is a fair amount of javascript that is executing at once.
The centering will completely mess up, and the block element will either pushed down too far or not far enough. It appears as if it tried to calculate the height, but was getting improper measurements.
Is there any reason why executing a script on DOM-ready would not have all the correct CSS values injected into the DOM yet? (all CSS is in the <head> via a <link>).
Also, here's the script that's causing the issue (yes, it's been taken straight from here):
(function ($) {
// VERTICALLY ALIGN FUNCTION
$.fn.vAlign = function() {
return this.each(function(i) {
var ah = $(this).height();
var ph = $(this).parent().height();
var mh = (ph - ah) / 2;
$(this).css('margin-top', mh);
});
};
})(jQuery);
Thanks.
From the 1.3 release notes:
The ready() method no longer tries to make any guarantees about waiting for all stylesheets to be loaded. Instead all CSS files should be included before the scripts on the page. More Information
From the ready(fn) documentation:
Note: Please make sure that all stylesheets are included before your scripts (especially those that call the ready function). Doing so will make sure that all element properties are correctly defined before jQuery code begins executing. Failure to do this will cause sporadic problems, especially on WebKit-based browsers such as Safari.
Note that the above is not even about actually rendering the CSS, so you may still see the screen change when ready() kicks in. But it should save you from problems.
Actually, I find it a bit strange that just putting the CSS above the JS will solve all issues. The CSS is loaded asynchronously, so JS loading can start and finish while the CSS is still being downloaded. So if the above is a solution, then executing any JS code is then halted until all earlier requests have completed?
I did some testing, and indeed, sometimes JS is delayed until the CSS is loaded. I don't know why, because the waterfall shows that the JS has completed loading long before downloading the CSS has finished.
See JS Bin for some HTML and its results (this has a 10 second delay), and see webpagetest.org for its waterfall results. This uses some script from Steve Souders' cuzillion.com to mimic slow responses. In the waterfall, the reference to resource.cgi is the CSS. So, in Internet Explorer, the first external JS starts to load right after the CSS was requested (but that CSS will take another 10 seconds to finish). But the second <script> tag is not executed until the CSS has finished loading as well:
<link rel="stylesheet" type="text/css" href=".../a script that delays.cgi" />
<script type="text/javascript" src=".../jquery.min.js"></script>
<script type="text/javascript">
alert("start after the CSS has fully loaded");
$(document).ready(function() {
$("p").addClass("sleepcgi");
alert("ready");
});
</script>
Another test with a second external JS after getting jQuery, shows that the download of the second JS is not started until the CSS has loaded. Here, the first reference to resource.cgi is the CSS, the second the JS:
Moving the stylesheet below all JS indeed shows that the JS (including the ready function) runs much earlier, but even then the jQuery-applied class --which is yet unknown when the JS runs-- is used correctly in my quick tests in Safari and Firefox. But it makes sense that things like $(this).height() will yield wrong values at that time.
However, additional testing shows that it is not a generic rule that JS is halted until earlier defined CSS is loaded. There seems to be some combination with using external JS and CSS. I don't know how this works.
Last notes: as JS Bin includes Google Analytics in each script when running from the bare URL (like jsbin.com/aqeno, the test results are actually changed by JS Bin... It seems that the Output tab on the edit URL such as jsbin.com/aqeno/edit does not include the additional Google Analytics things, and surely yields different results, but that URL is hard to test using webpagetest.org. The reference to Stylesheets Block Downloads in Firefox and JavaScript Execution in IE as given by strager is a good start for a better understanding, but I got many questions left... Also note Steve Souders' IE8 Parallel Script Loading to make things even more complicated. (The waterfalls above are created using IE7.)
Maybe one should simply believe the release notes and documentation...
CSS/JavaScript/JQuery ordering doesn't work for me, but the following does:
$(window).load(function() { $('#abc')...} );
The DOM ready fires when all the DOM nodes are available. It has nothing to do with CSS. Try positioning the style before or try loading it differently.
To the best of my knowledge the ready event is fired when the DOM is loaded - which means that all the blocking requests (i.e. JS) have loaded and the DOM tree is completely graphed. The ready state in IE relies on a slower event trigger (document.readyState change vs DOMContentLoaded) than most other browsers so the timing is browser dependant also.
The existence of non-blocking requests (such as CSS and images) is completely asynchronous and unrelated to the ready state. If you are in a position where you require such resources you need to depend on the good old onload event.
According to HTML5, DOMContentLoaded is a plain DOM ready event without taking stylesheets into account. However, the HTML5 parsing algorithm require browsers to defer the execution of scripts until all previous stylesheets are loaded. (DOMContentLoaded and stylesheets)
In molily's tests (2010),
IE and Firefox blocked all subsequent script execution until stylesheets loaded
Webkit blocked subsequent execution only for external scripts (<script src>)
Opera did not block subsequent execution for any scripts
All modern browsers now support DOMContentLoaded (2017) so they may have standardized this behavior by now.

Should I use DOM-ready functions if my scripts are at the end of the body?

I know that, in jQuery, we are told to use $(document).ready() in order to ensure that the DOM elements are ready for interaction. I know that this definitely applies if the script tags are in the <head>. If they are at the end of the <body>, after all of the DOM elements, should I still use a DOM-ready function? Are there browsers in which my code would fail if I did not?
Thanks!
There is one thing you can't do in a <script> block just before </body>: append DOM content to the body. This is the append-relative-to-parse problem that causes IE to throw a fit with the dreaded “Operation aborted”.
So if you have scripts or plugins that do that, you can't invoke them inline at the end of the body element. Otherwise go ahead.
It won't get you anything on up-to-date Mozilla, Opera or WebKit browsers since those will fire ready in a moment anyway. It will avoid an unpleasant but largely harmless hack loop in IE, and it'll fire much sooner for other (older or more obscure) browsers that otherwise fall back to waiting for onload.
There is another reason for putting your scripts at the end: browsers do not load JS files in parallel because they can affect everything that comes after. In fact, everything stops in the browser until the JS file is downloaded and parsed -- text, images, everything. So unless you have to load them early, load your JS at the end of the page.
The DOM-ready function ensures that the entire DOM has actually been created so that all elements in the document can be reached. The normal window.onload loads only when all images have been loaded, while jQuery's event triggers as soon as the document structure exists. If you need something to happen "right when the document loads", there is no reason to use anything but $(document).ready().
I have never encountered a browser where you could not access the DOM of html elements appearing before my script.
That said, if you are pulling in jQuery or so already, I would probably use the ready() method, just to make it easier for other people used to jQuery to understand the code.
Is there some reason why you would want to avoid this it?
(If you are looking for a way to avoid loading jQuery, then, well, yeah, in my experience, you can always access the DOM of elements appearing before the script.)

Lazy loading the addthis script? (or lazy loading external js content dependent on already fired events)

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>

document.readystate = undefined

From external JS script I use document.readystate to determine whether the document is loaded.
If its not loaded I attach Onload event listner.
However when I run the script from IEPRO (similar to FF greasemonkey) on IE7 document.readystate returns 'undefined'. (I guess that IE7PRO runs the script after the document has loaded already).
How can I tell if the document is loaded or not in other ways that will work across browsers?
Clarifications (following first two answers):
1.My question is not how to perform onload attaching. My question is how to determine whether the document (or DOM) has already loaded in other means than document.readystate
I cant change the document. my script is an addon to random pages I have no control of.
The only cross-browser way of doing this, that I know of, is to attach a function to the load event of window; this script should probably go in the head. When this fires, set a boolean so that you know everything is loaded later.
The Prototype library does this; see function fireContentLoadedEvent() in the source.
I am not sure of your whole conclusion but sounds like you can just use jquery and set up your code like this:
<script language="javascript" type="text/javascript">
$(document).ready(function(){
//have all your code of what you are trying to do here
//
//
});
</script>
The help site for IEPRO UserScripts seems to indicate they run after the page is loaded. So why do you need to check if the page is loaded?
You can do this in body.onload:
<body onload="document.ready=true;">...
This JavaScript will be executed after the document has been loaded. Just wait until document.ready is true (or no longer underfined).
The better way is to call a JS function in onload which then invokes your external script. This doesn't involve busy waiting but might not work in your case. If you load the external document in a frame, you can store the function to call in the parent document and use "window.parent.function()" from the nested document.

Categories

Resources