« July 2006 | Main | September 2006 »

August 29, 2006

Bug: Cannot Override Calculations

Description

The XFA language supports fields which are calculated yet still overridable by the person filling the form. For instance, you may have an invoice on which you calculate the tax associated with a purchase using a calculated field yet, for customers from other countries, you would like to allow them to override the calculated tax amount and enter the correct amount themselves.

To do this, you would use the Object palette’s Value tab and set the field’s Type property to Calculated - User Can Override. You could even go as far as specifying an Override Message which would appear if the user attempted to override the field’s calculated value.

In theory, when the user attempts to override the field’s calculated value, either the Override Message or, if none was specified, an Acrobat default message appears warning that the field is calculated and gives the user the option to continue with the override or to cancel. If the user proceeds with the override, subsequent calculations use the override value instead of the original calculated value regardless of subsequent changes to other fields which the overridden field’s calculations were dependent on.

Unfortunately, there’s a bug in Acrobat that causes the overridden value to be discarded. It manifests itself in a couple of different ways:

  1. Dynamic PDF: Not only will the value specified by the user (the override) not be retained but the Override Message (if specified) won’t be displayed. The behavior is simply to let the user type-in a value but then throw it away and run the calculation again without any warning.
  2. Static PDF: In this case, the Override Message (if specified) is displayed and the user has the option to discard their change or proceed with the override but the specified value is discarded regardless of what they choose to do and the calculation is run again.

Workaround

You could use two fields and a check box where the first field is simply “Calculated - Read Only” and the second field is the override value and is invisible until the check box is checked.

All calculations which depend on the calculated field in question would need to verify the value of the “override” check box: If it’s unchecked, calculations would use the value from the first field which itself may be calculated. If it’s checked, they would use the value from the second field which contains the override value.

Checking the check box would cause the first (calculated) field to become invisible and the second (override) field to become visible. You could even display an Override Message when the check box gets checked and use the

xfa.host.response("Question", "Title", "Default Value")

statement to give the user the option to cancel the override (the return value is null if the user picked the Cancel button).

Fix

Please refer to the Bug List for updated information on the version(s) affected by this bug as well as if and when it was/will be fixed.

August 25, 2006

The Bug List Goes Live!

Some of you may have noticed the new Bug List side bar module on my blog recently. I’m excited to launch this new section of my blog tonight and I hope that it’ll add even more value to your visits here as well as to your experience using Designer and Acrobat to design and develop your electronic forms.

Look for a few kick-off posts in the coming days about some bugs that fellow readers and users have reported as well as some that I’ve come across myself while making the various samples I’ve already posted to this blog. Of course, I there shouldn’t be any in the features I worked on… ;)

My hope for this new section is that it’ll serve as a reference for things to watch-out for in the versions of Designer and/or Acrobat that you may be using as well as what to do in order to get around them.

August 24, 2006

Complex Validations

A couple of days ago, Michael Ramirez asked me how to do complex validations on forms. He asked how one could have a validation as follows: Given 3 text fields A, B and C and a check box E, A is mandatory only if B and C are filled or if E is checked. I thought this would make a great little sample of both complex validation scripts and what I like to call the “Two Button Submit” technique.

Download Sample [pdf]

Minimum Requirements: Designer 7.x, Acrobat 7.x.

The Plan

First, let’s figure-out what the plan is before we start scripting. The idea is to place these fields on a new form along with a submit button such that the user cannot submit the form unless the validation rules succeed. Since Field A isn’t always mandatory, we can’t default it to be User Entered — Required by using the Value tab in the Object palette. We’ll need a way to make it required only when Fields B & C are filled or when Field E is checked. Furthermore, we don’t want the form’s data to be submitted unless these rules are met.

The easiest solution that comes to mind is one that’s passive as the user is entering data into the fields but aggressive at the point when they try to submit it by putting the validation script on the submit button’s Click event. The catch here is that there’s no way of stopping the submission process once the Click event has fired so regardless of whether your validation succeeds or fails, the form is going to be submitted. That’s where the “Two Button Submit” technique comes-in to save the day.

“Two Button Submit” Technique

Thankfully, there’s a scripting method called execEvent that lets you programmatically cause an event to be executed on a field. With this in mind, you place a regular (non-submit type) button on the form, make it visible and set its caption to read something like, “Submit”. Then you place the actual submit button (could be email, HTTP or even print) on the form and make it invisible. Please note: make it invisible, not hidden or else the execEvent call on it will fail because Acrobat won’t know it exists in the XFA Scripting Model. For this example, let’s call it “EmailSubmitButton”.

Now that both buttons are on the form, in the regular (fake submit) button’s Click event, put the following script (FormCalc or JavaScript works just the same):

EmailSubmitButton.execEvent("click");

With that script, when you click on the fake submit button, you’ll cause the Click event on the actual submit button to fire and the fake submit button will, to the user, behave just like a normal submit button.

