« May 2006 | Main | July 2006 »

June 29, 2006

Sorting Lists at Runtime

Have you ever needed to sort the content of a list box or drop down list at runtime (e.g. in a form loaded in PDF with Acrobat or HTML with a browser)?

Unfortunately, neither the XFA nor the AcroForm Object Models give you either properties or methods to achieve this functionality. The only thing available are Sort Ascending and Sort Descending buttons on the list of items you define in the Object palette’s Field tab in Designer — not very useful at runtime! Say you were loading data from an XML Data File into your form and this was populating a list with values that needed to be sorted: There wouldn’t be a built-in way for you to do that.

The only solution I’m aware of at this time is to flex your scripting muscles and write some functions that’ll get you sorting lists (from either list boxes or drop down lists) and that’s exactly what I did today while replying to a post on Adobe’s Designer Forums:

Download Sample [pdf]

Minimum Requirements: Designer 7.0, Acrobat 7.0.

Update: It looks like Acrobat 8.0 broke something that prevents my sample form from working correctly. It has to do with the call to

resolveNodes("#items[*]")

Fortunately, the bug will be fixed in Acrobat's next release.

As a workaround, I would encourage you to have a look at the new list object properties and methods now available in Acrobat 8.0. You should be able to use a combination of those new properties/methods along with some of the original script in this sample in order to come-up with an update solution that works.

All the code is located in the script object appropriately named, “ScriptObject”.

The idea was to make use of the JavaScript Array object’s built-in sorting function, sort, by giving it a custom sorting function that was able to sort pairs of text and value items. Remember that for XFA choiceList fields, there’s always one <items save=”1”> node but there may be a second <items> node if text and value items are defined.

From Designer’s UI, you define value items by default by entering items in the list on the Object palette’s Field tab. This defines an <items save=”1”> node. If you then go to the Bindings tab and specify values, the items you specified on the Field tab become the “text” items, defined in the second <items> node, and the values are set in the <items save=”1”> node.

When sorting the list, it’s important to maintain the association between text and value item pairs and that’s why the script is a little complex — not to mention the fact that getting items out of an XFA choiceList field (list box or drop down list) isn’t as easy as 1-2-3 either!

So the script attempts to find text and/or value items, create ListItem objects for each pair, sort them using the custom sort function __SortFunction and then replaces the current items in the list with the ones in the sorted order.


Updated: April 11, 2007

June 26, 2006

Process All Fields

A common requirement on the Adobe Designer Forums is to find all fields on a form and do something specific with them.

For instance, you may want all mandatory fields to be automatically highlighted when a user attempts to submit a form electronically before having filled all mandatory fields. While Acrobat provides a button to toggle mandatory field highlighting on/off (via the Highlight required fields check box in the yellow Form Field toolbar), Acrobat’s Scripting Object Model doesn’t provide a function to do the same. Therefore, you’re left having to write some script to achieve the same functionality.

Since this is requested so often, I thought I would try to put together some canned script that you can copy and paste into your form in order to instantly have the ability to make changes on everything that’s considered a field on your form.

Download Script [js]
Download Sample [zip]

Minimum Requirements: Designer 7.0, Acrobat 7.0.

The first download is for a JavaScript file which contains the “plug & play” script that you can simply copy & paste into any event handler or script object and immediately start concentrating on what you want to do when you find a button, a check box, a text edit, etc.

It’s divided into two parts: The first (at the top-end of the script) is a series of Field Processor Functions for each field/object type that may be found within a form. Each function receives a reference to the field in question. The second (at the bottom-end of the script) defines the ProcessAllFields function — the brains behind this operation. This function detects the field type and calls the appropriate Field Processor Function.

All you need to do is add script to the Field Processor Function(s) for the field types you need to do stuff with.

The second download contains the JavaScript file along with a sample form which demonstrates how to get from the bare-bones Field Processor Functions and ProcessAllFields function to a solution which finds all non-filled mandatory fields on a form and highlights them with the current Acrobat Highlight Color. You’ll find customized versions of the Field Processor Functions and ProcessAllFields function in the script object (named “ScriptObject” — yes, I know, this is an unbelievably original name!) while the script execution flow begins in the Submit button’s Click event.

When the Submit button is clicked, the form is search for non-filled (empty) mandatory fields. If such fields are found, they’ll be highlighted, the RemoveHighlight button will be made visible and an error message will be displayed. Otherwise, the Email Submit dialog will open.

June 18, 2006

Invalid Flashing Fields

So what’s the use of learning about new toys like AcroForm Objects and AcroForm Field Name Generators if you don’t take the time to play with them? Today felt like the right day to do just that and I came-up with a sample form where invalid fields flash red until the user has entered valid values into them. Only once all fields are valid can the form be submitted.

