Bookmarklet Coding
November 14th, 2007If you're familiar with my work, then you'll know I'm an avid user of bookmarklets.
They can be little snippets of code, or more complex applications such as the Microformats or the Speech Bubble bookmarklets that entirely hijack the web page for a new function.
Here I'll share my experience writing bookmarklets and offer some tips for development.
Think BIG, code small
The majority of bookmarklets are tiny pieces of JavaScript, succinctly written for a single task. A few others will be more complicated and rely on a much larger library behind the scenes. Either way, developing your bookmarklet you will be faced with a series of limitations.
Limitations
First and foremost, if you want your bookmarklet to work in all browsers, it has to adhere to length limits:
- IE 5 - 2084 bytes
- IE 6 - 508 bytes
- IE 6 SP 2 - 488 bytes
- IE 7 - 2084 bytes
- Firefox + Safari - > 2000 bytes
IE6 has the smallest length, and that includes the javascript: pseudo protocol.
Don't quote me
Avoid the double quote character ("). Since the user is going to install your bookmarklet from a link, it will be wrapped with quotes (assuming xhtml). If you absolutely require quotes, try using the entity: %22
Optimise to be short
When you declare your variable, do it all at once. i.e.:
var ctr, doc, loc = window.location, max = 23, user = "remy";
This should be taken as far as possible. You can also consider whether you want to reuse variables to reduce the number you have to declare (watch out for inadvertently obfuscating your own code!).
Minify is your friend
Minify your code, and compare the benefit of Base62 encoding over shrinking variables. I highly recommend Dean Edwards' Packer.
If it just doesn't fit...
If you simply can't meet the length restrictions, you can load your bookmarklet externally (see below). However, if you do, make sure that you let your users see the source if they want to because your bookmarklet will have to act responsibly.
Installation
Most browsers will allow the user to drag the bookmarklet to their bookmark bar - however, IE is slightly different. To install in IE, you will need to right click on the link (that holds the bookmarklet) and add to favourites.
Play Nice
If you want your bookmarklet to interact with the existing page (rather than just redirecting off elsewhere), think about variable scope, in particular you may need to be careful not to overwrite existing variables.
Encapsulate
Rather than using void to run your bookmarklet, use the following:
javascript:(function(){ /* bookmarklet code */ })()
Then when you declare your variables, they'll be within the private scope of the anonymous function.
Style injection
If supporting IE6, and you need to apply new styles, you'll have to set them directly on the element, rather than injecting a link tag. i.e.
element.style.display = 'none';
Script Injection
Using script injection is a perfectly legitimate way to get your code to run on the page when it's longer than 488 bytes.
However, if your bookmarklet depends on external libraries (e.g. jQuery or Prototype) you need to be wary of race conditions if your code relies on these being in place first.
In this case, you need to use a test & callback/load combo as shown below in multi-script inject.
Single script injection
If you're only loading a single script that will execute or offer functionality when loaded, then the following will do fine (obviously all on one line):
javascript:(function () {
var s=document.createElement('script');
s.src='http://www.mysite.com/js/script.js';
document.body.appendChild(s);
})();
There's some argument to insert the script in to the head tag. I disagree. With bookmarklets like these, we're hijacking the web page, so perfect and semantic html doesn't have such a big play. I'd like to hear other people's thought on this.
Multi-script injection
The following function is the best way I've found to inject an external script and continue once it's confirmed to have been loaded.
Note: this is the server side bookmarklet code.
function MyBookmarklet() {
console.log('Testing whether jQuery is loaded (' + !!(typeof jQuery == 'function') + ')');
if (waitingForScript('http://jquery.com/src/jquery-latest.js', 'jQuery')) return;
console.log('Do some action with jQuery');
}
/**
* Only returns true when the external script has been loaded
* in to the DOM. It uses arguments.callee.caller to work out
* which function is the callback.
*
* @param url {String} URL of external script
* @param obj {String} The name of a function or variable within
* the external script to test for.
* @license: Creative Commons License -
* ShareAlike http://creativecommons.org/licenses/by-sa/3.0/
* @author Remy Sharp / leftlogic.com
*/
function waitingForScript(url, obj) {
function lateLoader(u,id,test,fn){
var d = document;
if (!d.getElementById(i)) {
var s = d.createElement('script');
s.src = u;
s.id = id;
d.body.appendChild(s);
}
var timer = setInterval(function (){
var ok = false;
try {
ok = t.call();
} catch(e) {}
if (ok) {
clearInterval(timer);
fn.call();
}
}, 10);
}
var callback = arguments.callee.caller;
if ((typeof window[obj] == 'undefined') && !window['loading' + obj]) {
window['loading' + obj] = true;
lateLoader(url, '_' + obj, function () {
return (typeof window[obj] != 'undefined');
}, callback);
return true;
} else if (typeof window[obj] == 'undefined') {
return true;
} else {
return false;
}
}
Bookmarklet Examples
Some personal favourites:
- Microformats - displays Microformats on the page and allows individual download (IE, FF & Safari)
- jQueryify - loads jQuery for console debugging
- Prototypeify - loads Prototype for console debugging
- del.icio.us - bookmarks a page on del.icio.us
- Firebug - dynamically loads Firebug Lite (IE + Safari)
- Globals - dumps global variables in Firebug console (FF only)
- Wikipedia - takes a selection on the page and loads the wiki entry for it.

Hi,
I found two typos in the multi-script injection code:
if (!d.getElementById(i)) {becomesif (!d.getElementById(id)) {ok = t.call()becomesok = test.call()cheers, ralf