wirm-logo2.gif (1722 bytes)

[Home Page]   [Overview]    [ Manual]   

WIRM Tutorial


In this tutorial, you will create a web-based application called the Lab Lending Library.  The application will maintain a repository of information about the books in your office, keeping track of who's borrowed what.  We will define two new classes for the application: Book and Loan.  In addition, the application will make use of WIRM's built-in User class for managing library users, and the File class for maintaining an image repository of book covers.  This tutorial will walk you through the steps of constructing the repository from scratch.  To get an advance look at the final application, explore the Lab Library Demo.

This tutorial assumes that you have successfully downloaded and installed WIRM on your system.  Your web server, database server, and Perl modules should all be installed and running.  You should have already created a working repository for your web application.  Please refer to the Installation Manual for instructions.


Part I: Exploring the WIRM Console

[Return to Top]

Access the WIRM Console

Use a web browser to view the WIRM console on your host machine (e.g. http://yourhost/cgi-bin/wrm/console.pl).   From this console, you will be able to register new users, log in, define classes, populate the repository, and issue queries.  While some of the links are simple hyperlinks, most of them are system-defined wirmlet activators. 

Note that the WIRM Console is distinct from the Main Menu for your application.  Click on [Main Menu] in the banner.  This takes you to a placeholder for your application's custom main menu, from which your domain-specific wirmlets can be accessed (such as borrowing a book).  Currently, there are no custom-built wirmlets, so return to the generic [WIRM Console].

Use the Schema Viewer

Click on [Schema Viewer] under Repository Browsing.   Notice the three built-in classes (File, User, and Annotation). Later, when you create your own user-defined classes (e.g. Book), your schemas will also appear here.

Create some Users

Go back to [WIRM Console].  Click on [Register New User] under User Services.  Enter the new user information (be sure to remember the password), then press <Submit>.  Congratulations, you have just created your first repository object (which happens to be an instance of the User class).

Proceed to log in.  Notice that the current user appears in the banner.   The hyperlinked username is the "label view" of the User class.   Click on the username.  You are now seeing the "page view" of the User class for that particular instance.  Notice the URL in your browser's address window:


By clicking on the User's hyperlink, you executed the "repo-view.pl"   wirmlet, which has the sole job of visualizing repository objects when you click on them.  Notice the parameter cx_subject on the URL following the '?'.   This is the Object ID of the subject to be viewed, in this case the User object that you just created.

Now go back to [WIRM Console] and create two more users for demo purposes.  Give them usernames of sally and joe.

Use the Explorer

Now that you have some instances (User objects), use the Explorer to issue some queries.   From the [WIRM Console], click on [Explorer].  The Explorer wirmlet lets you retrieve repository instances by type.  Select "User" as the class to explore, leave the Query Filter blank, and press <Submit>.   The Explorer submits a query to the repository and retrieves all User objects.  They are returned as a Query Result, which is then visualized as an HTML table using the repo_table command. 

The header for the table was created by the User's view_row_header method, and the rows are generated by calling the User_view_row method on each object in the Query Result.  Try drilling down into a particular User's page view by clicking on the username.  This takes you out of the Explorer wirmlet and back to the repo-view wirmlet.

Return to the Explorer.  This time, try using a Query Filter, such as "login > 'foo'".  The query filter will be translated into an SQL query, which will constrain the results.  It can be any boolean expression over the attributes of the target class (using the syntax of the WHERE clause of an SQL query).

Retrieving by OID

From the WIRM Console, click on [Retrieve by OID].  This takes you directly to the repo-view wirmlet that you experienced when you clicked on a username.  It looks different now because there is no subject to be viewed this time. Notice that the cx_subject parameter in the URL is empty.   When the repo-view wirmlet is invoked without a subject, it prompts for one.  Go ahead and enter the OID of a User (e.g. 1).  Pressing <Submit> reinvokes the wirmlet, this time with the subject context assigned, so the repo is visualized.

Register a File

You will now learn how to upload a file from your client into the repository.  The repository can handle any file, but for the purposes of this demo, you can use the following scanned image of a book cover:


Right-click on the link and save the image to your local hard disk, for future uploading.

Now, from the WIRM Console, click on [Register a File] under Repository Updates.  Choose "Upload a File" and press <Continue>.

Use the <Browse> button to locate the image camel.tiff on your hard drive.   When you have selected the image, the filename should appear in the box to the left of the browse button.  Enter the following label for the file: "camel cover".  You may also enter a text description.   Then press <Register File>.  It may take 30-60 seconds to upload the file.

You should see a message confirming that the file was imported into the File Storage Area, followed by the File_page_view for the new file.  Notice the file metadata, which includes the submit date, submittor, source, and the Locator (the full path name of the file location in the FSA).  Note that the File was assigned a unique filename within the default partition of your repository's FSA.  Finally, the contents of the file should be displayed.  Note that the displayed image of the cover is a copy of the uploaded tiff file, converted to a web-viewable jpeg type.   The jpeg copy resides in your repository's Vizualization Cache, which you can verify by using your browser's "view source" feature to inspect the underlying HTML.

Add an Annotation

If you are logged in and viewing the page view of a File you just registered, you should see two icons at the bottom of the screen:


By default, these icons are only present if a user is logged in.  Click on add comment.  Every repository object may be assigned comments.  Enter some text and press <Submit Annotation>.  Notice the submit date and author of the comment are automatically recorded.  Now click on the label of the object being commented on, to return to the page view for that object.

There should now appear a third icon at the bottom of the screen:

This only appears if the object has comment(s).  Click on it to verify that your comment is present.

Edit an Object

Now go back to the page view of the File (you can use your browser's BACK button), and click on the edit object button.  Alternatively, you can choose [Edit Object] from the [WIRM Console] and enter the OID of any object to edit.

You are taken to the Repo-Edit wirmlet, which allows you to update any field of the File object.  Try changing the label and press Submit Updates.  Notice that your changes have been recorded.



Here are three more book covers.  Download them to your machine and then register them all in the repository:



Part II: Defining Schemas

[Return to Top]

Define the Book schema

From the WIRM Console, click on [Define New Schema] under Class Definition Tools.
Enter a Schema Name of Book, with the following attributes:

Name Type Length
title char 200
author_last_name char 50
author_first_name char 50
owner User --
cover File --
status char 20

Note the convention that class names are capitalized ("Book") and attribute names are lower-case.
Press <Submit>. 

Create some Book instances

Return to the WIRM Console.  Click on [Create Object] under Repository Updates.  Select Book as the class of object to create, and press <Submit>.

You are shown the Default Make Prompt for the Book class.  Later, we will update the Book class to define a custom Make Prompt, but for now, use the Default Make Prompt to create a Book.  Enter the following attributes:

title author_last_name author_first_name owner cover status
Programming Perl Wall Larry sally camel cover IN LAB

Press <Submit Attributes>.

A new Book is created, and you are shown the Default Page View for that book.   Note that the owner and cover attributes are hyperlinks to the Page Views of the respective objects.  By default, any non-atomic attribute is visualized using the Label View of the referenced object (in this case the User Label View and the File Label View).  This allows immediate hierarchical navigation through a network of objects.

Make some more books

Use the Create Object wirmlet to make the following books:

title author_last_name author_first_name owner cover status
Learning Perl Schwartz Randal sally llama cover IN LAB
Enders Game Card Orson Scott joe ender cover IN LAB

Now go back to the [Explorer].  Notice that the Book class now appears as a candidate for exploring.  Select it, and press <Submit>.  The result is a table of all Book objects, using the Default Row View, which simply lists all the attributes.  Notice the first column: a hyperlink to the Page View for that class, using a text label composed of the class name and OID.  This is the Default Label View.  Together, these default views provide an instant hierarchical navigation interface.  Later, we will override this default with a custom Label View that uses the title instead of the OID.

Define the Loan Schema

Now define a Schema named Loan with the following attributes:

Name Type Length
borrower User --
book Book --
date_borrowed date --
date_returned date --
status char 20

The "status" attribute will be either ACTIVE or CLOSED, depending on whether or not a book has been returned.

Make a Loan

Use the Create Object wirmlet to create a Loan record.  Enter the following attributes:

borrower book date_borrowed date_returned status
joe Book 1 01-Mar-1999 15-Mar-1999 CLOSED

Later, we'll build a Borrow-Book wirmlet, which will provide a higher level interface for end users to borrow books.  In addition, we'll build a Return-Book wirmlet, which will allow end users to update the status of existing Loans.



Use [Evolve Schema] under Class Definition Tools to add an attribute named date_published to the Book object.  You may need the WIRM admin password for this operation (default is "secret").  Now update some of the books to give them a meaningful published date.

Part III: Customizing Classes

[Return to Top]

In the previous section, we created the schemas for two new classes, Book and Loan.   The default Views for these classes were useful, but now we will define custom methods for these classes, giving us control over the appearance and design of the user interface.  First, we will customize the View methods, providing a context-sensitive interface for navigating through the data.  Then we will customize the Make and Update methods, which will provide higher level interfaces for registering new books, taking out loans, and returning books.

Edit the Class Definition file

All of these customizations will be implemented as perl functions appended to the Class Definition file, which can be found in your repository lib directory,  (e.g. /usr/local/lib/wrm/repos/your-repo-name/class-defs.pl).   Locate this file and bring it into your editor now.  You should have your editor open simultaneously with your web browser.

Define the Book View methods

Use your web browser to visit the [Explorer], and look at the instances of the Book class.  Notice the first column: a hyperlink whose label consists of the class name followed by the instance's Object ID, eg. "Book 22".  That column is the output of the default view_label method.   Although it uniquely identifies the book, it is not very user-friendly, so we will override it with a new function called Book_view_label  that displays the book's title.  Paste the following function into the file class-defs.pl:

sub Book_view_label {
    my($r) = @_;

    $r = OBJ($r);
    return HT_href($r->{title}, repo_vlink($r));

Book_view_label first uses the OBJ function to "swizzle" the parameter $r, assuring that it is a reference to a repository object.  This allows the function to accept both references and OID's as the parameter.  For flexibility, all class methods should do this.

The call to HT_href generates an HTML hyperlink, whose label is specified as the contents of the title attribute, $r->{title}, and whose URL is specified by a call to the repo_vlink facility.  As expected, repo_vlink creates a URL to the Repo-View wirmlet, using the current object ($r) as the subject.  The end result is a hyperlink that takes the user to the full View_Page view of the object.

Save your changes to class-defs.pl, and then use the [Explorer] to look at the Book objects again (you may simply press your browser's reload button if it is still on the table of Book objects).  Notice that your changes have been immediately incorporated (the first column now shows a hyperlink labeled by the book's title).

The rows in the table of books are being generated by the default view_row method, which now exhibits some redundant information (the title is repeated).  We will now define customized Book_view_row and Book_view_row_header functions to eliminate the title column.  Furthermore, we will leave out the published and owner attributes from the row view, since this detailed information is more appropriate for the page view.  Paste the following functions into class-defs.pl:

sub Book_view_row_header {

    return ["Book",  "Author", "Cover"];

sub Book_view_row {
    my($r) = @_;

    $r = OBJ($r);

    return [
            "$r->{author_first_name} $r->{author_last_name}",

The first function takes no parameters and returns a reference to an array of strings, which will be used as column headers.  Notice that we can choose whatever labels we like for the headers in our custom version, and they don't have to correspond to the names of attributes in the schema.  The second function takes a Book as a parameter, and returns a reference to a list of strings which will be used as row values in the construction of a table of Books.  Note that the first column make use of the Book_view_label method, so if we ever change the label view it will automatically be updated in the row view.  The second item emits the Author field by concatenating the first_name and last_name attributes.  The third item emits an iconic version of the cover.

Save your changes and reload the table of Books in the [Explorer] to view the new improved row view. 

A note about debugging:  if you introduce any bugs that cause a compilation error, the error message should appear in the browser.  Try omitting the semi-colon after $r = OBJ($r) and reload the browser.  Notice the syntax error message.   Sometimes the error doesn't pipe to the browser, in which case you can view the web server's error log to see what happened.

Now let's define a custom page_view for the Book class.  Add the following function:

sub Book_view_page {
    my($r) = @_;
    my($tt, $out);

    $r = OBJ($r);
    $out = h3("Title: $r->{title}");

    $tt->{HEADER} = ["Author", "Owner", "Status", "OID"];
    $tt->{ROWS} = [ "$r->{author_first_name} $r->{author_last_name}",
    $out .= HT_table($tt);
    $out .= p() . b("Cover: ") . br();
    $out .= File_view_contents($r->{cover});
    $out .= p() . Annotation_view_icon($r);
    if ($CONTEXT{user}) {
        $out .= Annotation_view_icon_make($r);
        $out .= HT_space() . repo_edit_icon($r);

    return $out;

The function returns an HTML string which is used by the repo-view wirmlet to render a Book as a web page.  The HTML string is composed in pieces and stored in the variable $out, which is returned at the end of the function.

The function first emits the Book title using <H3> font.  Then an HTML table is generated, using a table template.  We assign a list of strings for the column headers, and a row of values for the body of the table.  Then we display the cover using the File_view_contents function.  The Annotation_view_icon function emits the read comments icon if there are any comments.   Finally, we display the make comment and edit object icons if the user context is defined (i.e. if the user has logged in).   Note: this is our first example of how to create a context-sensitive view.

Try the function by clicking on the label of a book in the [Explorer].



Define a Loan_view_label method that identifies a loan via book title and date borrowed. 

Define the Make Loan method

Now we'll define a more user-friendly interface for checking out books.  First let's take another look at the default interface for creating a Loan object.  Use the [Create Object] facility to create a Loan.  Notice that the user is prompted for a status, date_borrowed, borrower, etc.   It would be nicer if the Status was automatically set to "ACTIVE", the date_borrowed was automatically set to today's date, and the borrower was set to the current User.  The only prompt should be which book to borrow.  Furthermore, we'd like the act of making a loan have the side-effect of updating the status attribute of the book being borrowed.

Add the following method to class-defs.pl:

sub Loan_make {
    if (!$CONTEXT{user}) {
        return "You must first ", HT_href("log in", repo_login_link());

    if (!param("Submit Attributes")) {
        $out = Loan_make_prompt();
    } else {
        $out = Loan_make_finish();

    return $out;

The presence of this function will override the default make function when the Object Maker wirmlet is activated on a Loan object.  The first line requires that the user be logged in to make a loan.  The function then checks the Form-State parameter "Submit Attributes".  If doesn't exist, the user is prompted via the Loan_make_prompt function, defined below.  Otherwise, the user has already pressed Submit, so the Loan_make_finish function is executed.

sub Loan_make_prompt {
    my($out, $avail_books);

    $out = h3("Borrowing a Book");
    $out .= em("Which Book are you borrowing?") . HT_space();

    $avail_books = repo_query("Book", "status = 'IN LAB'"); 
    $out .=  repo_choice($avail_books, "book_choice") . p();
    $out .= submit("Borrow This Book");

    return $out;

The prompt is intelligent: it only shows books that are in the lab.  The repo_query function makes a query on the Book class, retrieving all instances whose status is "IN LAB", and storing them in a result variable named $avail_books..   The repo_choice function creates a popup menu based on the available books.  The labels of the choices will be generated by applying the Book_view_label method on each item in $avail_booiks.  When the user selects a book and presses the submit button, the selected object's OID is returned as a form parameter named book_choice.

When the user presses the submit button, the Make Object wirmlet is re-activated, and so Loan_make is executed again.  This time, the form parameter "Borrow This Book" is defined, so Loan_make calls Loan_make_finish:

sub Loan_make_finish {
    my($out) = @_;
    my($book, $book_id, $loan, $loan_id);

    $book_id = param("book_choice");

    $loan->{borrower} = $CONTEXT{user};
    $loan->{book} = $book_id;
    $loan->{date_borrowed} = db_date_now();
    $loan->{status} = "ACTIVE";
    $loan_id = repo_new("Loan", $loan);

    # update book status
    $book = repo_get($book_id);
    $book->{status} = "ON LOAN";

    $out  .= "The following loan has been recorded: <P>";
    $out .= repo_view($loan_id);

    return $out;

First, the chosen book is retrieved into a variable called $book_id. Next, we fill out a Loan template.  The borrower is set to the current User.   The book attribute is set to the chosen book.  date_borrowed is set to the current date, and the loan status is set to "ACTIVE".   Then the repo_new function is called to create a new Loan object using the template.  The OID of the new object is stored in the variable $loan_id.

Next, Loan_make_finish updates the status of the book being borrowed.   First we retrieve the book using the repo_get function.  Then we set the status attribute to "ON LOAN".  Finally, we call repo_update, which commits our changes to the database.

Copy the above functions into class-defs.pl, and then use the [Create Object] wirmlet to create a new Loan.  Borrow the Perl Programming book.  When you submit the request, the new Loan object should be displayed.  Click on the book label to verify that the status of that book is now ON LOAN.

Use the Explorer to view all Books. Click on a book title to see the Page view. In the page view, click on the Owner to see the Owner page. Go back to the Book Page (using your browser's back button), and click on the image of the cover, which takes you to the File Page for that image.


For books which are on loan, have the Book_view_page method specify which user is borrowing that book.  Hint: you'll need to make a repo_query on the Loan class.  Also, if you did the exercise in Part I that added a date_published attribute, then update the Book_view_page method to include the new attribute.

Part IV: Designing Custom Wirmlets

[Return to Top]

Whereas our previous customizations were all made to the class-defs file, defining a custom wirmlet involves creating a new perl script that resides in the repository-specific cgi-bin directory (e.g. /usr/lib/cgi-bin/wrm/repos/your-repo-name).   Go to that directory now and see what's there.  There should be two files: main-menu.pl and generic-wirmlet.pl.

Customize the Main Menu

The Main Menu is intended to be a domain-specific starting point for using your information system, in contrast to the [WIRM Console], which provides access to general purpose repository functions.  The wirmlet main-menu.pl is executed whenver the user clicks on the [Main Menu] link in the banner.  By custom-tailoring the [Main Menu], you can create a friendly interface that doesn't requiring your end-users to work with the [WIRM Console].  For example, your Main Menu can have a link called [Borrow Book] which activates the Object Maker wirmlet on the Loan class directly, rather than requiring the user to choose [Create Object] from the WIRM Console and then select the Loan class as a separate step.

Click on [Main Menu] (in the banner) now.  You'll see a generic title ("Main Menu") and a couple of place-holder links.  Now bring the file main-menu.pl into your editor.  Update the line where the $title is assigned, to something more descriptive:

$title = "Lab Library Front Desk";

Now change the list of items to the following:

$items = [
          "View Books", repo_explorer_link("Book"),
          "View Loans", repo_explorer_link("Loan"),
          "Register a New Book", repo_mlink("Book"),
          "Upload a Cover", upload-cover.pl,
          "Borrow a Book", repo_mlink("Loan"),
          "Return a Book", "return-book.pl",

For viewing books or loans, we use the repo_explorer_link function, which generates a URL to the Explorer wirmlet, using the given class type.  For registering a new book or borrowing a book, we use the repo_mlink function, which generates a URL to the Object Maker wirmlet.  The other two choices ("Upload a Cover" and "Return a Book") point to custom wirmlets which we will define later.

Save main-menu.pl and then reload the Main Menu in your browser.   Try clicking on the various options.  Borrow a book or two.



Make the Main Menu context-sensitive, so it shows a different set of options for users who aren't logged in.  Guest users should only see the View Books and View Loans options, plus an option to Become a Library Member.

Understand the Generic Wirmlet

generic-wirmplet.pl is a template for creating your own wirmlets.  Bring the file into your editor.  Notice the general structure of a wirmlet:

  1. Initialize the HTML form, and display banner and title.
  2. If no submit button has been pressed, display a prompt.
  3. Otherwise, process the results.

Some wirmlets have more a complex structure, such a multiple interactive processing stages, but they all follow this general format.

Make the Book Return wirmlet

Some operations don't fit well in the framework of the built-in wirmlets.  For example, returning a borrowed book. We are now going to build our own wirmlet for returning a book.  Create a copy of generic-wirmlet.pl and name it return-book.pl.  Bring the new file into your editor.  Change the title to be more descriptive than "Generic Wirmlet":

$title = "Return A Book";

Now edit the show_prompt function.  First, it should verify that the current User is logged in. Then, it should prompt for which Loan is being returned.

sub show_prompt {
    my($out, $loans);

    if (!$CONTEXT{user}) {
        $out .= "You must first " 
             . HT_href("log in", repo_login_link());
        return $out;

    $loans = repo_query("Loan", 
              "status = 'ACTIVE' and borrower = $CONTEXT{user}");

    if (!$loans) {
        $out .= "User " . User_view_label($CONTEXT{user})
             . " has no outstanding loans!";
    } else {
        $out .= em("Select loan being returned:") . HT_space();
	$out .= repo_choice($loans, "loan") . p();
        $out .= submit("Submit");

    return $out;

Notice that the prompt uses the repo_choice function to generate the popup-menu.  The prompt is intelligent, limiting the possible choices to only the user's outstanding loans.  This is done by filtering the repo_query function.  The prompt also handles the special case where repo_query returns no results.

Next, we'll edit the process_request function.  It should update the return date and status of the specified Loan, and update the status of the book.

sub process_request {
    my($out, $loan, $book);

    $loan = repo_get(param("loan"));
    $loan->{date_returned} = db_date_now();
    $loan->{status} = "CLOSED";

    $book = repo_get($loan->{book});
    $book->{status} = "IN LAB";

    print "The following Loan has been updated: <P>";
    print repo_view(param("loan"));

    return $out;
Tip: a common bug is to forget to swizzle an OID.  For example, in the above function, if we had assigned param("loan") to the $loan variable without applying repo_get, we would have encountered an error when trying to update the object's attributes, since it would be merely an OID (integer) instead of  a reference to a repository object. 

Now, save the file return-book.pl and make sure it is executable (you might have to run chmod 755 return-book.pl).  Now try the Return a Book option from the [Main Menu].  Notice how the prompt is limited to outstanding loans of the current user.

Make the Upload Cover Wirmlet

This wirmlet will have 3 phases: prompting for a book, prompting for a cover, and uploading the cover.  Copy generic-wirmlet.pl to a new file named  upload-cover.pl.

Edit the $title variable to be "Upload a Cover".

Edit the main body of the wirmet so that it tests for three possible stages:

if (!param("book")) {
    print prompt_for_book();
} elsif (!param("cover")) {
    print prompt_for_cover();
} else {
    print process_request();

Now create the function prompt_for_book, which generates a popup menu of books to associate with the cover:

sub prompt_for_book {
    my($out, $books);

    $books = repo_query("Book");
    $out .= em("Select book: ") . HT_space();
    $out .= repo_choice($books, "book") . p();
    $out .= submit("Continue");

    return $out;

Next, create the function prompt_for_cover, which uses the filefield function to create a file upload field. This will automatically include a "browse" button that allows the user to navigate their local file system for the desired file. When the submit button is pressed, the selected file will be uploaded over the internet to the web server, and a filehandle to the file data will be returned as the "cover" parameter:

sub prompt_for_cover {

    $out .= "Specify the cover file for ";
    $out .= repo_view_label(param("book")) . ": <BR>";
    $out .= filefield(-name=>"cover", -size=>40);
    $out .= p() . submit("Upload");
    $out .= hidden("book", param("book")); # carry book parameter

    return $out;

Notice that we are carrying the book value as a hidden parameter.  This allows the wirmlet to remember the book for the third phase, process_request.  In the third phase, we create a File Descriptor Template and pass it along with the file handle to repo_import_file_from_handle, which copies the file into the FSA and registers it. The resulting file OID is assigned to the cover attribute of the book object, and the book record is updated in the database:

sub process_request {
    my($out, $fh, $book, $fdt, $file_id);

    $book = repo_get(param("book"));
    $fh = param("cover");

    # create File Descriptor Template for importing cover image
    $fdt->{source} = "$fh";
    # translate backslashes to frontslashes
    $fdt->{source} =~ tr|\\|\/|; 
    $fdt->{label} = "cover of $book->{title}";
    $fdt->{context} = 
        "imported using cover-upload.pl for the Lending Library";

    # import the image, which creates a new File object
    $file_id = repo_import_file_from_handle($fh, $fdt);
    if (!$file_id) {
        $out .= "Couldn't import file. <P>";
    } else {
        $book->{cover} = $file_id;
        $out .= b("Cover uploaded into repository.");
        $out .= repo_view($book);

    return $out;

Now, save upload-cover.pl (and make it executable), and use the Main Menu to create a new Book, and then upload a cover.  After uploading the cover, click on the cover image to see the file metadata associated with that image.



Create a new wirmlet called register-book.pl that will provide a user-friendly interface for registering a new book.  The wirmlet should automatically assume the owner is the current user, rather than prompting for it, and assign the status to be "IN LAB". 

The process_request function should assemble a book template and then make a call to repo_new.   After creating a new book, the wirmlet should provide a link that allows the user to upload a cover for that book.  Hint: you can pass the book's OID directly to the Upload Cover wirmlet via the URL, e.g.:
HT_diamond("Upload Cover", "upload-cover.pl?book=$bookid)

When it's done, be sure to update the Main Menu to use your new wirmlet.


Copyright (c) Rex Jakobovits, all rights reserved.