Posts in Category "Debugging"

Updated Accessibility Checker

I recently got some feedback that the accessibility checker I posted for Designer would fail for some forms.

I have updated the script to handle the condition that was causing it to fail.  The new version is in the updated Accessibility.zip download.   This time I also included the mxml source used to create the SWF dialog — just in case you want to customize it for your own use.

Meanwhile, I also added a check for a new error condition:
"Invalid next node reference: <ref>"

This error will appear in the case where the markup generated for node traversal contains an invalid SOM reference.

If you’re using this checker and have any ideas for improvement, please let me know.  It’s a high priority for us to enable customers to create accessible content.

Usage notes

A couple more notes about the checker, and macros in general:

  • When a macro issues a message, the message appears in the log tab of Designer. If the macro script fails for some reason, this is also where the failure message will appear
  • With the very latest Acrobat update, you might find that the report PDF gets launched with a yellow bar warning.  The only way to get past this right now is to turn off enhanced security In Reader/Acrobat.  Obviously we’ll have to figure out a better solution if/when this capability makes its way to a supported product feature.

An Accessibility Checker for PDF Forms

One of the great things about PDF forms is that they have a really good accessibility story. You can create PDFs and PDF forms that allow the visually impaired user to interact with the document with the help of a screen reader. However, the quality of the experience is entirely in the hands of the PDF author. If your PDF consists of only a scanned page, the accessibility story is very poor. If your PDF form is richly annotated with audible cues, then the accessibility experience should be very good.

BTW, if you need help, here is a good resource for Accessibility Guidelines.

The problem is that your average form author does not test their result with a screen reader. They may understand the guidelines to make a form accessible, but they do not get feedback as to whether the guidelines have been followed correctly.

How well did your form author do with enabling accessibility? I have written an accessibility checker that will give at least part of that answer. I have written a designer macro (see Designer Macros) that will examine a form and validate the common properties that contribute to accessibility. (I need to repeat the usual caveat – Designer macros are a prototype/kick-the-tires/unsupported feature of Designer ES2).

When you run the macro, this is the dialog the designer user sees:

A graphic of a dialog showing accessibility checks that can be run on a form

Some explanation around the various checks:

Fields without captions
A caption is far more valuable in providing context than independent text that may have been placed near the field. With this option the macro will check that all fields have captions except for those that appear in a table cell.

Field with no assist text

There are several sources of data to provide context information for a field: field name, caption, tool tip, and custom (speak) text.  "assist text" refers to either a tool tip or custom text. If the field does not have one of these, then the macro reports a warning.

Images with no alternate text

Similar to above, we insist that an image has either a tool tip or custom text. If an image is explicitly marked with "Screen reader precedence: none" then we won’t report a warning.

Tables with no header row

Screen readers have special treatment for tables. Navigating through a table with a screen reader makes sense only if there is a header row providing some guidance.  In the absence of a header row we’ll issue this warning.

Read order and tab order diverge

Because of the hierarchical nature of form definitions and the way screen readers traverse them, there are limits on the flexibility of read order.  Specifically, it is possible to define a tab order where the order jumps out of a subform and back in again.  Read order does not allow exiting a subform before all content has been read.  The macro makes sure that the tab order for each subform has only one exit.  If there are two exits, then read order will not be able to follow tab order.

Report

Once the script has done its work analyzing the form, it generates a report using the included report template.  The report looks like:

A two column report with one column of SOM expressions and a second column with error descriptions

Ideally the macro mechanism would have access to the warnings palette, but for the time being, a report will have to be enough.

Here is a Zip file with the files needed for the macro, along with a sample form where I managed to break all the accessibility rules.

I am interested in getting feedback from users if there are other useful checks to make the coverage of the macro more extensive.

Using the Acrobat JavaScript Debugger with XFA

Many of you are already aware that the JavaScript debugger inside Acrobat partially works with XFA forms.  You can turn on the debugger in the Acrobat preferences:

image

When debugging is turned on, the Acrobat JavaScript console will allow you to navigate to objects in the XFA hierarchy and set break points in a subset of events:

 image

