Select Page

This is the third part of our three part series on Web Calipers. The 1st post was on the history and purpose of Web Calipers. The 2nd post was on how to use Web Calipers including short-cut keys and bookmarking instructions. This post will detail some of the code and methodology that went into making Web Calipers possible.

Bookmarklet

When trying to solve the issue of not having to install the Web Calipers every time I went to a new computer, the use of a bookmarklet was a great solution. Bookmarklets are pieces of javascript code that can be run in the browser by clicking the associated bookmark. Unlike regular bookmarks they do not take you to a new page but instead run the piece of javascript code on the page you are currently viewing.

A bookmarklet injects the code into the browser as a url when it is clicked. This presents two main limitations. The first is that the code can’t have any white space, the second is a length limit. This varies by browser but it drastically limits the length of the code that can be run. The solution to this is writing two javascript files. The first is in the actual bookmark and the second is code hosted on the internet. When the first piece of code is ran it downloads and runs the second piece of code. This allows for larger code to be implemented as well as code that has white space in it.

The first piece of code looks like this:

javascript:!function(){this.a='www.googledrive.com/host/0B_zNlcgQU7LhM2lSb1hPemZRVEU';this.b=document.createElement('SCRIPT');this.b.type='text/javascript';this.b.src=this.a?'+Math.round(Math.random()*99999);document.getElementsByTagName('head')[0].appendChild(this.b);}();

The first order of business when writing this 1st piece of bookmarklet code is to write an anonymous function. This is a function with no name, that is in this case run immediately. There are two main ways of doing this. This first is to write:

(function(){ ... })();

and the second is to write:

!function(){ ... }();

I used the notation that starts with a ! instead of wrapping the function in (). This is one less character, making for less load (albeit very little less), and I also find this notation easier to read. Firstly because as soon as I see !function I know that I’m looking at an anonymous function, and secondly because I don’t have to match ().

The second order of business is writing the code so that there are no spaces. This means you can’t write “var myVariable“. Leaving only two options. One is to simply write “myVariable” and the second is to write “this.myVariable“. Since the first option “myVariable” will put the variable into the global name space you run the serious risk of naming collisions. Some have chosen to do this and name their variable something very unique, but this makes the name lengthy, obscure, harder to understand and you still run the risk of collisions. Luckily the second method works much better. Using “this.myVariable” declares the variable as being local to the given scope, which is in this case the anonymous function. This means no naming collisions and you can use names that make sense, are short and are easy to read.

The rest is quite straight forward. It goes like this:

  1. The second script’s url is stored
    this.a='www.googledrive.com/host/0B_zNlcgQU7LhM2lSb1hPemZRVEU';
  2. A script element is created and stored
    this.b=document.createElement('SCRIPT');
  3. The script element’s type is set
    this.b.type='text/javascript';
  4. The script element’s src url is set to the url stored in step 1 with a random number attached to the end to make sure that a fresh version is downloaded.
    this.b.src=this.a?'+Math.round(Math.random()*99999);
  5. The script is attached to the document’s head node.
    document.getElementsByTagName('head')[0].appendChild(this.b);

This last line of code tells the browser to load the script into the current document. When this happens it loads the second script. I have seen bookmarklets where the url is put in directly in step 4, but I prefer having it set out in the first line so that when I make later bookmarklets I have easy access to the url. You may have also noticed that the url looks strange. It’s at “googledrive.com”. This is the next important technique used.

Google Drive Hosting

The second piece of javascript is an entire file. It is loaded just like any other script file in the html document. It must be accessible to anyone who clicks this link. This means that it needs to be hosted publicly. Which brings up the issue of hosting. If you have a website you could host it there, but there are a couple of problems with this. The first is that you may not have a server for your use. The second is that if you do have a server, everyone who clicks the bookmark will download the javascript file plus anything else it loads. This is now a burden on your server and possibly costing you money if you are paying for bandwidth. The third is that when you update your website or change servers the link may be lost and this will break the bookmarklet for anyone who’s downloaded it. This is where using Google Drive to host your code and assets works amazingly well.

It’s fairly easy, though somewhat cryptic to publicly host files on Google Drive. Here’s how to do it.

  1. Create a folder in your google drive and name it (ex: “web-calipers”)
    web-calipers-google-drive
  2. Share the folder publicly by selecting the folder and clicking on the link icon in the upper right
    web-calipers-google-drive-02
  3. Next click on “Sharing settings” and set the Link Sharing to “Anyone on the internet can find and view”.
    web-calipers-google-drive-03
  4. Copy the link by clicking “Copy link”.
  5. Paste the link into a new browser window and delete everything except for the id. It should go from something like: 
    https://drive.google.com/folderview?id=0B_zNlcgQU7LhfkZvcXdSMGIxbFBYWS1rN1dhYmFJU0syVnJjTU1tVFg3bXlHbDhnN0FJVjQ&usp=sharing
    to something like:
    0B_zNlcgQU7LhfkZvcXdSMGIxbFBYWS1rN1dhYmFJU0syVnJjTU1tVFg3bXlHbDhnN0FJVjQ
  6. Now add “https://googledrive.com/host/” to the beginning. It should look something like this:
    https://googledrive.com/host/0B_zNlcgQU7LhfkZvcXdSMGIxbFBYWS1rN1dhYmFJU0syVnJjTU1tVFg3bXlHbDhnN0FJVjQ
    This is the base url you will use for linking to files, etc.
  7. Now simply upload a file into your publicly shared folder. (Example: “web-calipers.html”). You can now just attach this to the end of the base url to see this file (or any of your files).
    https://googledrive.com/host/0B_zNlcgQU7LhfkZvcXdSMGIxbFBYWS1rN1dhYmFJU0syVnJjTU1tVFg3bXlHbDhnN0FJVjQ/calipers.html

By sharing your folder instead of each file you will have a much easier time with your code.

Javascript File : Anonymous Function
As with the first piece of javascript code that gets called when you click the bookmark, the second piece of javascript code should be placed inside an anonymous function for security reasons and so that variables declared within the function don’t effect variables that may exist in the current document.

!function(){ ... }();

Javascript File : Base URL

Next declare a variable for storing the base url you just created.

var baseURL = "https://googledrive.com/host/0B_zNlcgQU7LhfkZvcXdSMGIxbFBYWS1rN1dhYmFJU0syVnJjTU1tVFg3bXlHbDhnN0FJVjQ/";

Now you can simply append other file names you want to load. If you upload an image into your shared folder (ex: “thumbnail.jpg”), you can now link to it like this:
var myImageURL = baseURL+"thumbnail.jpg";

Javascript File : Images

I didn’t want the Web Calipers to look boring. I wanted them to have a bit of style so I used an image for both the left arm and right arm of the calipers. Since the user will be clicking and dragging on the calipers it works better to create a div and then set the image as the background-image.

div.style.backgroundImage="url('"+baseURL+"caliper-bg.gif')";

I used this method for all the imagery in the calipers. The images I used for the background were greyscale. I did this because I knew that I would later be applying color to them.

Javascript File : CSS Blend Modes For Color Changing

Next came the color changing. I didn’t want to add a new image every time the user wanted to cycle through colors, so I opted for adding background color and blending it with the background image. In order to do this, assuming that you already have a background image, add a background color.

div.style.backgroundColor="#f00";

And then set the backgroundBlendMode to one of the available blend modes. These modes are: screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, and luminosity. I found that luminosity gave me the best results.

div.style.backgroundBlendMode="luminosity";

If you simply use a png24 for your image and leave empty space around it, aka: it’s not a square, then blending color with it will result in your image getting color and the empty space around it getting color too. To stop this from happening I used  another div as a mask for my image+color blended div. I rotated the mask div, placed the color blended div inside it and then counter rotated the color blended div. Resulting in the angle at the bottom of the Web Caliper arms.

web-calipers-color1

Javascript File : Turning off Selection

When the user drags the Web Calipers across the screen, they are dragging their mouse across the screen. This results in selecting everything their mouse drags over. In order to counter act this I needed to tell the body of the document to be unselectable. This is done with the style:

-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none; 

But if I applied these directly to the body and then later took them off it could end up removing styles that had been applied directly to the body already. In order to apply the styles to the body without effecting the page adversely, I added a class and later removed it.

I did this by adding a new style to the head:

var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = '.hbUnSelectable { -webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;-o-user-select: none;user-select: none; }';
document.getElementsByTagName('head')[0].appendChild(style);

Then I would add and remove this style as necessary.

function addClass(elem, classToAdd)
{
     var classes;
     if(elem.getAttribute("class"))
     {
          classes = elem.getAttribute("class").split(" ");
          for(var a=0; a
     }
     else
     {
          classes = [];
     }
     classes.push(classToAdd);
     elem.setAttribute("class", classes.join(" "));
}

function removeClass(elem, classToRemove)
{
     if(!elem.getAttribute("class")) return;
     var classes = elem.getAttribute("class").split(" ");
     for(var a=0; a
          if(classes[a] === classToRemove) classes.splice(a,1);
     elem.setAttribute("class", classes.join(" "));

}

I hope this has been helpful and informative.

Thanks,
Gery