Reputation: 21032
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.
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
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
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
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:
deferAfterjQueryLoaded
lazily, so you don't need to inject that into head
.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.setTimeout(..., 0);
).For complete cleanliness you could have the scheduled function remove deferAfterjQueryLoaded
as well.
Upvotes: 10
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
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
Reputation: 4923
Simple use pure javascript version of $(document).ready();
:
document.addEventListener("DOMContentLoaded", function(event) {
//you can use jQuery there
});
Upvotes: 45
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!');
})
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
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
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
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
Reputation: 191058
You should be able to do this on a document
ready
event.
Upvotes: 1