BUT (and you knew there was a "but" coming) there are some serious limits:

  • Using the debugger with XFA forms is not officially supported
  • We cannot debug script objects
  • Storing break points doesn’t seem to work.  This makes it hard to debug an initialization script (unless you force your form to do an xfa.form.remerge())

On the other hand, even with these limits, many of you will find the debugger useful.

There’s another "but" and this is really my main reason for posting on this topic. 
You need to turn off the debugger when you’re not using it.

There are two reasons:

Exception handling.  Note the first dialog above has the option to break when an exception is thrown.  If you’re not expecting it, this option can circumvent normal JavaScript processing.  e.g. it is good JavaScript practise to use try/catch to quietly handle error conditions.  With try/catch we can detect an error condition and allow the application to continue uninterrupted.  But when the debugger has been told to break on exceptions, the quiet thing doesn’t happen any more.  The form stops and you get a message to the console.

Performance.  Do you remember the game of life form? I used it to illustrate some performance characteristics in this blog entry.  The form has a couple of buttons with around 30,000 lines of JavaScript in their click event.  Under normal circumstances, these scripts finished in between 4 and 10 seconds.  With the JavaScript debugger enabled, these took … 10 minutes.  That’s right, roughly a 50 times slow down in script performance with the debugger enabled.

The Inevitable Question

When will XFA have proper JavaScript debugging support?  This is a hard question to answer.  But it gets asked a lot.  Believe me when I say that  we’ve taken a run at this problem many times in the past.  But the fact remains that there are some substantial technical barriers that are holding us back. 

Updated Form Debugger

Last year I posted a sample form that was useful for debugging the internals of dynamic forms: Debug merge and layout.  In the mean time, I’ve discovered a couple bugs.  Time for a maintenance release.  The main changes are:

  • Does a better job finding the coordinates of objects — anything inside a subformSet, fields inside exclusion groups.
  • Reports the breakBefore and breakAfter properties correctly
  • General code cleanup and a bit of optimization

As I’ve mentioned before — this tool is useful not only as a way to debug your merge/layout, but also a good way to visualize what’s going on inside the the XFA processor.

Here’s the updated debugger form.

Updated Message Handler and Debug Fragments

As I mentioned, I need to update a number of previously distributed script objects as fragments.  The reasons for the updates are:

  1. Make sure the code passes the lint check
  2. Add version information so that we can check whether your copy of the fragment/sample is stale or not
  3. New functionality/bug fixes

Message Handling

This entry discussed handling exception objects in a JavaScript catch block.

Here is the updated fragment.  You ought to be able to simply copy it into your fragment library.

The updates to the script include:

Fixed line end handling in Designer

Scripts marked runAt="server" will run when you save a form in Designer (this is explained here).  Any log messages issued by those scripts are emitted to the log tab in Designer.  For line breaks to appear correctly in this window, the line endings need to include both a carriage return and line feed.  The getServerMessage() method now makes sure to format the message accordingly.

Handle String Exceptions

The message handler functions can now handle the case where the code throws a string.

To see how this is useful, suppose you’re writing code for a click event.  Part way through your 200 lines of code you encounter a condition where you want to give an error message and exit.  But you can’t.  You’re not in the context of a function call, so you can’t simply say "return;" and get out.  You end up putting a big conditional branch in your code:

try {
    if (errorCondition) {
        xfa.host.messageBox("This event failed");
    } else {
        … 150 lines of remaining code …
    }
} catch(err) {
    xfa.host.messageBox( scMessage.handleError(err) );
}

The alternative now is to simply throw a message string and get out.  Your revised event code looks like:

try {
    if (errorCondition) {
        throw "This event failed";
    }
    … 150 lines of remaining code …
} catch(err) {
    xfa.host.messageBox( scMessage.handleError(err) );
}

Debug Trace

This entry showed how to easily add debug trace information to your JavaScript functions.  The trace() method has been updated to include handling for function parameters with null values.

Here is the updated fragment.

Dependency Tracking

One of the really cool aspects of XFA forms is the dependency tracking mechanism.  Dependency tracking is the feature where your field (and subform) calculations and validation re-fire whenever any of their dependents change.  Today I’ll explain a bit about the mechanics of how dependency tracking is implemented as well as have a look at possible design issues related to this mechanism.

