Skip to content

Commit dda159e

Browse files
authored
Merge pull request #380 from DEFRA/docs/components
docs: generate component and page type documentation from forms-model types
2 parents 70c647d + 7932c8c commit dda159e

11 files changed

Lines changed: 1852 additions & 81 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ package
1414
.env
1515
tsconfig.tsbuildinfo
1616
docs/schemas
17+
docs/features/components
18+
docs/features/pages
1719
temp-schemas
1820

1921
# Docusaurus

docs/features/index.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
# Features
22

3-
forms-engine-plugin provides two categories of features to help you extend and customise your form journeys beyond the out-of-the-box behaviour.
3+
forms-engine-plugin provides built-in components and page types you can use immediately in your form definitions, as well as advanced features for driving dynamic behaviour or writing custom code.
44

5-
## [Configuration-based Features](./features/configuration-based)
5+
## [Components](./features/components)
6+
7+
A library of built-in form components — text fields, date inputs, radio buttons, file upload, payment, geospatial fields, and more. Add them to your form definition by name.
8+
9+
## [Page Types](./features/pages)
10+
11+
Built-in page controllers that define how a page behaves — question pages, repeating groups, file upload pages, summary and confirmation pages.
12+
13+
## Advanced
14+
15+
### [Configuration-based Features](./features/configuration-based)
616

717
Drive advanced functionality — such as calling APIs and rendering dynamic content — entirely through form definitions, with no custom code required.
818

9-
## [Code-based Features](./features/code-based)
19+
### [Code-based Features](./features/code-based)
1020

1121
Implement highly tailored behaviour by writing custom TypeScript/JavaScript that integrates with forms-engine-plugin's extension points.

docs/getting-started.md

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,27 @@
22

33
## Foundational knowledge
44

5-
forms-engine-plugin is a hapi plugin for a frontend service, which allows development teams to construct forms using configuration and minimal code. Forms are closely based on the knowledge, components and patterns from the GDS Design System. Forms should remain as lightweight as possible, with business logic being implemented in a backend/BFF API and forms-engine-plugin used as a simple presentation layer.
5+
forms-engine-plugin is a hapi plugin that lets teams build GOV.UK forms using configuration and minimal code, based on GDS Design System patterns. Keep business logic in a backend/BFF API and treat the plugin as a thin presentation layer.
66

7-
You should aim, wherever possible, to utilise the existing behaviours of forms-engine-plugin. Our team puts a lot of effort into development, user testing and accessibility testing to ensure the forms created with forms-engine-plugin will be of a consistently high quality. Where your team introduces custom behaviour, such as custom components or custom pages, this work will now need to be done by your team. Where possible, favour fixing something upstream in the plugin so many teams can benefit from the work we do. Then, if you still need custom behaviour - go for it! forms-engine-plugin is designed to be extended, just be wise with how you spend your efforts.
7+
Prefer built-in behaviour wherever possible — it's been developed, user-tested, and accessibility-tested to a consistent standard. If you need something the plugin doesn't support, consider fixing it upstream so all teams benefit. When custom code is genuinely the right call, the plugin is designed to be extended.
88

9-
When developing with forms-engine-plugin, you should favour development using the below priority order. This will ensure your team is writing the minimum amount of code, focusing your efforts on custom code where the requirements are niche and there is value.
9+
Favour this priority order:
1010

11-
1. Use out-of-the box forms-engine-plugin components and page types (components, controllers)
12-
2. Use configuration-driven advanced functionality to integrate with backends and dynamically change page content (page events, page templates)
13-
3. Use custom views, custom components and page controllers to implement highly tailored and niche logic (custom Nunjucks, custom Javascript)
11+
1. Built-in components and page types
12+
2. Configuration-driven features (page events, templates) to integrate with backends
13+
3. Custom views, components, and controllers for niche requirements
1414

15-
### Contributing back to forms-engine-plugin
15+
### Contributing back to forms-engine-plugin <!-- no-sidebar -->
1616

17-
When you build custom components and page controllers, they might be useful for other teams in Defra to utilise. For example, many teams collect CPH numbers but have no way to validate it's correct. Rather than creating a new CPH number component and letting it sit in your codebase for just your team, see our [contribution guide](./contributing) to learn how to contribute this back to forms-engine-plugin for everyone to benefit from.
17+
Custom components you build may be useful to other Defra teams. See the [contribution guide](./contributing) to share them upstream rather than keeping them in your own codebase.
1818

