Tuesday, June 30, 2009

Bookmarklets (p3): The Basics of Writing Bookmarklets


Continuing this series on bookmarklets, this week I wanted to cover the basics of writing bookmarklets.

I must assume that you are familiar with JavaScript. If not, then I highly recommend that you get quite familiar with it first; especially with the document object model (DOM). Many of the issues that JavaScript (JS) encounters, and therefore so does bookmarklets, are browser compatibility issues. In other words, your JS code would work in one browser, but not in one or more of the others. Writing universally correct JS code is important and key to a very successful bookmarklet.

It is also assumed that you understand HTML quite well, and that you are comfortable with such things as the HTML tags, what is a URL, secure vs. unsecure protocols, HTML entities (ISO 8859-1), etc. In addition to the DOM, which I mentioned above, you also need to know about certain HTML features and how to interact with them --Like frames and framesets. There are many HTML tutorials out there, which are only a google away.

Luckily, hardly anyone uses frames these days. Where I would have insisted that you detect frames in the past, it's rather safe to skip that now.

I don't want to go into the DOM, but it's worth mentioning that bookmarklets references the top-level object in a browser, which's the window object. From there you can browse to all other objects your browser supports. The window object is implicit, so window.document and document are the same thing. Another one of the basics of the DOM worth mentioning is that a window always contains a document object. From the there, the document contains the page elements, such as links, forms, etc.

Let's dive right in.

If I wanted an alert window/box to pop up with "Hello World" in it, I would simply do
alert('Hello World');
Of course, if you're writing this for a web page, you would include <script language="javascript"> before it and </script> at the end of it. However, when writing a bookmarklet, and since it goes directly in the URL field, you simply precede the alert call with a javascript:.

Let's go ahead and try that. Go to the URL field and type javascript:alert('hello world'); and then hit your ENTER (or Return) key. What you should get immediately after hitting the ENTER key is a pop up window that said "hello world" in it.

You've officially now written your first bookmarklet. The rest is making it more complex by calling on page elements, and then manipulating them as you see fit. There are a few rules to remember, but we'll get to that here shortly. For now, let's write one that really does something. But let's make it fun, shall we?

It's a bit more advanced, but I figured it would touch on several key things of writing a bookmarklet. We're going to write a bookmarklet that will take English as an input and then return Morse code. In other words, I want to know what would "Welcome to Ahmadism.com" be in Morse code (or your own name, etc.)? Let's find out.


Let's get started by writing the things we do know. We know that we will be taking in an input, and in JavaScript that's prompt call. Let's do that, but let's also assign it to a variable so we could so more with it should the need arise.
var myEngStr=prompt("Please type your English text here: ");
Next we need to map each character we could receive from that input to its corresponding Morse code. For instance, the letter A is ".-" in Morse code (without the quotes of course). There are several ways one could go about doing that, but the easiest is definitely to create an array. Again, if you don't know what that is, I strongly recommend that you look into JavaScript and get quite familiar with it. In short, though, an array is kind of like a table (as in a spreadsheet table). Without diving too deep into that, I think going through this example will help clarify it.

First, however, we need to list all the characters before we forget ... like so:
var letters="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 .,:?'-/()\"";
If you pay close attention to this variable, which's appropriately named "letters", you'll notice two things: There's a space after the 9, and that's because we want the space-character. Also, that towards the end we have two quotes. Allow me to quickly explain this as it is key to writing bookmarklets, and good JavaScript altogether. The quote right after the equal sign means the contents of the variable "letters" start here. And the quote at the end means the contents of that same variable ended there. But we want amongst the contents of the variable to have a quote as a character. To do so, you have to escape that character; otherwise, the JavaScript interpreter will think that the variable declaration ends with the first quote it sees. To escape something and make it where the interpreter reads it literally we use a backslash.