Simple Example

Suppose my purchase order form has order rows with fields: price, quantity and subtotal along with a total field.

The subtotal field has a JavaScript calculation: "price.rawValue * quantity.rawValue"

The total field has a FormCalc calculation: "sum(order[*].subtotal])"

Now whenever price or quantity fields change, the corresponding subtotal field will be recalculated automatically.
Whenever any of the subtotal field values change, the total calculation will re-fire.

Discovering Dependencies

When the form is first opened, all the calculations and validations are executed.  While executing a calculation or validation, the XFA engine keeps track of each object (field, subform, data node) that gets referenced during the script execution.  Each referenced object is added to its dependency list.  In our example, each subtotal field becomes dependent on the price and quantity fields from its row.  The total field becomes dependent on all the order subforms and on all the subtotal fields.  The mechanism is robust enough that even if you reference your objects indirectly, we still find the dependency.  e.g. the subtotal calculation could be written as:

parent.nodes.item(0).rawValue + parent.nodes.item(1).rawValue

Since the nodes we visited were still price and quantity, the dependencies are established just the same.

In some cases, the dependency list will grow as values change.  Consider this calculation:

if (A.rawValue < 100) {
    A.rawValue;
} else {
    B.rawValue;
}

Suppose the first time it executes, field A has a value of 20.  This means the code will not execute the else clause and will not access field B.  The initial dependency list will include only A.  However, if the A of changes to 200, the calculation will re-fire; the else clause will be evaluated and field B will be added to the dependency list.

If your calculation or validation calls a script object, dependencies will continue to be tracked during the execution of the script object method.

Dependent Properties

What constitutes a field change? What changes cause a calculation to re-fire?  I don’t have a complete list.  But changing a field’s visual properties (colours, captions, presence) do not cause a dependent to recalculate.  Changing the value or changing the items in a list box will cause a dependent calculation/validation to re-fire.

Turn off Automatic Calculations

If, for some reason, you’re not happy with the default dependency tracking, then you can turn it off. There are two switches to control calculations and validations independently:

xfa.host.calculationsEnabled = false;
xfa.host.validationsEnabled = false;

Note that turning validationsEnabled off disables not only script validations, but also turns off mandatory field and picture clause validations.

Dependency Loops

It is possible to get into a situation where sequence of dependency changes goes into an infinite loop. In these cases, the XFA engine will allow the calculation or validation to re-fire a fixed number of times (10) and then will quietly stop re-firing the calculation or validation until the next time a dependent changes.  While the 10 times limit is ok in most cases, I have seen forms where this has been the root of performance issues.  If you have a dependency loop and your calculations are re-firing, you want to be aware of it and you need to fix it. 

Dependency loops are typically introduced when a calculation or validation modifies another field.

I should highlight that statement.  If your calculation or validation minds its own business and never modifies another field you shouldn’t run into any loops.  There are lots of cases where a calculation or validation modifies another field and everything works fine — but tread carefully.

Looping example

In order to track when my validations were changing, I wrote validation scripts write to a debug field:

field T1 validation:

debugTrace.rawValue += this.rawValue;
true;

field T2 validation:

debugTrace.rawValue += this.rawValue;
true;

Both T1 and T2 now have a dependency on field: debugTrace

The sequence of events:

  1. Field T1 changes
  2. T1 validation fires and modifies debugTrace
  3. T2 validation fires and modifies debugTrace
  4. T1 validation fires and modifies debugTrace
  5. T2 validation fires and modifies debugTrace

Here is a sample form to illustrate this example.

Note that if we remove the validation from T2, the circularity stops.  The validation on T1 modifies debugTrace, but after the validation completes, debugTrace does not change and T1′s validation does not re-fire.

Client/Server Scripting Differences

One of the brand promises of XFA is that you can design your form once and re-use your form design in multiple contexts.  Most notably: design once and re-use for both data capture and for printing.  Re-use means that the form looks the same whether rendered as interactive PDF or PDF/A or Postscript or PCL or ZPL …  It also means that your calculations and script logic work the same in all environments.

