PDF In-Depth

Custom Objects in PDF JavaScript

August 23, 2000

Advertisement
Advertisement
 

Sooner or later, it's bound to dawn on you (if it hasn't already) that JavaScript really is object-oriented and can make your programming chores a lot easier, if only you'll exploit its latent power. For example, you can extend any of JavaScript's built-in objects (Date, Array, String, RegExp, etc.) with methods and properties of your own design. Also, you can define custom objects of your own, with associated constructors.

For example, suppose you want to invent a custom Rectangle object. The constructor might look like this:


function Rectangle(w,h) {

  this.width = w; 
  this.height = h; 
  this.area = theArea; // see below

} // end constructor function theArea() { return this.width * this.height; } // end method

This constructor has two properties and one method. Note the special use of the keyword 'this' inside the constructor and its member function, theArea(). In a constructor context, 'this' refers to the object being created. It does not refer to the PDF Doc object. (If you need to call Doc object methods or properties, such as getField or pageNum, from inside a constructor, be sure NOT to use 'this' on the front of such calls.)

The use of our custom Rectangle constructor is straightforward:

var myRect = new Rectangle(7,10); // create new Rectangle object

var a = myRect.area(); // 'a' == 70

myRect.height = 8;  // change the height

a = myRect.area(); // now 'a' == 56

In this case, we've created a new Rectangle object and stored the reference in myRect. The Rectangle begins life with dimensions of 7 by 10, but we later change the height property. The area() method always gives us the correct area, because it calculates the area as we need it, based on the most current dimensions at the time of the call.

A less-contrived, more practical example is probably in order. Consider the common situation (for advanced PDF JavaScripters, at least) of using a popup menu to present the form user with a list of choices. Often, the popup menu is superior to a Combo Box simply because it is more immediate and offers clearer feedback to the user. In Acrobat 4.0x, Combo Box selections do not take effect until the user clicks outside of (or tabs through) the Combo Box; the Combo Box has to lose focus before the user selection is effective. A popup menu, on the other hand, returns its result immediately, and the JavaScript programmer can take action on it immediately. Most users find popups to be very intuitive (which is why they are used in so many GUIs in commercial software products).

If you're not familiar with the popUpMenu() method of the App object, you can read about it in some detail in my previous column. (Note that the popUpMenu() method is not documented by Adobe. It's officially not part of the Acrobat JavaScript API, although that may change with the next major release of Acrobat.)

In GUI terms, the major downfall of the popUpMenu() method (at least in my opinion) is that it does not give the user adequate visual feedback as to which selection (which choice in the list of menu choices) is currently active. Usually, in any kind of list of control options, one option is shown as active (with a check-mark or bullet beside it). This is not true for the popUpMenu. So why not fix it?

The popUpMenu() method takes, as an argument, either a list of strings (representing choices) or an array (indicating choices under a heading, with the zeroth item of the array forming the heading). Since the list of choices is under our control, it should be a straightforward matter to place an "indicator" glyph (a bullet, say) beside the currently chosen item. Each time the user changes his or her selection, of course, we need to relocate the "indicator" to the proper item. Thus, we need to manage the indicator, with (probably) some kind of management routine. But we want to protect the user (and ourselves, if possible!) from the details of that management process. If you're thinking this sounds like a job for a JavaScript object, you're right!

Objects Have Behavior

Ideally, we should think in terms of the popup menu being a self-managing object. Once it's created, it should be an autonomous entity that encapsulates all the behavior we expect of a popup menu. One of the usual behaviors of a popup menu is that making a selection from it causes something to happen: a field updates, a calculation takes place, or whatever. Ideally, we'd like to incorporate this behavior as part of the object. But every instantiation of a popup menu is different: you typically apply different actions as the result of selections made in different popup menus. Therefore, the "action" part of the popup can't be hard-wired into the popup object. It has to be determined at instantiation time. To achieve this late binding, we need to make it possible for our constructor to accept a pointer to an action function: a callback routine.

In the interest of generality, we might as well make it possible for the programmer to be able to specify (in an argument to the constructor) what kind of "indicator" glyph the popup should display next to the currently chosen selection. This way, a programmer who might like bullets can use bullets, where someone who prefers asterisks can use asterisks, etc. In fact, for full generality we shouldn't limit our glyph to just a glyph. It should be any arbitrary string.

It would also be nice if our constructor could accept (optionally) an argument specifying which one of the list of choices should initially be displayed as "chosen" (the default selection).

In sum: We need a constructor that can take from zero to four arguments. The allowable arguments, in sequence, would be:

  1. An array containing the list of menu choices.

  2. An action callback (function reference). This is a user-defined function that will take, as its single argument, the return value from app.popUpMenu().

  3. The glyph or string of glyphs to be displayed next to the currently selected item.

  4. A numeric value indicating the zero-based array offset of the initially selected (default) menu item, if any.

We want to be able to use our constructor in the following sort of way:

var choiceList = ['Choose a City:', 
'Atlanta','Boston','Chicago','Los Angeles', 
'Miami','New Orleans','San Jose'];

