diff --git a/model/src/components/types.ts b/model/src/components/types.ts index 32aa27ca1..095840ca4 100644 --- a/model/src/components/types.ts +++ b/model/src/components/types.ts @@ -231,6 +231,10 @@ export interface MultilineTextFieldComponent extends TextFieldBase { customValidationMessage?: string; rows?: number; maxWords?: number; + conditionalTextBox?: { + dependsOnFieldName?: string; + dependsOnFieldValue?: string; + }; }; schema: { max?: number; diff --git a/runner/src/server/forms/ReportAnOutbreak.json b/runner/src/server/forms/ReportAnOutbreak.json index f5179b0f4..c702d4058 100644 --- a/runner/src/server/forms/ReportAnOutbreak.json +++ b/runner/src/server/forms/ReportAnOutbreak.json @@ -1622,26 +1622,35 @@ }, { "path": "/feedback-on-the-care-obra-tool", - "title": "Section 10 - Feedback on the Care OBRA Tool", + "title": "Section 10 - Give feedback on Report an outbreak", "components": [ - { - "name": "zTXeZb", - "options": {}, - "type": "Para", - "content": "Please provide your feedback.", - "schema": {} - }, { "name": "S10Q1", "options": { "required": true }, "type": "CheckboxesField", - "title": "S10Q1. How would you describe this risk assessment tool?", + "title": "How would you describe this risk assessment tool?", "nameHasError": false, "list": "icNyDJ", "schema": {}, - "hint": "Please note that you can select more than one option" + "hint": "Select all that apply" + }, + { + "name": "S10Q2", + "options": { + "required": false, + "optionalText": false, + "conditionalTextBox": { + "dependsOnFieldName": "S10Q1", + "dependsOnFieldValue": "Other (specify below)" + } + }, + "type": "MultilineTextField", + "title": "Tell us about any difficulties or highlights you experienced, and how we could improve the service.", + "nameHasError": false, + "schema": {}, + "hint": "Do not include any personal information here, for example patient or disease details" } ], "next": [ @@ -1923,13 +1932,25 @@ "name": "icNyDJ", "type": "string", "items": [ + { + "text": "Useful resource", + "value": "Useful resource" + }, + { + "text": "Easy to understand", + "value": "Easy to understand" + }, { "text": "Easy to complete", "value": "Easy to complete" }, { - "text": "Useful resource", - "value": "Useful resource" + "text": "Quick to complete", + "value": "Quick to complete" + }, + { + "text": "Some questions were unclear", + "value": "Some questions were unclear" }, { "text": "Not detailed enough", @@ -1939,21 +1960,13 @@ "text": "Too detailed", "value": "Too detailed" }, - { - "text": "Quick to complete", - "value": "Quick to complete" - }, { "text": "Too time consuming", "value": "Too time consuming" }, { - "text": "Easy to understand", - "value": "Easy to understand" - }, - { - "text": "Some questions were unclear", - "value": "Some questions were unclear" + "text": "Other (specify below)", + "value": "Other (specify below)" } ] }, diff --git a/runner/src/server/plugins/engine/components/CheckboxesField.ts b/runner/src/server/plugins/engine/components/CheckboxesField.ts index 057858f77..4a0f2c0a0 100644 --- a/runner/src/server/plugins/engine/components/CheckboxesField.ts +++ b/runner/src/server/plugins/engine/components/CheckboxesField.ts @@ -18,7 +18,6 @@ export class CheckboxesField extends SelectionControlField { let schema = joi.array().single().label(def.title); if (options.required === false) { - // null or empty string is valid for optional fields schema = schema .empty(null) .items(joi[this.listType]().allow(...this.values, "")); @@ -51,7 +50,10 @@ export class CheckboxesField extends SelectionControlField { getViewModel(formData: FormData, errors: FormSubmissionErrors) { const viewModel = super.getViewModel(formData, errors); - let formDataItems = (formData[this.name] ?? "").split(","); + let formDataItems = (Array.isArray(formData[this.name]) + ? formData[this.name].join(",") + : formData[this.name] ?? "" + ).split(","); viewModel.items = (viewModel.items ?? []).map((item) => ({ ...item, checked: !!formDataItems.find((i) => `${item.value}` === i), diff --git a/runner/src/server/plugins/engine/components/MultilineTextField.ts b/runner/src/server/plugins/engine/components/MultilineTextField.ts index a99640aea..d4c4241a0 100644 --- a/runner/src/server/plugins/engine/components/MultilineTextField.ts +++ b/runner/src/server/plugins/engine/components/MultilineTextField.ts @@ -1,9 +1,10 @@ import { FormComponent } from "./FormComponent"; -import { FormData, FormSubmissionErrors } from "../types"; +import { FormData, FormPayload, FormSubmissionErrors } from "../types"; import Joi, { Schema, StringSchema } from "joi"; import { MultilineTextFieldComponent } from "@xgovformbuilder/model"; import { FormModel } from "server/plugins/engine/models"; import { MultilineTextFieldViewModel } from "server/plugins/engine/components/types"; +import * as helpers from "./helpers"; function inputIsOverWordCount(input, maxWords) { /** @@ -20,6 +21,8 @@ export class MultilineTextField extends FormComponent { schema: MultilineTextFieldComponent["schema"]; customValidationMessage?: string; isCharacterOrWordCount: boolean = false; + dependsOnFieldName; + dependsOnFieldValue; constructor(def: MultilineTextFieldComponent, model: FormModel) { super(def, model); @@ -28,6 +31,10 @@ export class MultilineTextField extends FormComponent { this.schema = schema; let componentSchema = Joi.string().label(def.title).required(); + componentSchema = componentSchema.custom( + helpers.getCustomCheckboxValidator() + ); + if (options.required === false) { componentSchema = componentSchema.allow("").allow(null); } @@ -63,6 +70,11 @@ export class MultilineTextField extends FormComponent { ); } + if (options.conditionalTextBox) { + this.dependsOnFieldName = options.conditionalTextBox.dependsOnFieldName; + this.dependsOnFieldValue = options.conditionalTextBox.dependsOnFieldValue; + } + this.formSchema = componentSchema; } @@ -74,6 +86,20 @@ export class MultilineTextField extends FormComponent { return { [this.name]: this.formSchema as Schema }; } + getStateValueFromValidForm(payload: FormPayload) { + const checkboxSelection = payload[this.dependsOnFieldName]; + const textBoxValue = payload[this.name]; + + if ( + checkboxSelection.includes(this.dependsOnFieldValue) && + textBoxValue === "" + ) { + return "empty"; + } + + return textBoxValue; + } + getViewModel( formData: FormData, errors: FormSubmissionErrors diff --git a/runner/src/server/plugins/engine/components/helpers.ts b/runner/src/server/plugins/engine/components/helpers.ts index 001d1d3bf..91c42de60 100644 --- a/runner/src/server/plugins/engine/components/helpers.ts +++ b/runner/src/server/plugins/engine/components/helpers.ts @@ -173,6 +173,17 @@ export function customTextCheckboxValidator(fieldName: string) { }; } +export function getCustomCheckboxValidator() { + return (value, helpers: joi.CustomHelpers) => { + if (value === "empty") { + return helpers.error("textbox.conditionalFeedback", { + label: helpers.state.key, + }); + } + return value; + }; +} + export function internationalPhoneValidator( value: string, _helpers: joi.CustomHelpers diff --git a/runner/src/server/plugins/engine/pageControllers/validationOptions.ts b/runner/src/server/plugins/engine/pageControllers/validationOptions.ts index cdf46bc0b..1cdcfccb5 100644 --- a/runner/src/server/plugins/engine/pageControllers/validationOptions.ts +++ b/runner/src/server/plugins/engine/pageControllers/validationOptions.ts @@ -29,10 +29,13 @@ const messageTemplate = { dateDayYear: "{{#label}} must include a day and a year", dateDayMonth: "{{#label}} must include a day and a month", dateYear4digits: "The year must include 4 numbers", - dateChronological: "The date that symptoms started in the {{#compLabel}} must be the same as or after the date that symptoms started in the first case", + dateChronological: + "The date that symptoms started in the {{#compLabel}} must be the same as or after the date that symptoms started in the first case", cqc: "Enter your CQC location ID or select 'My setting is not registered with the CQC'", cqcRegex: "Enter a valid CQC Location ID", + conditionalFeedback: + "Give details of any difficulties or highlights you experienced, or how we could improve the service", }; export const messages: ValidationOptions["messages"] = { @@ -69,7 +72,9 @@ export const messages: ValidationOptions["messages"] = { "date.dayYear": messageTemplate.dateDayYear, "date.dayMonth": messageTemplate.dateDayMonth, "date.year4digits": messageTemplate.dateYear4digits, - "date.chronological": messageTemplate.dateChronological + "date.chronological": messageTemplate.dateChronological, + + "textbox.conditionalFeedback": messageTemplate.conditionalFeedback, }; export const validationOptions: ValidationOptions = {