Posts in Category "Scripting"

Tables with Variable Numbers of Columns

Support for tables in Designer assumes you know in advance how many columns there will be in your table. Occasionally this will not be the case.  You might want a table that adapts to the number of columns that occur in your data.  In other words, you want a table with rows where a cell is represented by a repeating subform.  And while Designer doesn’t support creating such a table via the table UI, it is possible to create one using a bit of script

The technique involves leverages the fact that tables and rows are really just subforms with special attributes.  So when designing the form, we create:

  • A subform to represent the table.  Make it a subform where content flows top-to-bottom.
  • Inside the table subform create one or more row subforms.  Make the row subforms flow left-to-right.
  • Inside each row subform, create combinations of fields, text and subforms that will be treated as columns.
  • It’s best to make the cells growable in width and allow the layout engine to determine best fit.  If you want control over width, leave the columns growable and set the columnWidths attribute on the table subform (see script below)

And then to turn this into a table, add this initialization script to the table subform:

// mark this subform as a table
this.layout = "table";

/** 
* if needed, we could explicitly set the column widths * If left alone, the columns will be auto-sized */ //this.columnWidths = "1in 2in 2in 2in 1in";

And then for each row in the table, add this initialization script:

// mark this subform as a row
this.layout = "row";
if (this.instanceIndex === 0) {
    // mark the first row as a header
    this.assist.role="TH";
} else {
    // mark each subsequent row as a regular table row
    this.assist.role="TR";
}

Note that setting the role attribute is very important for accessibility — it allows the screen reader to use the table header cells to describe the contents of the table body cells. Here is a sample form that implements this technique — along with sample data for previewing.

Fix Layout Problems

This is the fourth of four exercises that were part of the LiveCycle best practices lab at MAX.

Fix Layout Problems using XFADebugger

The purpose of this exercise is to fix layout problems by analyzing their PDF with XFADebugger.pdf.

In Designer, open: EX4 LayoutProblem.pdf (with preview data file: afiList.xml)
In preview, notice that :

  • Content was pushed off the first page
  • The second page is truncated

Open XFADebugger.pdf in Acrobat. Use the “Load XFA/PDF file” to open EX4 LayoutProblem.pdf.

  • Explore the content in the debugger view. Expand the trees. Click on nodes.
  • Click the “Find Warnings” button several times to see various warnings reported.
  • Fix the warnings in Designer

Here is the file with the layout problems fixed: EX4 LayoutProblem (Solution).pdf

Moving code into a shared script object

This is the thrid of four exercises that were part of the LiveCycle best practices lab at MAX.

Create a shared script object fragment

Open the file: EX3 addressCopy.pdf in Designer

Preview the file with sample data and note the behavior when you check "same as billing".  The shipping address values are greyed out and the values are synchronized with the billing address.

The Exercise:

  • The logic to copy values and to change the appearance of the destination subform is implemented under the
    “Same as Billing” checkbox.
  • Take the script methods to copy subforms and “grey-out” data, centralize them in a script object
  • Create a re-usable fragment from the script object

Bonus 1:

  • Change the form properties so that script changes are not preserved:
    Form Properties/Run-time/Preserve scripting change… : Manually
  • Open the form in Acrobat, select “Same as Billing”, save, close and re-open the form
  • Notice the Shipping address is no longer read-only
  • Fix by applying the read-only setting in an initialization script

Bonus 2:

  • Both instances of the country field populate a list of provinces or states. Centralize/share this code.

Solution: EX3 addressCopy (Solution).pdf

Customize a Validation Experience

This is the second of four exercises that were part of the LiveCycle best practices lab at MAX

Implement Field Validation

Open EX2 Validation.pdf in Designer

  • Preview and click the “Submit by email” button to validate the form
  • Notice:
    • Errors are reported with message boxes. 
    • Selecting the error message in the listbox highlights the error field
    • Tabbing out of the listbox will set focus to the selected error field.
  • Change the form properties to customize error handling to: “Don’t show any message boxes”
  • Modify the (propagated) validationState event under the root subform:
    • On error: color fields and modify the toolTip value
    • Else: revert field color and toolTip
  • Bonus:
    • Add a change event to the phone number to restrict entry to digits

The solution file is: EX2 Validation (Solution).pdf

