Validation Patterns: Part 1

Time to tackle my pet peeve. The aspect of customer written script that concerns me most is how customers validate field data. The number one reason for script bloat is that our users bypass the standard XFA/Reader validation framework.

Of course, there is a reason why script writers bypass validation. The problem is that each validation message appears in an individual dialog box. In some cases when a form is validated, the user may have to dismiss dozens of dialogs. Most users would prefer no dialog boxes at all – just show the invalid fields graphically. This is a problem we would like to solve in a product release, but in the mean time, you need forms that work in previous versions of Reader. Starting today I will tackle this topic in a series of blog entries.

Current Practise

Form authors currently work around the “validation message” issue by moving their validation logic outside the prescribed validation mechanisms. They typically place all the validation logic for their form under the click event of a “validate” button. They end up with a very large chunk of script that look like:

if (purchaseOrder.amount.rawValue >= 1000) {
     errorMessage += "Amount must be less than 1000\n";
     errorFlag = true;
}
if (purchaseOrder.quantity.rawValue == null || quantity.rawValue == 0) {
     errorMessage += "Quantity must be specified\n";
     errorFlag = true;
}
... repeat for each business rule ...
if (errorFlag) {
    xfa.host.messageBox(errorMessage);
}

The result is that field definitions are no longer encapsulated (self-contained). The problems introduced:

  • Fields cannot be copied/pasted/moved/deleted/renamed without breaking script
  • Adding new a field requires updating a global script – hard to correlate which fields have validations script and which do not.
  • Validation messages are stored and handled in script. Any attempt to isolate strings from the template for translation or spell checking is very difficult.
  • Validations happen only when the user explicitly asks for a validation – e.g. by clicking a button. They do not get incremental notifications.
  • Total amount of script is greater, and the script is more complex

Best Practise

Here is a sample form where:

  • Validation logic is placed in field validation scripts
  • Invalid fields are highlighted with a red border
  • No message boxes appear during data entry
  • There is a button that checks overall form validation status and if there are errors, places the user in the first invalid field

The solution has 3 parts:

  1. Global Script Objects
  2. Field validation logic
  3. Validation button

Global Scripts

There are six global scripts under the utilities script object. The ones that the form author should be aware of:

setStatus(vField, vStatus)

Called by field validation logic to handle the validation logic for a field.

highlightField(vObject)

Changes the appearance of a field (or exclusion group) to indicate that it is invalid. In this case we change the border color. Anyone who wants to re-use this mechanism can modify this script to get the visual effect they prefer.  Note that for this sample, the highlighting works best if fields do not have a raised border.  It also works best for dynamic forms.  It should be re-specified if used for a static form.

unhighlightField(vObject)

Reverts the field to the appearance defined in the template. If you modified code in highlightField(), you need to make the corresponding change in this function.

There are a three more scripts that are used internally:

registerFieldStatus(vField, vStatus)

Saves a list of invalid fields in a form variable.

getMessage(vObject)

Retrieves the validation message from a field object.

findTemplateField(vField)

Finds the template field that we use to reset visual properties.

Field Definition

Several fields on the sample form have validation logic. The general form of the validation script looks like this:

expenseReport.#subform[0].expenses.expense.amount::validate – (JavaScript, client)

// place field validation logic in the getStatus() method.

// Make sure it returns true (valid) or false (invalid).

function getStatus()
{     // place field validation logic here.

}
utilities.setStatus(this, getStatus()); // register valid/invalid state

Note the properties of this script:

  • The validation will fire every time the field (or any of its dependencies) change
  • Since the setStatus() function always returns true, the field is always valid according to the XFA processor.
  • The setStatus() function does the work to change appearances etc.

Other than the field validation script, the important aspect of this design is that the validation message is specified at the field level.

Validation Button

The validation button will:

  • Check the form variable holding the list of invalid fields
  • If there are invalid fields, extract the validation message from the first invalid field and issue a message box
  • Set focus to the first invalid field

Topic for next post: Handling mandatory fields.