Archive for October, 2008

Data Binding with Predicates

Have you ever come across “inverted XML”? Take this example:

<addresses>
  <row>
    <column name="first name">Halla</column> 
    <column name="last name">Ayers</column> 
    <column name="street address">7466 Etiam Avenue</column>
    <column name="city">Arlington</column>
    <column name="state">BC</column>
    <column name="postal">T4K4O7</column>
   </row>
</addresses>

The question is, how do we bind a “firstName” field to the data value: <column name=”first name”>?

We would much prefer to deal with this data in the form:

<addresses>
  <row>
    <firstName>Halla</firstname> 
    <lastName>Ayers</lastName> 
    <streetAddress>7466 Etiam Avenue</streetAddress>
    <city>Arlington</city>
    <state>BC</state>
    <postal>T4K4O7</postal> 
  </row>
</addresses>

Then we would bind our firstName field to addresses.row.firstName. Easy.

But we do not always have control over the format of the data that we receive. If we are given the inverted form of the XML we have two choices:

  1. Convert to non-inverted XML using XSLT
  2. Bind to the data using predicate expressions

My preference is to avoid XSLT when at all possible. It would be very tricky with this example, because the names have spaces in them, so cannot be re-used as XML element names. Instead we use predicate expressions for binding.

Predicates

We find predicate filtering in both XPath and E4X.
The XPath expression to identify the “first name” value from the sample is:

addresses/row/column[@name=="first name"]

The E4X expression is:

addresses.row.column.(@name=="first name")

The XFA SOM expression is either:

addresses.row.column.[name=="first name"]

Or

addresses.row.column.(name.value=="first name")

The difference between the two XFA variations is that when brackets [] are used, the expression inside the brackets is evaluated as Formcalc. When parenthesis () are used, the expression is evaluated as JavaScript.

In all these examples, predicates are evaluated the same way. We take each candidate instance of the <column> element and evaluate the predicate expression in the context of that element. When the expression returns true, the element is selected.

Data Binding

This sample form (and sample data) has fields with binding expressions that display the set of addresses.

The (repeatable) address subform binds to addresses.row[*]

The firstName field binds to $.column.[name == "last name"]

The lastName field binds to $.column.(name.value == "last name")

Data Creation Problem

One significant issue with using predicates for binding is that we cannot easily create new instance data. i.e. it is not possible to infer the XML structure that corresponds to the binding expression.  If we were to simply create a new instance of the address subform, the data we create would look like:

<row>
  <column name=""/>
  <column name=""/>
  <column name=""/>
  <column name=""/>
  <column name=""/>
  <column name=""/>
  <column name=""/>
</row>;

To get around that, we create our (empty) data before we create a new instance of the address subform. The script looks like this:

addresses.addRow::click – (JavaScript, client)
// define the data for a new row
var vNewRowData =
  <row>
    <column name="first name"/>
    <column name="last name"/>
    <column name="street address"/>
    <column name="city"/>
    <column name="state"/>
    <column name="country"/>
    <column name="postal"/>
  </row>;

// load the new row of data into the data DOM
addresses.dataNode.loadXML(vNewRowData.toString(), false, false);

// add a new instance of the address subform,
// and tell it to bind to the new data 
_address.addInstance(true);

Notice that the address row has been specified according to the data schema and has been inserted into our data DOM before the address subform was created.  Then when we create a new instance of the address subform, it will bind to the data we pre-created.

The Deep End

Using this same data set, it is interesting to apply predicates in a slightly different way. In this example I wanted my form to display the US addresses first and the Canadian Addresses second. The form has separate subform definitions for each. If we had been working with the non-inverted form of the data this would have resulted in a pretty simple binding expression: $.row.[country == "US"]

But because of the inverted form of the data, the binding expression gets very complicated. We want to qualify the row element based on the column where name="country" and the value is "US". To get there we use a nested predicate to bind the row:

$.row.[ $.resolveNode("column.[name==""country""]") == "US" ]

The outer predicate filters <row> and the inner predicate filters <column>.

Numeric Constraint: no zeros

I came across a form design problem where the form author wanted to disallow a value of zero in their numeric fields. The desired user experience is that when the user enters a zero, then on exiting the field the field reverts to empty (null) – no need to emit an error message – just do not allow the (invalid) zero entry.

Display Pattern

If the concern is about the appearance of the field only, then the easiest way to implement this functionality is with a special display pattern: zero{}.

In fact, using this solution you could also have a zero value display some other indicator.
E.g. using: zero{‘n/a’}|num{$zzzz9.99} a field with zero value will render as: “n/a”, and when it is non-zero will render as a currency amount.

However, if the form author really wants to avoid storing a zero value, then changing the display is not enough. We need to actually coerce the raw value to zero.

On-exit script

The solution the customer came up with was to add an on-exit script to each numeric field:

form1.NumericField1[0]::exit(JavaScript, client)
if (this.rawValue == 0)
    this.rawValue = null;

But this is a labor-intensive solution. It requires adding this script to every single numeric field on the form.

See the attached sample form for two alternate methods of enforcing the “no zero” constraint. The sample uses a subform calculate script to monitor (and modify) the values of all the nested fields. The form uses two different methods for setting the values to zero: a recursive loop and a predicate lookup.

Recursive Loop

We use a script to loop through a subform and all its dependents checking for numeric fields with a value of zero:

form1.MovingExpenses1::calculate(JavaScript, client)
numericCheck.eliminateZero(this);

form1.#variables[0].numericCheck – (JavaScript, client)
function eliminateZero(vContainer)
{
    if (vContainer.className == "field")
    {
        // The typeof() check makes sure that we don’t
        //
try this on <text> fields
        if (typeof(vContainer.rawValue) == "number" &&
                               
vContainer.rawValue == 0)
        {
            vContainer.rawValue = null; 
        }
    } else
    {
        // If this object is not a field, then it
        //
may be a subform/subformSet/area
        // If so, recursively check all descendents
        var vChildren = vContainer.nodes;
        for (var i=0; i<vChildren.length; i++)
        {
            var vChild = vChildren.item(i);
            // .isContainer returns true if the element
            // is not a property
            // If so, call ourselves recursively
            if (vChild.isContainer)
                eliminateZero(vChild)
        }
    }
}

Note especially the check to see if the field needs to be reset to null:

if (typeof(vContainer.rawValue) == "number" &&
                       
vContainer.rawValue == 0)
{

    vContainer.rawValue = null;
}

Text fields will not be impacted by the check. In the sample, you could type “0” into the “Method of travel” field, and it will not be reset to null.

Using the recursive script has the best backward compatible story. It worked for me in Reader 7.1, while the predicate version worked for me in Reader 8.1.

Performance

Since this script is recursive, it may be tempting to simply place it under your root subform. This way you need only one instance of the script in your whole form. For small forms, this is fine. But consider what is happening here: Every time a user modifies a field value, this script fires and traverses the entire form, examining each field. The performance of the script will be inversely proportionate to the size of your form. Sooner or later you will reach a form size where performance starts to become an issue. The alternative is to embed the script in subforms in lower level sections of your form.

Predicate lookup

If your subform does not have any nested subforms, then you can perform a more terse, predicate-based check:

Some of you may be familiar with evaluating predicates in order to bind to “inverted” schemas. They can also be used when evaluating SOM expressions in the form DOM:

form1.MovingExpenses2::calculate(JavaScript, client)
var vZeroList = xfa.resolveNodes("$.#field.(typeof(rawValue) == ‘number’ && rawValue == 0)");
for (var i=0; i<vZeroList.length; i++)
      vZeroList.item(i).rawValue = null;

Breaking down the SOM expression we passed to resolveNodes() :

$.#field.(typeof(rawValue) == ‘number’ && rawValue == 0)

By the rules of predicate evaluation, this is the same as:

"for each child element with a className: ‘field’, evaluate this script:
typeof(rawValue) == ‘number’ && rawValue == 0
If the evaluation returns true (1) then add the candidate field element to the result set. "

Ok, so clearly the predicate version does not really have any clear advantage over the recursive loop; it was just a feeble excuse to introduce the topic of predicate expressions.

There is a lot that could be said about predicates, but that is a topic for another blog entry…

Form Variables

In the sample from my previous post I introduced an integer form variable without drawing attention to it.  In this post I will describe form variables in much more detail.

Creating Variables in Designer

In order to create a form variable from Designer, you choose file/form properties/variables.  In this screen shot there are two variables: "foo" and "bar". 

FormProperties

The resulting XFA markup looks like:

<template>
  
<subform>
      <variables>
         <text name="foo">123</text>
         <text name="bar">abc</text>
      </variables>
   </subform>
</template>

There are some limitations to Designer support for variables:

  • Creates only <text> variables
  • Cannot create a variable with an initial value that is empty
  • Places variables under the root subform only