We process XFA documents in multiple environments — in Acrobat/Reader and in LiveCycle server — via Forms and Output.  We refer to Acrobat/Reader as "client" and LiveCycle as "server".  Our challenge is to make sure that the forms get consistent treatment in both client and server.

The good news is that you could design hundreds of forms and never run into any differences between the XFA implementations on the client and server.  But if you are making extensive use of JavaScript, you will want to be aware of some boundaries.  Many users begin designing forms for only one environment — usually for interactive PDF first.  I have witnessed a couple of occasions where users with extensive script frameworks were surprised when their forms did not work on the server right away.  There are some differences.  In this post I will list those that I am aware of.

Different Engines

I have previously pointed out that we use different JavaScript engines on the client and server.  The JavaScript engine inside Adobe Reader is the Mozilla engine.  The engine on the server is an Adobe-implemented engine (Extendscript).  Extendscript is also used in all the Adobe Creative Suite applications.  The good news is that JavaScript is a standard.  Being based on a standard allows differing JavaScript engines to behave compatibly.  The bad news is that standards are not always implemented consistently.  Just ask an experienced web author who writes JavaScript for multiple browsers.

Handling Differences

Before describing specific differences, it is important to note that you can write script conditionally to handle the different environments.  The xfa.host.name property allows you to write code such as:

if (xfa.host.name === "Acrobat") {
    // do client stuff
} else if (xfa.host.name === "XFAPresentationAgent") {
    // do server stuff
}

Different Exceptions

When the XFA runtime encounters an error, we generate a JavaScript exception.  The exception objects have different properties on the client and server.  If all you ever do with an exception is call the toString() method, you don’t need to worry about the differences.  If you want to examine more details of the exception, you need to know about the differences.  Read more here.

Strict Scoping

Lots has been written about strict scoping: here and here.  The main take away is that while the client has support for non-strict scoping, the server has always been strict.  For forms to be compatible, choose strict scoping.  (Choose strict scoping even if you’re not concerned about server compatibility).

Script Objects

I have noticed recently that on the client, script objects inherit properties of their parent subform.  e.g. the expression "this.border" inside a script object returns the border property of the parent subform.   On the server, the script object does not inherit:  "this.border" is undefined.  To ensure compatibility, make sure your script object doesn’t make use of the inherited properties.  If you need to access parent subform properties, reference the subform explicitly (by name).

Global Variables

JavaScript variables declared in calculations have different scope in client and server. Variables used in scripts on the server are global to the form.  Variables used in scripts on the client stay local to the script.  For example, suppose you have two fields F1 and F2 with these calculations:

form1.S1.F1::calculate – (JavaScript, client)
var foo = "field 1 value";
this.rawValue = foo;

and

form1.S1.F2::calculate – (JavaScript, client)
this.rawValue = foo;

On the server, F2 will evaluate to "field 1 value".  While on the client it will return a JavaScript error.

To ensure compatibility, make sure your variables are always declared.  Or better yet, limit the number of global variables you declare.  Put them inside functions.  Regardless, the best practise is to make your code strict by running jslint.

Logging

On the client we frequently use console.println() to issue debugging trace statements.  However console is an Acrobat object that appears only in the client context.  On the server use
xfa.log.message(); and your debug trace will appear in the server log file.

References to "$"

If you write script in FormCalc you will know that the "$" symbol refers to the current object. It is the equivalent of "this" in JavaScript.  What you may not know is that "$" works just fine in client side JavaScript.  However, it does not work on the server side.  Extendscript reserves the use of "$" as a global object to get and set system information.  e.g. $.stack will return a string with the current call stack.  For compatibility reasons, avoid using "$" in JavaScript.  Do not use it as a shortcut to "this" and do not use it as a variable name.

Script to Data

There are client/server differences in how you access data via script.
The short explanation:

If your root dataNode is named "form1" then on the client you can reference "xfa.datasets.data.form1".  On the server, "form1" does not appear as a child of "xfa.datasets.data".  Instead, you get to your root dataNode using $record (or xfa.record).  Using $record works reliably on both client and server.

The detailed explanation (definitely in the deep end):

There are settings in configuration (xfa.config) that impact how a form is processed.  Some config settings will be different on the server than on the client.  One such setting is xfa.config.present.common.data.adjustData.

