Jordan Reiter
Jordan Reiter

Reputation: 21032

Handling code which relies on jQuery before jQuery is loaded

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

Upvotes: 43

Views: 30552

Answers (11)

Aleksandar
Aleksandar

Reputation: 1776

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.

Upvotes: 0

Yorki
Yorki

Reputation: 21

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
});

Upvotes: 1

radiaph
radiaph

Reputation: 4121

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.

Upvotes: 10

Ashkan Mobayen Khiabani
Ashkan Mobayen Khiabani

Reputation: 34180

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.
 });

Upvotes: 19

Viktor Tabori
Viktor Tabori

Reputation: 2207

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

Upvotes: 1

Paweł BB Drozd
Paweł BB Drozd

Reputation: 4923

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

document.addEventListener("DOMContentLoaded", function(event) { 
    //you can use jQuery there
});

Upvotes: 45

brother Filip
brother Filip

Reputation: 111

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

Upvotes: 1

Parris
Parris

Reputation: 18456

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

Upvotes: 8

Rich O&#39;Kelly
Rich O&#39;Kelly

Reputation: 41767

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);

Upvotes: 20

Andrew
Andrew

Reputation: 13863

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');

Upvotes: 4

Daniel A. White
Daniel A. White

Reputation: 191058

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

Upvotes: 1

Related Questions