1

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.

rink.attendant.6
  • 44,500
  • 61
  • 101
  • 156

1 Answers1

1

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:

  1. The exact timing of the loading of the scripts and thus the timing of when the complete() function gets called.

  2. The caching in the browser (which can affect the load timing).

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

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

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

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Added debugging suggestions to the answer. – jfriend00 Dec 12 '13 at 00:42
  • Added note about making sure you have a new enough version of the yepnope library. – jfriend00 Dec 12 '13 at 00:45
  • Added note about the `complete` not necessarily waiting for .css files to be loaded. – jfriend00 Dec 12 '13 at 00:48
  • Thanks for your response. I've tried using the function declaration instead of the function expression with no success (also, this won't work for variables that aren't functions). I've also removed the CSS from the array of resources, that didn't change the result. The functions that I tested with are in the global scope (no `$(document).ready` or `window.onload`) and `console.log` is giving me `undefined`. I've updated my original question. – rink.attendant.6 Dec 12 '13 at 01:20
  • @rink.attendant.6 - if you put all the `console.log()` statements in, can you show the entire log (all the statements)? You just reported on one `console.log()` statement. – jfriend00 Dec 12 '13 at 01:27
  • Debugging output now added for all three browsers. – rink.attendant.6 Dec 12 '13 at 07:13
  • @rink.attendant.6 - OK, that debug output confirms that in IE, you are getting the complete function called BEFORE `bar.js` has been loaded. that is some sort of problem with the `.load()` function you're using. And it shows that neither `initBar()` or `window.initBar()` is available. Per the yepnope documentation, you need to make sure you have the latest version for reliable `complete` timing. – jfriend00 Dec 12 '13 at 07:18
  • [This question](http://stackoverflow.com/questions/16150762/modernizr-download-file-but-it-is-still-undefined-afterwards-only-in-ie9-afte) is more or less of the same problem but there is no answer... so is there a way to fix this? – rink.attendant.6 Dec 12 '13 at 07:20
  • 1
    @rink.attendant.6 - Looks like a bug in that library. There are other ways to dynamically load a script file with a load notification. Here's a simple code snippet in [this answer](http://stackoverflow.com/questions/11160948/how-to-know-if-jquery-has-finished-loading/11161045#11161045), jQuery can do it with `$.getScript()` and there are many other libraries around that can do it. Give me a few moments and I'll extend that code snippet to support multiple script files. – jfriend00 Dec 12 '13 at 07:38
  • @rink.attendant.6 - Two script loading functions added to my answer. – jfriend00 Dec 12 '13 at 07:59