Without getting into too much detail, the purpose of turning this setting on is to make sure that form data gets adjusted to match the form template so that we can reliably round-trip our forms — i.e. make sure a form will bind to data the same way after save/close/re-
open.  Having adjustData turned on requires more expensive processing.  On the server we usually set adjustData=false — since round-tripping isn’t a concern.  The impact on script is that when adjustData is false, we disconnect our data records from the datasets.data node.  Since they are disconnected, the expression xfa.datasets.data.form1 (or $data.form1) will not work.  But $record will reliably return the form1 node.

Enforcing Strict JavaScript

I have recently discovered JSLINT (www.jslint.com).  This is lint processing for JavaScript.  For the under-40 crowd, lint is a unix utility we used back in the days when C compilers were not strict.  Used to be that you could do things in C that were blatantly wrong — and detectable — but the C compilers and linkers happily gave you enough rope to hang yourself.  lint analyzed your C source code, reported errors, and encouraged you to make your code more strict. 

Now we have JavaScript.  A wonderful utilitarian language that also has a ready supply of hanging rope.  A sizeable share of our JavaScript programming errors happen because we inadvertently make use of the laxness that is part of JavaScript.

This is where JSLINT comes in.  JSLINT is a utility (written in JavaScript) that will analyze your script and report on errors it finds.  When you visit www.jslint.com you can paste your code into the HTML page, and you will get a report of errors and other information about constructs in your script.  JSLINT will continue to complain about your code until you have reduced it to a strict subset that can be used reliably.

I ran some of the code I’ve written recently through JSLINT and I was… embarrassed.  I was determined to make all my code strict.  (Hard to find a better motivator than embarrassment.)  The problem is that copy/pasting code from Designer to www.jslint.com got pretty tedious.  Happily, the author of JSLINT (Douglas Crockford) has made the source code shareable.  I took the source for JSLINT and incorporated it into LintCheck.pdf.  Clicking the button on the form will prompt you for a PDF or XDP file.  It will extract all the script, run JSLINT and report on what it finds.  Here is a sample form with some coding errors and the resulting report.  (You need Acrobat to run LintCheck.pdf.)

One of the things I quickly discovered is that JSLINT reports many issues that are not necessarily related to language/grammar strictness, but rather are more a matter of enforcing good programming style.  Good programming style in turn reduces the likelihood of bugs.  eg. At first I was annoyed when JSLINT insisted that all my control blocks be enclosed in braces, but I do understand how that coding practise makes source code more predictable — especially when you consider the potential ambiguity of an expression such as: "if (..) if (..) else (..)"

Some more details on using LintCheck.pdf:

Global References

In the script output, global references are highlighted in blue.  If you see something highlighted in blue that is not a reference to a form object, then it is likely an undeclared variable. If you want to turn off this highlighting, insert a special comment in your code.  e.g. /*global Price Quantity */ will turn off highlighting on script references to the Price and Quantity fields.  commonly referenced globals such as "xfa", "console", "app" etc. have already been turned off.

Options

If you look at www.jslint.com you will see a number of options that make JSLINT more or less strict.  Some of those options are relevant only to JavaScript embedded in HTML pages.  The more generic options are included in LintCheck.pdf.  When I check my code, I turn on the options for "Strict white space", "Disallow undefined variables", "Disallow == and !=" and "Disallow bitwise operators".

Embed JSLINT directives in your code

If you want specific options to be enforced selectively in your code, you can control them with comments.  For example, to turn on white space checking, insert this comment: 

/*jslint white: true, indent: 4 */

To help you build up this string, there is a field on the form that shows what the comment would look like for the chosen options.

Conflicts with XFA Properties and Methods

Script writers who use variable names that conflict with XFA properties or methods will run afoul of the "naked field processing" (as explained here).  Since JSLINT provides a list of variables and global references, LintCheck.pdf will report cases where users have a conflict in their script.

Tabs in Source Code

JSLINT is unhappy with combinations of spaces and tabs.  If your line of code starts with 2 spaces followed by a tab, most code editors will bring you to column 4.  JSLINT assumes this to be column 6.  It assumes that a tab character always adds 4 spaces.  If you mix tabs and spaces you will get complaints from JSLINT when strict white space is on.  You will also find that the character position of errors is reported incorrectly.

