John Brinkman on form design

Posts in Category "mandatory"

When Adobe first released Reader 9.1 I wrote a series of blog entries describing the new forms features. But of course, at that time it would have been easy to overlook them because you didn’t have a designer that could target 9.1. Not to mention that your user base would take some time to upgrade to Reader 9.1.

But now some time has passed and it’s a good idea to revisit some of the 9.1 features. Today we’ll look at field.presence = “inactive”. For background, you may want to re-read the original post here.

The sample for today shows a scenario where the form requests a payment type. If the payment type is “credit card”, we need to expose the credit card fields to fill. Not only that, but the credit card fields need to be mandatory. With the inactive setting, this becomes a very simple script:

And this is with a very simple scenario where the optional section consists of three fields. Imagine if the optional section had dozens of fields with mandatory settings, validation scripts etc.

The presence=”inactive” setting was added to help reduce the amount and complexity of code authors needed to write to author forms. If you make use of it, you should find your forms easier to code and less difficult to maintain.

There is one question I get asked frequently enough that it warrants its own blog entry.

Someone tries a validation script that looks like this:

this.rawValue !== null;

But the validation never fails. In spite of the fact that the field is null, this field always reports back that it is valid.

The reason is because of this rule: "empty/null fields never fail a script validation". The reason behind the rule is that we want to allow designers to define validations without worrying about null as a special case. Take an example: the user needs to enter a value in a ‘Age’ field where the value must be greater than 18. The way to express this is with this validation on the Age field:

this.rawValue > 18;

In an empty field this evaluates as "null > 18;" — which evaluates to false. Until the user enters a value, this validation script will always return false.

But allowing a script validation to fail on an empty field would be a lousy user experience. As soon as they open a blank form they would get a pile of invalid fields — even before beginning to enter data. What should a form designer do? They could change their script to explicitly allow null:

(this.rawValue === null) || (this.rawValue > 18);

But now there’s the problem that we really do want to make sure they enter a value. i.e. the experience we want is that the field starts out in a valid state. Don’t complain about script validations until they’ve actually entered a value in the field — but also don’t let them submit their data until the field has been populated with a value!

Of course, the answer is to make the field mandatory (the null test validation). By making the Age field mandatory we know that the user will not be hassled as soon as they open the form; but they also won’t be allowed to submit until they’ve provided a value. And the form author doesn’t have to handle a null value as a special case.

—–

Gone Again

I see Merriam-Webster has added "Staycation" to their dictionary. Sounds good to me. I will be away for another week at the family cottage. I’ll be be back to respond to comments on the 27th or 28th.

Rules controlling the validationState Event

Immediately following the initialize event — allowing the form author to control the initial appearance of the object

When the field/subform/exclusion group transitions between a valid and invalid state (or vice versa).

When the reason for the invalid state changes e.g. a field is in an invalid state because it is not filled in (missing mandatory). The user then fills in a value that violates the validation picture clause. In this case the field goes from one invalid state to a different invalid state and the event will fire.

The form session begins tracking mandatory fields

The last point needs a bit more explanation. When a field is marked as mandatory, it starts the form session in a valid state — even though it does not have a value. The reason is that we don’t want users notified about a whole bunch of invalid empty fields as soon as they open a blank form. There are 3 things that will put a missing mandatory field into an invalid state:

A populated mandatory field gets emptied

A call to execValidate() on the field or one of its ancestors

A submit action (which is really the same as an execValidate())

When any one of these three operations happen, the validationState event will fire for any unpopulated mandatory fields.

To be on the safe side, script associated with the validationState event should be robust enough to allow the event to fire multiple times — even if the validation state hasn’t actually changed.

The new errorText property

So the validationState event has fired… how does the script author know whether the field is now in a valid or invalid state? Authors can check the new property on field/subform/exclusion group: errorText. When a field is in a valid state, this property will return an empty string. When in an invalid state, it will return the user-defined message for that error.

Objects may have 3 different user-defined messages — one for each kind of validation failure. These messages are represented in the scripting model as:

The errorText property will be populated with the message from the associated validation failure. If the user has not defined a message, a default message is provided ("Validation failed.").

Turning off Acrobat/Reader Field Highlighting

If you are using the validationState event to change the appearance of fields, the automatic field highlighting of Acrobat/Reader can get in the way. Fortunately, it can be turned off. This script (placed on a form:ready event) will do the trick:

if (xfa.host.name == "Acrobat") app.runtimeHighlight = false;

Example

This example will hide the border on valid fields and for invalid fields will colour the border red. Note that the example places the script on a subform and uses event propagation to cause it to apply to all nested objects.

Of the XFA 3.0 enhancements, it’s safe to say this one was overdue. Today XFA processor in Acrobat/Reader notifies users about invalid fields by popping up dialog boxes. However, form authors feel so strongly about wanting control over the notification mechanism that they develop elaborate JavaScript frameworks to avoid using the message boxes associated with the built-in validation/notification mechanism.

