@button-inc/form-schema
TypeScript icon, indicating that this package has built-in type declarations

1.0.0-alpha.8 • Public • Published

Form Schema

Contents

Overview

This library is built on top of react json schema form, with an aim to make it easier to create forms that have the following properties:

  • Progressive enhancement: When used with a SSR platform, the forms will be functional for users who haven't loaded the javascript or css, and progressively enhance when they are added.
  • User Experience: This package focuses on making it easy to create single question-per-page forms, to create a smooth user experience.
  • Accessibility: This package is designed to plug in easily with the service-development toolkit component-libraries, which have a focus on accessible components.

Note that while this package makes it easier to meet these three goals, it is not limited to them. For example, it can be used with a client-side rendering framework like create react app if you are interested only in accessibility and single-page form questions.

Getting Started

Install package:

  • npm i @button-inc/form-schema or yarn add @button-inc/form-schema depending on your package manager.

Add to your app:

  • import builder from @button-inc/form-schema, or to use with themed components, import {govBuilder} from @button-inc/form-schema

See below for specific use cases, or browse our examples for a working setup.

Usage

This package is setup to split a json schema into a series of pages, as well as handle redirection, form data, and front-end/back-end validation for users with or without javascript. To do this, it requires three main configuration pieces:

  • Schemas: Schemas defining your forms and their ui.
  • Rendering Forms: A dynamic GET route is required to handle rendering the split-up forms.
  • Posting Data: A dynamic POST route is required to handle saving data for the split-up forms.

The Form components, functions and middleware to set this up will be returned from the main builder function (default import). E.g:

import builder from '@button-inc/form-schema';
export const { postMiddleware, getHandler, Forms } = builder(schema, uiSchema, options);

Where the Forms will be an array of form components.

Schemas

Since this library utilizes react json schema form, the schema configuration is largely the same. The differences are:

form-schema:

  • Splitting Pages: The schema you provide will be split into individual properties, each of which can be rendered on a page. However, for fields that need to be displayed together you can group them in a property with type: 'object'. They will be shown together on a single page. E.g
  {
    ...
    properties: {
      groupedFields: {
        type: 'object',
        properties: {
          onPageOne: {
            type: 'string',
            ...
          },
          alsoOnPageOne: {
            type: 'string',
            ...
          }
        }
      }
    }
  }
  • dependencies: Currently, only the dependency oneOf is supported. anyOf will be added in the future.
  • files: To allow uploading files with multipart encoding, you can add a custom hasFiles boolean property to fields of type string. See files for more information.
  • names: To support non-javascript users, fields will include a name attribute. This needs to be set in the schema property, as name: string.
  • html5 validations: HTML5 validation attributes such as maxlength, minlength, and patterns can be set on the schema to be passed down to fields.

ui-schema: The ui schema has the same properties as react json schema form.

Rendering Forms

To handle rendering forms, you will need to know any previous form data to load into the form, as well as the index of the current form to render. The getHandler returns these for you, as well as whether or not the current route corresponds to an actual form-page. You can make a request to this route, and use the returned props to render the correct form. For example:

  • getRoute.js
app.get('/getRoute/:id', (req, res) => {
  const { formIndex, formData, validPage } = getHandler(req);
  res.json({ formIndex, formData, validPage });
});

Note that this is if you want to use sessions to store data. To handle data in between pages yourself, see the custom data handling section.

  • form.jsx
// Import the array of form components from the file you created them in.
import { Forms } from 'form-schema';

export default function Form({ formIndex, formData, validPage }) {
  const Form = Forms[formIndex];
  return <>{validPage && <Form formData={formData} />}</>;
}

The Form component will be an rjsf form component, and can be passed in the usual props, such as templates. In addition, it can take a rerouteHandler prop, which is a function to handle redirection when javascript is enabled. This allows you to use whatever SPA front-end router you need. E.g:

form.jsx

  ...
  const rerouteHandler = (nextPage: string, _isValid: boolean, lastPage: boolean) => {
    // Re-route using your routing library
    router.push(lastPage ? '/end' : nextPage);
  };
  return <>{validPage && <Form formData={formData} rerouteHandler={rerouteHandler} />}</>;
}

Note: If not providing this prop, redirection will be handled via window.location which will cause a re-render.

Posting Data

To save data as the user progresses through pages, a dynamic post route needs to be setup. This post route requires body parsing middleware to be enabled, which is enabled by default in express (4.16+) and nextjs. In the dynamic route, simply pass the middleware in. E.g:

app.post('/postRoute/:num', postMiddleware);

If the useSession option is set as true, you should setup session middleware for this route, and it will parse and save the data for you. If not using sessions, see the custom data handling section which allows you to save the data how you want in between forms. Lastly, when a form is completed a final onFormEnd function will fire, allowing you to see if it validated correctly on the backend, and save the data. This is configured in the options argument of the builder function.

Files

For handling file uploads, the data needs to be parsed differently. To support this, a separate POST route needs to be configured ar postRoute/:num/file, where postRoute is the route configured in options. This route can be configured similarily to the base route:

postRoute/:num/file.js

app.post('/postRoute/:num', fileMiddleware);