Creating Variables in Script

In a previous post I described the code for creating a form variable in JavaScript.

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

The first parameter to createNode() can be any of the XFA content types.  The set that you would likely find useful in a scripting context are: <boolean>, <decimal>, <float>, <integer>, <text>.  You can create other types such as <date>, but a <date> behaves like a <text> (a string) because there is no date data type in JavaScript.

Typed variables

The .value property on a variable is a variant.  It returns a typed value based on the content type.  It will return string for <text> , it will return number for <integer>, <decimal> and <float> and returns boolean for <boolean>

Typed variables have the advantage that script operations that use them will do the right thing.  e.g. if you define two <text> variables: v1.value="3"; v2.value="5", then the expression "v1.value + v2.value" will return "35".  If they are defined as <integer>, the result of the expression is "8".

Use any Subform

The <variables> element may appear under any subform.  It often makes sense to create the variable in the context where it is used e.g. in the case where it is used within a repeating subform or a fragment.

Hidden from the Designer

When you define script objects that use variables, it can be inconvenient to create the form variables from the Designer UI.  If you do, your script then has an external dependency, and other people modifying the form can muck with your variables.  It really only makes sense to create them from Designer if they truly have global scope.  Of course, defining them in Designer does have the advantage that they show up in object assist.

Form State

Anatole Matveief wrote a nice form state tutorial on Stefan Cameron’s blog.  As of Acrobat 9, form variables are stored as part of the preserved form state i.e. after save/close/open, any form variables will be restored.  This can be a very valuable feature, but can also require some caution — when creating a new variable, you should first check for its existence.  If not, you may end up with multiple copies of the variable.  The existence check looks like:

if (mySubform.variables.nodes.namedItem("vMyVar") == null)
{
   
var vNewVariable = xfa.form.createNode("integer", "vMyVar");
    vNewVariable.value = 42;

    mySubform.variables.nodes.append(vNewVariable);
}

Dependency Tracking

Unfortunately, variables do no participate in dependency tracking.  If you define a calculation that uses a variable, the calculation will not re-fire automatically when the value of the variable changes.

What about Fields?

It is really convenient to be able to define variables in the context of a subform, but what about the case where your context is a field?  The good news is that there are two places where we can place content elements within a field: <desc> and <extras>.  For example, to create content under a field you can code:

if (myField.desc.nodes.namedItem("vFortyTwo") == null)
{
   
var vNewVariable = xfa.form.createNode("integer", "vFortyTwo");
    vNewVariable.value = 42;

    myField.desc.nodes.append(vNewVariable);
}

The content can then be referenced in script as: "myField.desc.vFortyTwo.value". 

Sample

I have added a sample that has code to demonstrate creating and referencing form variables and content elements under a field <desc> element.  The script lives under the "Create Variables" button.

Exclusion Groups v3.0

This is the third and final post describing a pattern for building your own exclusion group.  The first two posts are required reading for this entry:
Build a better exclusion group   Exclusion Groups v2.0

The included sample builds on version 2 and adds a couple of useful pieces of functionality:

  • Allow the exclusion group to have multiple entries selected
    (specify the minimum and maximum selected entries)
  • Make the exclusion group mandatory
  • Make the sample work in Reader 7

The Sample

Go ahead and open the attached sample. There is a new exclusion group, and for completeness I have also included the previous two examples.  When you open the sample, turn on field highlighting so that you can see when fields are mandatory (highlighted with a red border).  The sample shows a case where the user needs to select a minimum of one phone feature and a maximum of three.  Note these behaviors when you fill the form:

  • On opening the empty form, the fields are all highlighted red (mandatory) because none are selected — we are below the minimum selected.
  • When you select one entry, the mandatory setting is turned off. 
  • When you get to three selected entries, the remaining unselected entries are made read-only in order to prevent the user from selecting too many. 
  • If any entries are subsequently unselected, then the read-only setting is removed.

Code Changes

Initialization

There is now an initialization script on the exclusion subform to establish the minimum and maximum entries:

utility.setMinAndMax(this, 1 /* min */, 3 /* max */);

When users drags this object in from the object library, they need to tweak the initialization script to get the settings they want.  Note that if they set the maximum to one, then we use the toggling behavior when selecting entries (selecting one entry unselects another).  If they set a maximum greater than one we use the explicit select/unselect ui experience.

New functions

The script includes three new functions setMinAndMax(), setReadOnly() and setMandatory() plus new logic in makeExclusive() to set the mandatory and read-only states of exclusion group members.  From the behaviors described above and the code comments you can probably figure out how they’re used.

