Main

March 24, 2007

Where did that border come from?

Have you ever wondered to yourself, "where did that border come from?", when you simply wanted to set a field's fill color? I know I did for a while until I learned about how the quirkiness of borders in XFA.

For example, if I wanted to set a field's background (fill) color to green in a button's Click event, the first thing I would do is:

this.fillColor = "0,255,0";

or

this.border.fill.color.value = "0,255,0";

Both methods amount to the same result which is the following:

Notice the black edge that appears around the perimeter of the field. What is that doing there? All I wanted to do was set the background color to highlight the field!

What I really wanted was this:

Is that too much to ask? All I did was set the fill color; I never messed with the edge, honestly!

In order to understand where that black edge came from and how to make it go away, we'll need a better understanding of how borders are specified in XFA.

XFA Borders

First of all, the term "border" signifies all elements that compose the rectangular area that surrounds the entire field or the field's value (two distinct borders). That means it's made-up of corner, edge and fill properties, amongst others. In Designer, the field's border is edited via the Border palette and the field value's border is edited via the Appearance property on the Object palette's Field tab.

Next is the fact that unlike most other XFA elements, the mere presence of the <border> element, and of some of its child elements such a <fill>, determine what the field's border will look like. For instance, if no <border> element is specified, the field does not have a border even if the <border> element has a presence attribute with a default value of "visible". Likewise, if the <fill> element isn't specified, the border has a transparent fill (i.e. no fill) even though it, too, has a presence attribute with a default value of "visible".

For example, the following field definition has no border:

<field></field>

If the <border> element is specified, then you also get the default <edge> element which is a thin black line and since specifying only a single <edge> element affects all sides of the border, you end-up with a black edge surrounding the border -- just like in the first image above (without the green fill).

In this case, the field's definition is simply this:

<field><border/></field>

When you use the script I proposed earlier for setting a field's background (fill) color to green, this is what you get in the XFA syntax which describes the field:

<field>     <border>         <fill>             <color value="0,255,0"/>         </fill>     </border> </field>

As you can see, the <border> element has been specified and therefore the border gets the default thin black edge (even though an <edge> element wasn't explicitly specified since an edge is implied when not explicitly specified).

Finally, edges and corners are a little tricky because there can be as little as none or as many as 4 <edge> and <corner> elements specified. What you need to remember is that they're defined in clockwise order:

  • No <edge> element is the same as a single <edge> element which defines all edges at once (all 4 edges are identical).
  • Two <edge> elements define the top/bottom (edge 0) and left/right (edge 1) edges.
  • Three <edge> elements define the top (edge 0), left/right (edge 1) and bottom (edge 2) edges.
  • Four <edge> elements define the top (edge 0), right (edge 1), bottom (edge 2) and left (edge 3) edges.

The same goes for the <corner> element.

The Solution

So just how is it that we get rid of that edge? Well, given that the edge is implied (as mentioned earlier), all we have to do is hide it! It's quite simple, in the end, yet quite frustrating if you don't understand how borders work in XFA.

The <edge> element has a presence property which you can set to "hidden" in order to hide it. Therefore, the following script would set the field's background (fill) color to green and ensure that the default black edge doesn't appear along with it:

this.fillColor = "0,255,0"; border.edge.presence = "hidden";

That JavaScript, on a button's Click event, will yield the border as I intended it to be (in the second image mentioned earlier).

Fun With Borders

In order to demonstrate a few things that can be done with borders using script (you may use similar scripts to show/hide borders on fields or field values for all kinds of purposes), I've provided this sample form. I encourage you to open it up in Designer and look at the script in order to get an idea for how you might implement your own border solution.

Download Sample [pdf]

Minimum Requirements: Designer 7.1, Acrobat 7.0.5.

March 7, 2007

ADBC Now Disabled by Default

Ever since the release of Acrobat 8.0 last November and, more recently, the Acrobat 7.0.9 update (for those still using Acrobat 7.x), I've had a few inquires about forms using ADBC (Acrobat DataBase Connectivity) that suddenly stop working.

The source of the problem is likely the fact that Acrobat 7.0.9 and 8.0 now disable ADBC by default, regardless of whether you were using it prior to the update or not. The reason behind this change is that ADBC poses a sizeable security risk as it doesn't provide any mechanism to protect the databases it accesses from malicious use.

Although I wish the installation programs for Acrobat 7.0.9 and 8.0 would've warned users that ADBC was being disabled and provided them with information on re-enabling it rather than silently disabling it and causing people a lot of grief, I think disabling ADBC when it isn't needed is much safer than leaving it enabled in case you ever need it.

Alternatively, you may choose to migrate to ODBC data connections in XFA forms (which you can save as PDF files for use in Acrobat). Since these are still enabled in Acrobat 7.0.9/8.0 and also make use of DSNs to locate and access databases, the migration should be relatively seamless to the people using your forms. Please note, however, that exposing a database to any type of audience always creates a certain security risk which should be assessed and addressed accordingly. You might consider creating a web service to act as the bridge between the database and your form (where your form retrieves data from and pushes data into a database via methods provided by a web service), thereby restricting access to your database's structure and data.

For those needing to (re-)enable ADBC, the Acrobat 8 SDK Readme contains information on how to do that. For your convenience, here is the excerpt which contains the instructions (as they were stated on December 13, 2006):

ADBC Support

Acrobat Database Connectivity (ADBC) can now be turned on and off via a registry setting. To activate ADBC, create a registry key of type DWORD with the name “bJSEnable” and a value of “true” (1) in the following location:

HKEY_CURRENT_USER\SOFTWARE\Adobe\Adobe Acrobat\8.0\ADBC

This activates ADBC in Acrobat 8.0. In previous releases of Acrobat, ADBC was active by default. In Acrobat 8.0, this setting has been changed to require user intervention to activate ADBC because most users do not want to have ADBC accessible from PDF.

Windows shell command to activate ADBC:

reg add "HKEY_CURRENT_USER\SOFTWARE\Adobe\Adobe Acrobat\8.0\ADBC" /v bJSEnable /t REG_DWORD /d 1

For Acrobat 7.0.9 users, simply change all instances of "8.0" to "7.0" in the above instructions.

Please note that since ADBC poses a security risk (as outlined earlier), Adobe does not recommend modifying the registry to re-enable this feature. Also note that ADBC is still only available via Acrobat Standard or Professional and on Windows only. The Adobe Reader does not have the ability to use ADBC.

February 14, 2007

Accessing Objects with Periods in their Names

Did you know that periods (.) are valid characters in XFA object names? On the surface, this may seem innocent but it's really kind of strange when you think about it -- especially when it comes to writing scripts: How would you access the properties or methods of an object if had a period in its name?

Consider a simple form with a button and a text field which is named "TheText.Field". Notice the period in the text field's name. If you wanted to write a little line of script in the button's Click event which set the text field's value to some arbitrary text, you would not be able to write the following:

TheText.Field = "text" // FormCalc

It would produce an error because the script engine would think that you're referencing an object named "Field" which is a child of a parent object named "TheText" when you're actually trying to reference the "TheText.Field" object.

If XFA allows for periods in object names, there must be a way to access them correctly. To find the answer, one of the things you can do is a little test which exposes the text field's SOM expression (say, in the text field's Enter event):

