If you’ve ever tried building a jQuery object from scratch then you’ve probably found that it is a little time consuming and probably a little ugly by the time you’re finished. You could put additional HTML templates as hidden elements on the page but maybe you’d rather keep that extra mark up out of your page.
One option would be load a template from a website address and then make your changes once it arrives. The code would probably look something like this.
var element = null;
$.ajax({
url:"template.html",
success:function(html) {
element = $(html);
}
});
There isn’t anything wrong with this code but it does put a bit of separation of your declaration of an element and the actual usage of the element. The problem is that you can’t do the whole process at once since the AJAX call takes a little time and you have to rely on a call back before you have access to the object.
It would be nice if we could have immediate access to our jQuery object on an AJAX call, but not block anything else on the page.
Time For Some Javascript Magic ™
I’m not really sure what you would call the example below, but the general idea is to capture (and queue up) any actions that would be invoked on our jQuery object and then release them once the AJAX call has been finished.
Let’s look at some code and see what it would look like (extra comments to try and explain the process)
jQuery.template = function(params) {
if (typeof(params) === "string") {
params = { url:params };
}
if (!$.isFunction(params.beforeUpdate)) { params.beforeUpdate = function() {}; }
if (!$.isFunction(params.afterUpdate)) { params.afterUpdate = function() {}; }
var self = {
container:null,
actual:null,
queue:[],
setup:function() {
self.container = $("<div/>");
for(var item in self.container) {
if (!$.isFunction(self.container[item])) { continue; }
self.register(item);
}
},
register:function(method) {
self.container[method] =
function(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14) {
if (self.container.actual) {
return self.container.actual[method](
p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14
);
}
else {
self.queue.push({
action:method,
params:[p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14]
});
return self.container;
}
};
},
update:function(html) {
self.container.actual = $(html);
$.each(self.queue, function(i, arg) {
self.container.actual[arg.action](
arg.params[0], arg.params[1], arg.params[2], arg.params[3], arg.params[4],
arg.params[5], arg.params[6], arg.params[7], arg.params[8], arg.params[9],
arg.params[10], arg.params[11], arg.params[12], arg.params[13], arg.params[14]
);
});
},
download:function() {
$.ajax({
url:params.url,
data:params.data,
dataType:"html",
success:function(html) {
params.beforeUpdate(html);
self.update(html);
params.afterUpdate(html, self.container.actual);
},
error:params.error,
complete:params.complete
});
},
init:function() {
self.setup();
self.download();
}
};
self.init();
return self.container;
};
This code allows you to have direct access to a jQuery object even before it has finished loading the content from the server. So, instead of using a callback we can write our jQuery like we normally would.
var template = $.template("template.txt")
.css({"color":"#f00", "width":"200", "background":"#333"})
.animate({width:800, height:900}, 3000)
.appendTo($("#container"))
.find(".title").text("howdy")
.parent().css({"background":"#00f"});
template.find("div").click(function() {
alert('clicked');
});
setTimeout(function() {
template.css({"color":"#0f0"});
}, 10000);
What Is Going On Here?
As I mentioned before the real problem is that the jQuery object we create isn’t ready as soon as we want to assign to it. Because of that we have to use a callback to resume the work. However, the cool thing about dynamic languages is that we can override anything we want.
In this example we start by creating a container that has all the same functions as a typical jQuery object but has one slight modification — all the methods are overridden and placed into a queue (man, I love enclosures). Once our AJAX call has completed we run an update command that executes everything we have saved against our new jQuery object instead of the one that received the calls to begin with — madness!
After we’ve caught up and our actual object is created we can stop saving actions to the queue and instead just invoke them immediately. I nicknamed this method forwarding for the sake of having a cool, buzzword sounding sort of name but if anyone knows what this is really called, please tell me :)
It is worth noting that this is only going to work with functions that return the jQuery object (at least until the ‘real’ object is created). The reason is that since we’re just capturing methods and saving them for later then we don’t know what the actual return type is. Needless to say, using a property probably won’t be accurate either (since we can’t intercept the request for the property like we would with a method)
In any case, this might simplify the next time you need to download content in jQuery but you don’t want to break up your functionality.