FormCalc

Lint processing does not apply to FormCalc.  But since LintCheck.pdf produces a script report, it seemed best to allow FormCalc scripts to be included in the output.  There is an option to control whether FormCalc is included or not.

No-op expressions

Suppose you have a calculation that looks like:

form1.#subform[0].check[6].result::calculate – (JavaScript, client)
Price.rawValue * Quantity.rawValue;

JSLINT will generate an error: "Expected an assignment or function call and instead saw an expression.".

However, this is a valid construct in our forms.  Calculations and validations are the result of the last expression evaluated in a script.  The version of JSLINT embedded in LintCheck.pdf adds a new option to control this behaviour.  To turn this check on (e.g. for script objects) add this directive in your script: /*jslint nonoop: true */

One reason to turn this check on is because for forms to work in Form Guides, calculations must be expressed as an assignment. e.g.

form1.#subform[0].check[6].result::calculate – (JavaScript, client)
this.rawValue = Price.rawValue * Quantity.rawValue;

Track Members

One common JavaScript coding error is to misspell a member name.  Look at this sample script:

var fruits = ["apple",
              "orange",
              "peach",
              "plum"];
// truncate the array
fruits.lenght = 2;

The intention was to truncate the array to 2 — but it doesn’t because I misspelled the length property.  The script runs without a runtime error — because JavaScript assumed we wanted to populate a new property called "lenght".  The solution here is to add a comment at the top of your script listing all the members you expect to see:

/*members length rawValue */

Now JSLINT will complain about the offending line because the "lenght" property is not in the l
ist of sanctioned members.

Performance

Forms with lots of script will generate very large reports.  Very large reports take a long time to generate.  Running LintCheck.pdf on itself takes around 15 seconds to complete on my desktop system (and generates 133 pages).

E4X Handling

Unfortunately, the parser in JSLINT is not able to handle E4X expressions.  Since LintCheck.pdf makes use of E4X to handle XDP files, I needed a workaround to avoid having JSLINT stop when it encountered my E4X expressions.  Turns out there are JSLINT friendly variations.  The script expression to find the root subform and script elements of an XDP originally looked like:

var sRootSubform = xXDP.*::template.*::subform[0];
var vScripts = vRootSubform..*::script;

The alternate expressions looked like:

var vRootSubform = xXDP.elements(
                                  QName(null,"template")
                                 )[0].elements(
                                  QName(null,"subform")
                                 )[0];
var vScripts = vRootSubform.descendants(QName(null,"script"));

 

 

Displaying Parallel Columns of Text

There is a common form design challenge where we want to place parallel columns of text side-by-side on a page.  We want the text in each column to grow vertically and to split over to the next page(s) as needed.

One Growable, Splittable Field

First the basics:  Before tackling side-by-side fields, how to make a single field growable and splittable:

  1. Make sure you have saved your form as an "Adobe XML Dynamic Form" or as an "Adobe XML Form".
  2. Drag a text field on to your canvas. 
  3. On the Object/Field tab select: "Allow Multiple Lines"
  4. On the layout tab select: "Expand to Fit" under the Y: measurement.
  5. For all subforms above this field in the form hierarchy select "Auto fit" under the layout tab
  6. For all subforms above this field in the form hierarchy select "Allow Page Breaks Within Content" under the Object/subform tab.

If you fill that field with enough data, it will grow to the bottom of the page.  Fill it with more data and it will cause an overflow.  A new page will be added and the field will split between pages.

Two Growable, Splitable, Positioned Fields

The next part of the challenge is to make this work for growable fields situated side-by-side.  If the parent subform has positioned layout, then it’s fairly easy.  Just place the two fields side-by side.

Two Growable, Splitable, Flowed Fields

If the parent is flowed left-to-right (Western text) then you won’t get the effect you want.  If the left-most field has enough data to overflow the page, then the result is that the other growable field doesn’t appear until the second page.  This is because in a flowable environment, the layout algorithm will finish layout for the first field before starting the second.  Once we have spilled over on to a new page we don’t go back and fill in more data on a previous page.