The advantage of this is that you now control if and when the real submit button’s Click event is executed and therefore if and when the form is submitted.

If you were to add the following JavaScript statement before the execEvent line:

if (FieldA.rawValue != null && FieldA.rawValue.length > 0)

the form wouldn’t submit unless Field A was filled.

Validations Prior to Submitting

Using the “Two Button Submit” technique, we can now perform any kind of validations — really simple to very complex — we want and have total control over if and when the form may be submitted.

Given our example of fields A, B, C and E, we can do something like this:

var bCanSubmit = true;

if ( (IsSpecified(FieldB) && IsSpecified(FieldC)) ||
    IsChecked(FieldE) )
{
  if (!IsSpecified(FieldA))
  {
    xfa.host.messageBox("Please fill Field A.");
    bCanSubmit = false;
  }
}

if (bCanSubmit)
  EmailSubmitButton1.execEvent("click"); // submit the form

This will prevent the form from being submitted if Field A isn’t filled when Fields B & C are filled or Check Box E is checked.

August 15, 2006

Invalid Flashing Fields 2.0

A colleague of mine here at Adobe pointed-out today that the use of the AcroForm Document object’s getField method wasn’t necessary in the script I used for my original Invalid Flashing Fields sample.

There’s an alternative which uses xfa.form.resolveNode in the app.setInterval script. xfa.form.resolveNode takes a SOM Expression and returns a reference to an XFA node. What’s more is that this API call can be made from within the context of the AcroForm Scripting Object Model.

The app.setInterval script therefore changes from this:

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);

to this:

moFlashTimerID = app.setInterval(
  "var f =  xfa.form.resolveNode('" +
    oField.somExpression + "');  " +
  "if (f.ui.oneOfChild.border.fill.color.value == '255,0,0')" +
    "{ f.ui.oneOfChild.border.fill.color.value = '232,232,232'; }" +
  "else" +
    "{ f.ui.oneOfChild.border.fill.color.value = '255,0,0'; }",
500);

Also note the changes in the way the color values are compared and assigned (whereby the newer version uses more familiar XFA script rather than the AcroForm script from the first version).

Since the use of the AcroForm Scripting Object Model should always be secondary to using the XFA Scripting Object Model (because AcroForm objects are, after all, in a separate Object Model which may change separately from the XFA Scripting Object Model), I wanted to highlight this alternative which makes more extensive use of the XFA Scripting Object Model than the first version did.

Download Sample [pdf]

Minimum Requirements: Designer 7.1, Acrobat 7.0.5.

August 12, 2006

Importing Data in Acrobat

Data is central to every form. Some forms simply collect data while others use pre-collected data to do various things to forms, such as pre-populate names, phone numbers, addresses, affect the layout of a form or various other things.

Using pre-collected data to affect a form as described above involves importing data into a form via the host application (assuming a server isn’t part of the picture). This time around, I want to talk specifically about importing data using Acrobat.

As you all know, Adobe distributes the Reader application for free. Because of this, you can save the forms you design in Designer as PDF and anyone with the free Reader application can fill your form and submit its data electronically.

The catch is when your PDF form requires data to be imported. Unless the host application is Acrobat Professional or Acrobat Standard, a regular PDF form cannot import data — no matter if it comes from an XML Data file or from a data connection to an ODBC or WSDL data source. Acrobat Pro/Std comes with all the tools you need to import data into a form and permits data to be automatically imported via an ODBC or WSDL connection. PDF forms opened in Reader (or Elements for that matter), on the other hand, aren’t privy to that functionality by default — that is, Reader 7.0.7+ (the latest version is 7.0.8) doesn’t allow data import by default.

In Reader 7.0.5, there was a bug that resulted in Reader having the ability to import data into forms. Unfortunately (well, that’s my opinion), that bug was fixed in Reader 7.0.7 such that Reader can no longer, by default, import data into a PDF form.

In general (at least as of Reader 7.0.7), PDF forms opened in Reader must be individually extended, using Acrobat Professional or Adobe LiveCycle Reader Extensions, to allow the use of “hidden” functionality in Reader (such as Data Import or Commenting). (You can reader-extend forms using Acrobat Professional’s “File | Form Data | Initiate Data File Collection Workflow” menu item in order to collect data via Reader for a limited number of submissions.)

Another option is to use Adobe LiveCycle Forms to deploy your forms. Using this server product, you can pre-populate forms with data on the server prior to deploying them to the client application (on the user’s system). Using this option, you don’t need to reader-extend a PDF form which imports data because the data is imported and merged into the PDF form on the server and then deployed to the client application (any version of Acrobat/Reader on the user’s system), which, in turn, doesn’t need to import any data.

To summarize, here’s a little table that illustrates the conditions under which you can import data into a PDF form in Acrobat:

VersionPDF FormWith Reader ExtensionsWith LiveCycle Forms
Readerno yesyes
Elementsno yesyes
Standardyesyesyes
Professionalyesyesyes


Updated: September 20, 2006

August 04, 2006

