Book HomeCGI Programming with PerlSearch this book

7.4. Bookmarklets

We'll end this chapter with a much less common use of JavaScript: bookmarklets. Bookmarklets are JavaScript URLs that have been saved as bookmarks. The basic concept behind bookmarklets has been around since JavaScript was first created, but it has been slowly growing in popularity since Steve Kangas first coined the term bookmarklet and created a web site devoted to them at http://www.bookmarklets.com/. Many people consider bookmarklets a novelty, but they have a much greater potential. Bookmarklets really shine when they are combined with custom CGI scripts, which is why they are of interest to us.

7.4.1. Bookmarklet Basics

First, let's see how bookmarklets work. Bookmarklets are much easier to show than to explain, so let's look at the world's most popular program, "Hello World," as a bookmarklet. The source for it is as follows:

javascript:alert("Hello world!")

If you were to type this into your browser as a location, it would display the alert shown in Figure 7-4.

Figure 7-4

Figure 7-4. Result from our "Hello World" bookmarklet

You can enter this directly into your browser because this simple program is also a valid URL. The javascript scheme tells browsers, which support it, that they should interpret the rest of the URL as JavaScript code in the context of the current web page and return the result as a new web page. You can also create hyperlinks that have this format. If you were to embed the following into an HTML web page, then you could click on the link to get the alert as well:

<A HREF='javascript:alert("Hello world!")'>Run Script</A>

However, neither of these examples are actually bookmarklets until you save the URL as a bookmark in your browser. Doing so is browser-specific, of course. Most browsers allow you to click on a hyperlink with your right mouse button and choose an option to save the link as a bookmark. Once you have done this, you have captured the script as a bookmarklet that you can run whenever you want by choosing it from your list of bookmarks.

Let's look at a more complicated example. We have referenced RFCs several times thus far. Let's make a bookmarklet that allows you to look up a particular RFC. In this case, we'll use http://www.faqs.org/rfc/ as the RFC repository.

Here is how we might write the JavaScript for this:

rfcNum = prompt( "RFC Number: ", "" );
if ( rfcNum == parseInt( rfcNum ) )
    open( "http://www.faqs.org/rfc/" + rfcNum + ".html" );
else if ( rfcNum )
    alert( "Invalid number." );

We ask the user for an RFC number. If the user enters an integer, we open a new browser window to fetch the corresponding RFC. Note that we don't handle the case in which the RFC doesn't exist; the user will simply get a 404 error from the www.faqs.org web server. However, if the user enters a value that isn't a number, we do report that error to them. If the user enters nothing or clicks Cancel, we do nothing.

Now let's convert to this to a bookmarklet. First, we must need to make sure we do not return any values from our code. If the code in your bookmarklet returns a value, some browsers (including Netscape's) will replace the current page with the value. You will confuse users if, for example, they get an empty page with a [null] in the top left corner every time they use your bookmarklet. The easiest way to avoid returning a value is to use the void function. It takes any value as an argument and returns nothing. We can wrap the void function around the last statement that returns a value, or simply append it to the end. We'll do the latter because in this script there are three different lines that could be executed last, depending on the user's entry. So we add the following line to the end of our script:

void( 0 );

Next, we should need to remove or encode any characters that are not valid within a URL. This includes whitespace and the following characters: <, >, #, %, ", {, }, |, \, ^, [, ], `.[11] However, Netscape Communicator 4.x will not recognize encoded syntax elements (such as brackets) within JavaScript URLs. So although it means that bookmarklets containing these characters are invalid URLs, if you want your bookmarklets to work with Netscape's browsers, you must leave these characters unencoded. Other browsers accepts these characters encoded or unencoded. In any event, you should remove any unnecessary whitespace.

[11]Control and non-ASCII characters are invalid as well, but these values must be escaped within JavaScript anyhow. Also, you may notice that this list is different than the list provided in Section 2.1.3, "URL Encoding". That list is for HTTP URLs, so it includes characters that have special significance to HTTP. JavaScript URLs are different than HTTP URLs, so this list includes only characters considered illegal for all URLs.

