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…