To put the flexibility back in the hands of the form author, XFA 3.0 has a new configuration setting to control how errors are handled:

Don’t worry about remembering the syntax or where it goes — Designer will have a UI for setting this option. The impact of this setting is pretty self-explanatory — but I’ll explain anyway:

noMessages – Acrobat/Reader will not emit any messages for invalid fields. It will be up to the form author to notify the user in some way — likely by changing the appearance of the invalid fields.

firstMessageOnly – When there are multiple errors on the form, Acrobat/Reader will display a message box for the first error and will suppress messages for all subsequent errors. This prevents the user from being inundated with a long series of messages — one from each invalid field.

allMessagesIndividually — (default) This corresponds to today’s behaviour. Well, not quite. There are instances today where we combine messages — for example messages for missing mandatory fields. But combined messages don’t happen as much as you might like.

allMessagesTogether — Aggregate all the messages from all invalid fields and emit a single dialog with all messages combined.

The sequence of validation script execution remains the same no matter which messaging option is chosen. If the user attempts to submit when there are still missing or invalid fields, the submit operation will cancel with a message box.

This is the first in a short series of posts to describe the new functionality found in XFA 3.0.

If you have developed a form with even modest complexity, you have almost certainly manipulated the presence property of fields, subforms and exclusion groups. With XFA 3.0 we have extended the possible values of this property to include "inactive".

The existing presence attribute can be one of these properties:

visible – (default) Object is visible

invisible – Object is invisible, but still participates in layout and event processing. i.e. space is allocated in the layout to display this object, its calculations and validations still fire.

hidden – Object is invisible and excluded from layout. No space is reserved for this object. Its calculations and validations still fire.

In XFA 3.0 we now add:

inactive – Object is hidden and excluded from event processing. Calculations, validations and other events do not fire.

Scenario

A mortgage application form is sent to a client. The form has an optional section where the user may apply for mortgage life insurance. The life insurance application section is pre-populated with client information from the server. If the user chooses not to apply for life insurance:

the optional section will remain hidden

The calculation and validation scripts in the optional section will not fire

Other event scripts such as layout:ready, form:ready, prePrint, preSubmit etc. do not fire

We do not check for mandatory fields

All these behaviours will be true as long as the subform that defines the life insurance application is set to presence="inactive".

Once the user checks the box to apply for insurance, we toggle the setting to presence="visible" and then that section of the form becomes fully functional.

Containers, Properties

The new behaviour for presence applies only to these container objects: subform, field, exclGroup. The presence attribute is also found on draw, fill, items, edge, border, caption and corner — however in these contexts it will not introduce any new behaviour. It will behave the same as "hidden".

The Deep End

In a previous post, I described the various stages of processing we go through when we open a form:

Loading content

Merging data with template (create Form DOM)

Executing calculations and validations

Layout (pagination)

Render

The enumeration of the presence attribute determines which of these stages a form object will participate in:

Here is the last (for now) in the series on how to validate templates in a user-friendly manner. I have attached a new and improved sample (with data).

What is new in this version:

An email submit button that becomes active only when there are no errors on the form

A listbox field that shows all the errors on the field. When you enter into the list field, the field corresponding to the error gets highlighted. Try selecting different elements in the list.

But more importantly, this sample form now holds a toolkit of functions that allow you to take control of the way Reader validates form fields.

Behind the scenes on the script side, there is a fair bit more in the toolbox. My goal is that you can re-used these script objects in your forms and keep your form designs as clean and simple as possible. If you look at the form you will see that the vast majority of script is inside the script objects. The fields and subforms on the form itself have minimal amounts of script.

An added benefit of following this particular form development methodology is that is should be very consistent with future enhancements we add to XFA and Reader.

Here is a summary of the script functions that you can use in your form development:

/** * setStatus(vContainer, vStatus) * Call this method as the last line of your validation script. * This function does two things: * 1) highlights or resets the field according to whether it is valid or not * 2) If invalid, logs the error * * @param vContainer — the container we are validating. * If a subform, process recursively. * @param validStatus — true | false * @return the status for the validation script to use * (for now hardcoded to "true") */

/** * formHasErrors() * Useful for changing the state of form objects depending on if there are * validation errors or not. * Place this call inside a validation or calculation script and make sure * you have called registerListener(this) from your initialization event. * @return true if there are errors. False otherwise. */

/** * registerListener(vListener) * Call this from you initialization event if you want to be able to * call formHasErrors() in your calculation or validation events. * @param vListener — The object that cares about whether there are errors. * We’ll keep track of all the listeners. Every time a field changes valid * state (becomes valid or invalid), we will loop through all listeners and * force their calculate and validate scripts to run. * If a listener is a choiceList object, we’ll synchronize the choicelist * entries with field errors. * When a choicelist has zero entries, we hide it. * @return void */

