Home > Tutorial > How to Write a Bookmarklet

How to Write a Bookmarklet

Bookmarklets can be very handy. One bookmarklet I use all of the time if the “Note in Reader” for the Google Reader. If I find an interesting web page I want to archive or share with friends I just call the bookmarklet from my bookmark bar.
The google "note in reader" bookmarklet in action
This opens a window inside of the current web page, which offers to add this page to my google reader.

What are Bookmarklets?

All web browser support the special “javascript” URL protocol. If you type “javascript:” followed by JavaScript code into a browser’s location bar, the JavaScript will be evaluated in the context of the active web page (host page). Such strings are handled by the browsers just like any other URL. You can set them as “href” of a link tag and of coarse bookmark them. To get a feeling of this try typing this in the URL bar:

javascript:alert(document.getElementsByTagName("title")[0].innerHTML);

This will alert the document’s title. The bookmarklet’s JavaScript has full access to the document’s DOM and is run in the same JavaScript namespace as all other JavaScript in the host page.

Bookmarklet Mantra

When writing bookmarklets you have to keep two things in mind:

  1. Don’t make assumptions about the document. You don’t know which box model the document uses, whether it is HTML or XHTML or whether JavaScript libraries are loaded. Bookmarklets have to be programmed very defensively.
  2. Avoid unwanted side effects of your code to the document. If possible don’t add any JavaScript entries to the global JavaScript namespace and make as little changes to the DOM as possible. The page doesn’t know that you have injected code and you don’t want your bookmarklet to break the page. This means that you should not use global variables or function. The module pattern can help to achieve this.

The first bookmarklet

Simple JavaScript can be easily encoded as strings but as soon as the JavaScript becomes more complex this is no longer an option. It’s much easier to write normal JavaScript and once confident the code works as expected turn it into a bookmarklet string. To do this you can leverage a nice JavaScript feature. In JavaScript all functions are also objects and support the toString method. If toString is called on a function the function code is returned as a string. The above bookmarklet could have ben generated by the following code:

function bookmarklet() {
  document.getElementsByTagName("title")[0].innerHTML
};

document.getElementById("bookmarklet").href =
  "javascript:(" +
  bookmarklet.toString() +
  ")();"

This code converts the method “bookmarklet” into a string and sets this string as “href” attribute of a link tag with the id “bookmarklet”. Since the returned string is only the function declaration, the two parentheses are appended to immediately execute the function. This link can easily be dragged into the browser’s bookmark bar.

Using external JavaScript files

The next logical stet is to externalize the JavaScript code into an external JavaScript file. This is possible because we can use DOM methods to dynamically create a script tag and insert it into the document. During development the external JavaScript file can be statically embedded into a test HTML-document. A very simple external bookmarklet could look like this:

(function() {

var Widget = function(id) {
this.element = document.createElement("div");
this.element.id = id;
this.element.innerHTML = "<h1>Juhu Kinners!</h1> click to hide";

var that = this;
  this.element.onclick = function() { that.element.style.display = "none"; };
};

function main() {
  var widgetId = "bookmarkletWidget";
  var widget = new Widget(widgetId);
  document.body.appendChild(widget.element);
}

main();
})();

This script follows the module pattern to prevent variable and functions to leak into the global JavaScript namespace. The main method instantiates a widget, which is basically a wrapper for a DIV element, and inserts it into the document. The widgets is hidden if the user clicks on it.

The loader script has to dynamically create a SCRIPT element with the absolute URL to this script and insert it into the body.

function loadBookmarklet() {
  var script = document.createElement("script");
  script.src = "http://juhukinners.com/posts/bookmarklet/widget.js";
  script.type = "text/javascript";
  document.body.appendChild(script);
};

document.getElementById("bookmarklet").href =
  "javascript:(" +
  loadBookmarklet.toString() +
  ")();"

The JavaScript URL is generated the same way as in the previous example.

What about CSS?

The bookmarklet doesn’t look very well yet. It could use some CSS styling. Just like scripts, stylesheet links can be added dynamically into the document. The bookmarklet can be extended like this to use CSS:

(function() {

var ROOT_URL = "http://juhukinners.com/posts/bookmarklet";

function loadCss(url) {
  var el = document.createElement("link");
  el.type = "text/css";
  el.rel = "stylesheet";
  el.href = url;

  var head = document.getElementsByTagName("head")[0];
  head.appendChild(el);
};

...

function main() {
  loadCss(ROOT_URL + "/style.css");
  ...
}

Note that the URL to the stylesheet must be absolute since relative URL’s are relative to the host page and not relative to the bookmarklet’s JavaScript file. To minimize side effects of the loaded stylesheet, all CSS rules should be scoped with the ID of the top level bookmarklet element. Further its a good idea to prefix all CSS class names and IDs with an unique string to prevent name collisions with the host page.

Finishing touches

We now the demo bookmarklet is almost ready. We have a link, which can be saved as a bookmark. If the link is clicked the browser dynamically inserts an external JavaScript file into the current page and this script uses CSS to style its UI. But what if the user clicks this links several times? Obviously the bookmarklet’s JavaScript is inserted again and again. Since this is not desired in this case the loader has to be tweaked a little to only load the bookmarklet once:

function loadBookmarklet() {
  var widgetId = "bookmarkletWidget";
  var widgetElement = document.getElementById(widgetId);
  if (widgetElement) {
    widgetElement.style.display = "block";
    return;
  }

  ...
};

Test this bookmarklet or checkout the source code on github.

“With great power comes great responsibility” (Peter Parker)

Since bookmarklets run in the scope of the host page they can read and modify every piece of data on the page. The same is true for all globally accessible JavaScript data. Bookmarklets are even allowed to establish HTTP connections to the domain the host page was served from. In this regard a bookmarklet is very similar to a browser plugin. This implies that every user of a bookmarklet has to trust its author and the author on the other hand must be aware of his responsibility.

Happy coding!

Advertisements
Categories: Tutorial Tags: , ,
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: