2005-09-01

GreaseMonkey techniques

Like all better hacks, GreaseMonkey is evolving -- and with it, so could your GreaseMonkey scripting habits. With early versions, it was a good habit encasing all of your code in a big nameless function scope -- (function(){ /*your code*/ })(); -- to not unintentionally spill any bits into the invasively compositioned page. Since 0.3.3, this is done quite transparently by GreaseMonkey anyway, so you don't need to clutter up your code with it.

Similarly, many early scripts seem to package up everything in one init() method which does the real action, and invoke the script by adding an event handler for the load event -- window.addEventListener( 'load', init, false ); -- which doesn't quite seem to cut it anymore; I believe it's from recent (0.5.1 final, in my case) versions don't inject your code until the load event has already fired, and then you can't very well sit indefinitely waiting for it to happen again. At least I hope this is the story; UTSL, to find out for real. It would make a lot of sense - why burden all developers with another indirection before getting their script code run?

One indirection that does, however, still seem to need to be there in your code is a chunk of code that ensures you haven't already been invoked before on this page, and avoids doing it again. My boilerplate (well, not really, as you'll see) now starts with this chunk of code:

if( 1 == (window.ID=typeof window.ID=='undefined'?0:1) )
return;

where ID is a unique identifier I come up with for every script. Quite against the GreaseMonkey spirit (pages that don't want to get injected and know of the identifier I picked may opt out of my script by defining that value themselves), but it does work. I expect we won't have to keep doing these things forever.

And having adapted and extended a few scripts made by other benevolent scriptwrights recently, I thought I'd drop a kind hint or two to the less bearded post-millennial ecmanauts out there who were not plagued and formed in the pre-DOM days: there are lots of nifty shortcuts in the pre-DOM document object model to rid you of the boring typing of document.getElementsByTagName('body')[0], and so on. Here are a few of them:


document.getElementsByTagName('a'):

document.links

document.getElementsByTagName('img'):

document.images

document.getElementsByTagName('embed'):

document.embeds

document.getElementsByTagName('body')[0]:

document.body



Also, in places like that last one, where you are tempted to index an array (or collection / nodeset, really, as the case may be) of nodes with a constant like this, there is a safer option, to duck indexing out of bounds errors -- use .unit(0) instead of [0], and you will get a null value when the item was not there. Then break free if there was no such element, and your script will spew less embarrassing junk in the console when it failed to execute.

(Of course you may want to GM_log( 'Some friendly debug aid' ) to yourself so you quickly find what assumption didn't hold, for the day when the page you inject into changes into something slightly different -- just don't be lazy.)

That concludes today's hints and tips.
blog comments powered by Disqus