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…