function Confirm(n) { 
  app.alert("You chose " + n); 
}

  // now invoke the constructor:

var city = new PopUp( choiceList, Confirm, '.', 2 ); // pop the menu for the user to see:

city.doPopUp();

The total code needed to pop the popup menu, grab the user's choice, act on the user's choice, and update the selection for the next menu display is contained in that final line of code:

city.doPopUp().

How do we make this magic happen? Let's take a look at our custom constructor and its support functions.

Our custom PopUp object (see constructor below) has four properties and one method. The properties include choices (the array of choices), indicator (the selection-indicator glyph string), action (a pointer to the user's callback function), and tickPosition (index of the currently selected item). The sole method is doPopUp(), which invokes app.popUpMenu() and carries out the action function if the user made a selection; plus, it takes care of indicator-management.

The code comprises about 70 lines of JavaScript. Without further ado, that code looks like:

// CONSTRUCTOR: PopUp - - - - - - - - - - - - - 
// 
// Takes up to 4 args, which should be: 
// 1. An array of choices. 
// 2. A callback function. 
// 3. An "indicator" char/string. 
// 4. An initial selection # (zero-based).

function PopUp() { this.choices = []; this.indicator = "*"; this.action = function() {}; this.doPopUp = CallPopUp; this.tickPosition = -1; // nothing selected if (arguments.length > 0) this.choices = arguments[0];

if (arguments.length > 1 && arguments[1] != null) this.action = arguments[1];

if (arguments.length > 2 && arguments[2] != null) if ('-' == arguments[2]) this.indicator = ' ' + arguments[2]; // insert a space else // (otherwise we'll get an unintended separator bar) this.indicator = arguments[2];

if (arguments.length > 3) this.tickPosition = arguments[3]; } // - - - - - - - - - - - - - - - - - - - - - function CallPopUp() { var selectionArray = MakeSelectionArray(this.choices, this.tickPosition, this.indicator); var reply = app.popUpMenu(selectionArray); // show menu if (!reply) return; // did user not make a choice? reply = reply.slice(this.indicator.length); // remove leading chars this.action(reply); // take action this.tickPosition = MaintainIndicator(this.choices,reply); } // - - - - - - - - - - - - - - - - - - - - - - - - - - function MakeSelectionArray(choices,position,glyph) { var tmp = new Array(choices); // make a copy tmp = tmp.toString().split(','); var space = ''; for (var i = 0; i < glyph.length; i++) space += ' '; var len = tmp.length; for (var i = 0; i < len; i++) if (i == position) tmp[i] = glyph + tmp[i]; else tmp[i] = space + tmp[i]; return tmp; // return the new array } // - - - - - - - - - - - - - - - - - - - - - function MaintainIndicator(choices,selection) { for (var i = 0; i < choices.length; i++) if (choices[i] == selection) return i; return -1; }

Notice that the constructor takes no arguments. In JavaScript, that actually means it can take any number of arguments, because there is a built-in arguments property available inside every function. By inspecting the arguments array at runtime, you can determine how many arguments were passed in (if any) and what their values are. That's what we do.

In the interest of brevity, there is no error-checking to see if the first argument is actually an Array (as it should be). You might want to think about how to type-check each of the passed-in arguments to see that they are usable. This is a good exercise for seeing how well you know JavaScript, because core JavaScript does not include methods like isArray() or isString(). Maybe we'll get to this in a future column.

For safety, the constructor starts out by assigning reasonable default values to all properties. After that, we go through the arguments one by one (checking them for existence, first) and assign their values to the relevant properties.

Achieving Good Behavior

The key to our object's behavior is the doPopUp() method, which in turn calls an external function, CallPopUp().

This function does six things:

  1. First, it converts the array of choices to a list (a temporary array) in which the indicator glyph is shown next to the active selection. This temporary array is just for display purposes. It will later be discarded.
  2. Next, app.popUpMenu() is invoked. The user sees the menu and makes his selection.
  3. We check to see if the user selection was null (i.e., no selection; the user let go of the mouse without highlighting anything). If nothing was selected, we return without doing anything further.
  4. If a selection was made, a string was returned. We need to clip the leading characters off that string. (The indicator glyph string is prepended to the selected item. Spaces are prepended to all other items. We need to get rid of this leading junk.)
  5. We call the action callback, passing our user's selection as the only argument.
  6. We maintain the tick (indicator) position. This is the index or offset of the user's selection in the list of choices. The next time doPopUp() is called, our menu list must display the current selection properly.

The support functions, MakeSelectionArray() and MaintainIndicator(), should be self-explanatory in purpose if not in implementation.

The neat thing is, you don't have to understand the detailed workings of this code in order to use it. Cut and paste the foregoing code to the top-level-scripts area of your next PDF forms project. Then try creating PopUp objects in your code. Play with the code a bit and see how you like it. Make changes to it if you find areas that need improving. (What? My code might need improving?? Unthinkable...)

Welcome to the world of objects!

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.