Can anyone explain why, in Internet Explorer, code sample 1 doesn't work while code sample 2 works?
Code 1 (non-functional)
Modernizr.load([
{
load: [
'../includes/css/foo.css',
'../includes/js/foo.js',
'../includes/js/bar.js'
],
complete: function() {
initBar();
}
}
]);
Code 2 (functional)
Modernizr.load([
{
load: [
'../includes/css/foo.css',
'../includes/js/foo.js',
'../includes/js/bar.js'
],
complete: function() {
window.initBar();
}
}
]);
bar.js
var initBar = function() {
// code here
};
It works fine in other browsers. I've tried moving the blocks to the head section as well as beneath the page. I've also tried wrapping the contents of the callback in $(document).ready(), but none have worked with code 1.
The error I am getting specifically is:
SCRIPT5009: « initBar » est indéfini
It is almost as if the callback is executed before the resources are finished loading, but if that was the case then why does code sample 2 work?
Also I will note that on refresh the page loads fine (most likely due to the fact that the resources are cached), but it also loads fine after clearing the cache. I have to restart my browser session after clearing the cache to reproduce the problem.
UPDATE:
This problem extends to more than just functions. Any global variable defined in a JS file that is loaded doesn't seem to be accessible directly. It also occurs if I load the CSS at the top of the page rather than with the other resources asynchronously. In fact I'm also noticing this problem with some jQuery plugins that are loaded in this manner.
UPDATE 2:
Here is the console.log() output as per debugging instructions below. I've changed bar to be an object instead of a function for the sake of illustrating this.
Internet Explorer:
HTML1300: Une navigation s’est produite.
Fichier : test18.php
before .load() called
before bar accessed
typeof bar = undefined
typeof window.bar = undefined
SCRIPT5009: « bar » est indéfini
Fichier : test18.js, ligne : 14, colonne : 13
before bar defined
So it appears that the complete function executes before bar is defined. I find it strange that window.bar is also undefined yet works...
Firefox
[02:10:46,448] "before .load() called"
[02:10:47,184] "before bar defined"
[02:10:47,184] "before bar accessed"
[02:10:47,184] "typeof bar = object"
[02:10:47,184] "typeof window.bar = object"
Chrome
before .load() called
before bar defined
before bar accessed
typeof bar = object
typeof window.bar = object
Both Firefox and Chrome appear to be loading and executing the resources in the correct order.
First off, you should know that .load() in modernizr comes from the yepnope library so that's where you find the detailed documentation for it.
Here are the possible things that could be different in different browsers that I can think of:
The exact timing of the loading of the scripts and thus the timing of when the complete() function gets called.
The caching in the browser (which can affect the load timing).
Because you are defining initBar by assigning it to a variable instead of a regular function initBar() definition, the function will not exist until that line of code executes whereas function initBar() will exist at script parse time.
Make sure you version 1.5 or higher of the yepnope loading library (I don't know what modernizr version that corresponds to. The yepnope doc for .load() says this: "In versions of yepnope prior to 1.5 this [when the complete function is called] could vary from time to time".
There are notes on this page that the yepnope library may not wait for .css files to load before calling the complete callback unless you have an add-in present. I don't know if that throws off the whole complete timing or what as I note that you do have .css files in your load list.
So, here's what I'd suggest to debug this:
1) Change your initBar definition to this:
function initBar() {
// code here
}
2) Make sure your initBar definition is in the proper scope and reachable from your other code. Look out for things like being inside another function (onload, document.ready, etc...) which might make it unreachable.
3) Insert some console.log() statements like this to do some timing debugging:
console.log("before .load() called");
Modernizr.load([
{
load: [
'../includes/css/foo.css',
'../includes/js/foo.js',
'../includes/js/bar.js'
],
complete: function() {
console.log("before initBar() called");
console.log("typeof initBar = " + typeof initBar);
console.log("typeof window.initBar = " + typeof window.initBar);
initBar();
console.log("after initBar() called");
}
}
]);
console.log("before initBar() defined");
function initBar() {
// code here
}
Then, see what order things come out in and what the typeof statements say. The idea here is to try to figure out if things are executing in the wrong order or if a scope is wrong.
4) Try loading the .css file separately so it won't be affecting the .js loading.
Here's a replacement script that can load multiple scripts dynamically to replace the modernizr buggy .load() code. This one loads them all in parallel. This works only for scripts files (though the same concept could be used for .css files.
function loadScriptsInParallel(scripts, completeCallback) {
var head = document.getElementsByTagName('head')[0];
var remaining = scripts.length, i, scriptTag;
function complete() {
// make sure it's not called again for this script
this.onreadystatechange = this.onload = function() {};
// decrement remaining count and check if all are done
--remaining;
if (remaining === 0) {
// all are done call the callback
completeCallback();
}
}
for (var i = 0; i < scripts.length; i++) {
scriptTag = document.createElement('script');
scriptTag.type = 'text/javascript';
scriptTag.src = scripts[i];
// most browsers
scriptTag.onload = complete;
// IE 6 & 7
scriptTag.onreadystatechange = function() {
if (this.readyState == 'complete') {
complete.apply(this, arguments);
}
}
head.appendChild(scriptTag);
}
}
Sample usage:
loadScriptsInParallel([
'../includes/js/foo.js',
'../includes/js/bar.js'
], function() {
// put code here for when all scripts are loaded
initBar();
});
Working demo: http://jsfiddle.net/jfriend00/qs44R/
If you need them loaded sequentially (one after the other because of dependencies between them), then you could use this:
function loadScriptsInSequence(scripts, completeCallback) {
var head = document.getElementsByTagName('head')[0];
var remaining = scripts.length, i = 0;
function loadNext() {
var scriptTag = document.createElement('script');
scriptTag.type = 'text/javascript';
scriptTag.src = scripts[i++];
// most browsers
scriptTag.onload = complete;
// IE 6 & 7
scriptTag.onreadystatechange = function() {
if (this.readyState == 'complete') {
complete.apply(this, arguments);
}
}
head.appendChild(scriptTag);
}
function complete() {
// make sure it's not called again for this script
this.onreadystatechange = this.onload = function() {};
// decrement remaining count and check if all are done
--remaining;
if (remaining === 0) {
// all are done call the callback
completeCallback();
} else {
loadNext();
}
}
loadNext();
}
Working demo: http://jsfiddle.net/jfriend00/9aVLW/
Related
I want to lazy load a css file, and then have javascript modify a page after said css loaded. To that end, I'm using this approach, as illustrated here:
lazyLoadCSS = function (pathToStyleSheet, options, callback) {
var stylesheet = document.createElement('link');
stylesheet.href = pathToStyleSheet;
stylesheet.rel = 'stylesheet';
stylesheet.type = 'text/css';
// temporarily set media to something inapplicable to ensure
// it'll fetch without blocking render
stylesheet.media = 'only x';
// set the media back when the stylesheet loads
stylesheet.onload = function() {
stylesheet.media = 'all';
// here be callbacks...
callback();
};
document.getElementsByTagName('head')[0].appendChild(stylesheet);
};
So, the idea is, javascript fetches the CSS, but smuggles it in as 'only x' so the browser doesn't think it needs to wait for the file before rendering the rest of the page, which is the whole point. This approach seems to be pretty standard and the above code does work...mostly.
The only problem is the callback. It happens after the CSS is loaded, but before the styles have been applied to the document.
If I put a timer on the callback, like so:
window.setTimeout(function(){
callback();
}, 2000);
then everything works exactly the way it should (only slow).
Question is, how can I wait not only for the CSS to load, but also to be applied before I run the callback()?
As per some comments above, this is what I've come up with and it work great (though it's not thoroughly tested):
function waitForStylesToApply(cycleNumber, canary, callback){
if(cycleNumber < 100){
window.setTimeout(function(){
var computedValue = window
.getComputedStyle(window.document.body, null)
.getPropertyValue('max-width');
if(computedValue !== canary){
if(typeof callback === 'function'){
console.log('done in ' + cycleNumber + ' cycles');
callback();
}
}
else {
waitForStylesToApply(cycleNumber++, canary, callback);
}
}, 10);
}
}
Checks on the computed style for <body> over and over until it changes. Then it does the callback. So far, the code has never had to cycle through more than once, so the wait is infinitesimal.
Even a delay of 10 is too much. Code will work with a delay of 0 and still only cycle once.
EDIT: use this function in place of simply using callback() in the example above.
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!
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;
};
I have an issue. One of my JS scripts needs Facebook SDK and Twitter widgets JS to load first. Facebook creates FB object, Twitter creates twttr object. Both of them create these objects AFTER my script fires, even though they're loaded from <head>.
I think solution is to periodically check if FB and twttr are defined, and then proceed with executing my script. But I have no idea how to do this.
I tried creating a loop
while (typeof FB === 'undefined' || typeof twttr === 'undefined' || typeof twttr.widgets === 'undefined') {
// run timeout for 100 ms with noop inside
}
But this clearly does not work as it keeps firing timeouts at a high speed and page hangs.
Please help me, I can't sleep because of this issue.
If the scripts are loaded in the normal, synchronous way, then just make sure that your <script> include appears after the library scripts in the document's <head>. If, on the other hand, those scripts are loading objects asynchronously (as seems to be the case), then create something like this:
function whenAvailable(name, callback) {
var interval = 10; // ms
window.setTimeout(function() {
if (window[name]) {
callback(window[name]);
} else {
whenAvailable(name, callback);
}
}, interval);
}
And use it like this:
whenAvailable("twttr", function(t) {
// do something
});
The function given in the second argument to whenAvailable will not execute until twttr is defined on the global window object. You can do the same thing for FB.
Important note: Those libraries probably also provide some built-in way to execute code after they have loaded. You should look for such hooks in their respective documentation.
Have you put your script to be executed on page load? (ie. body onload="do_this ();")
That should make your code execute once all external resources has been loaded.
Regarding the use of setTimeout
setTimeout will return immediately, if you'd like to wait for certain variable to be defined, use something as the below.
function when_external_loaded (callback) {
if (typeof FB === 'undefined' || typeof twtter === 'undefined') {
setTimeout (function () {
when_external_loaded (callback);
}, 100); // wait 100 ms
} else { callback (); }
}
...
when_external_loaded (function () {
alert (FB);
alert (twtter);
});
const checkIfLoaded = ('lib', cb) => {
const interval = setInterval(() => {
if (lib) {
typeof cb === 'function' && cb();
clearInterval(interval);
} else {
console.log('not yet');
}
}, 100);
}
If the Facebook scripts are being loaded asynchronously, Facebook has a supported way to execute code when it's library loads which should be much more efficient than polling for it. See this answer for an example: https://stackoverflow.com/a/5336483/816620.
If the Facebook scripts are being loaded synchronously, then you don't have to wait for them - they will load before any other scripts after them run.
I have a small script
document.write("<html><head><script src='/js/jquery-1.4.2.min.js' type='text/javascript'></scr"
+ "ipt><script>alert($"+"().jquery);</scri" + "pt></head></html>");
But I get a $ is undefined in Internet Explorer. I think it tries to run the script before loading the library.
However this runs in Firefox. Please help.
Edit: I open a new window and write to that window's document.
What's wrong with creating the <script> tag the proper way? document.write is evil, end of discussion.
Try with this:
var load_script = function(options) {
options.owner_document = options.owner_document || document;
var script_tag = options.owner_document.createElement('script');
script_tag.setAttribute('type', 'text/javascript');
script_tag.setAttribute('src', options.src);
script_tag.onload = function() {
script_tag.onreadystatechange = null;
options.callback && options.callback();
};
script_tag.onreadystatechange = function() {
if (script_tag.readyState == 'loaded' || script_tag.readyState == 'complete') {
script_tag.onload = null;
options.callback && options.callback();
}
};
options.owner_document.getElementsByTagName('head')[0].appendChild(script_tag);
};
as you see, there's a simple API on that snippet:
src - source of the script
owner_document - document where the script will be inserted, defaults to the current document where the script is running from
callback - function to run after the script has loaded, anything that requires the src script is safe to be run inside this closure.
example usage:
// sample loading of jQuery
load_script({
src: '/js/jquery-1.4.2.min.js',
callback: function() {
// jQuery is available at this point, run your code.
}
});
alternatively, you can use loaders like requiere.js and LABjs
The thing is that you have unintentionally triggered IE to load your scripts in a non-blocking way. Read this for more information: Loading JavaScript without blocking.
The page suggests that you use this code to get notified when the script has finished loading:
//Internet Explorer only
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "/js/jquery-1.4.2.min.js";
script.onreadystatechange = function(){
if (script.readyState == "loaded" ||
script.readyState == "complete"){
script.onreadystatechange = null;
// Your code goes here.
alert("Script is ready!");
}
};
document.body.appendChild(script);
try running your second script on page load(window.onload) , or try inserting the script in the body, not in the head section.
try :
document.write("<html><head><script src='/js/jquery-1.4.2.min.js' type='text/javascript'></scr"
+ "ipt><script>window.onload = function(){alert($"+"().jquery);};</scri" + "pt></head></html>");
the browser won't load the library instantly (unless it has it in its cache) so your script might get called before the library is loaded.
See this solution:
JavaScript's document.write Inline Script Execution Order
(also note the comment, it's important)
You could write a recursive function that waits for the jquery library to load and then executes any code once the library is loaded. You probably want to add a conditional that breaks out of the recursion if it gets too deep.
document.write("<html><head><script src='/js/jquery-1.4.2.min.js' type='text/javascript'></scr"+ "ipt><script type='text/javascript'>var wait_for_jQuery = function() {if (typeof $ === 'undefined') {setTimeout('wait_for_jQuery()', 1000);} else {alert($"+"().jquery);}}; wait_for_jQuery(); </scri" + "pt></head></html>");
Working example here: http://www.jsfiddle.net/YqTgM/36/
Have you tried moving the script tag running your code to the end of the body tag? According to Yahoo's best practices (creators of YSlow):
The problem caused by scripts is that
they block parallel downloads. The
HTTP/1.1 specification suggests that
browsers download no more than two
components in parallel per hostname.
If you serve your images from multiple
hostnames, you can get more than two
downloads to occur in parallel. While
a script is downloading, however, the
browser won't start any other
downloads, even on different
hostnames.
So the result would look like...
document.write("<html><head><script src='/js/jquery-1.4.2.min.js' type='text/javascript'></scr"
+ "ipt></head><body><script>alert($"+"().jquery);</scri" + "pt></body></html>");
Although I would also advocate not using document.write().
Give this a try?
http://jsfiddle.net/MP75r/
If you want simplified conditional loading of jQuery, there are many options out there already; no need to reinvent the wheel. Already mentioned are RequireJS and LabJS. I am using Head.js and it couldn't be easier to accomplish the goal of non-blocking loading while respecting execution order.
you are just creating a tag to reference jQuery, and as soon as its created next statement is an alert. There is no way specified by you that alert statement should run after after file load.
A simple timer should do it.
(function(){
function wait() {
if (typeof jQuery == 'undefined') {
setTimeout(wait, 50);
} else {
run(); // your code to execute
}
}
wait();
})();
Example on jsfiddle: http://jsfiddle.net/madr/Gtafq/
Inside the <script></script> tag wrap your code in a document ready function so the libraries are loaded before running.
$(document).ready(function() {
// put all your jQuery code here
});