Skip to content

Instantly share code, notes, and snippets.

@cowboy
Created June 25, 2010 20:53
Show Gist options
  • Save cowboy/453429 to your computer and use it in GitHub Desktop.
Save cowboy/453429 to your computer and use it in GitHub Desktop.
jQuery ready WIP 100% UNTESTED
/*!
* jQuery ready - v0.4pre - 06/28/2010
* http://benalman.com/projects/jquery-ready-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
(function($,undefined){
'$:nomunge'; // Used by YUI compressor.
// A handle by which the polling loop can be cancelled.
var timeout_id,
// Callback queues, by element ID.
queues = {},
// How many queues do we have?
num_queues = 0,
// Compare page HTML to cached copy when polling in an attempt to increase
// performance.
doc_elem = $('html'),
doc_elem_html,
// jQuery object references.
jq_fn = $.fn,
jq_fn_ready_orig = jq_fn.ready,
// Internal method reference.
jq_fn_ready,
jq_unready;
// TODO: DOCUMENT THIS
jq_fn.ready = jq_fn_ready = function( even_if_unclosed ) {
var that = this,
// Match simple-ID-selected jQuery collections only!
id = get_id( that ),
args = arguments,
callback;
// If selector matches regexp, set `id` to be the 1st match item, which is
// the ID string sans leading # and do some cool stuff. Otherwise, run the
// original jQuery .ready method.
if ( id ) {
// Convert `args` to a real array and strip off the first argument.
args = Array.prototype.slice.call( args, 1 ),
if ( $.isFunction( jq_fn[ even_if_unclosed ] || even_if_unclosed ) ) {
// Since `even_if_unclosed` is a function / $.fn.method, it wasn't
// explicitly passed. Shuffle `callback` and `even_if_unclosed`
// around a little.
callback = even_if_unclosed;
even_if_unclosed = undefined;
} else {
// Since `even_if_unclosed` isn't a function / $.fn.method, it was
// explicitly passed, so `callback` must be the first remaining
// argument.
callback = args.shift();
}
// If `callback` is a $.fn.method, use that method.
callback = jq_fn[ callback ] || callback;
if ( that.length && is_element_ready( that[0], even_if_unclosed ) ) {
// The element is ready, so execute the callback in the context of the
// element, passing any extra arguments through.
callback.apply( that[0], args );
// Since function was executed immediately, return `true`.
return true;
} else {
// The element isn't ready, so queue this callback for later execution.
// Initialize the queue if it doesn't yet exist, and increment the
// queues counter.
if ( !queues[ id ] ) {
queues[ id ] = [];
num_queues++;
}
// Push this callback with options and arguments onto the queue.
queues[ id ].push({ fn: callback, args: args, uc: even_if_unclosed });
// Start the polling loop, if not already started.
timeout_id || poll();
// Since function wasn't executed immediately, return `false`.
return false;
}
}
// Selector wasn't a simple ID, so execute (and return) the original jQuery
// .ready method.
return jq_fn_ready_orig.apply( that, args );
};
// TODO: DOCUMENT THIS
$.unready = jq_unready = function() {
queues = {};
num_queues = 0;
};
// TODO: DOCUMENT THIS
// Mention https://developer.mozilla.org/en/Case_Sensitivity_in_class_and_id_Names ?
jq_fn.unready = function( callback ) {
var that = this,
// Match simple-ID-selected jQuery collections only!
id = get_id( that ),
// Get the queue for this ID (if it exists).
queue = queues[ id ];
if ( queue ) {
// A queue for this ID must actually exist!
if ( callback ) {
// Remove the passed callback from this ID's queue if it exists.
queues[ id ] = $.map( queue, function(obj){
return obj.fn === callback || obj.fn.guid === callback.guid ? null : obj;
});
} else {
// Since `fn` was omitted, delete the entire queue for this ID.
delete queues[ id ];
num_queues--;
}
}
return that;
};
// TODO: DOCUMENT THIS
jq_fn_ready.delay = 30;
// Start polling.
function poll() {
// TODO: test if page innerHTML changed?
// Compare page HTML to cached copy when polling in an attempt to increase
// performance.
var html = doc_elem.html();
if ( html !== doc_elem_html ) {
doc_elem_html = html;
// Iterate over each per-element-ID callback queue.
$.each( queues, function(id,queue){
var elem = document.getElementById( id );
if ( elem ) {
// If the element exists in the DOM, iterate over the queued callbacks.
queues[ id ] = $.map( queue, function(obj){
if ( is_element_ready( elem, obj.uc ) ) {
// The element is ready, so execute the callback in the context of
// the element, passing any extra arguments through.
obj.fn.apply( elem, obj.args );
// Since this callback has executed, remove it from the queue.
obj = null;
}
return obj;
});
// If no more callbacks remain in the element queue, delete the queue.
if ( !queues[ id ].length ) {
delete queues[ id ];
num_queues--;
}
}
});
}
// If the DOM is ready, remove any remaining IDs / callbacks.
$.isReady && jq_unready();
// Continue polling if any remaining queues, otherwise unset timeout_id.
timeout_id = num_queues ? setTimeout( poll, jq_fn_ready.delay ) : null;
};
// Is DOM element `elem` ready?
function is_element_ready( elem, even_if_unclosed ) {
// Since the element exists, if we don't care that it's closed or the DOM
// is ready, the element is considered "ready".
if ( even_if_unclosed || $.isReady ) {
return true;
}
// As an HTML page is initially loading and an element is rendering, it can
// be assumed that it is fully ready or "closed" if either it or any of its
// ancestors has a nextSibling. If it or any of its parents are the last
// element on the page, while none of them will have a nextSibling, since
// the DOM will then be ready (and $.isReady will be true), the elemen will
// be considered "ready".
do {
if ( elem.nextSibling ) {
return true;
}
} while ( elem = elem.parentNode );
};
// If selector is a basic ID string, return the ID, otherwise null.
function get_id( jq_obj ) {
var id = jq_obj.selector.match( /^#([\w-]+)$/ );
return id && id[ 1 ];
};
})(jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment