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.

In English Man!

I’m really not a particularly good programmer. I really like programming—or scripting as it were—but my strength is in how I apply it, not my deft code juggling, nor my mathematical prowess. In fact, my brain often locks up when trying to figure out even fairly simple problems, such as how to write a dice probability calculator, and in the long run it often keeps me from actually coding simply because I know how long it takes me to suss out how to do the more ‘complex’ stuff.

But dammit, I like figuring out stuff I can’t figure out, and I found a trick which helps me keep eyes on the ball. I write down what I need to do beforehand, like so:

Create a multi-dimensional array keeping track of skill level vs. results. First array is skill level, under which each element has an array of potential results, which in turn holds the count of how many times the result is likely to be seen.

For each skill level, iterate for each set of dice the possible outcomes, then increment the array element of the highest die.

Calculate the percentiles, draw a chart and watch the cash roll in.

Pretty straight-forward stuff when it’s written out in English. And yeah, multi-dimensional arrays, even two dimensional ones like this one, really break my balls. But when I have it written down, it goes down a lot smoother.

If I run into a problem I can’t quite figure out, I do the exact same thing again, and more often than not, it does the trick. Another ‘trick’, is to keep in mind in your writing, the structure of the code that comes after and keep the sentence and paragraph structures so that it essentially reads like a really high-level version of the final code.

What’s your secret?

You Keycode. I Can Has?

Today I started coding Monolith, and the first thing on the menu is… yeah. And for that, I need your help. It’s a 3-step program:

  1. Go here
  2. Click the button just below the escape key on your keyboard.
  3. Leave a comment on this entry with the number that popped up.
  1. Profit!

What I’m doing, is trying to figure out how to best use that button for the menu, so I need the keycode for that particular button. But since it’s different in the various territories (192 on my US keyboard, 52 on my Danish), it’s not as easy as it could’ve been.

Update: Turns out I’m getting radically different keycodes with my own code. Never mind for now. I’ll try and figure something out tomorrow. Thanks anyway :)

Prototype 1.5 Released

Yeah, so I’m still at home, eating oat meal and yoghurt very slowly. Very carefully. Which by the way, for those of you keeping track, means I missed out on the burgers at work today. Which sucks, because they are legendary burgers. And free.

Anyway, it does give me an opportunity to mindlessly check my feeds when I’m not feeding myself with painkillers. And as luck would have it, Prototype 1.5 was released today, complete with a new site and brand-spanking new documentation! Shiny!

I’ve already updated our K2 SVN repository, which by the way is as near as I can tell ready for the imminent release of WordPress 2.1.