Tracking Mouse Clicks

I just recently received another comment from Zack. This time, he was wondering about how one would go about tracking mouse clicks on an image field.

I had never attempted to do that so I took it on as a challenge and thought I would share the results in this post.

I knew from the start that XFA alone wasn’t going to be able to handle this simply because (to my knowledge) it doesn’t provide any information as to the position of the mouse pointer when an event occurs. The most logical place I thought would’ve provided the information — the Event Pseudo Model (the xfa.event object available in all XFA events) — didn’t live up to my expectations. Thankfully, XFA at least provides a Click event so that I could know when the image got clicked.

The next logical place to look was in Acrobat’s Scripting Object Model (in the AcroForm Objects). In the Acrobat Document object, I found what I was looking for: the mouseX and mouseY properties which provided the location of the mouse with respect to the document window.

The last thing I needed was information about the dimensions and location (within the Acrobat Document Object’s coordinate space) of the image field and the Acrobat Field object’s rect property would give me just that.

The combination of the XFA Click event, the Acrobat Document object’s mouseX and mouseY properties and the Field object’s rect property was just what I needed to get this to work.

Of course, I soon discovered that I had another problem to figure-out: The behaviour of an image field in a PDF form running in Acrobat is that when clicked, it opens a browse dialog that lets you pick the content for the field. Unfortunately, there isn’t any way to suppress that dialog other than making the image field read-only or by using a static image object but then both alternatives prevent the Click event from firing. So I needed some clever way to capture a mouse click over an image (whether it was a field or a static object) and I decided to use a button with a transparent fill and no border (so it was essentially transparent). Since buttons are fields just like image fields, the mouseX, mouseY and rect properties would still be available for the button and if I sized the button to fit the image and placed it over-top, I would essentially end-up with an HTML <map>.

Download Sample [pdf]

Minimum Requirements: Designer 7.1, Acrobat 7.0.5.

The first challenge was getting an instance of the Acrobat Field object which represents the button using the

event.target.getField

method. That was easily accomplished by using the script I provided on my AcroForm Field Name Generator article.

The next problem to be solved was the fact that the default behaviour, in Acrobat, for a button when it’s clicked is to invert its content area. Since I was trying to hide the button, I needed a way to suppress the inversion so that clicking on the button would give no visual feedback to the user. That way, it would give the impression that the user was actually clicking on some sort of hyperlink on the image itself. That was easily solved by using the highlight property of the Acrobat Field object representing the XFA button in its Enter event (had to be Enter and not Initialize because Initialize is too early in the form’s initialization process for the association between the XFA button and it’s Acrobat Field counterpart to be established):

event.target.getField(ScriptObject.GetFQSOMExp(this)).highlight =
  highlight.n;

The last problem was with respect to calculating the coordinates of the hot spots on the button that would trigger a reaction (in this sample, I was just going to set the value of a text field somewhere else on the form to reflect the area that was clicked). The problem there was that while I had the mouse location and the button’s dimensions all in the same coordinate space (Acrobat Document), the coordinates were specified with (0,0) set to the document’s lower left corner.

While this may not seem like a big deal to some of you, it really messes me up when (0,0) isn’t at the top left corner (with the maximum (x,y) set to the bottom right corner). I guess that’s a result of years of writing code for Windows where an MFC CWnd’s coordinate space places (0,0) at the top left corner. Anyway, after lots of hair pulling, I finally figured-out how to properly calculate the hot spots in this strange — no, alien — coordinate system.

Zack, if you have any other questions, please post a comment.

August 02, 2006

Auto-Localizing Your Forms

A few days ago, I posted about Designer 7.1’s new Dynamic Properties feature. In that post, I explained how this feature could be used to automatically populate a list box or drop down list field with data from a data connection without having to write any script.

Today, I thought I would highlight one of the main advantages to using this feature: localization of your forms!

By using the Dynamic Properties feature to bind the caption of form fields to data nodes in a data connection, you can easily localize your forms without having to write any script!

To illustrate how this would work, I’ve designed a simple little form which has an address block on it (taken from the “Address Block” custom object that ships with Designer, found under the Custom tab in the Library palette). Each field in the address block (which excludes the “Locale” field at the top that’s just there for informational purposes) has its Caption property bound to a specific data node in the data connection I’ve defined, based on some different localized XML Data files. To localize the form at run-time (e.g. in Acrobat), just open the form and then load the XML Data file pertaining to the locale you want to use.

Download Sample [zip]

Minimum Requirements: Designer 7.1, Acrobat Pro 7.0.5.

Note: If you open the form in Acrobat, don’t forget to load a data file into it by using the options under the “File | Form Data” menu.

I should also mention that since it’s possible to have multiple data connections pointing to databases and/or WSDL connections, you could use one connection to localize your form and another to capture the data entered into your form (by binding the field values as usual, using the Binding tab in the Object palette, to data nodes in the second data connection). If you have any questions about this, I’ll be glad to answer them!