Using .ready() Before Enabling jQuery

This note proposes a method that allows the use of the jQuery.ready() method anywhere in the document, even if jQuery itself is included at the very bottom. Deferred initialization of jQuery.

The idea is to move the JavaScript code to the end of the HTML document, using JavaScript itself for this purpose. The idea was taken from here Stop paying your jQuery tax.

The method is as follows:

  1. In the head, include a script that:

    • Creates an array.
    • Creates a fake $ function that pushes the call arguments into this array.
  2. In the body, after including jQuery, place a script that:
    • Uses jQuery to iterate over the contents of our array.
    • ...and calls the real $ function, passing the saved arguments (functions).

Implementation for WordPress

For WordPress, this idea is implemented with the following PHP code in the functions.php file:

// Ability to use jQuery.ready() before jQuery is enabled
// 0 priority is important in some cases
add_action( 'wp_head', 'jquery_ready_catch', 1 );
function safe_jquery_ready(){

	add_action( 'wp_footer', __FUNCTION__, PHP_INT_MAX );

	echo '<script>' .
		 (
		 doing_action( 'wp_head' )
			 ? '(function(w,d,u){w.readyQ=[];w.bindReadyQ=[];function p(x,y){if(x=="ready"){w.bindReadyQ.push(y);}'.
			   'else{w.readyQ.push(x);}};var a={ready:p,bind:p};w.$=w.jQuery=function(f){if(f===d||f===u){return a}'.
			   'else{p(f)}}})(window,document)'
			 : '(function($,d){$.each(readyQ,function(i,f){$(f)});$.each(bindReadyQ,function(i,f){$(d).bind("ready",f)})})(jQuery,document)'
		 )
		 . "</script>\n";
}

Explanations

Let's consider the minified JS code above in a normal view:

(function (w, d, u) {

	// Define two queues for handlers
	w.readyQ = [];
	w.bindReadyQ = [];

	// Push a handler into the correct queue
	function pushToQ(x, y) {
		if (x == "ready") {
			w.bindReadyQ.push(y);
		} else {
			w.readyQ.push(x);
		}
	}

	// Define an alias object (for use later)
	var alias = {
		ready: pushToQ,
		bind: pushToQ
	}

	// Define the fake jQuery function to capture handlers
	w.$ = w.jQuery = function (handler) {
		if (handler === d || handler === u) {
			// Queue $(document).ready(handler), $().ready(handler)
			// and $(document).bind("ready", handler) by returning
			// an object with alias methods for pushToQ
			return alias;
		} else {
			// Queue $(handler)
			pushToQ(handler);
		}
	}

})(window, document);

If you look at the documentation for the method jQuery.ready(), it explains that if handlers are bound to DOM ready using the .bind(), they actually fire after all other handlers. That is why we have two queues - to maintain this behavior.

By expanding the body script (immediately after jQuery), we get:

(function ($, doc) {
	$.each(readyQ, function (index, handler) {
		$(handler);
	});
	$.each(bindReadyQ, function (index, handler) {
		$(doc).bind("ready", handler);
	});
})(jQuery, document);

Just like in Sam's example, we use the jQuery.each() method to correctly bind all our queue handlers to DOM ready, but since $(document).bind("ready", handler) could have been called earlier, we will bind them correctly as well.

Example

<!DOCTYPE html>
<html>
	<head>
		<title>Example</title>
		<script>
		(function(w,d,u){w.readyQ=[];w.bindReadyQ=[];
		function p(x,y){if(x=="ready"){w.bindReadyQ.push(y);}else{w.readyQ.push(x);}};
		var a={ready:p,bind:p};w.$=w.jQuery=function(f){if(f===d||f===u){
			return a}else{p(f)}}}
		)(window,document)
		</script>
	</head>
	<body>
		<script>
			$(document).bind("ready", function () {
				console.log("Example D: $(document).bind(\"ready\", handler)");
			});
			$(document).ready(function () {
				console.log("Example A: $(document).ready(handler)");
			});
			$().ready(function () {
				console.log("Example B: $().ready(handler)");
			});
			$(function(){
				console.log("Example C: $(handler)");
			});
		</script>

		<!-- HTML CODE OF THE PAGE -->

		<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
		<script>
		(function($,d){$.each(readyQ,function(i,f){$(f)});
		$.each(bindReadyQ,function(i,f){$(d).bind("ready",f)})})(jQuery,document)
		</script>
	</body>
</html>

Output:

Example A: $(document).ready(handler)
Example B: $().ready(handler)
Example C: $(handler)
Example D: $(document).bind("ready", handler)

Note that although Example D is first, it uses $(document).bind("ready", handler), so it gets queued and executed after the other three examples. It behaves exactly as intended by jQuery.

--

Original article