Danish Dynamite

Old Blog

Using Thickbox in the WordPress Admin

A complete guide to using Thickbox in the WordPress admin, despite the ingenious traps laid by the developers of the media manager and Thickbox itself. With this guide in hand, you too can shout out ‘Spaaaartaaaaa’ and kick bad code into a big-ass hole in the ground.

Anyway, I knew that WordPress shipped with Thickbox, for use with the media manager, and I wanted to use it to rewrite a pop-up window solution in a plugin for Wordy into using the iframe functionality of Thickbox. You know, the kind of thing that sounds easy until you start digging, right? Now, I could have gone with one of the newer Thickboxesque projects, but I wanted to have the plugin feel like it was a part of WordPress, and since the WordPress admin uses Thickbox, that’s the way to go.

Well, the first thing I learnt was how Thickbox is less than well-documented (not to mention stagnant since 2007), and after a short while of trying to make head and tail of the code, I found out that the version shipping with WP actually contains a bug. Nonetheless, I soon found a way to get it up and running to a point where I had only two major problems facing me.

First of all, most JS in the admin is loaded using load-scripts.php, which basically rolls up all the default JS and loads it in one big file at the end of the file, just before </body>, which in turn means that when Thickbox and its load-scripts bretheren queue up some initialization code using .ready(), it will inevitably come after any code I can enqueue, and thus my code can’t be sure that Thickbox has even been initialized when it’s run, which is compounded by the fact that Thickbox doesn’t leave behind any obvious signs of having been initialized.

Ugh.

There is a work-around however, because Thickbox attaches an event to certain elements with the ‘thickbox’ class. We can go in and find that event, and when it exists—which means Thickbox has initialized—run our stuff. We do this by going through the click event stack for the first found a.thickbox, turning it into a string, removing newlines, tabs and spaces from it and comparing it to what we know Thickbox puts there. Rince, lather, repeat until we find what we’re looking for, and we’re good to go (though, just to be sure, we force it to happen after 2 seconds regardless).

It ain’t purdy, but it works:

/**
 *	Checks the first found a.thickbox element to see if Thickbox has attached a click event.
 */
function checkThickboxInit() {
	jQuery.each( jQuery(‘a.thickbox:first’).data(‘events’)[‘click’], function(i, event) {
		var thisEvent	= event.toString().replace(/\n/g, ‘’).replace(/\t/g, ‘’).split(’ ‘).join(’‘);
		var TBEvent		= ‘function(){vart=this.title||this.name||null;vara=this.href||this.alt;varg=this.rel||false;tb_show(t,a,g);this.blur();returnfalse;}’;
	    if (thisEvent == TBEvent) {
	    	clearTimeout(forcePayment);
	    	clearInterval(checkThickboxInitInterval);

			postThickboxInit();

			return;
	    }
	})
}

function postThickboxInit() {
	/* Thickbox-related code can now be run */
}

checkThickboxInitInterval = setInterval(checkThickboxInit, 50);
forcePayment = setTimeout(‘clearInterval(checkThickboxInitInterval); postThickboxInit();’, 2000);

The Media Manager Horror

The second problem is much bigger. Mostly because it’s much stupider. Thickbox was added to WordPress along with the media manager, and whoever wrote that apparently decided that Thickbox wasn’t going to be used by anyone else anyway, so they might as well go ahead and break its core functionality. Get this. In media-upload.js, the tb_position() function, which is what Thickbox uses to control the size and position of a Thickbox window, is replaced in its entirety with a function meant to be used only with the media manager, which comes with hardcoded widths and which is attached to the window.resize event.

That took me a while to figure out.

Why this was necessary I have no idea; Thickbox allows you to set the size you want through URL parameters, which you’d think would be good enough for the media manager as well, but apparently not.

So we fight in the shade…

/**
 * Add ‘sparta’ class to the Thickbox window. Called from inside the TB iframe.
 */
function sparta_iframe_loaded() {
	jQuery(’#TB_window’).addClass(‘sparta’)
}

/**
 * Checks how to resize the TB window. Called on window.resize.
 */	
function sparta_window_resize() {
	if (jQuery(’#TB_window’).hasClass(‘sparta’))
		sparta_resize_thickbox();
	else
		tb_position();
}

/**
 * Resizes the TB window our way, not the highway.
 */
function sparta_resize_thickbox() {
	var SpartaWidth			= 1000;
	var TB_newWidth			= jQuery(window).width() < (SpartaPaymentWidth + 40) ? jQuery(window).width() – 40 : SpartaPaymentWidth;
	var TB_newHeight		= jQuery(window).height() – 70;
	var TB_newMargin		= (jQuery(window).width() – SpartaPaymentWidth) / 2;

	jQuery(’#TB_window’).css({‘marginLeft’: -(TB_newWidth / 2)})
	jQuery(’#TB_window, #TB_iframeContent’).width(TB_newWidth).height(TB_newHeight)
}

jQuery(document).ready(function() {
	/**
	 * Cast the media managers window.resize event into the fire.
 	 */
	jQuery.each( jQuery(window).data(‘events’)[‘resize’], function(i, event) {
		var thisEvent		= event.toString().replace(/\n/g, ‘’).replace(/\t/g, ‘’).split(’ ‘).join(’‘);
		var expectedEvent	= ‘function(){tb_position()}’;

	    if (thisEvent == expectedEvent) {
			delete jQuery(window).data(‘events’)[‘resize’][i];
			jQuery(window).bind(‘resize.ournamespace’, sparta_window_resize)
	    }
	})

	// Open a Thickbox window
	tb_show(‘This is Sparta’, ‘sparta.html?TB_iframe=true’);
}

Now create sparta.html and at the bottom of it, in a script block, insert the folllwing, so we can attach our own class to the TB window:

parent.sparta_iframe_loaded();

And there you have it. A letter-opener.

Update: A belorussian translation of this article.