Update: Check-out the newer version on the new Invalid Flashing Fields 2.0 post.

Download Sample [pdf]

Minimum Requirements: Designer 7.1, Acrobat 7.0.5.

Note: A basic understanding of AcroForm Objects is required for this sample.

The sample form works like this: When the user clicks on the Submit button, there’s a script which looks at all fields on the form and validates them for valid content. In this particular form, the only requirement is for the fields to be filled (i.e. have non-null values). If all fields are filled, the form is them submitted however, if there’s at least one field which isn’t filled, the first-found non-filled field is set to flash red until the user has filled it.

Since XFA doesn’t natively support flashing fields, this is all done using the Acrobat app, Document and Field objects, discussed in greater detail in my previous post on AcroForm Objects, as well as my AcroForm Field Name Generator code.

When a non-filled field is found, the Submit button’s script will get the AcroForm Field object name for the invalid field and use it to generate a small script which will run every time an Acrobat Timer object expires. This timer is created in the following block of code:

moFlashTimerID = app.setInterval(
    "var f = this.getField('GetAcroFormFieldName(oField)');" +
    "if (color.equal(f.fillColor, color.red))" +
    "{ f.fillColor = [" + moAcroFieldFillColor.toString() + "]; }"
    "else" +
    "{ f.fillColor = color.red; }",
  500);

In this block of code, the Acrobat app object’s setInterval method is used to create a timer which will continously expire at a specific interval (in this case, every 500 milliseconds, or 0.5 seconds) and every time it expires, it’ll execute the code specified in the first parameter. Since the timer’s code will execute within the context of the document from which the setInterval method was called, the this keyword will represent Acrobat Document object pertaining to the form. The GetAcroFormFieldName method can then be used to get the AcroForm Field object name pertaining to the invalid field (oField) which is then passed to the Acrobat Document object’s getField method. From there, the AcroForm Field’s fill color is compared to red: If it’s already red, it’s set to a light gray color; otherwise, it’s set to red.

It’s important to note that the setInterval method returns an Acrobat Interval object which can subsequently be used to cancel the interval timer in order to get the field to stop flashing red once the user has filled it with a value. This object is also required in order to ensure the timer is stopped when the form is closed if the user ever decides to close the form while an invalid field is flashing (see the code in the root subform’s (form1) DocClose event).


Updated: August 15, 2006

June 14, 2006

AcroForm Field Name Generator

So you’ve read my previous post about AcroForm Objects and now you’re wondering what you do with that stuff. Those more adventurous might even have tried to use the event.target.getField method to do something cool.

If you’re going to do things on the application or the document without attempting to affect a specific field, it’s pretty straight-forward: You just call methods on the app (Acrobat Application) or event.target (Acrobat Document) objects. Getting an AcroForm Field object, however, isn’t as simple as you may think because of the naming conventions used when the Acrobat Form objects are created, based on the XFA Form objects.

Let’s say you have a field, named “MyField”, parented to the second page, named “Page2”, of your form, named “form1”. For some reason, you need to access that field’s pertaining AcroForm Field object. If you tried

event.target.getField("MyField")

you wouldn’t get very far because the actual name of the field is:

form1[0].Page2[0].MyField[0]

Basically, each AcroForm Field object is named with a verbose form of the XFA SOM Expression of its pertaining XFA Form object (also known as a Fully-Qualified SOM Expression).

To help with generating the correct AcroForm Field name for a given XFA Form object, I thought I would write a little bit of script which outputs the Fully-Qualified SOM Expression (which is the AcroForm Field name) for a given XFA Form object:

// Returns the fully-qualified name for the specified object.
//  This means the name always specifies the index.
//  Therefore, if the name is specified, it's "name[index]".
//  If the name isn't specified, it's "#className[index]".
function GetVerboseFieldName(oNode)
{
  // Unnamed nodes have default names which their class name
  //  ("subform", "field", etc.) with a "#" prefix.
  //  Unfortunately, oNode.name won't return this if the node
  //  doesn't have a specific name. It'll just return an empty
  //  string. Also, oNode.index will be undefined. If it weren't
  //  for oNode.index being undefined in this case, we could've
  //  done something like
  //  SOM = "#" + oNode.className + "[" + oNode.index + "]"
  //  but we'll have to use the somExpression property instead
  //  and extract the name out of it because this will return
  //  the "#" name with the correct index.
  // Since somExpression returns the name and index for the
  //  object in all cases (whether the name is specified or
  //  not), we can use the following syntax in all cases:
	
  var nPos = oNode.somExpression.lastIndexOf(".");

  // If the field has a name, check to see if it has any periods in its name.
  // If that's the case, they'll be escaped with backslashes and we need to
  //  look for another period.
  if (oNode.name != null && oNode.name.length > 0)
  {
    while (nPos > 0)
    {
      if (oNode.somExpression.charAt(nPos - 1) == "\\")
      {
        // we found an escaped period, keep looking
        nPos = oNode.somExpression.lastIndexOf(".", nPos - 1);
      }
      else
      {
        // stop looking since we have an unescaped period
        break;
      }
    }
  }

  if (nPos >= 0)
  {
    // get everything after the last "." to the end
    return oNode.somExpression.substr(nPos + 1);
  }
  else
  {
    // in this case, the SOM expression is a single name (unlikely
    //  but theoretically possible)
    return oNode.somExpression;
  }
}

