Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fbd5c82
Move pull request template to .github/
alexluckett Mar 24, 2025
93e2242
Split out readme into docs/* folder
alexluckett Mar 24, 2025
ddaa9c1
Organise README.md into docs/
alexluckett Mar 24, 2025
22cd609
Fix broken link to getting started page
alexluckett Mar 24, 2025
8518946
fix typo
alexluckett Mar 24, 2025
b12e061
Add link to getting started guide
alexluckett Mar 24, 2025
47f8ba2
Move features into feature folder
alexluckett Mar 24, 2025
e3ef031
Add link to custom services page
alexluckett Mar 24, 2025
b8c9075
update
alexluckett Mar 24, 2025
d66ebde
add documentation for page templates
alexluckett Mar 24, 2025
3232cc5
add link to contribution guide
alexluckett Mar 24, 2025
ab81a19
Merge remote-tracking branch 'origin/main' into developer-docs
alexluckett Mar 24, 2025
39560f0
Add page events documentation
alexluckett Mar 25, 2025
0922475
add foundational knowledge
alexluckett Mar 25, 2025
20e7e91
condense for brevity
alexluckett Mar 25, 2025
1faf751
fix broken link
alexluckett Mar 25, 2025
f922914
fix hierarchy
alexluckett Mar 25, 2025
56fbb26
fix broken link
alexluckett Mar 25, 2025
b1dd605
Add future of page event authentication to docs
alexluckett Mar 25, 2025
9c43846
Fix typo in PAGE_EVENTS
davidjamesstone Mar 25, 2025
1877bb2
Indent PAGE_TEMPLATE example
davidjamesstone Mar 25, 2025
29daf1e
Fix auth PAGE_TEMPLATE example
davidjamesstone Mar 25, 2025
f9dcf1f
Fix typo in PAGE_TEMPLATE
davidjamesstone Mar 25, 2025
9c591bd
use jinja2 not jinja in code block header
alexluckett Mar 25, 2025
35568a6
grammar fix
alexluckett Mar 25, 2025
1fec6b8
Switch to jsonc
alexluckett Mar 25, 2025
55e7c73
more typo fixes
alexluckett Mar 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
317 changes: 11 additions & 306 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,330 +6,35 @@ It is designed to be embedded in the frontend of a digital service and provide a

## Table of Contents

- [Demo of DXT](#demo-of-dxt)
- [Installation](#installation)
- [Dependencies](#dependencies)
- [Setup](#setup)
- [Form Config](#form-config)
- [Static Assets and Styles](#static-assets-and-styles)
- [Example](#example)
- [Environment Variables](#environment-variables)
- [Options](#options)
- [Services](#services)
- [Custom Controllers](#custom-controllers)
- [Custom Filters](#custom-filters)
- [Custom Cache](#custom-cache)
- [Exemplar](#exemplar)
- [Templates](#templates)
- [Template Data](#template-data)
- [Liquid Filters](#liquid-filters)
- [Examples](#examples)
- [Templates and Views: Extending the Default Layout](#templates-and-views-extending-the-default-layout)
- [Documentation](#documentation)
- [Publishing the Package](#publishing-the-package)
- [Semantic Versioning Control](#semantic-versioning-control)
- [Major-Version Release Branches](#major-version-release-branches)
- [Manual Workflow Triggers](#manual-workflow-triggers)
- [Workflow Triggers](#workflow-triggers)
- [Safety and Consistency](#safety-and-consistency)

## Installation

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

## Dependencies

The following are [plugin dependencies](<https://hapi.dev/api/?v=21.4.0#server.dependency()>) that are required to be registered with hapi:

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

- [hapi-pino](https://github.com/hapijs/hapi-pino) - [Pino](https://github.com/pinojs/pino) logger for hapi
- [@hapi/crumb](https://github.com/hapijs/crumb) - CSRF crumb generation and validation
- [@hapi/yar](https://github.com/hapijs/yar) - Session manager
- [@hapi/vision](https://github.com/hapijs/vision) - Template rendering support

Additional npm dependencies that you will need are:

`npm install nunjucks govuk-frontend --save`

- [nunjucks](https://www.npmjs.com/package/nunjucks) - [templating engine](https://mozilla.github.io/nunjucks/) used by GOV.UK design system
- [govuk-frontend](https://www.npmjs.com/package/govuk-frontend) - [code](https://github.com/alphagov/govuk-frontend) you need to build a user interface for government platforms and services

Optional dependencies

`npm install @hapi/inert --save`

- [@hapi/inert](https://www.npmjs.com/package/@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](https://design-system.service.gov.uk/components/select/), [Checkboxes](https://design-system.service.gov.uk/components/checkboxes/) and [Radios](https://design-system.service.gov.uk/components/radios/)

The [types](https://github.com/DEFRA/forms-designer/blob/main/model/src/form/form-definition/types.ts), `joi` [schema](https://github.com/DEFRA/forms-designer/blob/main/model/src/form/form-definition/index.ts) and the [examples](test/form/definitions) 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](https://hapi.dev/tutorials/routing/?lang=en_US) 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](#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 `formDefinition`s.

```
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](#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](https://hapi.dev/api/?v=21.4.0#plugins)

- `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](#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
- `viewPaths` (optional) - Include additional view paths when using custom `page.view`s
- `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](https://mozilla.github.io/nunjucks/templating.html#filters) and [liquid](https://liquidjs.com/filters/overview.html) 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](https://hapi.dev/api/?v=21.4.0#-serveroptionscache) 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](https://github.com/hapijs/catbox-redis)

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

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

## Exemplar
## Demo of DXT

TODO: Link to CDP exemplar

## Templates

The following elements support [LiquidJS templates](https://liquidjs.com/):

- 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 event`s 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

```json
"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.
## Installation

For vision this is done through the `path` [plugin option](https://github.com/hapijs/vision/blob/master/API.md#options)
For nunjucks it is configured through the environment [configure options](https://mozilla.github.io/nunjucks/api.html#configure).
[See our getting started developer guide](./docs/GETTING_STARTED.md).

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

`import { VIEW_PATH } from '@defra/forms-engine-plugin'`
DXT has a mix of configuration-driven and code-based features that developers can utilise.

Which can then be appended to the `node_modules` path `node_modules/@defra/forms-engine`.
[See our documentation folder](./docs/INDEX.md) to learn more about the features of DXT.

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

### Custom page view
[See our contribution guide](./docs/CONTRIBUTING.md).

## Publishing the Package
## Publishing the package

Our GitHub Actions workflow (`publish.yml`) is set up to make publishing a breeze, using semantic versioning and a variety of release strategies. Here's how you can make the most of it:

Expand Down
14 changes: 7 additions & 7 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ Thank you for considering making a contribution to DXT! Our goal is to make DXT

This guide aims to set clear expectations for everyone involved in our project, to make collaborating a smooth and enjoyable experience.

# I have a question
## I have a question

If you are within Department for Environment, Food & Rural Affairs, please primarily direct your questions to our Slack channel [#defra-forms-support](https://defra-digital-team.slack.com). Our team monitors this channel during working hours and will provide assistance.

# I want to request something
## I want to request something

## Reporting bugs
### Reporting bugs

Report bugs on the [#defra-forms-support](https://defra-digital-team.slack.com) slack channel. If you are not a member of Defra, [submit a GitHub issue](https://github.com/DEFRA/forms-engine-plugin/issues).

Expand All @@ -36,11 +36,11 @@ If your bug is with the plugin, ensure you are running the plugin in a supported
- An estimated timeframe for a resolution
- An update once the issue is resolved

## Suggesting features
### Suggesting features

Feature suggestions are welcomed from teams within Defra Group only. Our roadmap is continually updated as new requirements emerge. Suggest new features on our [#defra-forms-support](https://defra-digital-team.slack.com) slack channel.

# I want to contribute something
## I want to contribute something

All code contributed to this repository should meet the [Defra software development standards](https://defra.github.io/software-development-standards/). Our codebase, by exception, allows modification of Typescript files where appropriate. However, new code that is contributed should be Javascript with types via JSDoc, not Typescript.

Expand All @@ -50,10 +50,10 @@ Our GitHub Workflows will mark each pull request with a pass/fail based on tests

Draft pull requests are accepted if you are not yet finished, but would like early feedback. Pull requests that remain as a draft for over 30 days will be closed.

## Fixing bugs
### Fixing bugs

If you would like to fix the bug yourself, contributions are accepted through pull requests.

## Adding features
### Adding features

Features should be discussed with the Defra Forms team prior to implementation. This is to prevent wasted effort if the Defra Forms team decides not to accept it, or if we suggest any significant amendments. Reach out to us on [#defra-forms-support](https://defra-digital-team.slack.com) to discuss your requirements. If accepted by the product owner, we welcome a pull request.
Loading
Loading