43

I'd like to follow the general guideline of putting all JavaScript at the very bottom of the page, to speed up loading time and also to take care of some pesky issues with conflicting jQuery versions in a web app (Django).

However, every so often I have some code, code which depends on jQuery, but which must be further up on the page (basically the code can't be moved to the bottom).

I'm wondering if there's an easy way to code this so that even though jQuery is not yet defined the code works when jQuery is defined.

The following seems, I have to say, like overkill but I don't know of another way to do it:

function run_my_code($) {
    // jquery-dependent code here
    $("#foo").data('bar', true);
}
var t = null;
function jquery_ready() {
    if (window.jQuery && window.jQuery.ui) {
        run_my_code(window.jQuery);
    } else {
        t = window.setTimeout(jquery_ready, 100);
    }
}
t = window.setTimeout(jquery_ready, 100);

Actually, I might need to use code more than once in a page, code that doesn't know about other code, so even this probably won't work unless I rename each jquery_ready to something like jquery_ready_guid, jquery_ready_otherguid and so on.

Clarification

Just so this is clear, I am putting the include to JavaScript (<script type="text/javascript" src="jquery.min.js" />) at the very bottom of the page, just before the </body>. So I can't use the $.

Jordan Reiter
  • 20,467
  • 11
  • 95
  • 161

11 Answers11

42

Simple use pure javascript version of $(document).ready();:

document.addEventListener("DOMContentLoaded", function(event) { 
    //you can use jQuery there
});
Paweł BB Drozd
  • 4,483
  • 4
  • 21
  • 17
20

Your way is the only way that I know of, though I would ensure that the scoping is a little tighter:

(function() {
  var runMyCode = function($) {
    // jquery-dependent code here
    $("#foo").data('bar', true);
  };

  var timer = function() {
    if (window.jQuery && window.jQuery.ui) {
      runMyCode(window.jQuery);
    } else {
      window.setTimeout(timer, 100);
    }
  };
  timer();
})();

Update

Here's a little deferred loader I cobbled together:

var Namespace = Namespace || { };
Namespace.Deferred = function () {
  var functions = [];
  var timer = function() {
    if (window.jQuery && window.jQuery.ui) {
        while (functions.length) {
            functions.shift()(window.jQuery);
        }
    } else {
        window.setTimeout(timer, 250);
    }
  };
  timer();
  return {
    execute: function(onJQueryReady) {
        if (window.jQuery && window.jQuery.ui) {
            onJQueryReady(window.jQuery);
        } else {
            functions.push(onJQueryReady);
        }
    }
  };
}();

Which would then be useable like so:

Namespace.Deferred.execute(runMyCode);
Rich O'Kelly
  • 41,274
  • 9
  • 83
  • 114
  • Shouldn't that be [`typeof(jQuery) !== "undefined"`](https://stackoverflow.com/a/777213/453435) instead of `window.jQuery`? – Ky - May 13 '21 at 03:53
  • @BenLeggiero In practice, what's posted in my answer and your snippet will be the same, (as a matter of personal preference) I like using the truthy/falsey nature of JS when writing code. – Rich O'Kelly May 16 '21 at 13:43
19

The best way I have found is to write the code in a function and call the function after jquery is loaded:

function RunAfterjQ(){
// Codes that uses jQuery
}

<script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script type="text/javascript">
    RunAfterjQ();
</script>

Update: For master pages, you can define an array to push functions in the head of the master page:

var afterJQ = [];

then at the bottom of master page run all the functions pushed in to this array:

<script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script type="text/javascript">
    for(var i = 0; i < afterJQ.length; i++) afterJQ[i]();
</script>

Everywhere that you need to use javascript that relies on jQuery and is before jQuery is defined just push it in to this array:

afterJQ.push( function() { 
    // this code will execute after jQuery is loaded.
 });
Andrew Downes
  • 547
  • 7
  • 13
Ashkan Mobayen Khiabani
  • 33,575
  • 33
  • 102
  • 171
10

Here is a way to write injected code that will be run only after jQuery loads (whether synchronously or asynchronously).

<script>
if ( ! window.deferAfterjQueryLoaded ) {
    window.deferAfterjQueryLoaded = [];
    Object.defineProperty(window, "$", {
        set: function(value) {
            window.setTimeout(function() {
                $.each(window.deferAfterjQueryLoaded, function(index, fn) {
                    fn();
                });
            }, 0);
            Object.defineProperty(window, "$", { value: value });
        },

        configurable: true
    });
}

window.deferAfterjQueryLoaded.push(function() {
    //... some code that needs to be run
});
</script>

What this does is:

  1. Defines deferAfterjQueryLoaded lazily, so you don't need to inject that into head.
  2. Defines a setter for window.$. When jQuery loads, one of the last things it does is assign to the global $ variable. This allows you to trigger a function when that happens.
  3. Schedules the deferred functions to run as soon as possible after the jQuery script finishes (setTimeout(..., 0);).
  4. Has the setter remove itself.

For complete cleanliness you could have the scheduled function remove deferAfterjQueryLoaded as well.

radiaph
  • 4,001
  • 1
  • 16
  • 19
  • Poor solution. When jQuery is defined above it does not check if property is already defined and overrides it making jQuery broken. COde should check if jquery is in place and in that case do not redefine property but schedule function execution right away. – emdee.pro Nov 13 '17 at 15:49
  • @mdzieg The question explicitly says that the ` – radiaph Jan 09 '19 at 07:19
8

How about:

<script>
    window.deferAfterjQueryLoaded = [];
    window.deferAfterjQueryLoaded.push(function() {
        //... some code that needs to be run
    });

    // ... further down in the page

    window.deferAfterjQueryLoaded.push(function() {
        //... some other code to run
    });
</script>

<script src="jquery.js" />
<script>
    $.each(window.deferAfterjQueryLoaded, function(index, fn) {
        fn();
    });
</script>

This works because every script here is completely blocking. Meaning the creation of the deferAfterjQueryLoaded array and all functions being created and pushed to that array occur first. Then jQuery completely loads. Then you iterate through that array and execute each function. This works if the scripts are in separate files as well just the same way.

If you ALSO want DOMReady to fire you can nest a $(function() {}) inside of one of your deferAfterjQueryLoaded functions like such:

window.deferAfterjQueryLoaded.push(function() {
    $(function() {
        console.log('jquery loaded and the DOM is ready');
    });
    console.log('jquery loaded');
});

Ultimately, you should really refactor your code so everything is actually down at the bottom, and have a system conducive to that model. It is much easier to understand everything occurring and more performant (especially if you have separate scripts).

Parris
  • 17,833
  • 17
  • 90
  • 133
  • This really isn't different than some of the other approaches, but thanks for the answer anyway. The problem I have is that I need to insert some jQuery-dependent code onto a page that I have no real control over. That is, I can add some JavaScript but cannot control where it is placed. Making matters worse is that jQuery is loaded asynchronously so that I can't simply use `window.onload`. What I do now is wait for `window.onload` and check for the jQuery object every 300 milliseconds or so... a hack that I would like to remove. If it's not built into jQuery, maybe this is the only way. – Brad Mar 18 '15 at 17:17
4

I have this same problem, but with a ton of files, I use headjs to manage the loading, it's only 2kb so there isn't really a problem, for me anyway, of putting in the header. Your code then becomes,

head.ready(function(){
  $...
});

and at the bottom of the page,

head.js('/jquery.min.js');
Andrew
  • 13,757
  • 13
  • 66
  • 84
1
function jQueryGodot(code)
{
    if (window.jQuery)
    {
        code(window.jQuery);        
    }
    else
    {
        if (!window.$)
        {
            window.$ = { codes: [] };
            window.watch('$', function(p, defered, jQuery) {
                jQuery.each(defered.codes, function(i, code) {
                    code(jQuery);
                });
                return jQuery;
            });
        }

        window.$.codes.push(code);
    }
}

jQueryGodot(function($) {
    $('div').html('Will always work!');
})

Working example on JSFiddle.

Code passed to jQueryGodot function will always be executed no matter if it is called before or after jQuery is loaded.

The solution relies on Object.watch which requires this polyfill (660 bytes minified) in most of the browsers: https://gist.github.com/adriengibrat/b0ee333dc1b058a22b66

1

You can defer all calls like jQuery(function(){...}) without the loop of a setTimeout: https://jsfiddle.net/rL1f451q/3/

It is collecting every jQuery(...) call into an array until jQuery is not defined, then the second code executes them when jQuery is available.

Put this in the head or the beginning of the body:

<!-- jQuery defer code body: deferring jQuery calls until jQuery is loaded -->
<script>
  window.jQueryQ = window.jQueryQ || [];
  window.$ = window.jQuery = function(){
    window.jQueryQ.push(arguments);
  }
</script>
<!-- end: jQuery defer code body -->

And this at the very end of the body, after the jQuery script:

<!-- jQuery deferring code footer: add this to the end of body and after the jQuery code -->
<script>
  jQuery(function(){
    jQuery.each(window.jQueryQ||[],function(i,a){
      // to understand why setTimeout 0 is useful, see: https://www.youtube.com/watch?v=8aGhZQkoFbQ, tldr: having a lot of calls wont freeze the website
      setTimeout(function(){
        jQuery.apply(this,a);
      },0);
    });
  });
</script>
<!-- end: jQuery deferring code footer -->
Viktor Tabori
  • 2,087
  • 1
  • 13
  • 14
1

I've wrote simple js code for handle such cases: https://github.com/Yorkii/wait-for

Then you can use it like this

waitFor('jQuery', function () {
   //jQuery is loaded
   jQuery('body').addClass('done');
});

You can even wait for multiple libraries

waitFor(['jQuery', 'MyAppClass'], function () {
   //Both libs are loaded
});
Yorki
  • 21
  • 3
1

You should be able to do this on a document ready event.

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
  • 2
    So something like `document.addEventListener("load", function () { run_my_code(window.jQuery) });`? – Jordan Reiter Nov 28 '11 at 15:56
  • Seems like the problem with this is you can only attach the load function once, so I could only handle one instance per page. There might be more than one. – Jordan Reiter Nov 28 '11 at 15:58
  • Then why do the docs say this: "If multiple identical EventListeners are registered on the same EventTarget with the same parameters, the duplicate instances are discarded. They do not cause the EventListener to be called twice, and since the duplicates are discarded, they do not need to be removed manually with the removeEventListener method." https://developer.mozilla.org/en/DOM/element.addEventListener#Multiple_identical_event_listeners – Jordan Reiter Nov 28 '11 at 16:24
  • 2
    the 2nd parameter is different. – Daniel A. White Nov 28 '11 at 16:37
  • The problem with using an on ready event is that $ is still not mapped in the namespace. You get a JS error in Chrome. – Lucas Holt Feb 01 '13 at 20:53
0

I'm posting my answer using a JavaScript promise. It works, it is simple and reusable.

The advantage is, that the code get's executed as soon as jQuery is loaded, no matter how early or late on the page.

But, I'm by far not as experienced like some other people in this thread. So I'd love my answer to be peer reviewed. I'd like to know if it really is a valid solution and what the downsides are.

Write an objectExists function with a promise as high up in the code as you want.

function jqueryExists() {
    return new Promise(function (resolve, reject) {
        (function waitForJquery() {
            if (jQuery) return resolve();
            setTimeout(waitForJquery, 30);
        })();
    });
}

Then add your function which depends on jQuery. As far as I understand, it won't block any other script from execution, and the page will load just fine even if jQuery is loaded much later.

jqueryExists().then(function(){
    // Write your function here
}).catch(function(){
    console.log('jQuery couldn\'t be loaded');
})

jQuery can now be loaded after this code, and the function will execute as soon as jQuery is available.

Aleksandar
  • 1,496
  • 1
  • 18
  • 35