Finally, we prefix our code with javascript:, and we get the following:

javascript:rfcNum=prompt('RFC%20Number:','');if(rfcNum==parseInt(rfcNum))
open('http://www.faqs.org/rfc/'+rfcNum+'.html');else if(rfcNum)
alert('Invalid%20number.');void(0);

The line endings are not part of the URL but have been added to allow it to fit on the page.

There is one more thing that you should keep in mind when working with bookmarklets. Bookmarklets execute in the same scope as the frontmost page displayed in the user's browser. This has a number of advantages as we will see in the next section, Section 7.4.2, "Bookmarklets and CGI". The disadvantage is that you must be careful that the code you create does not conflict with other code that is on the current page. You should be especially careful with variable names and create names that are very unlikely to appear on other web sites. Variables are case-sensitive in JavaScript; using odd combinations of capitalization in variables is a good idea. In our last example, rFcNuM may have been a better (though less readable) choice as a variable name.

7.4.1.1. Compatibility

Because bookmarklets use JavaScript, they are not compatible with all web browsers. Some browsers that support JavaScript, such as Microsoft Internet Explorer 3.0 do not support bookmarklets. Other browsers impose limitations on bookmarklets. Unless you're distributing your bookmarklets as unsupported novelties, you should do extensive testing. Bookmarklets use JavaScript in a less than traditional manner, so test them with as many different versions of as many different browsers on as many different platforms as you can.

You should also keep your bookmarklets short. Some browsers do not impose a limit on the length of a URL; others limit URLs to 255 characters. This can even vary by platform: for example, Communicator 4.x allows only 255 characters on MacOS while it allows much longer URLs on Win32.

One of the features that some users of bookmarklets promote is that bookmarklets avoid some of JavaScript's browser incompatibility issues. Because Netscape and Microsoft have different implementations of JavaScript, if you want to create a bookmarklet that uses incompatible features of each, you can create two different bookmarklets instead of one bookmarklet that attempts to support both browsers. Then people can choose the bookmarklet that is appropriate to their browser. The problem with this approach is that Netscape and Microsoft are not the sole distributors of web browsers. Although these two companies create the majority of browsers on the web, there are other high-quality browsers that also support JavaScript and bookmarklets, such as Opera, and these browsers are growing in popularity. If you start supporting specific browsers, you may find yourself needing to choose which browsers to support and which users you are willing to loose. Hopefully, ECMAScript and DOM will quickly provide standards across all browsers.

7.4.2. Bookmarklets and CGI

So what do bookmarklets provide us as CGI developers? Bookmarklets can do anything that JavaScript can do including displaying dialog boxes, creating new browser windows, and generating new HTTP requests. Furthermore, because they execute in the context of the browser's frontmost window, they can interact with objects or information in this window without the security restrictions that an HTML window from your site would encounter. Thus, bookmarklets provide a very different or even transparent interface to our CGI scripts.

Let's look at an example. Say that you want to be able to create and store comments for web pages as you surf that you can retrieve when you visit the web pages later. We can do this with a simple bookmarklet and CGI script. First, let's create the CGI script.

Our CGI script needs to do two things. It needs to accept a URL and a comment and record them. It also needs to be able to retrieve a comment when given a particular URL. Example 7-6 provides the code.

Example 7-6. comments.cgi

#!/usr/bin/perl -wT

use strict;

use CGI;
use DB_File;
use Fcntl qw( :DEFAULT :flock );

my $DBM_FILE = "/usr/local/apache/data/bookmarklets/comments.dbm";

my $q       = new CGI;
my $url     = $q->param( "url" );
my $comment;

if ( defined $q->param( "save" ) ) {
    $comment = $q->param( "comment" ) || "";
    save_comment( $url, $comment );
}
else {
    $comment = get_comment( $url );
}