1919
## Step 1: Add forms-engine-plugin as a dependency
2020

21-
### Installation
21+
### Installation <!-- no-sidebar -->
2222

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

25-
### Dependencies
25+
### Dependencies <!-- no-sidebar -->
2626

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

@@ -40,7 +40,7 @@ Additional npm dependencies that you will need are:
4040
- [nunjucks](https://www.npmjs.com/package/nunjucks) - [templating engine](https://mozilla.github.io/nunjucks/) used by GOV.UK design system
4141
- [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
4242

43-
Optional dependencies
43+
### Optional dependencies <!-- no-sidebar -->
4444

4545
`npm install @hapi/inert --save`
4646

@@ -67,11 +67,14 @@ await server.register({
6767
Full example:
6868

6969
```javascript
70+
import { join } from 'node:path'
7071
import hapi from '@hapi/hapi'
72+
import vision from '@hapi/vision'
7173
import yar from '@hapi/yar'
7274
import crumb from '@hapi/crumb'
7375
import inert from '@hapi/inert'
7476
import pino from 'hapi-pino'
77+
import nunjucks from 'nunjucks'
7578
import plugin from '@defra/forms-engine-plugin'
7679

7780
const server = hapi.server({
@@ -93,6 +96,23 @@ await server.register({
9396

9497
const paths = [join(config.get('appDir'), 'views')]
9598

99+
await server.register({
100+
plugin: vision,
101+
options: {
102+
engines: {
103+
html: {
104+
compile(path, { environment }) {
105+
return (context) => nunjucks.compile(path, environment).render(context)
106+
}
107+
}
108+
},
109+
path: paths,
110+
compileOptions: {
111+
environment: nunjucks.configure(paths)
112+
}
113+
}
114+
})
115+
96116
// Register the `forms-engine-plugin`
97117
await server.register({
98118
plugin,
@@ -120,8 +140,8 @@ await server.register({
120140
const user = await userService.getUser(request.auth.credentials)
121141

122142
return {
123-
"greeting": "Hello" // available to render on a nunjucks page as {{ greeting }}
124-
"username": user.username // available to render on a nunjucks page as {{ username }}
143+
greeting: 'Hello', // available to render on a nunjucks page as {{ greeting }}
144+
username: user.username // available to render on a nunjucks page as {{ username }}
125145
}
126146
}
127147
}
@@ -134,7 +154,7 @@ await server.start()
134154

135155
1. Import forms-engine-plugin's styling
136156

137-
If you are using the CDP templates, you just need to ensure your `src/client/stylesheets/application.scss` file contains:
157+
If you are on CDP, ensure your `src/client/stylesheets/application.scss` file contains:
138158

139159
```sass
140160
@use "pkg:@defra/forms-engine-plugin";
@@ -156,6 +176,12 @@ Example: https://github.com/DEFRA/forms-runner/blob/24c5623946cdfddca593bcba8a68
156176

157177
## Step 5: Environment variables
158178

179+
The following variable is always required:
180+
181+
```shell
182+
SESSION_COOKIE_PASSWORD=your-secret-password-at-least-32-chars
183+
```
184+
159185
Blocks marked with `# FEATURE: <name>` are optional and can be omitted if the feature is not used.
160186

161187
```shell
@@ -184,20 +210,20 @@ GOOGLE_ANALYTICS_TRACKING_ID='12345'
184210

185211
## Step 6: Creating and loading a form
186212

187-
Forms in forms-engine-plugin are represented by a configuration object called a "form definition". The form definition can be stored in a location and format of your choosing by providing a `formsService` as a registration option. If you are using our 'loader' pattern as recommended in step 2, you will likely be writing YAML or JSON files in your repository as files.
213+
Forms in forms-engine-plugin are represented by a configuration object called a "form definition". The form definition can be stored in a location and format of your choosing by providing a `formsService` as a registration option. If you are using our 'loader' pattern as recommended in step 2, you will likely be writing YAML or JSON files in your repository.
188214

189215
Our examples primarily use JSON. If you are using YAML, simply convert the data structure from JSON to YAML and the examples will still work.
190216

191217
The configuration defines several top-level elements:
192218

193-
- `pages` - includes a `path`, `title`
219+
- `pages` - the form journey, each representing a single web page with a path, title, and components
194220
- `components` - one or more questions on a page
195-
- `conditions` - used to conditionally show and hide pages and
196-
- `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/)
221+
- `conditions` - used to conditionally show and hide pages
222+
- `lists` - data used 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/)
197223

198224
To understand the full set of options available to you, consult our [schema documentation](https://defra.github.io/forms-engine-plugin/schemas/). Specifically, the [form definition schema](https://defra.github.io/forms-engine-plugin/schemas/form-definition-v2-payload-schema).
199225

200-
### Config
226+
### Config <!-- no-sidebar -->
201227

202228
#### Pages
203229

docs/plugin-options.md

Lines changed: 81 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,25 @@
22

33
The forms plugin is configured with [registration options](https://hapi.dev/api/?v=21.4.0#plugins)
44

5-
## Required options
6-
7-
- `nunjucks` (required) - Template engine configuration. See [nunjucks configuration](#nunjucks-configuration)
8-
- `viewContext` (required) - A function that provides global context to all templates. See [viewContext](#viewcontext)
9-
- `baseUrl` (required) - Base URL of the application (protocol and hostname, e.g., `"https://myservice.gov.uk"`). Used for generating absolute URLs in markdown rendering and other contexts
10-
11-
## Optional options
12-
13-
- `model` (optional) - Pre-built `FormModel` instance. When provided, the plugin serves a single static form definition. When omitted, forms are loaded dynamically via `formsService`. See [model](#model)
14-
- `services` (optional) - object containing `formsService`, `formSubmissionService` and `outputService`
15-
- `formsService` - used to load `formMetadata` and `formDefinition`
16-
- `formSubmissionService` - used prepare the form during submission (ignore - subject to change)
17-
- `outputService` - used to save the submission
18-
- `controllers` (optional) - Object map of custom page controllers used to override the default. See [custom controllers](#custom-controllers)
19-
- `globals` (optional) - A map of custom template globals to include. See [custom globals](#custom-globals)
20-
- `filters` (optional) - A map of custom template filters to include. See [custom filters](#custom-filters)
21-
- `cache` (optional) - Caching options. Recommended for production. This can be either:
22-
- a string representing the cache name to use (e.g. hapi's default server cache). See [custom cache](#custom-cache) for more details.
23-
- a custom `CacheService` instance implementing your own caching logic
24-
- `pluginPath` (optional) - The location of the plugin (defaults to `node_modules/@defra/forms-engine-plugin`)
25-
- `preparePageEventRequestOptions` (optional) - A function that will be invoked for http-based [page events](./features/configuration-based/page-events). See [here](./features/configuration-based/page-events#authenticating-a-http-page-event-request-from-forms-engine-plugin-in-your-api) for details
26-
- `saveAndExit` (optional) - Configuration for custom session management including key generation, session hydration, and persistence. See [save and exit documentation](./features/code-based/save-and-exit) for details
27-
- `onRequest` (optional) - A function that will be invoked on each request to any form route e.g `/{slug}/{path}`. See [onRequest](#onrequest) for more details
5+
## Options
6+
7+
| Option | Required | Description |
8+
| -------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
9+
| `nunjucks` | Yes | Template engine configuration. See [Nunjucks configuration](#nunjucks-configuration) |
10+
| `viewContext` | Yes | A function that provides global context to all templates. See [viewContext](#viewcontext) |
11+
| `baseUrl` | Yes | Base URL of the application (protocol and hostname, e.g. `"https://myservice.gov.uk"`). Used for generating absolute URLs in markdown rendering and other contexts |
12+
| `model` | No | Pre-built `FormModel` instance. When provided, the plugin serves a single static form definition. When omitted, forms are loaded dynamically via `formsService`. See [model](#model) |
13+
| `services` | No | Object containing `formsService`, `formSubmissionService`, and `outputService`. See [services](#services) |
14+
| `controllers` | No | Object map of custom page controllers used to override the default. See [custom controllers](#custom-controllers) |
15+
| `globals` | No | A map of custom template globals to include. See [custom globals](#custom-globals) |
16+
| `filters` | No | A map of custom template filters to include. See [custom filters](#custom-filters) |
17+
| `cache` | No | Caching options. Recommended for production — either a cache name string or a custom `CacheService` instance. See [custom cache](#custom-cache) |
18+
| `pluginPath` | No | The location of the plugin. Defaults to `node_modules/@defra/forms-engine-plugin` |
19+
| `preparePageEventRequestOptions` | No | A function invoked for HTTP-based [page events](./features/configuration-based/page-events) to customise outbound request options |
20+
| `saveAndExit` | No | Configuration for custom session management. See [save and exit](./features/code-based/save-and-exit) |
21+
| `onRequest` | No | A function invoked on each request to any form route (e.g. `/{slug}/{path}`). See [onRequest](#onrequest) |
22+
| `ordnanceSurveyApiKey` | No | Ordnance Survey API key. Required to enable the inline map on geospatial components. See [geospatial map](#geospatial-map) |
23+
| `ordnanceSurveyApiSecret` | No | Ordnance Survey API secret. Required alongside `ordnanceSurveyApiKey` to enable the inline map on geospatial components |
2824

2925
## Option details
3026

@@ -34,9 +30,50 @@ See [our services documentation](./features/code-based/custom-services).
3430

3531
### Custom controllers
3632

37-
TODO
33+
The `controllers` option lets you register custom page controller classes that extend the built-in `PageController`. A custom controller is tied to a page in your form definition by setting the page's `controller` property to the key you register it under.
3834

39-
### nunjucks configuration
35+
```ts
36+
import { PageController } from '@defra/forms-engine-plugin/controllers/PageController.js'
37+
import { type FormModel } from '@defra/forms-engine-plugin/types'
38+
import { type Page } from '@defra/forms-model'
39+
40+
class ConfirmationPageController extends PageController {
41+
constructor(model: FormModel, pageDef: Page) {
42+
super(model, pageDef)
43+
}
44+
45+
makeGetRouteHandler() {
46+
return async (request, h) => {
47+
// custom logic before rendering
48+
return h.view(this.viewName, { ...await this.getViewModel(request) })
49+
}
50+
}
51+
}
52+
53+
await server.register({
54+
plugin,
55+
options: {
56+
controllers: {
57+
ConfirmationPageController
58+
}
59+
}
60+
})
61+
```
62+
63+
In your form definition, set the `controller` property of any page to the same key:
64+
65+
```json
66+
{
67+
"path": "/confirmation",
68+
"title": "Confirmation",
69+
"controller": "ConfirmationPageController",
70+
"components": []
71+
}
72+
```
73+
74+
When the engine instantiates pages, it first checks for a matching built-in controller, then falls back to the `controllers` map. If no match is found the default `PageController` is used.
75+
76+
### Nunjucks configuration
4077

4178
The `nunjucks` option is required and configures the template engine paths and layout.
4279

@@ -184,7 +221,7 @@ In your templates:
184221
Use the `filter` plugin option to provide custom template filters.
185222
Filters are available in both [nunjucks](https://mozilla.github.io/nunjucks/templating.html#filters) and [liquid](https://liquidjs.com/filters/overview.html) templates.
186223

187-
```
224+
```js
188225
const formatter = new Intl.NumberFormat('en-GB')
189226

190227
await server.register({
@@ -207,7 +244,7 @@ In production you should create a custom cache one of the available `@hapi/catbo
207244

208245
E.g. [Redis](https://github.com/hapijs/catbox-redis)
209246

210-
```
247+
```js
211248
import { Engine as CatboxRedis } from '@hapi/catbox-redis'
212249

213250
const server = new Hapi.Server({
@@ -288,3 +325,20 @@ await server.register({
288325
```
289326
290327
For detailed documentation and examples, see [Save and Exit](./features/code-based/save-and-exit).
328+
329+
### Geospatial map
330+
331+
Geospatial components ([Easting Northing](./features/components/easting-northing-field.md), [OS Grid Ref](./features/components/os-grid-ref-field.md), [National Grid Field Number](./features/components/national-grid-field-number-field.md), [Lat Long](./features/components/lat-long-field.md), [Geospatial](./features/components/geospatial-field.md)) render an inline Ordnance Survey map alongside their input fields. The map lets users click a location to auto-populate the coordinate inputs, rather than typing values manually.
332+
333+
The map is only activated when both `ordnanceSurveyApiKey` and `ordnanceSurveyApiSecret` are provided at plugin registration. Without them the components still function as plain text inputs.
334+
335+
```js
336+
await server.register({
337+
plugin,
338+
options: {
339+
ordnanceSurveyApiKey: process.env.ORDNANCE_SURVEY_API_KEY,
340+
ordnanceSurveyApiSecret: process.env.ORDNANCE_SURVEY_API_SECRET,
341+
// ... other options
342+
}
343+
})
344+
```

0 commit comments

Comments
 (0)