Reverting changes to fields:

To write re-usable code for changing values back to their original state, we should never hard-code the changes. E.g. we should never revert a color by writing: Field.fillColor = "255,255,255". The original color may not have been white.

Rather, after modifying an element, we can revert by removing the changed element. Removing an element forces the XFA processor to use the original value found in the form template.

E.g. Change the font color by modifying the font.fill.color element:
Field.font.fill.color.value = "255,0,0";

Revert the change by removing the color element:
Field.font.fill.nodes.remove(Field.font.fill.color);

Or by removing one of the ancestor properties:
Field.nodes.remove(Field.font);

Changing/filtering keystrokes

  • In the change event, xfa.event.change holds the contents of your key stroke (or paste buffer)
  • E.g. force all input to upper case:

    xfa.event.change = xfa.event.change.toUpperCase();

Script Performance Exercise

I enjoyed attending MAX last week. The best part was putting some faces to names of people who had previously been cyber-personalities.

We did a pre-conference lab on LiveCycle best practices. For the lab I prepared four exercises on various aspects of form design.  For those who weren’t able to attend, I’ll post those exercises as a series of blog entries.

Exercise #1: Measure and Improve JavaScript Performance

In this exercise we optimized a fairly simple script – a few lines in a loop. Depending on your machine, you should be able to improve the performance anywhere from a factor of three up to a factor of seven.

Open EX1 Performance Tests.pdf in Designer.

There are 50 subtotal values that need to be totaled. Each total has a test button that executes the Total script 500 times and measures the performance.

Note the 4 variations of the sum function:

  • Test 1: The original (slow) version of the calculation
  • Test 2: A copy of Test 1 that you will improve. 
    Look at the hints in the code comments in order to improve the script.
  • Test 3: The solution
  • Test4: Same calculation expressed in FormCalc

Understanding the solution:

  • Undeclared variables are very expensive to reference
  • Javascript variables are far less expensive to reference than XFA objects
  • Evaluate as few dots as possible. e.g. c.d is faster than a.b.c.d
  • Dots evaluated inside a resolveNode(s) expression are faster than dots evaluated in JavaScript objects. E.g. resolveNode("a.b.c") is faster than a.b.c

Why is FormCalc faster?

All references to XFA objects from JavaScript involve a round-trip from the script environment to the XFA processor and back. These round trips are expensive. 

FormCalc runs native in the XFA processor and there are no round-tripping costs.

Validate image size

Let’s talk about image size (again).  But this time in the context of data capture. We have this wonderful image field that allows your users to attach image files to their form.  This is great. but it’s also scary.  You likely don’t want to use this for the 10MB images that came straight from their digital camera.  Consider that these images will be base64-encoded and inserted with the rest of the form data — and stored in the PDF.  You want to limit them to images that are reasonably sized. 

Today’s sample has an image field with a validation script that checks the size of the loaded image.  For an image field, field.rawValue will return the base64 value of the embedded image. The length of that string will tell you how big the image is. My validation script does a rough conversion — image size is roughly 3/4 the size of the base64 string. If that size is greater than your threshold you can choose to either reject the image (set the field to null) or simply mark the field as invalid.

More Macro Goodies

It’s been quiet here.  I have been mostly heads-down preparing for MAX.  I hope I’ll see many of you there.  If not, the content I’m working on will eventually find its way to this blog.

But today the focus is on some things to get you further ahead with macros.

Updated Samples

I’ve included a zip file with the following:

  • updated lint check macro
  • updated accessibility checker macro
  • updated form report macro
  • sample macro to use as a ‘starter’ for new macros

You can simply unzip this file under your Designer install directory and they should immediately work.

If you’re writing new macros, you should appreciate sample.js.  It includes:

  • A convenience method for emiting messages to Designer’s log panel
  • A convenience method for recursively iterating over the contents of your form
  • A ‘catch’ block that does a good job of reporting script errors in your macro.

Have a peek at the comments in the macro for more details.

In full disclosure, I admit that one of the reasons for updating these macros is that I found a couple issues where old macros didn’t work in the new version of Designer.  But not to worry, there are workarounds available and the new versions of these macros work even better than the old ones.

Dreamweaver

Not surprisingly, I use Dreamweaver to edit my macro javascript files. It is possible to extend the object assist/hinting mechanism of Dreamweaver.  Take this designer.xml and place it under the Dreamweaver/configuration/CodeHints directory.  Then, next time you start DW, you will have code hinting for the designer object.

I’d like to extend this to more of the XFA object model, but that’s a project for another day.

Macros in ADEP Designer 10.0

Well, here’s a poorly kept secret now made public.  The macro feature that we previewed in the ES2 Designer is now an officially supported feature.

You can find the online documentation here.

The good news is that the macros you wrote for ES2 should continue to work in the new Designer.  The only changes is that we’ve changed the directory name where they’re stored (now under "macros").  As well, each macro is described by a macro.xml file.

There are a couple items I should hightlight:

There is a method: designer.callExternalFunction() that allows you to call an arbitrary method in a .DLL. Here is your escape key for every integration problem you might conceive.

designer.filterNodeTree() is added to enhance performance.  Traversing the entire DOM searching for specific elements can ge slow on large forms.  For example,
designer.filterNodeTree(xfa.template, "className", "font")
will return all the <font> elements in the form. 
designer.filterNodeTree(xfa.template, "name", "price")
will return all the objects named "price".

If you produce .chm help files to document your macro, you can invoke them with designer.showHelp()

And, just to give you an excuse to try this out, I’ve written a new macro.  This one will append a floating field reference for an existing field to a text object.  Here are the macro.xml and javascript files. Usage: select the text and field objects, then invoke the macro.

 

A Runtime Accessibility Checker

I continue to be interested in the challenges of developing forms that give a good experience for visually impaired users using screen readers.  Previously we have focussed on checking accessibility properties at design time. However, analysing the form design template doesn’t always fit the user workflow.  There are cases where accessibility properties are populated at runtime by script or by data binding. In this case, these properties are un-populated in the design version and consequently, the design-time accessibility checker will give warnings — yet at runtime the accessibility experience may be just fine.
In addition, we want to make sure that the accessibility experience adapts to changes in dynamic forms. We need to be able to test accessibility in various states — e.g. after subforms are added or removed.

The obvious answer is to perform accessibility checking at runtime — while the form is open in Reader or Acrobat.  Today we’ll look at a solution to that problem.  A mechanism for instrumenting your forms to allow you to analyse the state of accessibility information at runtime.

The Solution

For those of you who want to use the solution without necessarily understanding how it works, follow these steps:

  • Drag this fragment anywhere onto your form.  It will add a hidden subform with no fields (just script)
  • With the form open in Reader/Acrobat, paste the value “AccCheck” into any field on the form
  • From the resulting dialog, select which accessibility checks to run
  • Copy the results from the report dialog for future reference
  • All elements with accessibility issues will be re-drawn with a 2pt red border.  If the element is a field or draw, it will also be shaded light red and the accessibility error text will be assigned to the tooltip.
  • Close your form and go back to Designer to fix any identified issues

Try it out with this sample form. Note that this form dynamically populates some properties.  The Designer-based accessibility checker will report twice the number of errors.

Hopefully the embedded checker is innocuous enough that you could leave it in a production form.   But if not, you should be able to inject it during your testing phase and remove it when you’re ready to deploy your form.

The Deep End

For those who want to know how this works…

The challenge is to add something to your form without introducing an observer effect — i.e. analytics that can run without changes to your form UI and without impacting normal form processing.

The runtime accessibility checker is designed as a form fragment that you can drag onto any form.  The fragment is a single, hidden subform with no fields, no data binding — just script.
We want to trigger the analysis script without requiring the user add a “check accessibility” button to their form.  In this case, I added a propagating change event to the fragment subform.  This script checks the xfa.event.change event property.  If the change text is equal to “AccCheck”, we launch the accessibility checker.
The checker uses JavaScript dialogs to prompt for checks to perform and has a second dialog to display the results.
The JavaScript performing the checks is adapted from the Designer version of the accessibility checker.  The main difference is that it analyses the Form DOM (the runtime) instead of the Template DOM (the design). 

Code Sharing Example

Today I’d like to talk about code sharing within form definitions. We’ll start by talking about the advantages of code sharing and then get specific about techniques to support code sharing in form definitions. We’ll start with the advantages.  For those of you with a programming background, this is review.  But bear with me:

  • Less code.  Once you have a library of shared code, you will write less code in your event scripts.
  • Faster development. Writing less code means faster code development.
  • Lower maintenance costs.  Less code means les code maintenance
  • Consistent behaviours within a form and between forms.  Shared code means that the same action from different places in the same form or between different forms will behave consistently. And it means that changing the behaviour is done by modifying the centralized logic.
  • Simplify complex operations for use by novice form designers.  A shared code library can offer simple interfaces to complicated functions.

But … to get the benefits of code sharing you need to make an investment:

  • Recognize repeated patterns
  • Generalize the repeated operation
  • Isolate the variable parts of the operation into parameters

The generalized, shared version of functionality has higher up-front development costs.  However once the initial investment has been made in shared code, the ROI will more than compensate.

Ok, enough of talking about things you already knew.  Lets walk through a specific example.  I reviewed a form recently that had lots and lots of code to copy field values between subforms. The sort of thing you might do if, for example, you were copying a billing address to a shipping address.

Assuming subforms named S1 and S2 and fields named F1 – F5, the code initially looked like this:

S2.F1.rawValue = S1.F1.rawValue;
S2.F2.rawValue = S1.F2.rawValue;
S2.F3.rawValue = S1.F3.rawValue;
S2.F4.rawValue = S1.F4.rawValue;
S2.F5.rawValue = S1.F5.rawValue;

As written, this is not very good code.  It’s verbose and tedious.  And if any fields are added, removed or renamed, the code will need to be changed.

Now, let’s look at a progression of changes we can make to improve this code.  First of all, a little JavaScript tip. When you see an expression such as S2.F1, it can also be written as S2["F1"].  With that knowledge, we can re-write the script as:

var fields = ["F1", "F2", "F3", "F4", "F5"];

for (var i = 0; i < fields.length; i++) {
   S2[ fields[i] ].rawValue = S1[ fields[i] ].rawValue;
}

It’s a little less verbose, but still fragile.  Let’s change it so that our list of fields is not hard-coded:

var srcFields = S1.resolveNodes("$.#field[*]");
for (var i = 0; i < srcFields.length; i++) {
  var fieldName = srcFields.item(i).name;
  // if the same-named field exists in S2…
  if (S2.nodes.namedItem(fieldName)) {
     S2[fieldName].rawValue = srcFields.item(i).rawValue;
  }
}

Notice that the script starts by using resolveNodes() to get a list of fields from the source subform. It then checks if the same named field exists in the destination subform.  If it’s in both places, we copy a value over. This is a big improvement.  It means that if any fields are added, removed or renamed the script will continue to work.  But we’re still not sharing code.

The next step is to generalize the function:

function subformCopy(dst, src) {
  var srcFields = src.resolveNodes("$.#field[*]");
  for (var i = 0; i < srcFields.length; i++) {
    var fieldName = srcFields.item(i).name;
    // if the same-named field exists in S2…
    if (dst.nodes.namedItem(fieldName)) {
      dst[fieldName].rawValue = srcFields.item(i).rawValue;
    }
  }
}
subformCopy(S2, S1);

Here we’ve isolated the copy functionality into a function. The next step is to move that function into a script object.  Now our script is a one-liner:

utilities.subformCopy(S2, S1);

Great! we copy subform contents in one line of script.  Now to really increase the value, take the script object and make it a fragment so it can be shared between forms:

Once the function has been shared, it can be enhanced or fixed and all forms using it will benefit.  In this case we might choose to make the subformCopy() method handle fields in nested subforms.

Up to this point I’ve talked about using script objects for code sharing.  There are a couple more things to say:

  1. I see people using execEvent() to share code.  e.g. they put script in a change event and use execEvent to call it from the initialize event. I don’t favour this pattern: The code readability is poor.  The performance is not as good (much less overhead to call a script object than it is to call execEvent). 
  2. Propagating events offer another code sharing technique. Isolating functionality in a propagating event means that you write the code in only one script and it is reused in many fields.

And one last point — do you hard-code color values or border widths in your code?  Consider moving these hard-coded values into script objects or form variables. The impact isn’t as dramatic as with shared code, but it is good practise.