I came up with this interesting solution which is extensible and reusable for a lot of use cases. Here's a concise illustration of the technique:
<html><head><title>Test</title></head><body>
<script src="http://www.google.com/jsapi"></script>
<script>
google.load("jquery", "1");
google.setOnLoadCallback(function() {
DelayProxy = function(sel) {
this.sel = sel;
var self = this;
this.interval = setInterval(function() { self.try(); }, 100);
};
DelayProxy.prototype = {
queue: [],
'__noSuchMethod__': function(id, args) {
this.queue.push([id, args]);
},
'try': function() {
var res = $(this.sel);
if (res.length) {
clearTimeout(this.interval);
for each (var call in this.queue) {
res[call[0]].apply(res, call[1]);
}
}
}
};
$.whenAvailable = function(target) {
var els = $(target);
if (els.length) {
return els;
} else {
return new DelayProxy(target);
}
};
$('#run-test').click(function() {
$.whenAvailable('#target').attr('value', "hello world!");
});
$('#make-target').click(function() {
$('body').append('<input type="text" id="target">');
});
});
</script>
<a href="#" id="run-test">Set target value</a>
<a href="#" id="make-target">Make target</a>
</body>
</html>
You can try this demo here. First click "make target" and then "set target value". Nothing special going on - we're inserting a text field into the DOM, then setting its value using the jQuery
attr
function. Now reload the page so the inserted field goes away and hit the links in the opposite order. As soon as you hit "make target" the delayed "set target value" call should execute.The real trick behind this code is the
__noSuchMethod__
function. If an object has this property set to a function, it will be called for any missing member function call, and will receive the function name and arguments as arguments. It's similar to PHP's __call
or perl's AUTOLOAD
and comes in really handy. Here's how the code works:- When you call
$.whenAvailable
, it checks with jQuery to see if the selector has any matching elements. Since it doesn't, it returns a newDelayProxy
object. The constructor of theDelayProxy
sets up a timer which callstry
every 100ms. - When you call
attr(...)
on the DelayProxy, it fires the__noSuchMethod__
function. This pushes onto the internal queue. - When you later hit the "Make target" button, the selector becomes valid
- Some amount of time (<100ms) later, the timer fires and
DelayProxy.try
finds the selector. This kills itself (clearTimeout
) and then applies the queued up function(s) to the resulting selector.
Pretty neat! I used to hate JS, but it does have some pretty powerful tricks.
update: it turns out this is a Mozilla SpiderMonkey extension, and hence not really useful in the real world. Boo!! The same idea still applies, but you've got to manually register the function for each of the functions to be delayed.