To make parallel columns work in a flowed environment, add the two fields to a table.  Make them sibling cells in the same row.  The layout algorithm for table rows will negotiate the row height between all the cells, and will make sure they start and end together.

Splitting Problems

When the layout algorithm wants to split content, between pages, there’s a step where it negotiates a split point.  When there are side-by-side objects, they need to be splittable at the same point in order to successfully split.  Some objects are not splittable: e.g. rectangles, images.  If they are side-by side with a splittable object, the form will not split. 

Multi-line fields are splittable by default.  However, for side-by-side multi-line fields to split, we need to find a common baseline.  i.e. we cannot split in the middle of a line of text.  We either split above or below a line.  Side-by-side text fields need to have lines that end at the same point in order to be splittable.  There are a large number of properties that can cause lines to be offset from one another — including: top margin, paragraph space before/space after, line spacing, vertical justification, typeface, font size, vertical scale. For example, if two side-by-side fields have top margins that differ by 3mm, then they will not share any common baselines — and we will not find a split point.  We are working on changes to the split point calculation that will be more tolerant of baseline mismatches.  But until then, you need to be very careful to make the properties of your fields exactly the same.

Blank Pages in Output

Once you have a form where content is not splittable and is too big for a page, you will often see blank pages in your output.  The reason for this is because of the way the layout algorithm fills pages.  It works something like this:

  1. Attempt to place an object on a page
  2. If the object is too large for the remaining space on a page, attempt to split the object to fit on the page
  3. If the object does not have a split point in the available space, start a new page
  4. If the object does not fit on the new page, attempt to split it.  If it does not have a split point in the available space, start another new page.
  5. If the object still does not fit, then place the object on a page and truncate the content

Newer versions of the XFA processor have managed to reduce the number of blank pages we see.  In a sense, I regret that we "fixed" those conditions because they are usually a warning sign that there is a layout problem that needs to be fixed.

Sample

A common use for side-by-side growable fields is to show text side-by-side with a translation.  The attached sample shows the preamble to the Declaration of Human Rights in French and English.  It shows them a second time with the fields offset slightly so that they couldn’t split.

Debugger Support

The debugger has been updated to include a new warning for splittable table rows that have more than one multi-line field.  Eventually the same warning should appear for positioned content.

BTW if you’re using the debugger, you’ll want to monitor the post where it was introduced for occasional updates.

Collected Form Development and Debugging Tips

In this post I have consolidated a bunch of tools and tips for developing and debugging XFA/PDF forms.

Use the console Object Effectively

Always show the console on errors

When your form encounters a JavaScript error, the error text shows up in the console.  But by default the console doesn’t appear — which means you could be getting errors without being aware of them.  To see errors as soon as they happen, configure Acrobat to pop up the console as soon as a message appears.  Under Edit/Preferences, set the options highlighted below…

consoleDialog

 

The console methods

console.println() is an indispensable tool for tracing what is happening in your form script logic.  It is much more effective than xfa.host.messageBox() or app.alert(), since it doesn’t require dismissing a dialog with every message.

Did you know there are other methods on the console object?  clear(), hide(), show().  You might want to place a call to console.clear() when your form initializes.  When it’s time to deploy your form, make sure you haven’t left any extraneous messages in the console.

The Server Alternative

Since the console object is an acrobat-specific tool, you need to use something different to emit trace message for server-based XFA forms.  For the server, use xfa.log.message() to write your message to the server log file.

Trace Function Calls

As long as you’re in the mode of dumping information to the console or log file, you may as well make it as easy as possible.  This blog entry describes a method for tracing the input parameters to JavaScript functions.

Have a look at the data DOM

It’s often useful to see what the data currently looks like.  One way to get there is to add a big text field that displays the data dom.  Give it this one line calculation: xfa.datasets.saveXML("pretty");  Set the field as bind="none".  Now you will have an up-to-date snapshot of the state of the data dom.

Re-factor your form

Design for re-usability.  Develop common patterns that are shared between forms.  Create script frameworks inside script objects.  Put the framework script objects in your fragment library.

Use the Merge/Layout Debugger

If your problems are in merge or layout, then try this.  (Try it even if you’re not having problems with merge/layout — it’s cool.)