isSelected()

The logic inside isSelected() needed to be changed in order to support Reader 7.  Previously we relied on the selectedIndex property to determine if a checkbox was selected.  When selectedIndex is zero, the field is considered on/selected.  Grammar-wise, this is represented by this markup:

<field>
  <items>
    <text>yes</text>
    <text>no</text>
  </items>
</field>

selectedIndex refers to the position in the current chosen element in the <items> array. Since selectedIndex was not introduced until Reader 8, the alternate (Reader 7 compatible) strategy is to compare the value of the checkbox field to the first element under <items>.  If they are equal, the field is selected.

What Else?

I debated whether to expand on the logic behind isSelected().  For numeric fields, you might consider a value of zero to mean "not selected".  Also, there is probably more that could be done to support mandatory logic inside nested subforms.  For now I will leave these as an exercise for the user :-)

Summary

This is probably a good time to issue a caveat emptor.  These samples are intended to encourage some specific design patterns.  They are not supported objects for you to immediately use in your production forms.  You need to adapt them to your own needs and you need to test them in your own environments.

The main points I hope you take away from this exercise:

  • You can package your desired exclusion group experience into library objects without requiring users to add code to individual entries in the group
  • Tweak the provided sample to your own needs and include it in your object library
  • Centralize the script logic — do not have more than one copy in your form

Exclusion Groups v2.0

This is the second blog entry describing a DIY exclusion group. If you have not read part one, you should probably start there before trying to digest part two.

I wanted to build on the functionality of the exclusion group from the previous blog entry:

  1. Allow the form author to include nested subforms and radio button lists within the exclusion group
  2. Improve the handling of different field types
  3. Move the script logic outside the exclusion subform

The sample

The example is a form to choose a payment type. The payment type is an exclusion group with three kinds of content: a checkbox, radio button group and subform. The user chooses one of: cash (checkbox), cheque (radio button list specifying kind of cheque) or credit card (subform with all the credit card fields). When they choose a payment type, then the other payment types are cleared. Here is the sample.

Nested Subforms

Notice that the credit card fields are wrapped in a subform. The subform behaves as a single unit in the exclusion group. When any field inside the subform is selected, the subform is considered to be selected.

Field Types

The form in my previous post handled only check boxes and text/numeric fields. This sample includes handling for choice lists and radio button groups. The logic to check if a field is selected or not varies by field type.  This version of the exclusion group is more rigorous in its checking. Similarly, the logic to clear an entry varies according to the kind of object.

Separate the script

You will notice in the form that I moved the script into a script object called “utility”.  This is so that the script is not included (and duplicated) each time the form author includes another exclusion subform on their form.  It does have the downside that the form author needs to remember to add the utility script to the form.

To make this more foolproof, I have added a simple check at the beginning of the exclusion group calculate:

if (typeof(utility.makeExclusive) == “undefined”)
xfa.host.messageBox(
“You need to include the utility script object!”);

If utility.makeExclusive is found, the JavaScript typeof() function will return: “function” otherwise it returns “undefined”.
If your form author has forgotten the script (or put it in the wrong place) this simple check will notify them the first time they preview the form.

The Algorithm

Just as in the previous post, you do not really need to read past this point if you do not want gory details. Just take the sample, remove the contents from the exclusion subform and add the subform to your object library.  Do the same for the utility script. The whole point is that the logic is self contained, reusable and you can add content without writing any new script.

Hopefully reading the script code is self-explanatory, so I’ll just highlight a couple points here:

Get field type

In order to check the field type, the script needs to examine the content under the <field><ui> element. In the XFA grammar, this element is expressed as a “one of” relationship. The widget-type-child of <ui> must be one of: <barcode>, <button>, <checkButton>, <choiceList>, <dateTimeEdit>, <defaultUi>, <exObject>, <imageEdit>, <numericEdit>, <passwordEdit>, <signature> or <textEdit>. The convenient way to select this element is with the oneOfChild script property. So then the code to check if a field is a check button looks like:

if (vContainer.ui.oneOfChild.className == “checkButton”)

Code Additions

There are new functions: isSelected() and clearContainer(). Previously we assumed all members of the exclusion group were simple fields. Checking if a field was selected or clearing a field were one-liners. Now that we support subforms and more field types, these operations are more complex and we modularize them into functions.

That’s all for now.  Next up: min and max selections.

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.