As a JavaScript guru, I'm sure you know that you can write JavaScript with either quotes or single quotes. In other words, alert('hello world'); works just fine, as it did in our first example. And alert("hello world"); is just the same. There are times, when I've had to use both quotes and single quotes to differentiate between strings. I highly recommend that you make it a habit to use single quotes when writing all your JavaScript/bookmarklets. Back to the topic at hand, let's say that you wanted to say alert('I'm the Ahmad in Ahmadism.');. The interpreter will read that first single quote, and will stop at the apostrophe (since it's the same character) and throw out an error since it expected a closing of the parenthesis followed by a semicolon. Instead, the interpreter sees a bunch of characters and attempts to process them as code, and hence the error. To circumvent that, we could easily escape that apostrophe and everything will work. It would look like this:
alert('I\'m the Ahmad in Ahmadism.');

Back to the Morse code bookmarklet we're creating. We've created a variable called "letters" and we now need to create the corresponding Morse code. As I had mentioned earlier, the best option for this is an array. I already have the Morse code in alphabetical order (along with the extra needed characters) so you don't have to go hunting for it.
var morse=new Array(".-","-...","-.-.","-..",".","..-.","--.","....", "..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-", "...-",".--","-..-","-.--","--..","-----",".----","..---","...--","....-",".....", "-....","--...","---..","----."," ", ".-.-.-","--..--","---...","..--..",".----.", "-....-","-..-.","-.--.-","-.--.-",".-..-.");


I didn't want to fiddle with uppercase and lowercase letters, so if you have noticed the "letters" variable we declared earlier is all uppercase. We now need to make the input that we get from the end-user converted to all uppercase; otherwise, things won't match. After all, the character A and the character a are two different characters. Here's the code we need to help us do that:
var input=myEngStr.toUpperCase();

I don't want to get side-tracked and talk about what is and why we initialize certain variables. Just know that it's good practice to "clear the air" and make sure that a variable you're going to use, especially to reset its value or append to it, will need to be (for certain) blank or empty up front. So, I know I'm going to need an output to return back to the user; which will house the Morse code; so ...
output="";

The next part is the most complicated part of writing this. You need a "for loop" to loop through each character and, more or less, replace the English letters (and characters) with the corresponding Morse code ones. Let's get to it:

for(count=0;count<input.length;count++){
daChar=input.charAt(count);
for(i=0;i<letters.length;i++){
if(daChar==letters.charAt(i)){
output+=morse[i]+" ";
break;
}
}
}

Now the "output" variable contains the corresponding Morse code of whatever was typed in the prompt. You could have it all pop up in an alert, but a much clearer and more presentable way is to pop up a window and then write to that window (and I want to show how we do that for the purposes of this bookmarklet tutorial).

w=window.open('','Links','scrollbars,resizable,width=450,height=250,top=0,left=0');
w.document.write(output);


The first line is your basic run of the mill window open command. A quick Google search should show you thousands of examples on that one. Since we popped up the window in a variable we assigned (the variable was "w" but you could call it myWin or whatever), we now can write to it. What are we writing to it? Well, how about posting the resulting Morse code in there? And that variable, if you recall, was "output" and that should explain the whole process.

We're not quite done yet, however!

In order for you to make all of this a bookmarklet, you must have it all on one line. No line breaks. No returns. Nothing. Although JavaScript interpreters are forgiving, that's where it's absolutely essential to be syntactically correct and including the semicolon at the end of each line. When you concatenate all the lines of code together, the javascript: runs your one-line JavaScript bookmarklet through the interpreter for that page (and only that page). In the case of the Morse code bookmarklet, we're not really doing anything to the page itself. We're simply using the URL to pop up a question, collect the answer, swap out the characters with corresponding Morse code and spitting the result out to a pop up window.

In addition to having all of your JavaScript code onto one line, which will condense it and make smaller (and therefore faster to run), you also need to encapsulate it with either a void to catch all return values, or ... a neater trick ... an empty function. I could spend hours describing that, but that would be outside the scope of bookmarklets. But to help drive the point home, take the code in this article and put it in the "editor" at http://subsimple.com/bookmarklets/jsbuilder.htm

This bookmarklet "builder" already provides you with "javascript:" in the textarea, and with buttons to help you encapsulate (put the empty function name around) your bookmarklet.
When you first add it to the text area, you should have it all look something like this:

javascript:
(function(){
var myEngStr=prompt("Please type your English text here: ");
var letters="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 .,:?'-/()"";
var morse=new Array(".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..", "--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-", "-.--","--..","-----",".----","..---","...--","....-",".....","-....","--...","---..", "----."," ",".-.-.-","--..--","---...","..--..", ".----.","-....-","-..-.","-.--.-","-.--.-",".-..-.");
var input=myEngStr.toUpperCase();
output="";
for(count=0;count<input.length;count++){
daChar=input.charAt(count);
for(i=0;i<letters.length;i++){
if(daChar==letters.charAt(i)){
output+=morse[i]+" ";
break;
}
}
}
w=window.open('','Links','scrollbars,resizable,width=450,height=250,top=0,left=0');
w.document.write(output);
}
)()


Name it first (on top) and then simply select the "Compress" button and voilĂ . Test it by clicking on the name that the tool made into a link for you to make sure it works. If all's good, just drag that linked name to your browser's toolbar (see last week's article on how to organize your bookmarklets) and you're set.

Since there may be issues with scrolling on this article (characters going behind other elements on the page), I wanted to make sure to post the bookmarklet here. Feel free to add the English to Morse Code bookmarklet to your collection.

Now, the questions is, can you make a "Morse Code to English" bookmarklet (the reverse)?
Perhaps you want to expand this to include numbers, punctuation or even non-American letters. Well, if you do, please share your code back with me.

You could make bookmarklets do all kinds of things. There are bookmarklets that will turn the background of a page to black to make it easier to read after its designer did a horrible job with font & background color combos. Other bookmarklets allow you to manipulate the URL so that you could do more. And many many more. Next week, I hope to list a few of my favorite bookmarklets for your use.

Next week's post, and part 4 of the bookmarklet series, can be found here.  ▣

Click here to see other Tuesday posts (including the current one).


No comments: