Posts in Category "Uncategorized"

xfa.context

Yesterday I discovered that we have an undocumented script object property that you might find useful.

The root xfa object has a property called “context”.  This property is a reference to the object that is hosting a calculation/validation or event.  i.e. it returns what is normally referenced by “this” in those scripts.

I recently came across a scenario where knowing the source context is very useful.  In my case, I wanted a function in a script object to know the calling context.  It worked well to use xfa.context.

I have attached a sample form where I have a set of expense report items.  I wanted to write a sum() function in JavaScript (rather than using formcalc).  Of course, I wanted the sum function to be re-usable so I put it in a script object on the root subform.  You call the function with a SOM expression that returns the fields to be summed:

utils.sum("expense[*].amount");

The challenge here is that the SOM expression is relative to the field calculation that uses it.  That means this code will not work:

function sum(sExpression) {
  // "this" is at the form root
  // and won’t find the result
  this.resolveNodes(sExpression);
  ...
}

One workaround is to provide a fully explicit SOM expression:

utils.sum("ExpenseReport.expenses.expense[*].amount");

But I don’t like this approach.  The calculation is now not encapsulated.  If the form author modifies the hierarchy in any way, the script will fail.  It also means that it’s very difficult to place this logic in a fragment where you don’t know the absolute context.

A second workaround is to resolve the nodes before calling the method:

utils.sum(this.resolveNodes("expense[*].amount"));

I don’t really like this either – it reduces the readability of the code. 
Instead, I used this:

function sum(sExpression) {
  // resolve sExpression in the context
  // of the calling script
  xfa.context.resolveNodes(sExpression);
  ...
}

Note that xfa.context is a read/write property, but at this point I have not found a useful reason to assign a value to xfa.context.

Away for a week

Ok, this time I really am ignoring you.  After today I’m off to cottage country.  I will have no access to the Internet, but plenty of access to boats, swimming, campfires, fishing, mosquitos,…  Back to work on July 6. Meanwhile, happy Canada Day and happy Independence Day.

200607160046

Scope of JavaScript Objects

In a previous post, I went into the details of how XFA objects are integrated with the JavaScript interpreter.  One of the challenges in integrating the XFA object model with JavaScript has been determining how long to keep objects/variables in scope.  Keeping too many objects in scope leads to high memory usage and performance issues.  To get a sense of the size of the problem, bear in mind that it is not just fields and subforms that result in JavaScript objects, but their properties as well.  A script that accesses field.border.fill will create three JavaScript object references.  When you multiply the number of fields and subforms by the number of properties that can be accessed via script, you can see that there is potential to create an unwieldy number of XFA object references in JavaScript. 

JavaScript Variable Cleanup

We wanted to change the Reader to clean up JavaScript object references more aggressively, but we couldn’t change the default behaviour without breaking existing forms.  The solution was to expose strict scoping rules as an option in Designer:

image

This setting allows us to change form behaviours to clean up object references more frequently.  Strict scoping was first available for forms targeting Reader 8.1.  However, in 8.1, the cleanup was more aggressive than necessary.  Reader 8.1 released references to *all* JavaScript variables after each script execution.  In Reader 9 we eased off a bit and no longer release JavaScript variables declared in XFA Script Objects. 

As a result, when targeting forms for 8.1 with strict scoping on, you need to make all your XFA Script Objects re-entrant.  Do not rely on JavaScript variables sticking around.  You might not immediately realize that your variable references have been released, since they will continue to work until the JavaScript engine decides to perform its garbage collection.

Private Properties

I have seen customers defining script that looks like this:

MyField.myCustomProperty = "hello world";

What happens here is that the JavaScript interpreter does what is expected for any JavaScript object — it adds a new property to the object hash map.  This coding practise is pretty attractive when you consider the benefits of associating arbitrary JavaScript variables or functions with XFA objects. 

But there is a problem — associating new properties with JavaScript object references requires keeping those JavaScript objects in scope.  Since with strict scoping the JavaScript references are released, the private properties are also lost.  Rather than have people discover this the hard way by having variables mysteriously disappear at random intervals (during garbage collection), Reader 9 will issue an error when script attempts to assign an unknown property — when strict scoping is on.

Recommendation

The non-strict mode of variable management will continue to be supported, but I do not recommend relying on those behaviours.  I recommend strict scoping for a couple of reasons:

  1. Bug fixes:  We have branched our code internally to take one path for strict scoping (variable cleanup) and a separate path for non-strict scoping.  Going forward, only the strict scoping code path will get script bug fixes.
  2. Scalable Design Patterns:  When creating script-intensive large forms with non-strict scoping you are liable to eventually run into the performance issues of too many JavaScript objects.  My preference is (where possible) to use design patterns that work well with both small and large forms.

My preferred alternative is to use form variables on subforms and the desc or extras elements on fields as described in a previous post.

Reader Survey

Happy new year!

The Adobe Reader team is taking a poll on priorities for upcoming Reader releases.
You are encouraged to go and complete a survey at the Reader blog.

Build a better exclusion group

,,,,

Some of the topics that I want to cover are too complicated to cover in a single blog entry. My plan is to break up topics into digestible chunks – progressively disclosing the solution. Exclusion groups will probably require two or three entries.

You probably know them as radio button lists. Internally in the XFA grammar they are called exclusion groups (
<exclGroup>)
. We had always planned for or exclusion groups to be more than just groups of radio buttons. But so far, enhancing them has not made the top of our feature request list.

Our radio button list object has some limitations that cause users to avoid them and write some (or lots) of script:

  • Ability to tab between selections in a radio button group. Current behavior is that you tab into a group and use the cursor keys to toggle different selections
  • Leave a group empty – (unselect entries)
  • Add text fields to radio button groups

Typical solution

I have seen users create a series of checkboxes and put on-change scripts on each field to make sure that the exclusion behavior is maintained. Take an example where there are three checkboxes named CheckBox1, CheckBox2 and CheckBox3. I find users writing this sort of script on each field:

form1.#subform[0].CheckBox1::change - (JavaScript, client)
if (this.rawValue == "1")
{
   CheckBox2.rawValue = null;
   CheckBox3.rawValue = null;
}

This script must appear on each field in the group, and is slightly different for each field in the group. Adding or removing field to/from the group means modifying the script of each field in the group.

An exclusion subform

In light of the fact that today is election day in Canada, I’ve developed an appropriately themed sample: exclusionSubform1.pdf

The design pattern that I have been working on is a subform object that enforces exclusion group behavior on the enclosed fields. Have a look at the sample, and you will see where this is going. Try tabbing between fields. Try selecting/unselecting. Try typing into the “other” field. Try adding a new field to the subform in Designer. Notice that it works automatically – no need to add extra script in order to add a new field.

If you are not interested in the gory details, you can go ahead and re-use the subform from this sample without worrying about how it works. Take the subform, remove the fields and add it to your object library. In the future you can drag one of these onto your canvas and any fields you place inside will inherit the exclusion group behavior.

For those interested in the specifics of this solution, I need to start by disclosing some of the techniques used in the sample.

Subform Calculate

In my sample, the logic lives entirely within the subform calculate script. A subform calculation is the ideal location to place our exclusion group logic. Thanks to the wonders of dependency tracking, each time any of the child values change, the calculate script fires and ensures that there is still only one child selected. 

When you look at the script, you will notice that I put all the logic into a function. This function can (and should) be moved into a central location where it can be shared. This is cleaner, since it means that the script will not be duplicated for each new exclusion group. It just adds a little book-keeping since the object you bring in from the object library is no longer self-contained.

Temporary Results

I needed to store a temporary result (the name of the selected field) in the subform. The most appropriate place to put the temporary result is under the subform variables element. Grammar-wise, this looks like:

<subform name=”ExclusionGroup”>
  <variables>
    <text name=”vOldSelectedField”/>
  </variables>
</subform>

To create this variable on-the-fly, we write this JavaScript code:

var vNewVariable = xfa.form.createNode("text", "vMyVar");
ExclusionGroup.variables.nodes.append(vNewVariable);

Exclusion Algorithm

Looking at the script itself, there should be enough comments to see what is happening. It flows like this:

  • Make sure we have a temporary

    <variables> element to hold the name of the old selected field
  • Loop through all the child fields; building an array of all those that have a value
  • Examine the array of selected fields. If there is more than one field with a value, turn off the old selected field
  • Set the
    <
    variables> element to point to the new selected field

Compatibility

This sample illustrates how you get the UI experience you want for an exclusion group – but you need to be aware that it will have some differences from the built-in exclusion (radio button) group. An element has a single rawValue property that represents the value of the selected field, and this is the value that gets saved to the XML data file. Our sample exclusion group will have individual values for each of the nested fields. Each individual field gets saved to the data file.

That is all for now. We have a new re-usable exclusion group in 37 lines of JavaScript code.

Next up: Subforms in the exclusion group…

Hello!

Welcome to my blog.  My name is John Brinkman, I’m the LiveCycle forms architect.  I have been with Adobe (and previously JetForm) for 13+ years.  Most of those years have been spent working intimately with the XFA technology. 

Does cyberspace need another yet-another-blog?  Well, for starters, I have to say that the form design space has some excellent coverage.  Mainly from Stefan Cameron: (http://forms.stefcameron.com/) but also the Designer team: (http://blogs.adobe.com/lcdesigner/) and Steve Tibbet: (http://blogs.adobe.com/stevex/).

My initial reason for starting a blog is to communicate some best-practises in form design to our customers and partners.  Over the past nine months I have been looking in detail at customer forms.  We know that for various reasons, form authors write too much script.  Having too much script in a form is bad for the environment:

  • Lots of script makes forms complex and expensive to develop, debug and test
  • Lots of script makes it hard for anyone but the author to maintain the form
  • Poorly written script results in poor performance

We want form design to be easier.  Writing script is not easy.  Our ultimate goal would be a form design experience that is script-free.  We intend to enhance Reader to address some of the issues that require scripting — but you can’t wait for the next version.  Many of you are designing forms that target Reader 7 or 8.  My goal here is to recommend some design patterns that will allow you to design better forms with less script for currently shipping versions of Reader.

What I will encourage you to do is to create re-usable general-purpose scripts. Put your complex and clever script into re-usable objects and then free up your novice form designer to use these as black-box magic to make their normal form design operations as simple as possible.

First up on my radar: Improving the radio button experience.