Forms are created by creating a page that uses FormApp from the schemaform folder, a form config object, and routes generated from that config file. See src/js/edu-benefits/1995
for an example.
Form config options:
urlPrefix
: Prefix string to add to the path for each pageintroduction
: The introduction page component to use. Intro page is skipped if not providedconfirmation
: The confirmation page component to use after form is successfully submittedtrackingPrefix
: The prefix for Google Analytics events that are sent for different form actionstitle
: The title of the form. Displayed on all pagessubTitle
: The subtitle (e.g. form number) of the form. Displayed on all pages, if there's also a titledefaultDefinitions
: Schema definitions to include on all pages. Can be overriden using definitions object in page schemachapters
: Object containing the configuration for each chapter. Each property is the key for a chaptertitle
: The title of the chapterpages
: Object containing the pages for each chapter. Each property is the key for a page and should be unique across chapterspath
: The url for the pagetitle
: The title of the page. This will show up only on the review pageinitialData
: Any initial data that should be set for the formuiSchema
: Object containing the uiSchema for the page. Follows the format in the react-jsonschema-form docs, which some vets.gov specific additions. See below.schema
: JSON schema object for the page. Follows the standard JSON schema format
By convention, starting field names with view:
will exclude them from the output sent to the backend. If the field is an object, its properties will be merged into the parent object of the view:
field (TODO).
The schema
and uiSchema
objects should have similar structure. In other words, they should have the same fields organized the same way. The main difference between the structure of the two objects is that the uiSchema object does not have to contain all the fields that the schema object does and it does not need a properties
object for sub-fields. So given this schema, for example:
{
type: 'object',
properties: {
field1: {
type: 'string'
}
}
}
The matching uiSchema would be:
{
'ui:title': 'My form',
field1: {
'ui:title': 'My field'
}
}
This does not apply to array fields; for those, you still need to specify an items
object that contains the fields for each row in the array.
In addition to the uiSchema options listed in the library docs, we have some additional options that are supported for all forms:
-
ui:validations
: This is an array of validation functions that can be used to add validation that is not possible through JSON Schema. See below for the properties passed to the validation functions and how to use them. -
ui:title
: We use this instead of the title property in the JSON Schema -
ui:description
: We use this instead of the description property in the JSON Schema. This can be a string or a React component and would normally used on object fields in the schema to provide description text or html before a block of fields -
ui:required
: Use this to provide a function to make a field conditionally required. First argument is the current form data and the second is the formContext object, which will contain the form data for other pages (tbd). You should avoid making a field required in the JSON schema and usingui:required
on the same field. -
ui:errorMessages
: An object with field specific error messages. Structured by error name (from JSON schema error types). This is passed to custom validations inui:validations
if you want to allow configurable error messages in a validator. -
In the
ui:options
property:widgetClassNames
: This is a string of class names that will be added to the widget for the current field. Similar to the defaultclassNames
property, but will put the class names on the input/select/etc element itself, rather than a surroundingdiv
.viewField
: For Array fields, this is a component that is shown when the item in the array is being shown read-only on a normal form page (i.e. not on the review page).expandUnder
: If you want a field to only be shown when another field is true, set this option to the property name. It will follow our ExpandingGroup pattern and expand underneath the field it is set to.hideOnReview
: Set this if you want to hide this field on the review page.hideOnReviewIfFalse
: Set this if you want to hide this field on the review page when the field value is falsyhideIf
: This is a function that receives the current page data and the overall form data object. If true is returned, the field will be hidden on the form and any data set for it will be removedupdateSchema
: This is a funciton that receives the current page data and the overall form data object. It can return an object with properties to change in the schema for the current field. You can use this to update enum lists based on user data, for example.
JSON Schema does not provide all the validation options we need in our forms, so we've created an additional way to add field validations, using ui:validations
in the uiSchema object. ui:validations
is an array and each item can be a function or an object. If you pass a function, it will be called with the following arguments:
- errors: The errors object for the field.
- currentData: The data for the field.
- formData: The current form (page) data.
- schema: The current schema for the field.
- errorMessages: The error messsage object (if available) for thie field.
Every validation function should update the errors object with any errors found. This is done by calling its addErrors()
method. Here's an example:
function validateSSN(errors, ssn) {
if (!isValidSSN(ssn)) {
errors.addError('Please enter a valid nine digit SSN (dashes allowed)');
}
}
Items in the ui:validations
array can also be objects. Objects should have two properties:
- options: Object (or anything, really) that will be passed to your validation function. You can use this to allow your validation function to be configurable for different fields on the form.
- validator: A function with the same signature as above, plus the options object.
You don't have to limit your use of ui:validations
to non-object fields (i.e. the ones that become visible inputs on the form). You can also validate objects, which allows you to compare subfields. For example, given this schema:
{ type: 'object', properties: { email: { type: 'string' }, confirmEmail: { type: 'string' } } }
If you use ui:validations
on this object field (instead of on the email or confirmEmail fields) you can compare the two fields:
export function validateEmailsMatch(errors, formData) {
const { email, confirmEmail } = formData;
if (email !== confirmEmail) {
errors.confirmEmail.addError('Please ensure your entries match');
}
}