@envage/hapi-govuk-journey-map
v0.1.1
Published
A hapi plugin for mapping journeys through a hapi web service
Downloads
5
Maintainers
Keywords
Readme
Hapi GOV.UK Journey Map
Overview
This plugin makes it easier to visualise, create and maintain journeys through a hapi web service.
It achieves this by placing reusable journeys into modules each containing a set of pages or routes that when combined make a self contained reusable journey.
Examples of such modules would be an address module, a contact module and a file upload module.
A way of configuring the journey within the module and connections between modules is with mapping files. Within the POC, YAML was used to describe the journey configuration within each mapping file.
Installation
npm install --save @envage/hapi-govuk-journey-map
Usage
The best way to describe this is with an example:
Registering
Please note that the required "setQueryData" and "getQueryData" functions will be explained in Branching.
Register the plugin as follows:
const cache = {}
const { resolve } = require('path')
module.exports = {
plugin: require('@envage/hapi-govuk-journey-map'),
options: {
modulePath: resolve(`${process.cwd()}/src/server/modules`),
setQueryData: (request, data) => {
Object.assign(cache, data)
},
getQueryData: (request) => {
return { ...cache }
},
journeyMapPath: '/journey-map',
journeyMapView: '/journey-map.njk'
}
}
Mapping
Please note that each of the entries within the following files ultimately generate routes within a hapi service.
Example mapping files for a simple journey:
- Root map:
--- # Root map
home:
path: "/"
route: home.route
applicant:
path: "/applicant"
module: contact
complete:
path: "/complete"
route: complete.route
- Contact map:
--- # Contact map
name:
path: "/name"
route: contact-name.route
address:
path: "/address"
module: address
email:
path: "/email"
route: contact-email.route
- Address map:
--- # Address map
search:
path: "/search"
route: address-search.route
select:
path: "/select"
route: address-select.route
entry:
path: "/entry"
route: address-entry.route
The idea is that the navigation through the routes (pages) starts in the root map and flows through each adjacent route. When the module property is set, the flow moves to the start of that modules map and flows through that map. After processing the last route, the flow returns to the previous map and continues.
As I have included no branching in the above map, I would expect the paths (pages) to be traversed in the following order:
- /
- /applicant/name
- /applicant/address/search
- /applicant/address/select
- /applicant/address/entry
- /applicant/email
- /complete
Note that the paths are generated with the parent module path prefixing the current path in each module's map.
The routemap object can be retrieved with the getRouteMap
function.
File structure
The file structure in the project for these modules would be as follows:
.
+-- modules
| +-- complete.route.js
| +-- home.route.js
| +-- map.yml
| +-- address
| | +--address.map.yml
| | +--address-entry.view.njk
| | +--address-entry.route.js
| | +--address-search.view.njk
| | +--address-search.route.js
| | +--address-select.view.njk
| | +--address-select.route.js
| +-- contact
| | +--contact.map.yml
| | +--contact-email.view.njk
| | +--contact-email.route.js
| | +--contact-name.view.njk
| | +--contact-name.route.js
The following is an example of a route file. I have chosen "contact-name.route.js" for this purpose. Please note that in the following example "Application" is used to persist the contact name:
const Application = require('../../dao/application')
const view = 'contact/contact-name.view.njk'
const pageHeading = 'Please enter your name'
module.exports = [{
method: 'GET',
handler: async function (request, h) {
const { contact = {} } = await Application.get(request)
return h.view(view, {
pageHeading,
value: contact.name
})
}
}, {
method: 'POST',
handler: async function (request, h) {
const { contact = {} } = await Application.get(request)
const { name = '' } = request.payload
contact.name = name
await Application.update(request, { contact })
return h.continue
}
}]
Branching
In order to allow branching, it's necessary to allow a query to be asked with a set of alternative routes to go to based on the result of that query.
--- # Address map
manual-check:
path: "/manual-check"
route: address-manual-check
next:
query: postcodeLookUpEnabled
when:
yes: search
no: entry
search:
path: "/search"
route: address-search.route
select:
path: "/select"
route: address-select.route
entry:
path: "/entry"
route: address-entry.route
In the above map, the value of "postcodeLookUpEnabled" (please notes that you can call this query whatever you like) is used to determine the branching.
In the above case a value of "yes" would branch to "search" where as "no" would skip both "search" and "select" and jump straight to "entry"
In order to make this work the "postcodeLookUpEnabled" value needs to be set to "yes" or "no" within the route file. This can be done using the "setQueryData" method.
Please see the extract of a route file below as an example:
.
.
const { setQueryData } = require('@envage/hapi-govuk-journey-map')
.
.
}, {
method: 'POST',
handler: async function (request, h) {
if (process.env.POSTCODE_LOOKUP_ENABLED) {
setQueryData(request, { postcodeLookUPEnabled: 'yes'})
} else {
setQueryData(request, { postcodeLookUPEnabled: 'no'})
}
return h.continue
}
}
.
.
.
Enquiry routes
The enquiry routes will be available if the journeyMapPath option is configured In the following example the journeyMapPath is set to '/journey-map'
/journey-map
/journey-map/{id}
These routes will be automatically loaded when the plugin is registered to return json describing the internal generated map
If the JourneyMapView parameter is set, the view will be displayed with the dat in the variable map otherwise json is returned.
Getting route details
When writing a route handler you may want to retrieve route details. The following functions can be used for this:
getRoute
retrieves details about a named route:
const { getRoute } = require('@envage/hapi-govuk-journey-map')
.
.
const route = await getRoute('route-id')
getCurrent
retrieves details about the current route:
const { getCurrent } = require('@envage/hapi-govuk-journey-map')
.
.
const route = await getCurrent(request)
getNextRoute
retrives the route that is next in the flow:
const { getNextRoute } = require('@envage/hapi-govuk-journey-map')
.
.
const nextRoute = getNextRoute(request)
Module options
When reusing a module it is desirable to be able to pass options to it. For example, a file upload screen could have a filetype string and a list of allowed file extensions passed to it, allowing it to be reused for different types of file. These options are passed in the following way:
site-plan-upload:
path: "/site-plan-upload"
module: "upload"
options:
filetype: "Site plan"
extensions: ["PNG", "PDF"]
These can then be accessed from a route handler within the module like this:
const { getCurrent } = require('@envage/hapi-govuk-journey-map')
.
.
const route = await getCurrent(request)
const { filetype, extensions } = route.parent.options
Development and Test
When developing this plugin, simply clone this repository
git clone https://github.com/DEFRA/hapi-govuk-journey-map.git
and run
npm install
Running tests
Unit tests can be run using
npm run unit-test
Publishing to npm
Note that each time the module is published to npm, the version number in the package.json file must be updated in accordance with semantic versioning Also note that the module must be published as public
npm publish --access public
Contributing to this project
If you have an idea you'd like to contribute please log an issue.
All contributions should be submitted via a pull request.
Note that we use Standard JS style, you can check your code using
npm run lint
Licence
THIS INFORMATION IS LICENSED UNDER THE CONDITIONS OF THE OPEN GOVERNMENT LICENCE found at:
http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3
The following attribution statement MUST be cited in your products and applications when using this information.
Contains public sector information licensed under the Open Government licence v3
About the licence
The Open Government Licence (OGL) was developed by the Controller of Her Majesty's Stationery Office (HMSO) to enable information providers in the public sector to license the use and re-use of their information under a common open licence.
It is designed to encourage use and re-use of information freely and flexibly, with only a few conditions.