xfa.host.messageBox($.somExpression) // FormCalc

Doing this for the text field would give the following result:

xfa[0].form[0].form1[0].#subform[0].TheText\.Field[0]

Notice the backslash which precedes the period in the "TheText.Field" name: Backslashes are used to escape period characters in XFA names in SOM expressions.

Knowing this important detail, you could change the earlier script to the following:

$.parent.resolveNode("TheText\.Field") = "text" // FormCalc

and everything would work just fine. The resolveNode method is necessary in this case since the script engine doesn't support backslashes in statements in FormCalc (and neither in JavaScript for that matter).

Finally, don't forget that backslashes in strings in JavaScript are used to escape other characters such as "\n" for a new line character. This means that when you write the object's name in the resolveNode method call, be sure to escape the backslash itself in order to give the correct expression to the resolveNode method:

this.parent.resolveNode("TheText\\.Field").rawValue = "text"; // JavaScript

February 1, 2007

New List Object Properties and Methods

Some of you may recall my tutorial on sorting lists at runtime which I posted last June. Oh how I wish I had waited until the release of Designer and Acrobat 8.0 in order to write it! With the new release of these two products comes a new API for list objects that would've significantly simplified things: Rather than having to know details about how list object items are represented in XFA (using an <items> node for item text values and an <items save="1"> node for item data values) and manipulating these nodes in script, I could've used methods such as getDisplayItem and getSaveItem.

New Properties

  • length: Returns the number of items in the list object. This property is read-only.
  • selectedIndex: Returns the zero-based index of the first-found item which is selected or -1 if the list has no selection. You may set this property in order to set a new selection in the list or set it to -1 in order to remove the selection entirely. Note that setting this property will first de-select any currently-selected items prior to applying the new selection.

New Methods

  • bool getItemState(int index): Returns true (1 in FormCalc) if the list item specified by the zero-based index parameter is currently selected in the list.
  • setItemState(int index, bool state): Add or remove a list item from the selection by specifying its zero-based index and true to select or false to de-select it (1 or 0, respectively, in FormCalc). Note that if the list object does not support multiple selection (drop down lists typically don't), using this method will have the same effect as setting the selectedIndex property.
  • string getDisplayItem(int index): Returns a list item's text value (this pertains to the list's <items> element in XFA) based on a zero-based index into the list.
  • string getSaveItem(int index): Returns a list item's data value (this pertains to the list's <items save="1"> element in XFA) based on a zero-based index into the list.
  • bool deleteItem(int index): Deletes the list item specified by a zero-based index and returns true (1 in FormCalc) to indicate that the item was effectively deleted.

Demo Form

In order to show-off this cool new API (available in both JavaScript and FormCalc), I designed a little form that uses all the new properties and methods. Play with it in Acrobat 8.0 and check out the script in Designer 8.0. I'm certain it'll make your life a whole lot easier when it comes to scripting list boxes and drop down lists.

Download Sample [pdf]

Minimum Requirements: Designer 8.0, Acrobat 8.0.

Note that you must click away from the "List Box" object after you've set a selection since the list is set to commit its selection on exit (as is recommended when supporting multiple selection). You can specify this by setting the "Commit On" property on the Object palette's Field tab to "Exit".

January 10, 2007

Setting JavaScript as the Default for New Forms

If you've ever written scripts in Designer, you will have noticed that the default scripting language is FormCalc which is a problem if you need to (or prefer) to write your scripts in JavaScript.

The easiest way to make sure that all your new scripts default to the JavaScript language is to use the Form Properties dialog and set JavaScript as the default scripting language (in the Defaults tab). The problem with this setting is that it affects only the current form. If you're like me, you create lots of forms every day, you constantly forget that the default scripting language is FormCalc and you only remember once you're running your form and you get a FormCalc error message box in Acrobat telling you that your syntax is incorrect. How annoying!

Fortunately, there's a way to set JavaScript as the default scripting language for all new forms -- based on a specific template. The default setting for the scripting language is actually specified as a processing instruction in the XML Source. If you create a new document and go to the XML Source tab, you'll find the following processing instruction set as a child of the <template> element (also shown in the image below):

<?templateDesigner DefaultLanguage FormCalc?>

Since all new documents created in Designer are based on templates and that each template comes with its own set of processing instructions, modifying the processing instruction in the template would effectively modify the default scripting language on all new forms based on that template. Going one step further and making that modified template your default template would ensure makes things even easier when you're creating new forms.

Fortunately, you don't have go to the XML Source view to modify this processing instruction. Instead, you can set its value using the Form Properties dialog (accessible via the File menu): In the Defaults tab, set the Default Language property to "JavaScript".

Modifying Designer Templates

In Designer, you use the Template Manager to organize your templates. You can access the Template Manager from the Tools menu.

To modify a template, you can follow these easy steps:

  1. Create a new document based on the template you wish to modify.
  2. Make the necessary modifications (like setting the processing instruction for the default scripting language setting to "JavaScript").
  3. Save the new document as an "Adobe LiveCycle Designer Template (*.tds)" file. If you want to replace an existing template (say "Letter"), use that same name as the file name for the new (modified) template file you're saving.
  4. Using the Template Manager, select the tab pertaining to the group in which you want to add your new template. If you're wanting to replace the "Letter" template, for example, select the "Standard" tab.
  5. Right-click in the list panel and select "Add Template...". Select the template you just saved and it'll be added to Designer's "template store". Note that if the name of your new template file is the same as an existing template in the group you selected (e.g. the "Letter" template in the "Standard" group), you'll be prompted to overwrite the existing template.

One important thing to note in the way Designer handles templates is that any template file you add to the Template Manager gets copied into the "template store" which means that new documents based on that template aren't actually based on the template file in the location where you saved it but rather on the copy that was placed in the "template store".