// Returns the Fully-Qualified SOM Expression for the specified field.
function GetFQSOMExp(oField)
{
  var sFQFieldName = GetVerboseFieldName(oField);
  var oParentNode = oField.parent;

  // The absolute root of the XFA Object Model is the xfa object
  //  which contains a single form object, which then contains
  //  what the Hierarchy palette shows as being the "root
  //  subform" ("form1" by default). So we stop when we reach
  //  xfa.form.
  while (oParentNode != xfa.form)
  {
    sFQFieldName = GetVerboseFieldName(oParentNode) +
      "." + sFQFieldName;
    oParentNode = oParentNode.parent;
  }
	
  return sFQFieldName;	
}

You can now take this code and either place it directly inside the event in which you need an XFA Form object’s pertaining AcroForm Field name and call it directly or you can place it in a form-wide Script Object where you can access the code from anywhere.

With this code, changing an XFA Button object’s highlight property such that it gets an outline rectangle when clicked (as opposed to the default which is an inverted fill color) is as easy as this (in JavaScript in the button’s Enter event, assuming you’ve placed the above code directly inside the Enter event):

event.target.getField(GetFQSOMExp(this)).highlight =
  highlight.o;

Beautiful? I think so. Get the Acrobat JavaScript Scripting Reference and go crazy! Just remember: This functionality is only available when your form is loaded in Acrobat.


Updated: February 14, 2007 — Added support for objects with periods in their names.

June 13, 2006

Adobe Developer Week

I just got word of this free online event this week at Adobe.

Here’s the blurb:

Join us to learn about the Adobe engagement platform, including Flex, and other Adobe technologies by attending Adobe Developer Week. This free, week-long event features live, online sessions presented by Adobe technology experts. See live demos and get your questions answered by the experts during interactive Q & A sessions. Register online at the Adobe website.

While there are lots of very interesting sessions, these are the ones I found, after a quick search, which are related to LiveCycle:

Don’t wait to sign-up for sessions — spots are limited and they’re going fast!

June 10, 2006

AcroForm Objects

Today I thought I would tackle the subject of AcroForm Objects — objects available via scripting in the Acrobat Form Object Model — because they offer unique possibilities for your forms when they’re running in Acrobat in the PDF format.

Just to be clear, AcroForms are specific to Acrobat and therefore this functionality doesn’t apply when rendering your forms to a target other than PDF (e.g. when using LiveCycle Forms to render your XFA Forms as HTML).

First, let’s explain what XFA (XML Forms Architecture — a W3C Submission) does: It lets you describe a form, using a defined set of rules that govern an XML structure, which can target many different clients (e.g. PDF, HTML, etc.) — as long these clients support the XFA format. Today, the Adobe LiveCycle Designer targets PDF out-of-the-box and, along with LiveCycle Forms, targets HTML.

The fact that XFA is always translated into a format which can be understood by a client with which a user interacts in order to fill a form and possibly submit its data to a receiver means that the scripts you write in your XFA forms get executed in the target client application (such as Acrobat or a web browser). If the target client also contains a Scripting Object Model — like Acrobat does — there may be ways that you can take advantage of specific functionality exposed by the client which is hosting your XFA forms.

This brings us to the topic at hand: Acrobat’s Form (AcroForm) Object Scripting Model. If you’re designing your form only to target PDF (or you add code to your form to detect when your form is being hosted by Acrobat using xfa.host.name, for example), you can get access to the Acrobat app, Document and Field objects, amongst others, and do some really cool things like have a field with invalid data start flashing red when the user attempts to submit the form’s data.

xfa.host

When writing scripts in an XFA form, you have access to the special xfa.host object. This object gives you access to methods which are specific to the application hosting your form (such as Acrobat, a form server or a web browser). For example, the

xfa.host.name

property tells you the name of the application hosting your form at the time the script is interpreted. If your form is being viewed/hosted in Acrobat, this property will return “Acrobat” while it’ll return the name of the browser if it has been served to a web browser. Furthermore, it’ll return “Presentation Agent” if the script was flagged to run on the server and the server application serving the form to PDF or HTML is Adobe’s LiveCycle Forms product.

