@defra/forms-engine-plugin

0.0.6 • Public • Published

@defra/forms-engine-plugin

The @defra/forms-engine-plugin is a plugin for hapi used to serve GOV.UK-based form journeys.

It is designed to be embedded in the frontend of a digital service and provide a convenient, configuration driven approach to building forms that are aligned to GDS Design System guidelines.

Installation

npm install @defra/forms-engine-plugin --save

Dependencies

The following are plugin dependencies that are required to be registered with hapi:

npm install hapi-pino @hapi/crumb @hapi/yar @hapi/vision --save

Additional npm dependencies that you will need are:

npm install nunjucks govuk-frontend --save

Optional dependencies

npm install @hapi/inert --save

  • @hapi/inert - static file and directory handlers for serving GOV.UK assets and styles

Setup

Form config

The form-engine-plugin uses JSON configuration files to serve form journeys. These files are called Form definitions and are built up of:

  • pages - includes a path, title
  • components - one or more questions on a page
  • conditions - used to conditionally show and hide pages and
  • lists - data used to in selection fields like Select, Checkboxes and Radios

The types, joi schema and the examples folder are a good place to learn about the structure of these files.

TODO - Link to wiki for Form metadata TODO - Link to wiki for Form definition

Providing form config to the engine

The engine plugin registers several routes on the hapi server.

They look like this:

GET     /{slug}/{path}
POST    /{slug}/{path}

A unique slug is used to route the user to the correct form, and the path used to identify the correct page within the form to show. The plugin registration options have a services setting to provide a formsService that is responsible for returning form definition data.

WARNING: This below is subject to change

A formsService has two methods, one for returning formMetadata and another to return formDefinitions.

const formsService = {
  getFormMetadata: async function (slug) {
    // Returns the metadata for the slug
  },
  getFormDefinition: async function (id, state) {
    // Returns the form definition for the given id
  }
}

The reason for the two separate methods is caching. formMetadata is a lightweight record designed to give top level information about a form. This method is invoked for every page request.

Only when the formMetadata indicates that the definition has changed is a call to getFormDefinition is made. The response from this can be quite big as it contains the entire form definition.

See example below for more detail

Static assets and styles

TODO

Example

import hapi from '@hapi/hapi'
import yar from '@hapi/yar'
import crumb from '@hapi/crumb'
import inert from '@hapi/inert'
import pino from 'hapi-pino'
import plugin from '@defra/forms-engine-plugin'

const server = hapi.server({
  port: 3000
})

// Register the dependent plugins
await server.register(pino)
await server.register(inert)
await server.register(crumb)
await server.register({
  plugin: yar,
  options: {
    cookieOptions: {
      password: 'ENTER_YOUR_SESSION_COOKIE_PASSWORD_HERE' // Must be > 32 chars
    }
  }
})

// Register the `forms-engine-plugin`
await server.register({
  plugin
})

await server.start()

Environment variables

Options

The forms plugin is configured with registration options

  • services (optional) - object containing formsService, formSubmissionService and outputService
    • formsService - used to load formMetadata and formDefinition
    • formSubmissionService - used prepare the form during submission (ignore - subject to change)
    • outputService - used to save the submission
  • controllers (optional) - Object map of custom page controllers used to override the default. See custom controllers
  • filters (optional) - A map of custom template filters to include
  • cacheName (optional) - The cache name to use. Defaults to hapi's [default server cache]. Recommended for production. See [here] (#custom-cache) for more details
  • pluginPath (optional) - The location of the plugin (defaults to node_modules/@defra/forms-engine-plugin)

Services

TODO

Custom controllers

TODO

Custom filters

Use the filter plugin option to provide custom template filters. Filters are available in both nunjucks and liquid templates.

const formatter = new Intl.NumberFormat('en-GB')

await server.register({
  plugin,
  options: {
    filters: {
      money: value => formatter.format(value),
      upper: value => typeof value === 'string' ? value.toUpperCase() : value
    }
  }
})

Custom cache

