My guide to creating jQuery-based bookmarklets
Updated 2008-10-14: there's a very similar jQuery-lovefest on Sam Ruby's weblog with plenty of useful tips.
To illustrate just how little code this can require, here's an example which uses jQuery to install a function which sanitizes input (we have a legacy app chokes on smart-quotes and people paste text in from Word), copies the submit buttons from the bottom of the form to the top and adds a graphical datepicker for every date field on the page:
jQuery":text,textarea"bind"change"sanitizer; jQuery"form"bind"submit" function jQuery":text,textarea"each sanitizer; ; var submit_buttons = jQuery'input[type="submit"]'; submit_buttonsparentclonetrueprependTo submit_buttons parents filter'form' ; jQuery'input[id*="DATE"]'datepicker;
That's the complete, ready-to-go, “even works with crotchety old Internet Explorer” guts of the code (the take-home lesson is that jQuery is awesome for busy developers). The downside is that this requires a little but of work: you need to have jQuery (and possibly dependencies like the UI plugin I used above) available and you need to jump through some hoops to load jQuery into an existing page efficiently and without conflicts.
Didn't we used to pay for hosting?
One drawback to all of this is that you need somewhere to host your external libraries since you can't fit the core jQuery into a URL, much less UI components or the less svelte libraries. This meant setting up a server, getting an SSL certificate if you need to work on HTTPS sites, etc. Not that much work but it's now a lot easier and quite noticeably faster because Google makes it trivial to get the popular AJAX libraries from their CDN.
Developing with Bookmarklets
The deployment scenario for the major projects where I've used these techniques is a situation where you have some limited access to the page source: perhaps inserting a single
script tag into a template or using something like MonkeyGrease or an Apache proxy with mod_substitute to rewrite the generated HTML as it passes through. This is great for making minimal changes but a bit cumbersome to develop and test with, particularly if you need to work on a production site or your instructions begin something like “Go change your browser's proxy settings…”
If I was only working in Firefox I could use GreaseMonkey but I need to test in Safari and Internet Explorer, too. The portable solution is a simple bookmarklet. I use a simple template (bookmarklet-template.js) which loads jQuery from the Google CDN and, after everything is ready to go, runs either a simple function or the external script of my choosing. This makes it easy to prepare an injector bookmarklet which can be used to pull my code into the current page, after which I can run and debug it using Firebug.
This is also a useful technique for fixing other people's pages. Here are two bookmarklets and the commented source for tools which I use often:
- Enable autocomplete - changes autocomplete="off" to on throughout the page (Source: enable-autocomplete.js)
- Resizable Textareas - makes all textareas resizable (alá Safari 3) using jQuery UI Resizable (Source: resize-textareas.js)
I keep both of these in my Firefox & IE bookmark toolbar since they come in handy throughout the day and I've created more any time I find myself regularly needing to deal with a cranky legacy site. The process is simple: copy bookmarklet-template.js, add the code which does whatever fixups the target page needs, run the entire thing through JSLint and, finally paste it into Ted Mielczarek's very handy Bookmarklet Crunchinator.
Good Code Injection Practices
Use Anonymous functions
What's the difference between this bit of code and the first example above?
function jQuery":text,textarea"bind"change"sanitizer; jQuery"form"bind"submit" function jQuery":text,textarea"each sanitizer; ; var submit_buttons = jQuery'input[type="submit"]'; submit_buttonsparentclonetrueprependTo submit_buttons parents filter'form' ; jQuery'input[id*="DATE"]'datepicker; ;
window.foo you can still touch the rest of the page if you need to - for example, replacing the broken validation logic on Comcast's forms.
Reliably detecting when external code has loaded
When jQuery has loaded, it's easy to say "Load this .js file and run this function when it's ready" - here's how the text-area resizer works:
jQuerygetScriptdocumentlocationprotocol + "//ajax.googleapis.com/ajax/libs/jqueryui/1.5.2/jquery-ui.js" function jQuery"textarea"resizable; ;
Loading jQuery itself requires you to do this the hard way: generate a script tag on the fly, insert it into the document and listen for the load events to tell when it's safe to run code which depends on the library you're loading. This is easy for Safari, Firefox, etc. which support the standard W3C DOM addEventListener: simply run your code after the script tag fires a "load" event. Unfortunately, it's not that simple for Internet Explorer: in theory
attachEvent("onload") would be equivalent but unfortunately load events are quite unreliable for script tags with IE and so we need to use an
onReadyStateChange handler as seen below and check for either of two events which may be fired:
It's conceivable that a buggy browser could fire the same event twice in an unusual scenario and if you have any sort of user-driven or timer-based code, you'll want to prevent your payload from being run multiple times using a guard like this which allows the function to check whether it has executed before without using the more common approach of relying on a global variable. Besides cleanliness, this also makes it easy if you might inject multiple things onto a page and don't want to have to rely only on a global variable naming convention to prevent chaos:
// Avoid executing this function twice: if argumentscallee_executed return; argumentscallee_executed = true;
Avoid HTTP/HTTPS conflicts
If you're injecting code into pages which may or may not use SSL, you have a problem: if you hard-code a URL in your code and the protocol doesn't match you'll either incur the extra overhead of starting an SSL session (which isn't a major problem) by using
https even when you don't need to or encounter Internet Explorer's popular mixed-mode security warning. This is easy to avoid by using the current page's protocol for your scripts as long as you're using a server which can handle either protocol (Google's CDN does; Yahoo's does not):
documentlocationprotocol + '//path.to.example.com/something.js'