print $q->header( "text/html" ),
      $q->start_html( -title => $url, -bgcolor => "white" ),
      $q->start_form( { action => "/cgi/bookmarklets/comments.cgi" } ),
      $q->hidden( "url" ),
      $q->textarea( -name => "comment", -cols => 20, -rows => 8, -value => $comment ),
      $q->div( { -align => "right" },
          $q->submit( -name => "save", -value => "Save Comment" )
      ),
      $q->end_form,
      $q->end_html;


sub get_comment {
    my( $url ) = @_;
    my %dbm;
    local *DB;
    
    my $db = tie %dbm, "DB_File", $DBM_FILE, O_RDONLY | O_CREAT or
        die "Unable to read from $DBM_FILE: $!";
    my $fd = $db->fd;
    open DB, "+<&=$fd" or die "Cannot dup DB_File file descriptor: $!\n";
    flock DB, LOCK_SH;
    my $comment = $dbm{$url};
    undef $db;
    untie %dbm;
    close DB;
    return $comment;
}


sub save_comment {
    my( $url, $comment ) = @_;
    my %dbm;
    local *DB;
    
    my $db = tie %dbm, "DB_File", $DBM_FILE, O_RDWR | O_CREAT or
        die "Unable to write to $DBM_FILE: $!";
    my $fd = $db->fd;
    open DB, "+<&=$fd" or die "Cannot dup DB_File file descriptor: $!\n";
    flock DB, LOCK_EX;
    $dbm{$url} = $comment;
    undef $db;
    untie %dbm;
    close DB;
}

We use a disk-based hash called a DBM file in order to store comments and URLs. The tie function associates a Perl hash with the file; then anytime we read from or write to the hash, Perl automatically performs the corresponding action on the associated file. We will cover how to use DBM files in more detail in Chapter 10, "Data Persistence".

The JavaScript that we will use to call this CGI script is as follows:

url = document.location.href;
open( "http://localhost/cgi/bookmarklets/comments.cgi?url=" + escape( url ),
      url, "width=300,height=300,toolbar=no,menubar=no" );
void( 0 );

As a bookmarklet, it looks like this:

javascript:dOc_uRl=document.location.href;open('http://localhost/cgi/bookmarklets
comments.cgi?url='+escape(dOc_uRl),dOc_uRl,'width=300,height=300,toolbar=no,
menubar=no');void( 0 )

If you save this bookmarklet, visit a web site, and select the bookmarklet from your bookmarks, your browser should display another window. Enter a comment and save it. Then browse other pages and do the same if you wish. If you return to the first page and select the bookmarklet again, you should see your original comment for that page, as in Figure 7-5. Note that the comments window will not update itself each time you travel to another page. You will need to select the bookmarklet each time you want to read or save comment for a page you are on.

Figure 7-5

Figure 7-5. Updating a comment to comment.cgi via a bookmarklet

If you were to distribute this bookmarklet to friends, the comments would be shared and you could see what each other has to say about various web sites. The CGI script could also be placed in a secure directory and be extended to maintain separate databases for each user; you may want users to only be able to read other users' comments.

We would not have been able to build an application like this with a standard HTML page due to JavaScript's security restrictions. One HTML page cannot access objects in another HTML page if the two pages are from different domains (i.e., different web servers), so our comment form cannot determine the URL of any other browser windows. However, bookmarklets circumvent this restriction. Browsers allow this because the user must actively choose to run a bookmarklet in order for it to execute.

There are numerous other ways that you can put bookmarklets to use. You can see many examples of bookmarklets that use existing Internet resources at http://www.bookmarklets.com. Many of these are novelties, but bookmarklets can do more. Bookmarklets are most powerful when you have goods or services that can take advantage of accessing information on other sites as people surf. For example, companies such as the Better Business Bureau could offer bookmarklets that users can select when they are on another site to see how that site has been rated. Companies that sell add-on products or services like warranties can provide users with a bookmarklet that users can select when they are going to make a purchase online. Other possibilities are up to you to create.



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.