xfa.host also gives you access to other properties and functions such as

xfa.host.messageBox // displays a dialog box on the screen
xfa.host.validationsEnabled // turns the form's validations on/off with a single call

but take note that not all functions and properties are available on all hosts (e.g. since a server host can’t display a dialog which requires user input, the xfa.host.messageBox function is only supported on client hosts like Acrobat and web browsers).

You can obtain more information on xfa.host on page 185 of the Adobe XML Form Object Model Reference.

Acrobat app Object

Since the scripts you write in functions and events within an XFA form are interpreted by and executed within the context of Acrobat’s Scripting Engine, you have access to a special object called app. This object gives you access to the collection of active documents and plugins, amongst other things, and lets you display alert messages

app.alert("Hello world!");

and even set Acrobat into full screen mode with a red background!

app.fs.backgroundColor = color.red;
app.fs.isFullScreen = true;

Note that while color.red isn’t an object provided by the XFA Scripting Object Model, it still exists within the context of your scripts because the scripts are ultimately interpreted and executed within Acrobat. You can get more information on the app object in the Acrobat JavaScript Scripting Reference.

xfa.event

This is a special object which exists only via the XFA Plugin which executes XFA scripts on XFA object events inside Acrobat. Whenever an event occurs (such as the click of a button or a field gaining input focus), your script has access to the xfa.event object which gives lots of important information about the event.

For example, if you want to know the value that was selected in a list box or a drop down list in order to change the state of another object on your form, you would script against the list box’s or drop down list’s Change event. If you used the following code to get the value of the item that the user selected:

this.rawValue

you would get the previously-selected value because the object’s rawValue property isn’t updated until after the Change event has occurred. In order to get the information you need, you must use the following code:

xfa.event.newText

which will give you the value of the item the user just selected.

xfa.event also gives you access to a very useful property called target: In Acrobat, this property specifies the Acrobat Document object (which contains the Acrobat Field object which wraps the XFA object whose event is being scripted). This means that you can get at the Acrobat Document object for the “active document” just by using:

event.target

(Note that you don’t need — and shouldn’t use — the “xfa” prefix when accessing the “event.target” property — I don’t know why yet but you’ll have trouble using it if you use the “xfa.event.target” syntax.)

Using this information, you can:

event.target.zoom *= 2; // increase the zoom level two-fold
event.target.zoomType = zoomtype.fitW; // zoom to page-width level
event.target.zoomType = zoomtype.fitH; // zoom to page-height level

or you can use the getField method to get an Acrobat Field object and do some more interesting things.

You can get more information on the Document and Field objects in the Acrobat JavaScript Scripting Reference.

Putting it all Into Perspective

To tie this all together, I’ve drawn-up a simplified version of the Scripting Object Model the way I picture it in my head:

Scripting Object Model (simplified)

This image illustrates how things work from the perspective of a script running within an XFA Event or an XFA Script Object Function (note that you don’t have access to the xfa.event object there unless you pass it into the function by calling it from an XFA Event). You can see how, from the XFA object, you can get to the:

  • Acrobat app object (directly);
  • Acrobat Document object (via event.target or the app object);
  • Acrobat Field object (via the Document object);
  • xfa.host object (directly).

Hopefully this post will have given you a general idea of the Acrobat-specific tools at your disposal when you’re writing XFA scripts using the JavaScript language. Please note, however, that changes may occur to the way XFA Form Objects are hosted within Acrobat in future releases and therefore using the AcroForm Object Model should be a last resort if an XFA equivalent simply isn’t available.


Updated: August 30, 2006

June 05, 2006

XFA SOM Expression Generator

Have you ever had trouble figuring-out what the SOM expression is for a particular object which you’re trying to address within your script?

If so, here’s a tip that might come in handy the next time you’re stumped:

With the input focus (the “I” beam) set within the event you’re scripting on the Script Editor palette in Designer, hold-down the Control key and move your mouse over the canvas. Notice that as you hover over objects, the mouse pointer switches to a “V” cursor. If you click, the full SOM expression for the object you clicked will be automatically inserted into your script. But that’s not all! The generated SOM expression will be automatically formatted to work with your script’s language settings:

If you haven’t named the “page 1” subform and you pick an object on it, you’ll get the following code if your script’s language is FormCalc:

form1.#subform[0].myField

If your script’s language is JavaScript, you’ll get the following code (for the same object):

xfa.resolveNode("form1.#subform[0].myField")

Note that if you click on the object on which you’re scripting the event, “$” will be inserted for a FormCalc script and “this” will be inserted for a JavaScript script.