### Install and run the form wizard example Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/example/README.md Use these commands in the project root to install dependencies and start the development server. ```bash $> npm install $> npm start ``` -------------------------------- ### Example Entry File for Bundling Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md An example entry JavaScript file that imports GOV.UK Frontend and hmpo-form-wizard, and initializes all components. ```js import { initAll } from 'govuk-frontend'; import 'hmpo-form-wizard/all.js'; // ...your custom JS here initAll(); ``` -------------------------------- ### Install Dependencies for ESM Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Install necessary npm packages for govuk-frontend, hmpo-form-wizard, and Rollup for bundling ESM JavaScript. ```bash npm install govuk-frontend hmpo-form-wizard rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-terser ``` -------------------------------- ### Implement a full non-linear journey configuration Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md A complete example showing step definitions and wizard initialization for a non-linear journey pattern. ```javascript // steps.js module.exports = { '/dashboard': { noPost: true, checkJourney: false, hub: true }, '/section-a': { prereqs: ['dashboard'], editable: true, next: 'section-a-details' }, '/section-a-details': { editable: true, next: 'dashboard', setValuesOnSave: [{ key: 'sectionAComplete', value: 'completed' }] }, '/section-b': { prereqs: ['dashboard'], editable: true, next: 'section-b-details' }, '/section-b-details': { editable: true, next: 'dashboard', setValuesOnSave: [{ key: 'sectionBComplete', value: 'completed' }] }, '/summary': { checkJourney: false, prereqs: ['dashboard'] } } // index.js app.use(wizard(steps, fields, { name: 'my-wizard', editBackStep: 'summary', nonLinearJourney: true })); ``` -------------------------------- ### Example Rollup Configuration Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md A Rollup configuration file to bundle ESM JavaScript, including Node.js module resolution and CommonJS support. ```js import commonjs from '@rollup/plugin-commonjs'; import { nodeResolve } from '@rollup/plugin-node-resolve'; import terser from '@rollup/plugin-terser'; export default { input: 'assets/javascripts/app.js', output: { file: 'public/javascripts/application.js', format: 'esm', sourcemap: true, }, plugins: [ nodeResolve(), commonjs(), terser() ], }; ``` -------------------------------- ### Custom Controller Implementation Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Extend the base 'Controller' class to create custom controllers. This example shows adding custom middleware and overriding the 'locals' lifecycle method. ```javascript // controller.js const Controller = require('hmpo-form-wizard').Controller; class CustomController extends Controller { /* Custom middleware */ middlewareSetup() { super.middlewareSetup(); this.use((req, res, next) => { console.log(req.method, req.url); next(); }); } /* Overridden locals lifecycle */ locals(req, res, callback) { let locals = super.locals(req, res (err, locals) => { locals.newLocal = 'value'; callback(null, locals); }); } } module.exports = CustomController ``` -------------------------------- ### Session Injection JavaScript Helper Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/injection/injection.html A helper function to populate the payload input field based on the selected example type. ```javascript function showExample(type) { document.getElementById("payload").value = document.getElementById(type+"Example").value; return false; } ``` -------------------------------- ### EJS Template for Session Injection Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/injection/injection.html EJS template logic for rendering notices, payload examples, and session state information. ```ejs <% if (obj.notice) { %> <%- notice %> ------------- <% } %> ``` ```ejs [Show Simple Example](javascript: showExample('simple')) <%- simpleExample %> | [Show Full Example](javascript: showExample('full')) <%- fullExample %> <%- payload %> ``` ```ejs <% if (obj.journeyKeys) { %> ### Current Journey Keys: <%- journeyKeys %> <% } %> ``` ```ejs <% if (obj.featureFlags) { %> ### Current Feature Flags: <%- featureFlags %> <% } %> ``` -------------------------------- ### Session Injection Payload Example Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md This JSON structure represents the data that can be injected into a session for debugging purposes via the `/debug/session` endpoint. ```json { "journeyName": "name", "journeyKeys": { "key": "name" }, "allowedStep": "/full/path", "prereqStep": "/full/path", "featureFlags": { "flag": true }, "wizards": { "wizardName": { "wizardKey": "value" } }, "rawSessionValues": { "sessionKey": "value" } } ``` -------------------------------- ### Manage Form Session Data with Session Model API Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Interact with form data stored in the session using the session model. This includes getting, setting, and removing values, as well as accessing journey data across wizards. ```javascript const { Controller } = require('hmpo-form-wizard'); class MyController extends Controller { middlewareActions() { super.middlewareActions(); this.use(this.handleSession); } handleSession(req, res, next) { // Get a value from session const name = req.sessionModel.get('name'); // Set a single value req.sessionModel.set('processedAt', Date.now()); // Set multiple values req.sessionModel.set({ status: 'processing', step: 3 }); // Remove a value req.sessionModel.unset('temporaryData'); // Get all session data as JSON const allData = req.sessionModel.toJSON(); console.log('Session data:', allData); // Reset session (clear all wizard data) // req.sessionModel.reset(); // Journey model for cross-wizard data const journeyHistory = req.journeyModel.get('history'); req.journeyModel.set('sharedValue', 'accessible-across-wizards'); next(); } } ``` -------------------------------- ### Step Configuration Options Reference Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Complete reference for all available step options when defining wizard steps. ```APIDOC ## Step Configuration Options Reference Complete reference for all available step options. ### Description This section details the various configuration options that can be applied to each step within an HMPO Form Wizard journey. These options control navigation, access, form behavior, session management, and more. ### Example Step Configuration ```javascript const steps = { '/example-step': { // Navigation next: 'next-step', // Next step (string, array of conditions, or function) backLink: 'previous-step', // Override auto-calculated back link backLinks: ['step-a', 'step-b'], // Valid referrers for back link // Entry and access control entryPoint: true, // Allow direct access without history checkSession: true, // Check session validity (default: true) checkEntryPointSession: false, // Check session on entry points checkJourney: true, // Validate step is allowed (default: true) prereqs: ['required-step'], // Steps that must be completed first // Fields and templates fields: ['field1', 'field2'], // Fields used on this step template: 'custom-template', // Template name (defaults to route) templatePath: 'pages/wizard', // Template directory // Form behavior noPost: true, // Disable POST (informational page) skip: true, // Skip rendering, run POST lifecycle // Session control reset: true, // Reset wizard session on access resetJourney: true, // Reset journey history on access // Edit mode editable: true, // Allow editing from review page editSuffix: '/edit', // Edit URL suffix editBackStep: 'confirm', // Return destination after edit continueOnEdit: true, // Continue journey instead of returning // Advanced controller: CustomController, // Custom controller class hub: true, // Mark as hub for non-linear journeys params: '/:id', // URL parameters forwardQuery: true, // Forward query string on redirect decisionFields: ['extraField'], // Additional routing decision fields revalidate: true, // Re-show page if marked invalid revalidateIf: ['field1'], // Re-show if these values change setValuesOnSave: [ { key: 'completed', value: true } ] } }; ``` ### Option Categories - **Navigation**: Controls how the user moves between steps. - `next`: Specifies the next step. - `backLink`: Overrides the default back link. - `backLinks`: Defines valid referrers for the back link. - **Entry and Access Control**: Manages who can access a step and under what conditions. - `entryPoint`: Allows direct access. - `checkSession`: Enables session validation. - `checkEntryPointSession`: Session validation specifically for entry points. - `checkJourney`: Validates if the step is allowed in the current journey. - `prereqs`: Lists steps that must be completed before this one. - **Fields and Templates**: Defines the content and presentation of a step. - `fields`: An array of field names used on the step. - `template`: The name of the template to render. - `templatePath`: The directory where templates are located. - **Form Behavior**: Modifies how the form submission process works. - `noPost`: Disables the POST method for informational pages. - `skip`: Skips rendering and proceeds directly to the POST lifecycle. - **Session Control**: Manages the wizard's session state. - `reset`: Resets the wizard session upon accessing the step. - `resetJourney`: Resets the entire journey history upon access. - **Edit Mode**: Configures behavior when editing a step, typically from a review page. - `editable`: Enables editing. - `editSuffix`: Suffix for the edit URL. - `editBackStep`: Destination after editing. - `continueOnEdit`: Continues the journey after editing instead of returning. - **Advanced**: For more complex or custom configurations. - `controller`: Specifies a custom controller class. - `hub`: Marks the step as a hub for non-linear journeys. - `params`: Defines URL parameters for the step. - `forwardQuery`: Forwards the query string on redirects. - `decisionFields`: Additional fields used for routing decisions. - `revalidate`: Forces re-showing the page if marked invalid. - `revalidateIf`: Re-shows the page if specified fields change. - `setValuesOnSave`: Automatically sets values when the step is saved. ``` -------------------------------- ### Create a Basic HMPO Form Wizard Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Sets up a basic wizard with defined steps and fields. Requires Express.js and the hmpo-form-wizard library. Configure the wizard with a name and template path. ```javascript const express = require('express'); const wizard = require('hmpo-form-wizard'); const app = express(); // Define form steps with navigation flow const steps = { '/': { entryPoint: true, resetJourney: true, next: 'your-name' }, '/your-name': { fields: ['name'], next: 'your-age' }, '/your-age': { fields: ['age'], next: 'your-favorite-color' }, '/your-favorite-color': { fields: ['color'], template: 'favorite-color', next: 'submit' }, '/submit': { next: 'done' }, '/done': { backLink: null, noPost: true } }; // Define field validation and types const fields = { name: { type: 'text', validate: 'required' }, age: { type: 'number', validate: [ 'required', 'numeric', { type: 'range', fn: value => value >= 0 && value < 120 } ] }, color: { type: 'select', validate: 'required', items: ['red', 'blue', 'green', 'purple', 'pink'] } }; // Mount the wizard on the app app.use('/form', wizard(steps, fields, { name: 'my-wizard', templatePath: 'pages/form' })); ``` -------------------------------- ### Configure Application-Level Error Handling Middleware Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Set up middleware to catch and handle wizard-specific errors like redirects, session timeouts, and missing prerequisites. Ensure session middleware is mounted before the wizard. ```javascript const express = require('express'); const wizard = require('hmpo-form-wizard'); const app = express(); // Session middleware required app.use(require('express-session')({ secret: 'your-secret', resave: false, saveUninitialized: false })); // Mount wizard app.use('/form', wizard(steps, fields, { name: 'my-form' })); // Handle wizard redirects (validation errors, missing prereqs) app.use((err, req, res, next) => { if (err.redirect) { return res.redirect(err.redirect); } next(err); }); // Handle session expiry app.use((err, req, res, next) => { if (err.code === 'SESSION_TIMEOUT') { return res.render('session-expired'); } if (err.code === 'MISSING_PREREQ') { return res.redirect('/form'); } next(err); }); // Generic error handler app.use((err, req, res, next) => { console.error(err); res.status(err.status || 500); res.render('error', { message: err.message, error: process.env.NODE_ENV === 'development' ? err : {} }); }); ``` -------------------------------- ### Configure a hub step in the wizard Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Define a step as a hub to allow other steps to declare it as a prerequisite without interfering with linear navigation. ```javascript '/dashboard': { noPost: true, checkJourney: false, hub: true } ``` -------------------------------- ### Next Steps Configuration Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Define the next step in a form wizard, which can be a relative path, an external URL, or an array of conditional next steps. ```APIDOC ## Next steps The next step for each step can be a relative path, an external URL, or an array of conditional next steps. Each condition next step can contain a next location, a field name, operator and value, or a custom condition function: ```javascript // steps.js '/step1': { // next can be a relative string path next: 'step2' }, '/step2': { // next can be an array of conditions next: [ // field, op and value. op defaults to '===' { field: 'field1', op: '===', value: 'foobar', next: 'conditional-next' }, // an operator can be a function { field: 'field1', op: (fieldValue, req, res, con) => fieldValue === con.value, value: true, next: 'next-step' }, // next can be an array of conditions { field: 'field1', value: 'boobaz', next: [ { field: 'field2', op: '=', value: 'foobar', next: 'sub-condition-next' }, 'sub-condition-default-next' ] }, // a condition can be a function specified by fn { fn: (req, res, con) => true, next: 'functional-condition' }, // a condition can be a controller method { fn: Controller.prototype.conditionMethod, next: 'functional-condition' }, // a condition can be a controller method specified by name { fn: 'conditionMethod', next: 'functional-condition' }, // the next option can be a function to return a dynamic next step { field: 'field1', value: true, next: (req, res, con) => 'functional-next' }, // use a string as a default next step 'default-next' ] } ``` ``` -------------------------------- ### Create and Use Wizard Middleware Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Create an instance of the form wizard and use it as middleware in an Express application. ```javascript // index.js const wizard = require('hmpo-form-wizard'); const steps = require('./steps'); const fields = require('./fields'); app.use(wizard(steps, fields, { name: 'my-wizard' })); ``` -------------------------------- ### Implement Non-Linear Hub Journeys in Form Wizard Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Enable non-linear navigation by marking a step as a 'hub' and setting 'nonLinearJourney' to true. Use 'prereqs' to define dependencies. ```javascript const steps = { '/dashboard': { noPost: true, checkJourney: false, hub: true // Mark as hub - doesn't affect linear navigation }, '/section-a': { prereqs: ['dashboard'], editable: true, next: 'section-a-details' }, '/section-a-details': { editable: true, next: 'dashboard', setValuesOnSave: [{ key: 'sectionAComplete', value: 'completed' }] }, '/section-b': { prereqs: ['dashboard'], editable: true, next: 'section-b-details' }, '/section-b-details': { editable: true, next: 'dashboard', setValuesOnSave: [{ key: 'sectionBComplete', value: 'completed' }] }, '/summary': { checkJourney: false, prereqs: ['dashboard'] } }; app.use('/multi-section', wizard(steps, fields, { name: 'multi-section', editBackStep: 'summary', nonLinearJourney: true // Enable non-linear navigation support })); ``` -------------------------------- ### Central Journey Storage Configuration Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Use 'journeyKey' to specify a field that saves into the 'journeyModel' for sharing values between wizards in the same journey. ```javascript // fields.js module.exports = { 'localFieldName': { journeyKey: 'centralfieldName', } } ``` -------------------------------- ### Define Step Configuration Options for HMPO Form Wizard Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Configure various aspects of a wizard step, including navigation, access control, form behavior, session management, and advanced options like custom controllers and URL parameters. ```javascript const steps = { '/example-step': { // Navigation next: 'next-step', backLink: 'previous-step', backLinks: ['step-a', 'step-b'], // Entry and access control entryPoint: true, checkSession: true, checkEntryPointSession: false, checkJourney: true, prereqs: ['required-step'], // Fields and templates fields: ['field1', 'field2'], template: 'custom-template', templatePath: 'pages/wizard', // Form behavior noPost: true, skip: true, // Session control reset: true, resetJourney: true, // Edit mode editable: true, editSuffix: '/edit', editBackStep: 'confirm', continueOnEdit: true, // Advanced controller: CustomController, hub: true, params: '/:id', forwardQuery: true, decisionFields: ['extraField'], revalidate: true, revalidateIf: ['field1'], setValuesOnSave: [ { key: 'completed', value: true } ] } }; ``` -------------------------------- ### Render HMPO Form Wizard Template Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/example/views/pages/multiwizard/your-age.html Uses the hmpoForm, hmpoNumber, and hmpoSubmit macros to construct a form page. Requires the hmpo-template.njk base template and the context object (ctx) to be available. ```nunjucks {% from "hmpo-form/macro.njk" import hmpoForm %} {% from "hmpo-number/macro.njk" import hmpoNumber %} {% from "hmpo-submit/macro.njk" import hmpoSubmit %} {% extends "hmpo-template.njk" %} {% set hmpoTitleKey = "fields.age.label" %} {% block content %} {% call hmpoForm(ctx) %} {{ hmpoNumber(ctx, { id: "age", isPageHeading: true }) }} {{ hmpoSubmit(ctx) }} {% endcall %} {% endblock %} ``` -------------------------------- ### Render HMPO Form Wizard Page Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/example/views/pages/multiwizard/index.html Uses the hmpoForm and hmpoSubmit macros within a template extending hmpo-template.njk. ```nunjucks {% from "hmpo-form/macro.njk" import hmpoForm %} {% from "hmpo-submit/macro.njk" import hmpoSubmit %} {% extends "hmpo-template.njk" %} {% set hmpoPageKey = "multiwizard.index" %} {% set backLink = "/" %} {% block content %} {{ super() }} {% call hmpoForm(ctx) %} {{ hmpoSubmit(ctx) }} {% endcall %} {% endblock %} ``` -------------------------------- ### Custom Controllers Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Create custom controllers by extending the base `Controller` class to add custom middleware or override lifecycle methods. ```APIDOC ## Custom Controllers Creating a custom controller: ```javascript // controller.js const Controller = require('hmpo-form-wizard').Controller; class CustomController extends Controller { /* Custom middleware */ middlewareSetup() { super.middlewareSetup(); this.use((req, res, next) => { console.log(req.method, req.url); next(); }); } /* Overridden locals lifecycle */ locals(req, res, callback) { let locals = super.locals(req, res (err, locals) => { locals.newLocal = 'value'; callback(null, locals); }); } } module.exports = CustomController ``` Examples of custom controllers can be found in the [example app](./example/controllers/submit.js) ``` -------------------------------- ### Define Form Steps Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Define the sequence and logic for form steps, including field requirements and conditional branching. ```javascript // steps.js module.exports = { '/step1': { next: 'step2' }, '/step2': { fields: ['name'], next: 'step3' }, '/step3': { fields: ['age'], next: [ { field: 'age', op: '<', value: 18, next: 'not-old-enough' }, 'step4' ] }, '/step4': {}, '/not-old-enough': {} } ``` -------------------------------- ### Conditional Next Steps Configuration Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Define 'next' steps for a form wizard step. This can be a simple path, an array of conditions with specific next steps, or a function for dynamic routing. ```javascript // steps.js '/step1': { // next can be a relative string path next: 'step2' }, '/step2': { // next can be an array of conditions next: [ // field, op and value. op defaults to '===' { field: 'field1', op: '===', value: 'foobar', next: 'conditional-next' }, // an operator can be a function { field: 'field1', op: (fieldValue, req, res, con) => fieldValue === con.value, value: true, next: 'next-step' }, // next can be an array of conditions { field: 'field1', value: 'boobaz', next: [ { field: 'field2', op: '=', value: 'foobar', next: 'sub-condition-next' }, 'sub-condition-default-next' ] }, // a condition can be a function specified by fn { fn: (req, res, con) => true, next: 'functional-condition' }, // a condition can be a controller method { fn: Controller.prototype.conditionMethod, next: 'functional-condition' }, // a condition can be a controller method specified by name { fn: 'conditionMethod', next: 'functional-condition' }, // the next option can be a function to return a dynamic next step { field: 'field1', value: true, next: (req, res, con) => 'functional-next' }, // use a string as a default next step 'default-next' ] } ``` -------------------------------- ### Enable non-linear journey mode Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Configure the wizard to support non-linear navigation by enabling the nonLinearJourney option. ```javascript app.use(wizard(steps, fields, { name: 'my-wizard', editBackStep: 'summary', nonLinearJourney: true })); ``` -------------------------------- ### Field Options Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Configuration options for individual form fields, including storage, defaults, placeholders, validation, and conditional logic. ```APIDOC ## Field Options See [hmpo-components](https://github.com/HMPO/hmpo-components) for additional field options. * `journeyKey` - Name of the cross-wizard field storage name * `default` - Default value for this field * `placeholder` - Placeholder selection value for select box or radio button fields when first loading the form. Can be set to `true` for an empty placeholder, or with `{ key: }` to set a specific value. * `multiple` - Allow multiple incomming values for a field. The result is presented as an array * `formater` - Array of formatter names for this field in addition to the default formatter set, or formatter objects * `type` - Formatter name * `fn` - Formatter function * `arguments` - Array of formatter arguments, eg. `{ type: 'truncate', arguments: [24] }` * `ignore-defaults` - Disabled the default set of formatters for this field * `validate` - An array of validator names, or validator objects * `type` - Validator name * `fn` - Validator function * `arguments` - Array of validator arguments, eg. `{ type: 'minlength', arguments: [24] }` * `items` - Array of select box or radio button options * `value` - Item value * `dependent` - Name of field to make this field conditional upon. This field will not be validated or stored if this condition is not met. Can also also be an object to specify a specific value instead of the default of `true`: * `field` - Field name * `value` - Field value * `invalidates` - an array of field names that will be 'invalidated' when this field value is set or changed. Any fields specified in the `invalidates` array will be removed from the `sessionModel`. Future steps that have used this value to make a branching decision will also be invalidated, making the user go through those steps and decisions again. * `contentKey` - localisation key to use for this field instead of the field name ``` -------------------------------- ### Add Build Script to package.json Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Add a script to your package.json to build the JavaScript bundle using Rollup. ```json "scripts": { "build:js": "mkdir -p public/javascripts && rollup -c" } ``` -------------------------------- ### Inject Mock Session Data for Testing with SessionInjection Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Use the SessionInjection helper to mock session data for debugging and testing. This middleware should ideally be enabled only in development environments. ```javascript const { SessionInjection } = require('hmpo-form-wizard'); // Enable only in development if (process.env.NODE_ENV === 'development') { app.use('/debug/session', new SessionInjection().middleware()); } // POST to /debug/session with JSON body: const injectionPayload = { journeyName: 'my-journey', journeyKeys: { applicantName: 'John Doe', applicantEmail: 'john@example.com' }, allowedStep: '/my-wizard/confirm', // Skip to this step prereqStep: '/my-wizard/details', // Mark as completed featureFlags: { newDesign: true }, wizards: { 'my-wizard': { name: 'John', age: 30, color: 'blue' } }, rawSessionValues: { customKey: 'customValue' } }; // Using curl: // curl -X POST http://localhost:3000/debug/session \ // -H "Content-Type: application/json" \ // -d '{"journeyKeys":{"name":"Test User"},"allowedStep":"/form/confirm"}' ``` -------------------------------- ### Error Handling Middleware Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Configure application-level error handling to work with wizard validation errors and redirects. ```APIDOC ## Error Handling Middleware Configure application-level error handling to work with wizard validation errors and redirects. ### Description This middleware group handles various error conditions that can occur during the form wizard process, including validation errors, missing prerequisites, and session timeouts. ### Usage Mount these handlers after your wizard middleware in your Express application. ```javascript const express = require('express'); const wizard = require('hmpo-form-wizard'); const app = express(); // Session middleware required app.use(require('express-session')({ secret: 'your-secret', resave: false, saveUninitialized: false })); // Mount wizard app.use('/form', wizard(steps, fields, { name: 'my-form' })); // Handle wizard redirects (validation errors, missing prereqs) app.use((err, req, res, next) => { if (err.redirect) { return res.redirect(err.redirect); } next(err); }); // Handle session expiry app.use((err, req, res, next) => { if (err.code === 'SESSION_TIMEOUT') { return res.render('session-expired'); } if (err.code === 'MISSING_PREREQ') { return res.redirect('/form'); } next(err); }); // Generic error handler app.use((err, req, res, next) => { console.error(err); res.status(err.status || 500); res.render('error', { message: err.message, error: process.env.NODE_ENV === 'development' ? err : {} }); }); ``` ### Error Types Handled - **`err.redirect`**: For general redirects, often due to validation failures or prerequisite issues. - **`SESSION_TIMEOUT`**: When the user's session has expired. - **`MISSING_PREREQ`**: When a required prerequisite step has not been completed. - **Generic Errors**: Catches any unhandled errors, rendering a generic error page. ``` -------------------------------- ### Share Field Values Between Wizards Using Journey Keys Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Utilize 'journeyKey' in field definitions to store and retrieve values across different wizards mounted on the same journey. Ensure wizards share the same 'journeyName'. ```javascript // Wizard 1 fields const wizard1Fields = { fullName: { journeyKey: 'applicantName', // Stored in journey model validate: 'required' }, dateOfBirth: { journeyKey: 'applicantDOB', validate: ['required', 'date'] } }; // Wizard 2 fields - can access values from wizard 1 const wizard2Fields = { confirmedName: { journeyKey: 'applicantName', // Same key accesses shared value validate: 'required' } }; // Mount both wizards on same journey app.use('/step1', wizard(wizard1Steps, wizard1Fields, { name: 'wizard1', journeyName: 'application-journey' // Shared journey name })); app.use('/step2', wizard(wizard2Steps, wizard2Fields, { name: 'wizard2', journeyName: 'application-journey' // Same journey name })); ``` -------------------------------- ### Include Bundled Script in HTML Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Include the bundled JavaScript file in your HTML using a type="module" script tag. ```html ``` -------------------------------- ### Configure Editable Steps in Form Wizard Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Set the 'editable' property to true on steps that should be modifiable from a review page. Use 'continueOnEdit' to control navigation after editing. ```javascript const steps = { '/': { entryPoint: true, resetJourney: true, next: 'personal-details' }, '/personal-details': { fields: ['name', 'email', 'phone'], editable: true, // Can be edited from confirm page next: 'address' }, '/address': { fields: ['street', 'city', 'postcode'], editable: true, continueOnEdit: true, // Continue to next step after editing instead of returning next: 'confirm' }, '/confirm': { // Review page - links to step/edit URLs noPost: true, next: 'submit' }, '/submit': { next: 'done' }, '/done': { noPost: true } }; // Mount wizard with edit configuration app.use('/application', wizard(steps, fields, { name: 'application', editSuffix: '/edit', // URL suffix for edit mode (default) editBackStep: 'confirm' // Return to this step after editing })); // Edit URLs: /application/personal-details/edit, /application/address/edit ``` -------------------------------- ### Central Journey Storage Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Configure fields to share values between wizards in the same journey using `journeyKey`. ```APIDOC ## Central journey storage To facilitate sharing form values between wizards in the same journey a field can be specified to save into the `journeyModel` instead of the `sessionModel` using the `journeyKey` property: ```javascript // fields.js module.exports = { 'localFieldName': { journeyKey: 'centralfieldName', } } ``` ``` -------------------------------- ### Session Expired Template Implementation Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/example/views/pages/session-expired.html Extends the base template and includes the hmpoSubmit macro to provide a link to restart the form process. ```nunjucks {% extends "hmpo-template.njk" %} {% from "hmpo-submit/macro.njk" import hmpoSubmit %} {% set hmpoPageKey = "sessionExpired" %} {% block content %} {{ super() }} {{ hmpoSubmit(ctx, { key: "startAgain", href: "/" }) }} {% endblock %} ``` -------------------------------- ### Asynchronous locals lifecycle event Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md The locals method now supports an optional callback for asynchronous execution while maintaining backward compatibility for synchronous overrides. ```javascript locals(req, res, callback(err, locals)) ``` ```javascript locals(req, res) ``` -------------------------------- ### Custom Error Handling Middleware Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Implement custom error handling middleware to intercept errors and redirect as specified by the error object. ```javascript app.use((error, req, res, next) => { if (error.redirect) return res.redirect(error.redirect); next(error); }); ``` -------------------------------- ### Extend Controller for Custom Form Handling Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Extend the base Controller class to customize form handling, add middleware, override lifecycle methods, or perform custom saves. Use this to add custom logic to form submission and processing. ```javascript const { Controller } = require('hmpo-form-wizard'); class SubmitController extends Controller { // Add custom middleware middlewareSetup() { super.middlewareSetup(); this.use((req, res, next) => { console.log(req.method, req.url); next(); }); } // Override getValues to add computed values getValues(req, res, callback) { super.getValues(req, res, (err, values) => { if (err) return callback(err); values.fullAddress = `${values.street}, ${values.city}, ${values.postcode}`; callback(null, values); }); } // Add custom template locals locals(req, res, callback) { super.locals(req, res, (err, locals) => { if (err) return callback(err); locals.pageTitle = 'Review Your Submission'; locals.submissionDate = new Date().toISOString(); callback(null, locals); }); } // Override saveValues to submit to external API saveValues(req, res, next) { const data = { fullName: req.sessionModel.get('name'), age: req.sessionModel.get('age'), color: req.sessionModel.get('color') }; // Call external service fetch('https://api.example.com/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }) .then(response => response.json()) .then(result => { req.sessionModel.set('submissionId', result.id); next(); }) .catch(next); } // Custom validation beyond field validators validate(req, res, next) { if (req.form.values.password !== req.form.values.confirmPassword) { return next({ confirmPassword: new this.Error('confirmPassword', { type: 'mustMatch', message: 'Passwords must match' }, req, res) }); } next(); } } // Use in steps const steps = { '/submit': { controller: SubmitController, next: 'done' } }; ``` -------------------------------- ### Field Formatting API Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Configure field formatters to clean and normalize input values before validation occurs. ```APIDOC ## Field Formatting ### Description Apply formatters to clean and normalize input values before validation. Built-in formatters include trim, boolean, uppercase, lowercase, removespaces, singlespaces, hyphens, apostrophes, quotes, removeroundbrackets, removehyphens, removeslashes, ukphoneprefix, and base64decode. ### Request Body - **formatter** (array) - Optional - List of formatter names or custom objects to apply to the field value. - **ignore-defaults** (boolean) - Optional - If true, disables default formatters (trim, singlespaces, hyphens, apostrophes, quotes). - **validate** (string/array) - Optional - Validation rules to apply to the field. ### Request Example { "phoneNumber": { "formatter": ["trim", "removespaces", "ukphoneprefix"], "validate": ["required", "ukmobilephone"] } } ``` -------------------------------- ### Enable Session Injection Middleware Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md This middleware aids in session injection for debugging and testing a form journey. It's typically enabled in development environments. ```javascript const SessionInjection = require('hmpo-form-wizard').SessionInjection; app.use('/debug/session', new SessionInjection().middleware()); ``` ```javascript // Check if NODE_ENV is development and set app to dev mode app.set('dev', process.env.NODE_ENV === 'development') // IF app is in dev mode, add the SessionInjection middleware if (app.get('dev')) { const SessionInjection = require('hmpo-form-wizard').SessionInjection; app.use('/debug/session', new SessionInjection().middleware()); } ``` -------------------------------- ### Configure Dependent and Conditional Fields in JavaScript Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Define fields that are validated or stored only when certain conditions are met based on other field values. This includes simple boolean dependencies and invalidation of dependent fields. ```javascript const fields = { hasDisability: { type: 'radio', validate: 'required', items: [ { value: 'yes', label: 'Yes' }, { value: 'no', label: 'No' } ] }, // Only validated/stored when hasDisability is 'yes' disabilityDetails: { dependent: { field: 'hasDisability', value: 'yes' }, validate: 'required' }, // Simple boolean dependent (true when field has any truthy value) additionalInfo: { dependent: 'hasDisability' }, // Field invalidation - changing this clears dependent answers employmentStatus: { type: 'radio', validate: 'required', invalidates: ['employerName', 'jobTitle', 'salary'], items: [ { value: 'employed', label: 'Employed' }, { value: 'unemployed', label: 'Unemployed' }, { value: 'retired', label: 'Retired' } ] }, // These fields are cleared if employmentStatus changes employerName: { dependent: { field: 'employmentStatus', value: 'employed' }, validate: 'required' }, jobTitle: { dependent: { field: 'employmentStatus', value: 'employed' }, validate: 'required' } }; ``` -------------------------------- ### Default Field Value Configuration Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Specify a 'default' value for a field using the 'default' property. This value is used if the session data is missing or undefined. ```javascript // fields.js module.exports = { 'localFieldName': { default: 'defaultValue' } } ``` -------------------------------- ### Define Field Formatters in JavaScript Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Configure default and custom formatters for form fields. Built-in formatters include trim, uppercase, and boolean conversion. Custom formatters can be defined as functions for complex logic. ```javascript const fields = { // Default formatters (trim, singlespaces, hyphens, apostrophes, quotes) applied automatically name: { validate: 'required' }, // Disable default formatters preservedInput: { 'ignore-defaults': true, validate: 'required' }, // Custom formatters phoneNumber: { formatter: ['trim', 'removespaces', 'ukphoneprefix'], validate: ['required', 'ukmobilephone'] }, // Uppercase formatting referenceCode: { formatter: ['trim', 'uppercase', 'removespaces'], validate: 'required' }, // Boolean conversion agreeTerms: { formatter: ['boolean'], validate: 'required' }, // Custom formatter function postcode: { formatter: [ 'trim', 'uppercase', { type: 'formatPostcode', fn: value => { // Format as "AA0 0AA" const clean = value.replace(/\s/g, ''); if (clean.length > 4) { return clean.slice(0, -3) + ' ' + clean.slice(-3); } return clean; }} ], validate: ['required', 'postcode'] } }; // Built-in formatters: trim, boolean, uppercase, lowercase, removespaces, // singlespaces, hyphens, apostrophes, quotes, removeroundbrackets, // removehyphens, removeslashes, ukphoneprefix, base64decode ``` -------------------------------- ### Default Field Values Source: https://github.com/hmpo/hmpo-form-wizard/blob/master/README.md Specify a default value for a field using the `default` property, which is used when the session value is missing or undefined. ```APIDOC ## Default field values A default value for a field can be specified with the `default` property. This is used if the value loaded from the session is missing or undefined. ```javascript // fields.js module.exports = { 'localFieldName': { default: 'defaultValue' } } ``` ``` -------------------------------- ### Session Model API Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Access and manipulate form data through the session model attached to each request. ```APIDOC ## Session Model API ### Description Provides methods to interact with the session model to get, set, unset, or reset form data during the wizard journey. ### Methods - **get(key)**: Retrieve a value from the session. - **set(key, value)**: Set a single value or an object of multiple values. - **unset(key)**: Remove a specific value from the session. - **toJSON()**: Retrieve all session data as a JSON object. - **reset()**: Clear all wizard data from the session. ### Request Example { "action": "set", "data": { "status": "processing", "step": 3 } } ``` -------------------------------- ### HMPO Form Wizard Conditional Branching Source: https://context7.com/hmpo/hmpo-form-wizard/llms.txt Implements branching logic in form navigation using an array of conditions in the `next` property. Supports field value checks, custom functions, nested conditions, and fallback branches. Ensure correct operators are used for comparisons. ```javascript const steps = { '/': { entryPoint: true, resetJourney: true, next: 'choose-direction' }, '/choose-direction': { fields: ['direction'], next: [ // Check field value with different operators { field: 'direction', value: 'right', next: 'right-branch' }, { field: 'direction', value: 'left', next: 'left-branch' }, { field: 'age', op: '<', value: 18, next: 'not-old-enough' }, { field: 'age', op: '>=', value: 65, next: 'senior-path' }, // Custom condition function { fn: (req, res, condition) => req.sessionModel.get('premium'), next: 'premium-flow' }, // Nested conditions { field: 'type', value: 'business', next: [ { field: 'size', value: 'large', next: 'enterprise' }, 'small-business' ]}, // Default fallback (string at end) 'default-branch' ] }, '/right-branch': { fields: ['right-only'], next: 'done' }, '/left-branch': { fields: ['left-only'], next: 'done' }, '/done': { noPost: true } }; // Supported operators: '===', '==', '!=', '>', '>=', '<', '<=', 'before', 'after', 'in', 'all', 'some' ```