/* * setFieldMandatory(vObject, vMandatoryState, vForce) * Mark a field or exclusion group as being mandatory. * When calling this from a validation script, * be sure to also call setStatus() * * @param vObject — the field or exclusion group that we’re marking * @param vMandatoryState — true or false * @return boolean — true if the state changed */

/** * clearErrList() * Call this method before removing a subform or re-ordering subforms. * Our tracking is based on storing SOM expressions. * The SOM expression for a field can change if the order of its parent * subform changes. * After clearing the list and removing/moving subforms, * call xfa.form.execValidate() to rebuild the list. * Note that it is not necessary to call this method when appending new subforms. */

Then there are a couple of methods you might choose to modify for your own use — in case you do not like the way the sample highlights invalid fields:

/** * highlight(vObject) * Highlight a field (or exclusion group or subform) to indicate that it * has a validation error * @param vObject — the subform/field/exclusion group to highlight */

/** * unhighlight(object) * reset the form field/subform/exclusion group to the state it was in * the template. * @param object — the subform/field/exclusion group to highlight */

The one thing that does not work back to Reader 7 is the button script that sets focus on an error field. xfa.host.setFocus() came later.

But the rest of script functionality should work fine in Reader 7. The hardest part about making the scripts work in Reader 7 was that the convenience methods for manipulating choice lists are not available there. The script has to manipulate the XML structures directly.

Continuing from the previous post, we are looking at the problem of validating fields without inundating the user with message boxes during their form session in Adobe Reader — and without centralizing all the validation logic.

In the previous post, we established a design pattern for highlighting fields where a script validation fails. In this post we will deal with mandatory (required) fields. Today, when you mark fields as mandatory, then any time your form is validated you will get an error message for each missing field. We can do better.

author the form fields using the standard XFA “required field” settings

At runtime disable the “required field” setting and store our own mandatory flag elsewhere in the field

Use our setStatus() function during validation to monitor whether fields are populated or not.

Changes from the previous sample:

Marked various fields as being mandatory

Additional logic in scValidate.setStatus() to cause empty mandatory fields to show up as invalid

A new script function: scValidate.setFieldMandatory()

Storing the mandatory state in a field involves a technique described in a previous post where we store a variable under a field.desc element.

Once we have logic in the form that stores our mandatory state, we let the XFA processor believe that no fields on the form are required. This way we don’t get any warnings from Acrobat/Reader, but we graphically modify the fields to highlight them for the user.

The only deviation from standard form design is that for mandatory fields you need to add a call to utility.setStatus() in the validation script – even if there is no script validation required. i.e. if you mark a field as mandatory, you need to add this validation script:

Conditionally Mandatory

On the sample form, when the payment type field choice is "Direct Deposit", there are three more fields that need to be filled in. If the payment type is any other choice, these fields do not need to be filled in.

Normally we toggle a field’s mandatory status by setting the property: field.mandatory. But now that our design pattern for mandatory fields has co-opted the XFA mandatory mechanism, we will use one of our new global methods: scValidate.setFieldMandatory(vField, vMandatoryState).

For this to work, the form defines validation scripts on the financialInstitution, branchNumber and accountNumber fields that look like this:

Updated Exclusion Groups

If you are looking at the script code you will notice that I have included the exclusion group script objects that were defined in a previous post. The form has placed the direct deposit subform in an exclusion group with the other payment types. I have modified those scripts so that they work with the validation framework. (You will notice I have also broken them out into separate script objects with a new naming scheme).

When an exclusion subform has not yet reached its minimum number of entries, the subform gets highlighted instead of the individual fields in the subform.

The call to scGroup.setMinAndMax() now includes an optional validation message parameter. This is needed because designer does not yet expose a UI for assigning a validation message to a subform.

Note that this sample now introduces the concept of an invalid subform. If the user has not filled in all the credit card fields, then we mark the subform as invalid — similar to the case where the subform exclusion group did not reach its minimum number of entries. The script to do this looks like:

The validation button

The samples have also updated the logic for the button to validate the form. Now when there are no errors, the button caption will be updated to say: "no errors" (and will be made read-only). This works as long as you do not change the name of the button from : "checkValid". You might prefer a variation on this where the button is hidden when there are no errors.

Backward compatibility

We would like to make it easier you to control validation message handling in a future version of Reader, but for the here and now, you are likely targeting Reader 7/8/9 and need an immediate solution. You can take these script objects, copy them into new forms and use them in forms compatible as far back as Reader 7.

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

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:

Allow the form author to include nested subforms and radio button lists within the exclusion group

Improve the handling of different field types

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.