Inside this route, the body-parser middleware needs to be disabled to allow file parsing. To handle the file stream, the options configuration passes into builder takes two functions, handleReadStream and onFileLoad. These can be used to pipe the stream onward, and notify when the upload is complete.

Custom Data Handling

By default, a session can be used to store incomplete form data. To handle the data yourself, pass in useSessions: false to the options configuration, along with an onPost function. The onPost function can be used to clean the form data and save it in between pages. E.g:

options

  onPost: (postData: object, schemaIndex: number, cleanSchemaData: Function) => {
    const formData = getDataFromDB();
    const newData = cleanSchemaData(formData);
    saveDataToDB(newData);
    return newData;
  },

Note that the cleaned data is expected to be returned by this function.

To retrieve the data, in your GET route, simply retrieve it after calling the handler.

getRoute.js

app.get('/getRoute/:id', (req, res) => {
  const { formIndex, validPage } = getHandler(req);
  const formData = getDataFromDB(formIndex);
  res.json({ formIndex, formData, validPage });
});

Functions

Builder

Builder function is the default export of the package, and can also be imported with certain widgets pre-attached by importing the named import govBuilder. For example:

import { govBuilder } from '@button-inc/form-schema';

or

import builder from '@button-inc/form-schema';

It takes the following arguments:

Arguments Type Description
Schema Object A form-schema compatible with react json schema form . In addition to the base configuration options, any field under the properties key can be passed:
  • hasFiles: A boolean value indicating if the field is a file upload. File uploads should be given type string. See the [files](#Files) section for more details.
  • name: The name of the input field.
  • pattern: html5 pattern validation
  • minLength: html5 length validation
  • maxLength: html5 length validation
By default, all properties will be split into individual pages. To group fields onto a single page, create a property of type object. All of it's child properties will be grouped together.
uiSchema object A ui schema compatible with react json schema form
options object A configuration object. It can be passed the following keys:
  • getRoute*: A string value of the dynamic route the getHandler will be called at.
  • postRoute*: A string value of the dynamic route the postHandler will be called at.
  • useSession: A boolean value indicating whether or not to use sessions. If set true, ensure a session object is provided on req.session.
  • defaultLabels: A boolean value indicating whether or not to keep the default generated labels.
  • widgets: An object containing widget overrides.
  • validations: An object defining custom validation functions. Object should have the form
              {
                propertyName: {
                  validationFunction: function,
                  errorMessage: string
                }
              }
              
    See validationFunction for argument details.
  • handleReadStream: A function to handle file upload streams. See handlereadstream for argument details.
  • onFileLoad: A function to handle file upload streams. See onFileLoad for argument details.
  • onPost: A function to handle file upload streams. See onPost for argument details.
  • onFormEnd: A function to handle file upload streams. See onFormEnd for argument details.

Builder returns the values:

  • getHandler: A function to handle get requests. See getHandler for more details.
  • postMiddleware: Middleware to handle post requests. See postMiddleware for more details.
  • fileMiddleware: Middleware to handle file post requests. See fileMiddleware for more details.
  • Forms: An array of form components corresponding to each page.

validationFunction

A function to generate custom validations. It takes the following arguments:

Arguments Type Description
fieldValue string The value of the field being validated
formData object All data entered for the current application.

The return value should be a boolean indicating whether or not the input is valid.

handleReadStream

A function to handle file upload streams. It taks arguments:

Argument Type Description
filename string The name of the uploaded file.
readStream Readable Stream A readable stream of the uploaded file.

onFileLoad

A callback that will fire when files finish uploading. It takes the arguments:

Argument Type Description
filename string The name of the uploaded file.

onPost

A callback that will fire when form data is posted to the API. It takes the arguments:

Argument Type Description
postedData object The data that was posted by the user.
schemaIndex number Index of the schema that was posted from.
cleanSchemaData function Function to clean any form data you are using. This will remove fields for the current form page from the data, so that new user data will still be updated for fields such as un-checked checkboxes, which don't post anything in the non-javascript use case. It takes the current formdata as an argument, e.g
      const formData = getFormDataFromDB();
      const newData = cleanSchemaData(formData);
      saveNewDataToDB(newData);
    

The return value should be the cleaned data.

onFormEnd

A callback that will fire when the form is completes. It takes the arguments:

Argument Type Description
errors Array An array of error objects for any failed fields.
formData object The data of the final submission.

getHandler

This function should be used at the dynamic route following the getRoute you set in options. For example, if you setup your get route to be '/', you should call getHandler at '/:num'. It is used to return the information you need to render the correct form, see the return values below. It takes arguments:

Argument Type Description
request object The request object for the current route.

The return value for the function is an object with the following properties:

  • formIndex: A number showing the index of the form component to render.
  • formData: An object containing current formData to pass into the form. Intended for use with sessions enabled, if not using sessions will be an empty object.
  • validPage: A boolean indicating whether or not the current route points to a valid form page.

Readme

Keywords

none

Package Sidebar

Install

npm i @button-inc/form-schema

Weekly Downloads

8

Version

1.0.0-alpha.8

License

Apache-2.0

Unpacked Size

96.4 kB

Total Files

54

Last publish

Collaborators

  • wenzowski
  • buttontoolkit
  • kriscooke
  • junmin-at-button
  • jonlanglois
  • junminahn