The plugin will use the default server cache to store form answers on the server. This is just an in-memory cache which is fine for development.

In production you should create a custom cache one of the available @hapi/catbox adapters.

E.g. Redis

import { Engine as CatboxRedis } from '@hapi/catbox-redis'

const server = new Hapi.Server({
  cache : [
    {
      name: 'my_cache',
      provider: {
        constructor: CatboxRedis,
        options: {}
      }
    }
  ]
})

Exemplar

TODO: Link to CDP exemplar

Templates

The following elements support LiquidJS templates:

  • Page title
  • Form component title
    • Support for fieldset legend text or label text
    • This includes when the title is used in error messages
  • Html (guidance) component content
  • Summary component row key title (check answers and repeater summary)

Template data

The data the templates are evaluated against is the raw answers the user has provided up to the page they're currently on. For example, given a YesNoField component called TKsWbP, the template {{ TKsWbP }} would render "true" or "false" depending on how the user answered the question.

The current FormContext is also available as context in the templates. This allows access to the full data including the path the user has taken in their journey and any miscellaneous data returned from Page events in context.data.

Liquid Filters

There are a number of LiquidJS filters available to you from within the templates:

  • page - returns the page definition for the given path
  • field - returns the component definition for the given name
  • href - returns the page href for the given page path
  • answer - returns the user's answer for a given component
  • evaluate - evaluates and returns a Liquid template using the current context

Examples

"pages": [
  {
    "title": "What's your name?",
    "path": "/full-name",
    "components": [
      {
        "name": "WmHfSb",
        "title": "What's your full name?",
        "type": "TextField"
      }
    ]
  },
  // This example shows how a component can use an answer to a previous question (What's your full name) in it's title
  {
    "title": "Are you in England?",
    "path": "/are-you-in-england",
    "components": [
      {
        "name": "TKsWbP",
        "title": "Are you in England, {{ WmHfSb }}?",
        "type": "YesNoField"
      }
    ]
  },
  // This example shows how a Html (guidance) component can use the available filters to get the form definition and user answers and display them
  {
    "title": "Template example for {{ WmHfSb }}?",
    "path": "/example",
    "components": [
      {
        "title": "Html",
        "type": "Html",
        "content": "<p class=\"govuk-body\">
          // Use Liquid's `assign` to create a variable that holds reference to the \"/are-you-in-england\" page
          {%- assign inEngland = \"/are-you-in-england\" | page -%}

          // Use the reference to `evaluate` the title
          {{ inEngland.title | evaluate }}<br>

          // Use the href filter to display the full page path
          {{ \"/are-you-in-england\" | href }}<br>

          // Use the `answer` filter to render the user provided answer to a question
          {{ 'TKsWbP' | answer }}
        </p>\n"
      }
    ]
  }
]

Templates and views: Extending the default layout

TODO

To override the default page template, vision and nunjucks both need to be configured to search in the forms-engine-plugin views directory when looking for template files.

For vision this is done through the path plugin option For nunjucks it is configured through the environment configure options.

The forms-engine-plugin path to add can be imported from:

import { VIEW_PATH } from '@defra/forms-engine-plugin'

Which can then be appended to the node_modules path node_modules/@defra/forms-engine.

The main template layout is govuk-frontend's template.njk file, this also needs to be added to the paths that nunjucks can look in.

Readme

Keywords

none

Package Sidebar

Install

npm i @defra/forms-engine-plugin

Weekly Downloads

279

Version

0.0.6

License

SEE LICENSE IN LICENSE

Unpacked Size

3.01 MB

Total Files

557

Last publish

Collaborators

  • stuaa78
  • npm-envage
  • richard-mohammed-def
  • defradigitaladmin
  • defradigitalci
  • jaucourt
  • bensagar-ea
  • z424bravenpm
  • tjmason.defra
  • pwadmore.defra
  • niki.wycherley
  • psandrews
  • alexluckett
  • maxcbc-defra
  • pmshaw15
  • geordiefoo
  • dannyleech
  • feedmypixel
  • christopherjturner