Exception Handling

When an exception is thrown and displayed in the console (or in the server log file) the resulting message can be frustratingly cryptic. To get higher quality information (including a stack trace), catch errors and parse them into human-readable text.  There is sample code here to get you started.

String Manipulations

If you are manipulating lots of strings — especially if you’re parsing them, get to know the power of regular expression processing in JavaScript.  Have a look at this regular expression testing tool.

Be Version Aware

If you make extensive use of script objects or if you’ve dabbled in using dynamic properties on XFA objects then get educated about the effects of strict scoping and how it behaves in different versions of XFA and Reader.  Read lots more here and here.

Summarize your form

This post shows how to get a "big picture" summary of the form meta data and the content in the form.

Document your Script

Take the time to properly document your JavaScript methods.  Use JavaDoc conventions. Six months from now when you (or someone else) needs to modify the form, you’ll be glad you took the time to explain your form logic.  To generate a report of all the script in the form, look here.

Reduce and Isolate your problem

If you are struggling with a particular problem, try to reproduce the problem in a new, simplified form.  The act of isolating will often lead to discovering any outside influences are affecting your form behaviour.  If you’re still having the problem in your new simple form, then you have a simplified version of the problem that makes it easier to share with others when you ask for help.

Designer Script Editor

Turn on Line Numbers

In your designer script window, turn on line numbers by right clicking on the script window and selecting "Show line numbers" from the context menu.

Don’t Leave Blank Lines at the Top of your Script

Blank lines at the beginning of a script get trimmed before the JavaScript interpreter processes the code.  If there are blank lines, then any line numbers reported by exceptions won’t agree with the line numbers you see in Designer.

Shortcut Keys

<control>+f — bring up the find dialog. Any highlighted text is automatically used as the find string.  <control>+h — brings up the find/replace dialog.  F3 — find next.  <shift>+F3 — find previous.

Multiple monitors

Convince your boss you need two monitors.  Keep Designer in one monitor and place your script editor and Acrobat console in the second monitor.

"foo.bar is not a function"

Your script makes a call to foo.bar() and you get the mysterious message: "foo.bar is not a function".  But you look in your script object and it sure looks like it is defined.  The problem is that you have a syntax error somewhere in your foo script object.  This prevents the runtime from discovering the foo.bar() function.  When you see this message, run Designer’s script syntax checker.

Declare your Variables

Don’t code: for (i=0, i<myList.length; i++)
Instead, code: for (var i=0, i<myList.length; i++)
The JavaScript interpreters process explicitly declared variables more efficiently.

While you’re at it, use variable names that are unlikely to collide with names of objects on your forms. Such as prefixing variables with a "v" or "n" or "s".  (vObject, nOffset, sStringValue).

Beware of Ambiguous Expressions

If you’re writing code that is intended to be re-usable in multiple forms, beware of potential conflicts between property names and form objects.  For example, the expression subform.border.fill.color becomes ambiguous if this script in a context where the subform has a field child named &quot
;border".  To avoid ambiguity, use the expression: subform["#border"].fill.color.  The hash symbol specifies that what follows is a className instead of a container name.

Find the Root Subform

If you have re-usable script that needs to locate the root subform, you can use this expression:

xfa.form.resolveNode("#subform")

Or the slightly more terse:

xfa.form["#subform"]

Don’t Modify Your Form in XML Source Mode

If you need to change a property that Designer doesn’t expose in one of it’s property editors, then change the property via script.  For example: Designer doesn’t support making a positioned subform repeatable.  Rather than adding an <occur> element in XML source, set this property in script:

Subform1.occur.max = "2";
var vNewSubform = _Subform1.addInstance();

Batch Changes to your Form

For advanced users — if you need to make global changes to the contents of your form, this blog entry shows how a form author can write JavaScript to modify their template in Designer.  Likely a bit easier than writing XSLT.

Use FormCalc Functions — from JavaScript

FormCalc has some built-in functions that give access to some powerful functionality.  There is a way to bridge between JavaScript and FormCalc in order to use these functions.  Look here.

June 16, 2009 update

Use Strict JavaScript

Run lint against your JavaScript.  Choose options that make it as strict as possible.  Details here.