PDF In-Depth

JavaScript - Clean Up Your Popups

June 17, 2000

Advertisement
Advertisement
 

Keeping code easy to maintain requires a little foresight but can easily pay big dividends in the end. I was reminded of this recently by a consulting assignment that had me "cleaning up" the JavaScript in a somewhat elaborate PDF document that relied heavily on popup menus.

By now, I'm sure you're aware that in version 4.0 (and 4.05) of Acrobat, there lurks an undocumented JavaScript method called popUpMenu(), which is a method of the App class that lets you attach custom popup menus to mouse-down events in form fields. To use this method, supply an argument list consisting either of the strings you want to use for menu choices, or an array of strings. Each array will be treated as a nested item. For example:

var reply = app.popUpMenu(
   "Jody Foster",
   "Julia Roberts",
   "-",
   ["Arquette","Patricia","Rosanna"],
   ["Olsen","May-Kate","Ashley"],
   "-",
   "Demi Moore"
   );

I suggest, by the way, that you get used to formatting your popup code this way (with line breaks separating the choices) so that you can more easily read and replace items later.

It's important to note that this code should be attached to the mouse-down (not the mouse-up) event for whatever field uses it, because when it executes, it will make a menu appear until the next mouse-up. If the code is attached to a mouse-up handler, the menu will just flash by and the user won't have a chance to select anything. Here is what the popup created by the above code looks like at runtime:

Note that wherever a hyphen has been used in the code, a separator occurs in the popup menu. Likewise, wherever an array was used, a nested submenu occurs.

Return Values

The return value for app.popUpMenu() is the string for the item chosen by the user. If the user lets go of the mouse without selecting anything, the return value is null. It's important to check for this before executing any code that depends on a non-null value. Do something like this:

if (reply != null) {
//  ...do something...
  }
else {
//  ...do something else
}

Cleaner Popup Code

One problem you can quickly get into with popup code is that you find yourself writing a lot of specialized, one-off, inline code to handle possible user selections. Here's a quick example. Suppose you have a long document with lots of section headings but you don't want to waste screen space by making the user keep a Bookmarks frame open, so you've decided to provide a jump menu on page one in the form of a popup:

var choices = ["Go to:",
    "Introduction", ?
    "Business Model",
    "Key Personnel",
    "Projections",
    "For Further Info" ];

var reply = app.popUpMenu(choices);

// now process user's selection:
if (reply != null) {

    if (reply == "Introduction")
        this.pageNum = 3;

    if (reply == "Business Model")
        this.pageNum = 6;

    if (reply == "Key Personnel")
        this.pageNum = 19;

    // etc.
    }

The idea is to let the user select a section heading, then take him there by setting pageNum to the proper page number. But it takes a lot of repetitive code to do it. Most programmers would try to rewrite the selection handling code as a 'switch' or 'case' statement block, but that doesn't really solve the problem. It's still a lot of code.

Associative Arrays

A cleaner way to write this kind of code is using an associative array: an array that associates "Introduction" with 3, "Business Model" with 6, etc. In JavaScript, you can do this by creating a custom object and attaching properties to it as follows:

var myArray = new Object();

myArray["Introduction"] = 3;
myArray["Business Model"] = 6;

... etc.

The square brackets (array-index notation) let you create new properties based on string values. Usually, of course, you use dot notation to specify object properties. Brackets are interchangeable with dot notation. The advantage of brackets is that they allow the use of strings containing spaces.

The above code will work, but how do you go from this to constructing the argument list for popUpMenu()? The answer is, you can use JavaScript's for-in loop, which enumerates all the properties of an object:

var choices = new Array();

for (var i in myArray) // enumerate properties
    choices.push(i);    // add them to choices

// now choices == ["Introduction","Business Model", ...]

var reply = app.popUpMenu(choices);

When you check the return value of popUpMenu(), you can process it by just finding the associated lookup value. In other words, we can now 'case out' the user's selection by doing:

if (reply != null)
    this.pageNum = myArray[reply]; // done!

This replaces tons of "if" statements, collapsing a huge block of code to one expression.

So far, so good. But it's possible to do even better.

Code Organization

For purposes of easier code maintenance, it's important to separate code from data. Hence, our associative array should really be in its own (top-level) area, outside the function that accesses it, so that it can be maintained or updated separately from the code that uses it. Likewise, the action code should be in its own top-level function. The mouse-down handler should just refer to this function.

Let's declare our associative array as follows, putting it in a top-level area called top_level_stuff (using Tools:Forms:Document JavaScripts... in Acrobat):

myArray = {
    "Go to:" : null,
    "Introduction" : 3,
    "Business Model" : 6,
    "Key Personnel" : 19,
    "Projections" : 25,
    "For Further Info" : 40
    };

This notation, using curly braces to set off a list of choice/value pairs separated by colons, is called object-literal notation. It makes "myArray" an object with properties of "Go to", "Introduction", etc., and with those properties initialized to the values shown on the right side of each colon.

Notice very carefully that I have NOT used the 'var' declarator in front of myArray! This was deliberate. Leaving the 'var' keyword off makes myArray a global, but it is not parented to the Global object. Rather, it will be parented to the Doc object ('this'). That means the scope of myArray will be document-wide but not app-wide. Any routine anywhere in the document can access myArray. You can access it either by writing this.myArray or just myArray.

Also, remember that we're putting myArray in a top-level area (using Tools:Forms:Document JavaScripts), but not inside a function. Just enter the declaration "out in the open," by itself. Whenever the PDF document opens, the variable will be available, immediately, to any routine that needs it, document-wide.

Also at the top level, let's declare a function called Do_Jump() that looks like this:

function Do_Jump() {

    var choices = new Array();

    for (var i in myArray) // enumerate properties
         choices.push(i);  // build a list

    var reply = app.popUpMenu(choices);

    if (reply) this.pageNum = myArray[reply];

} // end function

Now in the JavaScript action for our jump button, where we used to have all our code, we have just one statement: Do_Jump().

Was It Worth It?

Separating code from data greatly enhances the readability and maintainability of this kind of code. And putting the action code in its own top-level function encourages code reuse. This may not be obvious to you from the above example. But in a recent consulting assignment, my client had a jump button (using the same huge tangle of inline code) at the bottom of EVERY page in the document! Replacing 25 lines of code with one Do_Jump() statement, 100 times for a 100-page document, saved this client a lot of space. Plus, I showed him how to clone the jump button using template spawning, for even better storage efficiency (and ease of document creation).

In the future, as this client's document grows, he will be able to add or change jump headings and jump targets very easily, just by changing the values in one easy-to-read object-literal declaration.

Is code reorganization worth the trouble? If you write enough JavaScript, you'll learn the only possible answer.

PDF In-Depth Free Product Trials Ubiquitous PDF

Debenu Quick PDF Library

Get products to market faster with this amazing PDF developer SDK. Over 900 functions and an equally...

Download free demo

Back to the past, 15 years ago! Open Publish 2002

Looking back to 2002, it's amazing how much of the prediction became a reality. Take a read and see what you think!

September 14, 2017
Platinum Sponsor





Search Planet PDF
more searching options...
Planet PDF Newsletter
Most Popular Articles
Featured Product

Debenu PDF Aerialist

The ultimate plug-in for Adobe Acrobat. Advanced splitting, merging, stamping, bookmarking, and link control. Take Acrobat to the next level.

Features

Adding a PDF Stamp Comment

OK, so you want to stamp your document. Maybe you need to give reviewers some advice about the document's status or sensitivity. This tip from author Ted Padova demonstrates how to add stamps with the Stamp Tool along with related comments.