Skip to content

Latest commit

 

History

History
354 lines (283 loc) · 14 KB

tutorial.md

File metadata and controls

354 lines (283 loc) · 14 KB

📖 US Forms System Documentation ➡️ Getting Started with the US Forms System

Tutorial: Building a simple address form

Learn the basics of the US Forms System library, with no knowledge of React required, by building a simple form that asks for an address.

In this tutorial

Step 1: Install the starter app

The fastest way to start building a form using the US Forms System library is to install the US Forms System Starter App. The starter app includes the initial files, configuration, build process, and web server you need to start building your form immediately. For installation instructions, see "Creating a new application with the US Forms System Starter App."

Step 2: Get familiar with the form config

The starter app code includes a file under js/config/ called form.js. This is the main file you'll be interacting with to build your form. It includes almost everything that goes into your form, including the questions your form will contain, how those questions are displayed to the user, and the organization of the different sections of your form. The library works by reading through your form config in order to determine how to build your form.

There are several properties in the top level of the formConfig object. You can learn more about them later in our Quick start guide.

Step 3: Understand chapters and pages

In this tutorial, you'll be working on the section of the formConfig within chapters. Forms are organized into chapters, which are broader topics, and pages, which are separate pages of questions within that topic. For example, a form may have a chapter on personal information, and separate pages within that chapter for name, address, and other contact information.

The starter app includes two chapters, firstChapter and secondChapter. The firstChapter object includes the title of the chapter, as well as an object containing the pages within that chapter. Each page is also an object.

const formConfig = {
  title: 'Form',
  subTitle: 'Test',
  ...
  chapters: {
    firstChapter: {
      title: 'First Chapter',
      pages: {
        firstPage: {
          path: 'first-chapter/first-page',
          title: 'First Page',
          ...
        }
      }
    }
  }
}      

Each page also includes path and title properties: path takes the url of that page, and title, unsurprisingly, takes the title of that page.

Step 4: Add the schema object

Now that you have the page structure of your form, you can start adding questions! To do this, add the schema object to the firstPage object.

You can think about the information contained in the schema object as a description of the type of data each question accepts. The schema follows the JSON Schema standard, which specifies the allowed shape of JSON objects. For more information about the JSON Schema Standard, see Understanding JSON Schema.

Each schema object must always contain the following properties:

  • type: 'object': this describes the shape of the schema data
  • properties: {}: an object containing the fields within that schema

Note: when you begin building forms, it's common to receive an error for forgetting one of these two required properties in your schema objects. The error will appear in your console as Error found in schema: Missing type in root schema..

With these two properties, your schema should look like this:

...
firstPage: {
  path: 'first-chapter/first-page',
  title: 'First Page',
  schema: {
    type: 'object',
    properties: {

    }
  }
}
...

Step 5: Render the form so far

Take a look at your form by runing npm start in your terminal at the starter app directory. This will automatically open a browser window with your form app rendered. The first page is an introduction, which you'll learn more about later. For now, skip it by clicking the "Start Form" button, which takes you to the first page of the form.

This is what you should see: Blank page

You'll notice a few things:

  • The title and subtitle of the form at the top, which you can change at the top level of the formConfig.
  • A progress bar with 2 segments. Each segment corresponds to a chapter of your form; the number of segments to display is determined by the library based on how many chapters you've added to your config.
  • The "step" of the form you're on out of how many steps there are. Again, steps correspond to the number of chapters, and the current step is the number of the current chapter.
  • The title of the chapter.
  • The navigation buttons, "Back" and "Continue".

There's nothing else on this page because we haven't added any questions yet.

Step 6: Add a question

For your simple address form, you'll start by asking for the street. Add a field for street within the schema: { properties: {}} object you just created.

...
  schema: {
    type: 'object',
    properties: {
      street: {
        type: 'string'
      }
    }
  }
...

The webpack server you started when you typed npm start in Step 5 should have automatically refreshed the page with your form. Go back to your browser to take a look at what's there.

This is what you should see: Street field

Excellent, now you have a form field! But how did the library know to render a text input with a label of street based on what you added to the schema?

The street field accepts string data. For basic data types, an automatic determination is made by the library on what type of HTML form element to render. string data renders a text input, a field with an enum property renders a select, and boolean data renders checkbox.

The label is taken from the name of the field in the formConfig. street becomes "street" in your form. But what if we want to change the label text? We can do that be editing the uiSchema object, which we'll learn about next.

Step 7: Add the uiSchema object

While the schema describes the fields of a form and the type of data each field accepts, there are UI-specific things you may want to change about how those fields appear to the user.

That's where uiSchema comes in. Think of the uiSchema object as a mirror of schema. It usually has the same fields as the schema object, but with different properties to specify UI-specific characteristics of that field.

Returning to the example of changing the label text of the street field, pass a property to the uiSchema under street called ui:title. ui:title lets you specify the exact label text you want displayed. This is how your formConfig would look:

...
  schema: {
    type: 'object',
    properties: {
      street: {
        type: 'string'
      }
    }
  },
  uiSchema: {
    street: {
      'ui:title': 'Street'
    }
  }
...