If you're wondering what the "template store" is, it's actually a folder inside Designer's Application Data folder on your system. While I don't encourage you to look in there (because you might see things that look interesting but you shouldn't be playing with ;), if you find them, then you should also be able to open them directly in Designer and modify them without having to create a separate copy.


Updated: January 17, 2006

January 4, 2007

New Scripting Basics Guide

I was looking around in the LiveCycle Developer Center today and came across a great beginner's guide to scripting in Designer that Alex Mitchell, a colleague of mine here at Adobe, recently posted to the site.

Even though it's labelled as "beginner", there's some great stuff in there regardless of your level of expertise. For example, there are some really nice flow charts illustrating the order of execution of form and field events and the section on Creating and Resuing JavaScript Functions should come-in handy for those complex forms. There are also some examples of common scripting tasks which could be useful in all types of forms.

December 18, 2006

Databases: Inserting, Updating and Deleting Records

Since all of the previous tutorials I've posted with regards to data connections have dealt with searching for records and displaying the results, I thought I should post a little tutorial on how to use data connections for inserting, updating and deleting records as well.

You may recall that the tutorial on connecting a form to a database did demonstrate one method of inserting, updating and deleting records from a database. The problem with using bindings to fields in order to modify records in a data connection is that you must set those fields to the values for the current record and then you have to use the data connection object scripting methods like "addNew()", "update()" or "delete()". This can get really awkward if all you're wanting to do is insert a new record, for instance, and just doesn't cut it if you want to avoid having to load-in data when the form is opened (the only purpose for the data connection may be such that new records can be inserted or existing ones can be deleted yet displaying existing records is not required).

This small tutorial uses a basic ODBC data connection defined in the form and then modifies it, via script, in order to be able to execute SQL statements which either insert, update or delete records from any table in the database for which the data connection was setup to work with. It then uses a separate data connection to the same database for query purposes only since there's no sense complicating things with an all-purpose data connection -- especially when it comes to running "select" SQL statements in order to iterate through records returned by the query.

Key Concept

The key concept with this tutorial is the fact that the <query> node inside an ODBC data connection (as we saw in the tutorial on selecting specific database records) can be used to execute all sorts of SQL statements -- not just "select" queries. This means that if you set the <select> node (inside the <query> node) to be an "insert" SQL statement and open the data connection, the result will be a new record in the database as per the insert statement's parameters (and the same goes for "update" and "delete" SQL statements). In fact, the XFA 2.4 Specification states, on page 772, that "despite the name [of the <query> node], this element can also be used to delete, insert, and update records."

BOF and EOF Actions

One very important thing to note is that the "Beginning of File" and "End of File" actions on the <query> node's <recordSet> child node (which describes how records in the data connection are navigated) must be set to "stayBOF" and "stayEOF", respectively, otherwise you may run into serious problems. That is, the result of reaching the beginning or end of the record set when opening the data connection must be to "stay" where you (the record set navigator) are when the SQL statement is one or more of "insert", "update" and "delete".

See the script in the "Database" script object in the sample form for more details on how to specify this. Use the Hierarchy palette to locate it under the root subform.

Sample Form

I've designed a form that should put this all into perspective for you as well as give you a very useful script object which you can place in your Custom Library tab and re-use in other forms.

In short, these are the steps I followed to design this form:

  1. I created the "RunSQLDataConnection" ODBC data connection to my "FormBuilder" database, specifying a short SQL query to the new "movie_comments" table (although any other table would've been just fine).
  2. I created the second "ListComments" ODBC data connection to my "FormBuilder" database, specifying an SQL query that exposes the "username", "title" (movie title obtained from a join on the movie table) and "comment" columns.
  3. I inserted the various buttons and fields and wrote the scripts.

I've included lots of comments through the scripts to detail what's going on at each stage as well as why certain things are being done so please have look at the sample (note that you don't have to have the data connections setup in order to open the form and look at the script) and let me know if you have any questions.

Download Sample [pdf]

Download FormBuilder Database Definition [sql]

Minimum Requirements: Designer 8.0, Acrobat Pro/Std 8.0

December 7, 2006

Better Form Design with XFA 2.5

You may have noticed that the new version of Designer and Acrobat that Adobe recently released uses a new version of XFA (2.5 to be exact). While the language has many new features in and of its own, the version of the language (2.5 vs 2.4 or older) also dictates how your forms will behave in Acrobat 8+ with respect to certification (digital signatures) and ubiquitization (Reader-enablement).

Background

Since this new behaviour, which is geared to encourage better form design going forward, will ultimately change the way you write certain scripts, I believe it's important to start with some basic knowledge of XFA forms, see what happens when they get certified/ubiquitized, examine how each used to behave in Acrobat 7 and then how they'll behave in Acrobat 8+.

XFA Primer

Simply said, an XFA form is described inside an XDP file. When it's saved as a PDF file, the XDP is actually embedded into the PDF so that it can be processed by Acrobat.

If you look at the XDP (via Designer's XML Source view), you'll see that it's simply a collection of packets that describe the form's various components. For example, <template> describes the form's layout and behaviour while <sourceSet> contains a collection of data connection descriptions you've defined using the Data View palette. When the form is loaded as a PDF into Acrobat, each packet is loaded into its own in-memory (temporary) model and that's what you actually reference in your scripts. For example, you access the sourceSet packet via the sourceSet model with "xfa.sourceSet". Changes to these models are reflected directly into their pertaining packets.

Form Certification/Ubiquitization

Without going into more details than are necessary, once a form is either certified and/or ubiquitized, modifications to any of the protected content (the various models) should be prevented. Modifying the form's protected content post-certification/ubiquitization would invalidate the certification/ubiquitization status. Such modifications mean that the document is no longer in the state in which it was when it was digitally signed and therefore can no longer be trusted by the recipient as being authentic. Even though the modification may have been caused by authored script in the form, no distinction is made between those kinds of modifications and malicious attacks by an unknown party to, for instance, cause form data to be submitted to an alternate server.

Acrobat 7 and XFA 2.4

Acrobat 7 supported XFA forms up to XFA 2.4 (which Designer 7.1 would author). Once an XFA 2.4 form would be either certified and/or ubiquitized, Acrobat 7 would detect modifications to the form's protected content and would invalidate its certification/ubiquitization status if such modifications occurred. It didn't, however, prevent the sourceSet model from being modified post-certification/ubiquitization even though the <sourceSet> packet was included as part of the certification/ubiquitization process.

The inherent danger in this was that while any form that would do common things like display all the records in a database or select specific records in a database (filter records) -- which required the modification of data connection nodes contained within the sourceSet model -- would function happily at first, problems would ensue later on when a form's certification/ubiquitization status would be inadvertently invalidated, for example, because of an unauthorized modification to a certified/ubiquitized model (sourceSet).

Acrobat 8 and XFA 2.4

In order to address the problem of inadvertent modifications (by form scripts) to certified/ubiquitized XFA 2.4 forms, Acrobat 8 was designed to prevent the sourceSet model (and any other certified and/or ubiquitized content) from being modified post-certification/ubiquitization. This means that if an XFA 2.4 form, loaded in Acrobat 8+, becomes certified and/or ubiquitized, any attempt by a form script to modify the sourceSet model (as in the two examples I mentioned earlier) will result in a security exception:

GeneralError: Operation failed.
XFAObject.setAttribute:25:XFA:form1[0]:initialize
This operation violates your permissions configuration.

One could argue, of course, that this change effectively "breaks" XFA 2.4 forms in Acrobat 8+ but in the end, inadvertently invalidating a form's certification/ubiquitization status is likely just as bad as a security failure in a form's script (because it attempted to modify a now-protected model) from a user experience point-of-view. As I mentioned earlier, Acrobat makes no distinction between authored scripts and malicious attacks when certified/ubiquitized content is modified -- and neither does the user (in their minds, the content simply can't be trusted any longer)!

Acrobat 8 and XFA 2.5

With a new version of Acrobat and XFA, there was an opportunity to further improve on the user experience of both certified/ubiquitized and non-certified/ubiquitized forms going forward. It was done simply by ensuring that modifications to any model that becomes protected post-certification/ubiquitization are now prevented from the start (whether the form is certified and/or ubiquitized or not) and by using XFA 2.5 as Acrobat 8+'s "trigger" for imposing the new behaviour.

The result is that we're now forced to think about security from the very beginning of the form design process by opting to work with copies of the in-memory models (which is achieved by cloning models) rather than with the base models such that our forms don't fail regardless of their certified/ubiquitized state. With XFA 2.5's support for "on-the-fly" certification/ubiquitization, a form may become secured and locked-down at any point in its "live cycle" which makes it imperative to use scripting techniques which won't fail post-certification/ubiquitization.

Legacy Mode

New forms authored in Designer 8.0 will be XFA 2.5 forms by default and you'll need to use the new cloning technique described later in this article. That being said, if you need things to be back the way they were, there is a way that you can still use Designer 8.0 to design XFA 2.4 forms and that's by using what's called the Legacy Mode processing instruction.

Put simply, switch to the XML Source view for an XFA 2.5 form in Designer 8.0 and insert the following processing instruction under the <template> node (as a child element):

<?originalXFAVersion http://www.xfa.org/schema/xfa-template/2.4/?>

The result will be that Acrobat 8.0 will run your form as though it was an XFA 2.4 form -- but be aware that this will also prevent you from using any of the new language extensions and APIs that come with XFA 2.5 (more on those in later posts).

(By the way, when you load an older form -- earlier than XFA 2.5 -- into Designer 8.0, even though the form's version is upgraded to XFA 2.5, the Legacy Mode processing instruction specifying the form's original XFA version is automatically added so that your form continues to work properly with respect to the XFA version is was originally designed for.)

Modifying sourceSet in XFA 2.5+ Forms

In order to avoid unexpected security exceptions in your forms after they get certified and/or ubiquitized and to handle the fact that you may not necessarily know for sure at which point in the form's workflow that it'll happen (if ever), you need to make sure that when you're working with the sourceSet model, you're actually using a cloned in-memory copy of the original sourceSet model rather than using the original sourceSet model directly.

Cloning Form Nodes

Don't worry: You don't have to be a scientist to use this simple technique. Using the

clone(deep)

method on the node that defines the particular data connection you're wanting to modify within the SourceSet model and making sure your script keeps using the clone instead of the actual definition will do the trick. This method accepts a boolean parameter which, when set to 1 (or true), will clone the node and all its children (which is definitely what you want to do or else you will only get a shell instead of the full data connection) and return a reference to the in-memory copy.

As an example, let's consider the following script taken from the Data Drop Down List object (found in the Library palette's Custom tab):

...
var oDB = xfa.sourceSet.nodes.item(nIndex);
...
// Search node with the class name "command"
var nDBIndex = 0;
while(oDB.nodes.item(nDBIndex).className != "command")
  nDBIndex++;

oDB.nodes.item(nDBIndex).query.recordSet.setAttribute("stayBOF", "bofAction");
oDB.nodes.item(nDBIndex).query.recordSet.setAttribute("stayEOF", "eofAction");

Notice that the script first obtains a reference to a data connection node found within the original sourceSet model and then goes on to modify some of its properties. In an XFA 2.4 form loaded in Acrobat 8+, prior to certification/ubiquitization, this will function properly although it'll stop functioning if the form ever gets certified/ubiquitized. In an XFA 2.5 form, however, it'll immediately fail with a security exception simply because Acrobat 8+ determines that the sourceSet model may eventually become protected and protects it from the start.

Applying the cloning technique to this script is trivial. All you need to do is change the line which accesses the sourceSet model to this:

var oDB = xfa.sourceSet.nodes.item(nIndex).clone(1);

Notice the clone(1) method appended to the end of the statement. At that point, "oDB" now receives a reference to a copy of the original sourceSet model which it's free to modify regardless of the form's certification/ubiquitization status. The rest of the script doesn't need to be modified at all!

Note that you could just as easily store the cloned data connection node into a Form Variable or a variable defined in a Script Object in order to reference it again at a later time if you make modifications to it that you would like to persist while the form is running in Acrobat.

Updated Library Objects

If you had already installed Designer 8.0 and tried using the Data List Box and Data Drop Down List objects under the Custom tab in the Library palette, you more than likely ran into the security exception I described earlier. That's because those custom objects managed to miss the ever so important update which they required in order to function properly in XFA 2.5+ forms with Acrobat 8+ (as we saw in the previous section).

For your convenience, I've posted updated versions of both the Data Drop Down List and Data List Box custom Library objects which you can save to your local system and add to your personal (or shared) Library in Designer 8.0.


Updated: December 11, 2006

November 11, 2006

Instance Manager Object Reference

It seems lately a lot of my posts and a lot of the comments that you've posted had something to do with the Instance Manager: The object available only on repeatable (dynamic) subforms which allows you to manage that subform's instances (add, remove, etc.).

Since it's at the core of repeatable subforms which are necessary when designing flowable (dynamic) forms that, say, display one row per record in a table connected to a database, I thought it would be useful to give a quick overview of the Instance Manager Object's properties and methods.

Accessing the Instance Manager

Before we get to the properties and methods, here's a refresher on how to get at the Instance Manager for a particular subform (or table row).

Repeatable Subform Required

First, you must make the subform repeatable. To make it repeatable, it must be placed in a flowed container -- that is, another subform whose Content type (found on the Object palette's Subform tab) is set to flowed.

Side Note: The simplest case is a report-style form that simply displays records from a database where each record's information is displayed in separate fields within a subform. So you have a subform with fields in it that are bound to data nodes in some data connection and the subform itself is bound to a repeating data section in that same data connection. For this scenario, you'll find it much easier to place your fields on the first page and then shrink the page (which is a subform) such that it's snug against the fields you placed on it and looks more like a row rather than a page. This is because the page subforms are, by definition, subforms parented to the root subform (named "form1" by default on new forms -- check it out at the top of the Hierarchy palette) and the root subform is, by definition, flowed. By using the page subform as your repeatable subform for your data records, you'll find it much easier to quickly get to a state where all records show-up on your form and new pages get added when previous ones are full (can't fit any more rows).

Once the subform is placed in a flowed container (parent subform), you must then specify that it's a repeatable subform by going to the Binding tab on the Object palette and checking the "Repeat subform for each data item" box.

After these two easy steps are complete, you'll then automagically get an Instance Manager object on the repeatable subform you just defined.

Script Access

It's nice to have a repeatable subform but unless you're just using the default behaviour (which may very well be just fine in most cases), you'll need to write scripts that use it's Instance Manager's properties and methods and you'll need to know how to access the Instance Manager in your scripts.

It turns out there are two ways of doing this:

  1. instanceManager Property: As long as you have at least one existing instance of the repeatable subform, you can access its instanceManager object directly like you would any other property: RepeatableSubform.instanceManager. Note, however, that this property will only be accessible if at least one instance of the RepeatableSubform object exists (which could be a problem if you've specified that its minimum allowed number of instances is zero and its initial instance count is zero as well).
  2. Underscore Prefix: The other recommended way to access a repeatable subform's Instance Manager is to use, for lack of a better term, "the underscore-prefixed repeatable subform name" object. That is, whenever a subform becomes repeatable, its Instance Manager is added to the Script Object Model as a child of the subform's parent container (remember, that's the flowed subform from earlier) and is given a name that is the repeatable subform's name with a "_" prefix. Therefore, if your subform was named "RepeatableSubform", its Instance Manager would get a name of "_RepeatableSubform" and you would access it as a property of the flowed container subform like this: FlowedContainerSubform._RepeatableSubform. The nice thing about accessing it this way is that you always have access to it -- even if no instances of the RepeatableSubform object currently exist.

Properties and Methods

Now that you know how to make a subform repeatable and get access to its Instance Manager object when writing scripts, here are the various properties and methods that you have access to:

Properties

  • count: Returns the number of instances that currently exist. This is very useful when writing loops that do something to each instance.
  • min: Returns the minimum allowed number of instances. When removing instances, you'll get a scripting error if the resulting number of instances is less than the minimum and the minimum number of instances will still remain (even if you meant to remove them). This can be set using the Min property on the Binding tab of the Object palette or my accessing the subform's "occur" element and setting its min property: RepeatableSubform.occur.min = "value".
  • max: Returns the maximum allowed number of instances. When adding instances, you'll get a scripting error if the resulting number of instances is more than the maximum and no additional instances will be added. You can modify this in the Object palette's Binding tab or with the following property: RepeatableSubform.occur.max = "value".
  • occur: This is a reference to the pertaining subform's &lt;occur&gt; element which lets you modify its minimum (min), maximum (max) and initial (initial) number of instances.
  • name: This property sets the name of the Instance Manager object itself. I doubt this would ever really be useful but it's a property nonetheless. Be forewarned, however, that it'll affect all subsequent scripts. For example, if you were to write "FlowedContainer._RepeatableSubform.name = 'myIM';" and then write "FlowedContainer._RepeatableSubform.addInstance(0);", you would get a an error stating that "_RepeatableSubform" doesn't exist. That's because you've changed its name to "myIM" and therefore should write "FlowedContainer.myIM.addInstance(0);".

Methods

  • addInstance(bool merge): Adds a new instance of the repeatable subform and returns a reference to the new instance (or null if no instance was added) . Setting merge to 1 will cause any additional data to be merged with the new instance. Also, don't forget that new instances aren't automatically added to the form's calculation dependencies. Also, be careful not to add more than the maximum number of allowed instances (see the max property).
  • removeInstance(int index): Removes the instance with the specified zero-based index. Remember that if you're running the form in Acrobat 7.x, you may need to call xfa.form.remerge() after removing an instance. Also, be careful not to remove more than the minimum number of allowed instance (see the min property).
  • moveInstance(int fromIndex, int toIndex): Moves the instance at the zero-based fromIndex such that it becomes the instance at the zero-based toIndex (and other instance in between are shifted up/down as necessary).
  • insertInstance(int position [, bool merge = 0]): [New in Acro 8.0] Inserts a new instance at the zero-based position specified and returns a reference to the new instance (or null if no instance was added). You may optionally specify 0 or 1 for the merge property which, if set to 1, will merge any additional data with the new instance. The same rules and gotchas apply for this method as they do for the addInstance method described above.
  • setInstances(int count): Adds or removes instances to/from the end of the set depending on the difference between the current instance count and the specified instance count. In other words, if there are currently 4 instances and you call "setInstances(6)", two new instances will be added to the end of the set. Conversely, if 3 instances exist and you call "setInstances(2)" the last instance in the set will be removed. Note that this method abides by the currently-specified min and max restrictions.

Recommended Scripting Language

On a last note, I simply wanted to recommend using JavaScript as opposed to FormCalc when scripting the Instance Manager -- especially when adding or removing instances. While FormCalc technically fully supports the Instance Manager, I personally find it's a little flakey. JavaScript support, however, is quite consistent and stable.


Updated: November 15, 2006

November 9, 2006

oneOfChild Scripting Property

Sometimes it's necessary to access the properties contained within a field object's UI property. For example, you might want to highlight only the content area (the value portion as opposed to the caption portion) of a text field. The problem with doing that is that you need to access the UI property's one-of property in order to get at the Border object that it contains. The "ui.{one-of property}.border" object controls the appearance of the field's content area. (Tip: It's also the object that's affected when you use the Appearance property on the Field tab in the Object palette).

If you were to look at the XFA specification supported by Designer 7.1, you would find, on page 664, that the UI element contains various one-of properties (which describe a set of properties from which only one may be specified at any given time) which vary depending on the type of field you've created on your form. For example, if it's a text field, it has a

field.ui.#textField

property whereas a drop down list would have a

field.ui.#choiceList

property.

Based on the various definitions of XFA fields, you would have to write the following script to change a text field's content area color (shown here in FormCalc):

$.ui.#textField.border.fill.color = "255,0,0"

But what about a drop down list? Because it uses a different UI one-of property ("choiceList" instead of "textField"), you would have to write the following script (shown here in JavaScript) to change its content area color:

this.ui.resolveNode("#choiceList").border.fill.color.value = "255,0,0"

This is all fine as long as you know the type of object on which the script will be executed but what if you wanted to change the content area color of all fields on your form? You might decide to define multiple arrays that contain references to text fields, numeric fields, etc. (one array for each type) but that wouldn't be very practical because you would have to remember to keep that list up-to-date for every change you made to your form.

A much easier way to do this is simply by using the oneOfChild scripting property: It's designed to return a reference to the one-of property that has been specified in the Object Model on the object on which it is used. In other words, if you used it on a text field's UI property, it would represent the "#textField" property whereas on a drop down list, it would represent the "#choiceList" property.

If we put this in practice, it means you could use the following script to change the content area color of any field object you find on your form (shown here in FormCalc):

FieldObject.ui.oneOfChild.border.fill.color = "255,0,0"

where "FieldObject" would be a variable holding a reference to a field object on your form.

Of course, the use of the oneOfChild property also works because the Border property is common to all UI one-of properties. There are some cases where some properties are specific to a certain "UI type" (like the "#choiceList.textEntry" property). In those cases, you may need to verify that you are, in fact, attempting to access those properties on the right kind of UI one-of property or else you'll get a scripting runtime exception.

October 31, 2006

Expanding to Fit the Entire Value

A while ago, I posted an article detailing a trick to make the value of a field be displayed entirely within the field's content area. Essentially, by setting the value font size to zero, this tells Acrobat to shrink the field's content area (value) font at will to make the entire value entered fit horizontally. This can certainly be useful but there's one significant drawback: the value font may shrink such that it becomes too small for anyone to read depending on how much data the user enters into the field.

Fortunately, there's an alternative method to making a value fit within a field's content area when you don't know how long the value will be: Making the field's width and/or height expandable!

The advantage of this solution is that the value font's size remains constant (the same as which you specified it to be when you designed the form). When a field is make expandable, its "w" (width) and/or "h" (height) attributes are replaced by "minW" (minimum width) and/or "minH" (minimum height) attributes, respectively. These attributes define the initial and minimum size of the entire field (that is, its caption and content areas combined). When the width is extended, however, only the field's content area is increased in width. Its caption area remains the same width. On the other hand, causing the height of a field's content area to be extended will also cause the caption area's height to be extended.

Making a Field Expandable

Specifying that a field's width and/or height is to expand to fit its content is quite simple: You just need to check the "Expand to fit" check box that pertains to the width and/or height property on the Layout palette.

Getting a Tight Fit

So far, you've learned how to make a field's width and/or height expandable, essentially by specify a minimum width and/or height instead of a set width and/or height. Now what if you wanted to ensure that the field's width and/or height was always just wide enough to contain whatever value was entered? For example, you don't want a whole bunch of empty space if you set a minimum width of 2 inches to have a nice initial size to enter a value into the field but the user only entered a value that required 1 inch to be entirely displayed.

In that case, you could simply set the minimum width to zero when the user leaves the field, if they've entered a value. This is done by scripting the field's Exit event (shown here in FormCalc):

$.minW = "0"

Look-out for Long Values!

One thing you have to look-out for is extra long values -- especially if you haven't specified a maximum length for the field. If the user enters too much data, the field might simply run off the page.

If you don't want to set a maximum data length for the field but you don't want it to expand beyond a certain width, you can set a maximum width and/or height. Since Designer's UI doesn't let you set this property directly, you can use the field's Initialize script to set it (shown here in JavaScript):

this.maxW = "4.5in"; // max width of 4.5 inches

Note, however, that if the user enters more data than can fit within the specified maximum dimensions, the value will be cut-off and won't print so you may consider setting a maximum data length or resorting to the previous solution (setting the font size to zero).

Sample

This sample form implements the solution I've described in this post.

Download Sample [pdf]

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

October 28, 2006

Scripting Table Columns

A few days ago, Sergio, one of my regular commenters, posted a question about programmatically adding new columns to a table. My reply to his comment quickly turned into something that I thought should be promoted to a blog post so here it is.

This question required some investigation because it led me to the discovery of a bug related to adding/removing instances of a table column in a form viewed in a version of Acrobat prior to 8.0. More on that later in this post.

The short answer to Sergio's question is that yes, in fact, you can modify the set of columns in a table programmatically at runtime. You can do this by either using the presence attribute -- although this isn't recommended because it can lead to data merging problems -- or you can use Instance Managers to do it, which is the recommended method to use.

Here's a sample form that contains a table with a "repeatable column". Using the add and remove buttons that are provided, you can add and remove instances of the 3rd column.

Download Sample [pdf]

Minimum Requirements: Designer 7.1, Acrobat 7.0.5.

Continue reading "Scripting Table Columns" »

October 20, 2006

Using URL Requests in PDF Forms

Here’s a sample in response to Ernest’s question on passing values to PDF forms via URL. His intent is to use it to provide a key to a PDF form such that it can be used to filter records from an ODBC Data Connection in order to pre-populate the form with data.

I love these kinds of questions because they challenge me to find answers!

Of course, this is quite specific but there are many other uses for this. In fact, you could have a whole lot of fun with it too! You could even use this to alter the appearance of your form: Say you had one form that you were using for multiple departments in your company and every department had its own header. You could place each header in a subform, make them all hidden and then, based on the URL request which would include a department code, decide which one to show — no XML Data file, database connection or web service needed!

Beware, however, that URL request strings are not secure because they get posted in plain text for anyone to read so be careful of the information you pass-in to your form this way.

This sample form looks for a “message” and a “color” key in the URL request in order to show a message in a text field and change the text field content area’s color (an RGB value). For example:

Download Sample [pdf]

Minimum Requirements: Designer 7.0, Acrobat 7.0.

Continue reading "Using URL Requests in PDF Forms" »

October 12, 2006

Displaying All Records from an ODBC Data Connection

So far, I’ve covered two ways of connecting forms to ODBC data sources:

  1. Connecting a Form to a Database — explains how to design a form which lets the user iterate through each record one at a time; and
  2. Selecting Specific Database Records — takes the first tutorial one step further by providing a means to specify which record(s) to view.

This time, in response to Y. Gautham’s recent comments, I’ve decided to post a little tutorial on displaying all records from an ODBC data connection for reporting purposes (as opposed to editing).

Continue reading "Displaying All Records from an ODBC Data Connection" »

October 7, 2006

FormCalc Expressions (Foreach)

A couple of weeks ago, I started a series of posts on FormCalc Expressions. The first ones I convered were the If and For expressions. This time, I thought I would explain the Foreach expression.

Continue reading "FormCalc Expressions (Foreach)" »

September 29, 2006

Selecting Specific Database Records

Every now and then, someone posts a comment with a question on how to do something and the answer requires more than just a quick response. In this case, it was Ricardo’s question on how to select a specific record from a data connection to a database for editing in a form.

If you read my previous post on Connecting a Form to a Database, you might’ve realized that the result was a single live data connection to the entire set of records in a database. This is great if you want to iterate through all records one at a time and update them on an individual basis. You might’ve also realized that you could narrow the scope of the data connection by specifying a more specific SQL statement (with a WHERE clause, for example). But what if you wanted the form to filter, on the spot, the data loaded from the data connection? For example, you might want to let the user pick from the different movie categories (action, comedy or drama) and then let them iterate through only that subset of the Movie Database.

If you’ve been scratching your elbow, pinching your nose and blinking your eyes in hopes that this might “just work”, well, it’s actually scratch your nose, pinch your elbow and roll your eyes — ok, just kidding…

Continue reading "Selecting Specific Database Records" »

September 23, 2006

Getting a List's New Selection

Have you ever struggled to figure-out what item from a list (list box or drop down list) a user had just selected in the list’s Change event? If so, it’s possible you were trying to use the

rawValue

property in order to get at this information.

Unlike other objects such as exclusion groups, the rawValue property of a list object doesn’t reflect the new selection until the selected value is committed to it (by the user tabbing or clicking away from the list). That means that if you’re trying to, say, make a certain field visible at the moment when a particular item in the list is selected, you can’t use the rawValue property because it still contains the old (previous selection) value.

Instead, you must use the

xfa.event.newText

object/property of the Change event itself and possibly the list object’s

boundItem

function in order to determine the value associated with the new selection.

Continue reading "Getting a List's New Selection" »

September 4, 2006

Making a Table of Contents

One of the hottest topics on the Designer Forums these days seems to be methods by which one can add a table of contents to their form. Since there are many different ways to achieve this, I thought I would post a little sample to demonstrate how this can be done.

Of primary concern when adding a table of contents to your form is ensuring that the links provided to the form’s various pages/sections remain valid at all times. Static forms whose page set never changes don’t really need to worry about this but dynamic forms do. That is, regardless of whether content pages are added, re-ordered or even deleted from your dynamic form, you need to ensure that when a TOC link is clicked, the user is taken to the correct page pertaining to the topic they selected.

The best way of ensuring that TOC links don’t get broken as a result of changes to the form’s pages is by using layout information provided by the XFA Layout Model. This model provides information such as an object’s actual dimensions (width/height), the page on which it is located as well as a few other interesting pieces of information.

By using the

xfa.layout.absPage

we’re able to get the page number on which an object is currently located, taking into account the various pages which may have been inserted or removed in-between the TOC page and the page in question. Taking that page number and assigning it to the

xfa.host.currentPage

property then sets the current page to the one on which is located the object in quetsion.

Download Sample [pdf]

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

Continue reading "Making a Table of Contents" »

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.

Continue reading "Complex Validations" »

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 4, 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.

Continue reading "Tracking Mouse Clicks" »

July 24, 2006

Previewing as Dynamic PDF

Have you ever lived through the frustration of previewing a form on which you’ve written a simple little script to affect the presence of an object when a button is clicked and pulling your hair out because you keep clicking on the button and nothing happens (object doesn’t disappear, there’s no error message, etc.)? Or maybe you’ve been in a situation where you’ve added script to a button in order to insert a new instance of a repeatable subform and while clicking on the button doesn’t produce one, the submitted XML data file contains entries for each new instance that was added?

Even if you haven’t, it may happen someday so here’s the remedy for the case of the “Common (Static PDF) Flue”…

In Designer, there are two different locations containing settings which affect the type of temporary PDF file created when you click on the Preview tab:

  1. Under the “File Options” section within the “Document Handling” panel of the “Tools | Options” dialog, you can set the Default File Type for New Forms. This type is used on new forms which you preview prior to saving (because Designer doesn’t know which format you’ll use at the time of preview). This is set to a static PDF format by default.
  2. Even if you’ve specified a dynamic PDF format for the Default File Type for New Forms, this setting may be overridden by the Preview Type and XDP Preview Format properties in the “File | Form Properties” dialog in the “Defaults” panel once you’ve saved your form. The Preview Type property, set to “Interactive” by default, determines whether the form will be previewed as an interactive (dynamic) form or as a print (static) form. This property supersedes the PDF format. The XDP Preview Format property usually picks-up the Default File Type for New Forms setting and determines what PDF format will be used to preview your form should it be saved as an XDP. (Note that if you’ve saved your form as a PDF file, then the XDP Preview Format setting is ignored).

Now that we’ve covered the different properties which affect the PDF preview format, here’s how to kick that flue I was talking about earlier (so that you actually do preview in a dynamic PDF format and stop pulling your hair out):

  1. If you haven’t saved your form, make sure the Preview Type is set to “Interactive” and that the Default File Type for New Forms is set to a dynamic PDF format. You may also want to set the XDP Preview Format to a dynamic PDF format while you’re at it.
  2. If you’ve saved your form as a dynamic PDF, make sure the Preview Type is set to “Interactive”.
  3. If you’ve saved your form as a static PDF, none of these settings will help you. You must first save your form as a dynamic PDF.
  4. If you’ve saved your form as an XDP, make sure the Preview Type is set to “Interactive” and the XDP Preview Format is set to a dynamic PDF format.
  5. If you’re tired of running into these problems and want to avoid them in the future, just set your Default File Type for New Forms to a dynamic PDF format.

I hope this tip improves your form design health. It did wonders for me!


Updated: January 27, 2007

July 16, 2006

Form Variable Tip

Form Variables can be useful because you can store string values into them that are persisted across object events as opposed to variables created locally (within scripted events) which are destroyed at the end of every event.

You can create Form Variables by going to the Variables tab within the Form Properties dialog, accessed via the main File menu.

The catch is in the way you get and set their values: If you’re writing a FormCalc script, then you can manipulate them just like any other variable. For example, let’s say you define one named “MyVar”. In your script, you can then write

MyVar = "test"

to assign the value “test” to it and you can write

xfa.host.messageBox(MyVar)

to display its value in a message box.

If you were then to change the script’s language to JavaScript and attempted to display the variable’s value in a message box as above, you would get the following error:

GeneralError: Operation failed.
XFAObject.messageBox:1:XFA:form1[0].#subform[0].Button1[0]:click
Argument mismatch in property or function argument

This is because FormCalc helps you by doing things “under the hood” based on the context in order to get and set the value of a Form Variable whereas JavaScript doesn’t.

If you look at the XFA definition of the variable, it looks like this:

<variables>
  <text name="MyVar">test</text>
</variables>

This means that the value of the “MyVar” Form Variable is actually contained within the MyVar element’s content which must be accessed via the value property. Therefore, in order to access a Form Variable’s value in JavaScript, you need to do this:

MyVar.value = "test"; // set the value
xfa.host.messageBox(MyVar.value); // get the value

Just like being able to simply write a field’s name like this in a FormCalc script in order to access its value

MyField

as opposed to having to write

MyField.rawValue

in JavaScript, you need to specify the property you’re accessing on the object in JavaScript when accessing the value of a Form Variable (which is always the value property).

July 8, 2006

Demystifying IM.AddInstance

The definition for the Instance Manager’s addInstance method is as follows:

addInstance( [BOOLEAN param] )

Personally, I find the word param a little vague. I think it should be called merge instead because that would give a clearer indication as to what it stands for: Essentially, if the data you’ve merged into your form has a repeating section that you’ve bound to a repeating (dynamic) subform and not all instances of this repeating data section have been merged into the form’s layout, you can control whether the new instance of the dynamic subform pertaining to that repeating data section will get merged with the next section or not by specifying true (merge — the default) or false (don’t merge — show empty instance) as the parameter value.

Usually, this doesn’t make a difference because you typically merge all instances of a repeating data section into your form and once there are no more data instances to merge, addInstance(true) will yield the same results as addInstance(false): an new empty (no data merged-in) instance.

Consider, however, the case where your form is a report and you’d like to show only a glimpse of the data instead of loading all 2000 instances of a repeating section. If your data was sorted from the most important to the least important, you could significantly improve the performance of your report (with respect to load time) by limiting the number of instances of that repeating data section that are initially merged into your form. Once your form is loaded, it could provide ways for the reader to get more instances if they wish to do so.

This can be achieved by using the Max property on the dynamic subform’s Binding tab in the Object palette along with addInstance(true).

Here’s a sample which has a data connection to an XML Data File that lists movies. There are 16 movies in total but the form limits the number of movies initially displayed to 10 and provides a Show More button. When this button is pressed, the movie dynamic subform’s Max count property, initially set to 10, is incremented by 1 and a new movie instance is added using true as the parameter.

Download Sample [zip]

Minimum Requirements: Designer 7.0, Acrobat 7.0.

Note: If you open the form in Acrobat, don’t forget to import the data into it using the “File | Form Data” menu.

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.

Continue reading "Process All Fields" »

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.

Continue reading "Invalid Flashing Fields" »

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

Continue reading "AcroForm Field Name Generator" »

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.

Continue reading "AcroForm Objects" »

June 5, 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.

May 25, 2006

Remove, Remerge

There’s a bug currently logged against Acrobat 7.x where removing an instance of a dynamic subform, using the Instance Manager (IM)’s removeInstance(int) method, doesn’t cause an update to the IM’s count of currently instantiated subforms.

OK, so maybe that was a little too technical so I’ll try to simplify: In Acrobat 7.x, when you remove an instance of a dynamic subform using the IM’s removeInstance(int) method and then check the number of remaining instances using the count property, the number you’ll get won’t reflect the number that remains.

Adobe is aware of this bug and will hopefully be providing a fix for it in an up-coming release.

Fortunately, there’s a simple work-around (and even if the bug gets fixed in a future release, you should probably be checking the version of Acrobat that’s running your form to determine whether you need to be using the work-around or not):

_DynamicSubformInstanceManager.removeInstance(3);

// Force a remerge of the form's current data set with its template
//  to cause an update to the "count" property of all IMs
xfa.form.remerge();

Continue reading "Remove, Remerge" »

May 20, 2006

Add, Recalculate

Here’s a little something that very important to know when working with dynamic subforms and using their Instance Managers (IMs) to add instances of them to a form: New subform instances aren’t automatically incorporated into the form’s calculations!

You would immediately notice this problem if you had a form which calculated a total based on line items which can be added or removed from the form. When you add a new line item using the IM’s addInstance(bool) method, the grand total of, say, the cost of all line items isn’t updated to reflect the data you enter into the new line item. This is because the new line item hasn’t been made part of the form’s set of calculation dependencies and is therefore not accounted for by your “grand total” calculation. As a matter of fact, until a call to xfa.form.recalculate() is made, none of the new instances added will be included in the form’s calculations.

To demonstrate this, I’ve created a sample form which has a “line item” section to which line items can be added. As line items are added and their costs changed, the grand total fields at the bottom calculate the total cost of the order: One grand total field performs its calculation using the FormCalc SUM function and a SOM Expression identifying the collection of fields to total while the other uses the LineItem dynamic subform’s IM object to calculate the grand total. Finally, there’s a check box at the top of the repeating “line item” section which, when checked, will cause the “Add Item” button to execute the xfa.form.recalculate(true) statement after adding a new line item. To see the difference, play around with the form by adding line items with the check box both checked and unchecked.

Download Sample [zip]

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

May 19, 2006

Is that Field Empty?

As a form designer, you can be almost certain that there will be a time when you’ll need to check a field to see whether its value is empty (whether the form filler has specified a value).

For longest time, I had been doing it like this in JavaScript:

if (theField.rawValue == null || theField.rawValue.length == 0)
  // field is empty
else
  // field has been filled

That served me quite well until yesterday when I found myself needing to do the same, but this time in FormCalc. Theoretically, the following should’ve worked:

if (theField == null | theField == "") then
  // field is empty
else
  // field has been filled
endif

But for some reason, the FormCalc interpreter was giving me a hard time with the null keyword (maybe it’s because I don’t use it very often and it was upset at me, I don’t know — if you’re a developer, you’ll understand that sometimes, code can have feelings and a mind of its own ;) ) so was forced to try and find some other way to check if a field’s value is null in FormCalc and I found one:

theField.isNull

The isNull property (of the XFA Scripting Model’s node object) “indicates whether the current data value is the null value”, as stated on page 422 of the Adobe XML Form Object Model Reference (located in the XML section).

This means that the following JavaScript expression checks whether a field’s value is empty:

(theField.isNull || theField.rawValue.length == 0)

And in FormCalc:

(theField.isNull | theField == "")

May 18, 2006

Instantiating Subforms at Runtime

Something that’s often desired, when working with dynamic forms, is to let the user specify, at run time (in Acrobat), how many lines of information are required to specify what they want.

The classic example is a Purchase Order form on which there are objects (usually buttons) which let you add and remove item lines from the section of the form which calculates the total cost of the order based on how many items are being ordered and the quantity and cost of each item.

In order to achieve this, the use of the Instance Manager is required. The Instance Manager is an object which exists only on repeatable subforms (this could be a subform which is parented to a flowed subform and set to repeat for each data item or it could be a row in a table — since rows and tables are, essentially, subforms). With this special object, you can modify the set of instances of a repeatable (dynamic) subform.

Continue reading "Instantiating Subforms at Runtime" »

May 15, 2006

Calculate Scripts

I just learned something very interesting while answering a forum thread just now which I thought I should share with the rest of you.

In all the scripts I’ve written so far, I had never realized this simple rule: When writing calculation scripts to set the value of certain fields, the line in your script which assigns a value to the field on which the Calculate event was fired should always be the last line in your script. This is because the last-executed line in your script is the one that the XFA Object Model will use to determine the value of that field.

While it’s generally not recommended to do much more than set a field’s value in the Calculate event, reality is that most of us use it for much more than that: Since it fires on all fields on a form after a field’s value changes, we may use it to affect the presence, color, dimension, etc., of some fields which depend on the values of other fields. Therefore, it’s very easy to fall into this trap where your calculation script isn’t working probably simply because you haven’t structured it correctly.

Continue reading "Calculate Scripts" »

May 5, 2006

Debugging Scripts

Here’s a simple tip that could make a huge difference in your ability to debug your scripts in Designer:

If you use the JavaScript language for a script, you can use the following function to output information to the Acrobat Console:

console.println("string");

Anyone who has attempted to debug their script(s) in Acrobat knows that it’s a painful thing to do. Unfortunately, many only know about

app.alert("string");

or

xfa.host.messageBox("string");

which gets the job done but not without some headaches and, in certain cases, RSI in your index finger! The other problem is that showing a message box can cause differences in the form’s behaviour especially if you’re trying to debug a script which is setting focus to an object on your form. The fact that a dialog is displayed can really mess things up.

By using console.println, you can output text to the Acrobat Console (when in Acrobat — even Preview in Designer — just press Ctrl + J to display it) so that you don’t change the behaviour of your scripts.

The ability to debug scripts is something we know needs serious attention in Designer and trust me, we’ve talked about it and we have plans to address these issues but I can’t speak about anything definite at this time.


Updated: November 1, 2006