Most properties that are passed to uiSchema are prefixed with ui:, which means they need to be passed as a quoted string to formConfig.

Go back to your browser window to see the form again. Because you overrode the default label text by adding 'ui:title': 'Street' to your formConfig, your label text has changed from "street" to "Street".

Step 8: Add another string question

Now that we understand the basics of adding fields to our formConfig, it's time to add more questions. On your own, add a field for the city in the address. The field should use a text input.

(We'll wait a few minutes while you add the field)

Did it work? Let's compare our formConfig:

...
  schema: {
    type: 'object',
    properties: {
      street: {
        type: 'string'
      },
      city: {
        type: 'string'
      }
    }
  },
  uiSchema: {
    street: {
      'ui:title': 'Street'
    },
    city: {
      'ui:title': 'City'
    }
  }
...

This is now what your form looks like: Street and city

You're now halfway to a complete address!

Step 9: Add a select field

In Step 6, you learned that the library makes default determinations about the type of field to render based on the type of data. This applies to select fields as well. A select is rendered when the data is type: 'string' and an enum property is passed in.

The enum property takes an array of the valid options for that field. For states, we pass string values to the enum array for the states we want to include. We'll just add five so we don't have to add all 50 states and 14 territories. Add a few of your favorite states to your formConfig.

Your formConfig for states might look like this:

...
  schema: {
    type: 'object',
    properties: {
      street: {
        type: 'string'
      },
      city: {
        type: 'string'
      },
      states: {
        type: 'string',
        enum: ['AK', 'IL', 'MA', 'TX', 'WY']
      }
    }
  },
...

This is how your form looks after adding this additional schema property: State dropdown

Behold, a dropdown of states! And it contains exactly the states we passed to it: Cities in dropdown

As you've seen, the state names come directly from the schema, but what's rendered isn't as user-friendly as it could be. The back-end may only accept abbreviations, but users should still be able to see the full state name.

You can specify the text that appears to the user for each option in the uiSchema by passing a labels object to that field:

...
  schema: {
    type: 'object',
    properties: {
      street: {
        type: 'string'
      },
      city: {
        type: 'string'
      },
      state: {
        type: 'string',
        enum: ['AK', 'IL', 'MA', 'TX', 'WY']
      },
    }
  },
  uiSchema: {
    street: {
      'ui:title': 'Street'
    },
    city: {
      'ui:title': 'City'
    },
    state: {
      'ui:title': 'State',
      'ui:options': {
        labels: {
          AK: 'Alaska',
          IL: 'Illinois',
          MA: 'Massachusetts',
          TX: 'Texas',
          WY: 'Wyoming'
        }
      }
    },
  }
...

Some properties, like labels, are passed in a ui:options object within that field. For more information about which properties are available and how they get passed to uiSchema, see "About the schema and uiSchema objects."

Step 10: Add specific data constraints and validation

The last question in this address form lets the user provide a ZIP code. While ZIP codes are usually string data, they also need to follow specific formats, which means specifying data constraints in your formConfig through the schema object. The JSON Schema specification includes several properties to add constraints to the data accepted for that field, including minLength, maxLength, format, and pattern.

To validate ZIP code input, use the pattern property, which matches the user entry against a regex, as in this schema:

...
  schema: {
    type: 'object',
    properties: {
      street: {
        type: 'string'
      },
      city: {
        type: 'string'
      },
      state: {
        type: 'string',
        enum: ['AK', 'IL', 'MA', 'TX', 'WY']
      },
      zip: {
        type: 'string',
        pattern: '^[0-9]{5,9}$'
      }
    }
  },
...

You're part way there; you have a ZIP code field. To test it, type an invalid ZIP code and focus away from the ZIP code field: ZIP code pattern error

While the form caught that the entry wasn't correct and displayed an error, the error isn't particularly human-readable. You can add custom error messages to your form for specific cases. Because custom error messages are displayed to the user, they go in the uiSchema:

...
  uiSchema: {
    street: {
      'ui:title': 'Street'
    },
    city: {
      'ui:title': 'City'
    },
    state: {
      'ui:title': 'State',
      'ui:options': {
        labels: {
          AK: 'Alaska',
          IL: 'Illinois',
          MA: 'Massachusetts',
          TX: 'Texas',
          WY: 'Wyoming'
        }
      }
    },
    zip: {
      'ui:title': 'ZIP code',
      'ui:errorMessages': {
        'pattern': 'Please enter a valid US ZIP code between 5 and 9 digits.'
      }
    }
  }
...

To display a custom error message, pass the ui:errorMessages object to uiSchema under the field for zip. ui:errorMessages takes key-value pairs, where the key is the name of the JSON Schema property that the entry violates, and the value is the user-friendly, descriptive message displayed for the violation.

Now you'll see a much more useful error message:: Helpful error message

Review

Well done! You've built your first form using only a JSON config file, and you're starting to see the benefits of describing your form rather than building components and UI patterns directly.

There are many more complex form patterns you can build with the US Forms System, including conditionally expanded fields, custom validation functions, groups of similar questions, and more. Learn more about what's possible in "Building a form".

Back to Getting Started with the US Forms System