Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
79 changes: 79 additions & 0 deletions designer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,85 @@ npm run test:since <commit-hash>
npm run test:uncommitted
```

## Adding a new question type (component type)

To add a new question type, you will need to add the new type to a series of files. As an example, if we were to add a new `UnicornField` question type and wanted it to be selectable as a new question from the 'question ype' radio list, (assuming we are happy to use a default 'preview' implementation for the time being), you have to add:

model/src/components/enums.ts:

```
UnicornField = ‘UnicornField’
```

model/src/components/types.ts:

```
export interface UnicornFieldComponent extends FormFieldBase {
type: ComponentType.UnicornField
options: FormFieldBase['options'] & {
condition?: string
}
}

. . .

export type InputFieldsComponentsDef =
| TextFieldComponent
. . .
| UnicornFieldComponent
```

model/src/components/component-types.ts:

```
{
name: 'UnicornField',
title: 'Unicorn field',
type: ComponentType.UnicornField,
hint: '',
options: {},
schema: {}
}

```

client/src/i18n/translations/en.translation.json:

```
fieldTypeToName: {
. . .
"UnicornField": "Unicorn field",
"UnicornField_info": "For internal processing"
```

model/src/form/form-editor/index.ts:

```
export const questionTypeSchema = Joi.string()
. . .
ComponentType.UnicornField
)
. . .
```

designer/server/src/models/forms/editor-v2/question-type.js:

```
const questionTypeRadioItems = /** @type {FormEditorCheckbox[]} */ ([
. . .
{
text: 'Unicorn',
hint: {
text: 'Rainbows and stuff'
},
value: ComponentType.UnicornField
},
. . .
```

Further work would be required to define advanced field settings (if appropriate), and potentially to validate/handle the payload being saved from the 'question details' screen.
Later, work would be required to properly implement the preview (this would involve changes to forms-runner to handle the new component class generation so error messages can be retrieved).

## License

THIS INFORMATION IS LICENSED UNDER THE CONDITIONS OF THE OPEN GOVERNMENT LICENCE found at:
Expand Down
7 changes: 1 addition & 6 deletions designer/client/src/javascripts/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,7 @@ export function showHideForJs() {
* @returns {PreviewQuestion}
*/
export function setupPreview(componentType) {
const PreviewConstructor =
/** @type {() => PreviewQuestion} */
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
(SetupPreview[componentType] ?? SetupPreview.Question)

const preview = PreviewConstructor()
const preview = SetupPreview(componentType)

showHideForJs()

Expand Down
4 changes: 3 additions & 1 deletion designer/client/src/javascripts/preview/date-input.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ComponentType } from '@defra/forms-model'

import {
questionDetailsLeftPanelHTML,
questionDetailsPreviewHTML
Expand All @@ -10,7 +12,7 @@ describe('date-input', () => {
it('should create class', () => {
document.body.innerHTML =
questionDetailsLeftPanelHTML + questionDetailsPreviewHTML
const res = SetupPreview.DatePartsField()
const res = SetupPreview(ComponentType.DatePartsField)
expect(res.renderInput).toEqual({
id: 'dateInput',
name: 'dateInput',
Expand Down
14 changes: 7 additions & 7 deletions designer/client/src/javascripts/preview/declaration.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DeclarationQuestion } from '@defra/forms-model'
import { ComponentType, DeclarationQuestion } from '@defra/forms-model'

import {
questionDetailsLeftPanelHTML,
Expand Down Expand Up @@ -122,7 +122,7 @@ describe('declaration', () => {
questionDetailsPreviewHTML +
declarationHTML
const question = /** @type {DeclarationQuestion} */ (
SetupPreview.DeclarationField()
SetupPreview(ComponentType.DeclarationField)
)
const declarationTextEl = /** @type {HTMLTextAreaElement | null} */ (
document.getElementById('declarationText')
Expand All @@ -146,7 +146,7 @@ describe('declaration', () => {
questionDetailsPreviewHTML +
declarationHTML
const question = /** @type {DeclarationQuestion} */ (
SetupPreview.DeclarationField()
SetupPreview(ComponentType.DeclarationField)
)
const declarationTextEl = /** @type {HTMLTextAreaElement | null} */ (
document.getElementById('declarationText')
Expand All @@ -169,7 +169,7 @@ describe('declaration', () => {
questionDetailsPreviewHTML +
declarationHTML
const question = /** @type {DeclarationQuestion} */ (
SetupPreview.DeclarationField()
SetupPreview(ComponentType.DeclarationField)
)
const declarationTextEl = /** @type {HTMLTextAreaElement | null} */ (
document.getElementById('declarationText')
Expand Down Expand Up @@ -200,7 +200,7 @@ describe('declaration', () => {
questionDetailsPreviewHTML +
declarationHTML
const res = /** @type {DeclarationQuestion} */ (
SetupPreview.DeclarationField()
SetupPreview(ComponentType.DeclarationField)
)
expect(res).toBeInstanceOf(DeclarationQuestion)
expect(res).toBeDefined()
Expand All @@ -213,7 +213,7 @@ describe('declaration', () => {
questionDetailsPreviewHTML +
declarationHTML
const res = /** @type {DeclarationQuestion} */ (
SetupPreview.DeclarationField()
SetupPreview(ComponentType.DeclarationField)
)
expect(res.question).toBe('Which quest would you like to pick?')
expect(res.hintText).toBe('Choose one adventure that best suits you.')
Expand All @@ -231,7 +231,7 @@ describe('declaration', () => {
questionDetailsPreviewHTML +
emptyDeclarationHTML
const res = /** @type {DeclarationQuestion} */ (
SetupPreview.DeclarationField()
SetupPreview(ComponentType.DeclarationField)
)
expect(res.declarationText).toBe('')
})
Expand Down
4 changes: 2 additions & 2 deletions designer/client/src/javascripts/preview/email-address.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EmailAddressQuestion } from '@defra/forms-model'
import { ComponentType, EmailAddressQuestion } from '@defra/forms-model'

import {
questionDetailsLeftPanelHTML,
Expand All @@ -13,7 +13,7 @@ describe('email', () => {
it('should create class', () => {
document.body.innerHTML =
questionDetailsLeftPanelHTML + questionDetailsPreviewHTML
const res = SetupPreview.EmailAddressField()
const res = SetupPreview(ComponentType.EmailAddressField)
expect(res).toBeInstanceOf(EmailAddressQuestion)
expect(res).toBeDefined()
expect(res.renderInput).toEqual({
Expand Down
24 changes: 14 additions & 10 deletions designer/client/src/javascripts/preview/list-sortable.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { ListSortableQuestion } from '@defra/forms-model'
import {
ComponentType,
ListSortableQuestion,
PreviewTypeEnum
} from '@defra/forms-model'

import {
list1HTML,
Expand Down Expand Up @@ -82,7 +86,7 @@ describe('list-sortable', () => {
document.body.innerHTML =
questionDetailsLeftPanelHTML + questionDetailsPreviewTabsHTML
const preview = /** @type {ListSortableQuestion} */ (
SetupPreview.RadiosField()
SetupPreview(ComponentType.RadiosField)
)
expect(preview.renderInput.fieldset?.legend.text).toBe(
'Which quest would you like to pick?'
Expand Down Expand Up @@ -497,7 +501,7 @@ describe('list-sortable', () => {
describe('editPanelListeners', () => {
it('should update the List class when listeners are called', () => {
const preview = /** @type {ListSortableQuestion} */ (
SetupPreview.ListSortable()
SetupPreview(PreviewTypeEnum.ListSortable)
)
const listEventListeners = new ListSortableEventListeners(
preview,
Expand Down Expand Up @@ -586,7 +590,7 @@ describe('list-sortable', () => {
'<button id="edit-options-button">Re-order</button>' +
'<button id="add-option-button">Add item</button>' +
list1HTML
SetupPreview.ListSortable()
SetupPreview(PreviewTypeEnum.ListSortable)
const reorderButton = /** @type {HTMLElement} */ (
document.getElementById('edit-options-button')
)
Expand All @@ -602,7 +606,7 @@ describe('list-sortable', () => {
'<button id="edit-options-button">Done</button>' +
'<button id="add-option-button">Add item</button>' +
list1HTML
SetupPreview.ListSortable()
SetupPreview(PreviewTypeEnum.ListSortable)
const reorderButton = /** @type {HTMLElement} */ (
document.getElementById('edit-options-button')
)
Expand Down Expand Up @@ -633,7 +637,7 @@ describe('list-sortable', () => {
x.textContent = 'Not up or down'
})

SetupPreview.ListSortable()
SetupPreview(PreviewTypeEnum.ListSortable)
const reorderButton = /** @type {HTMLElement} */ (
document.getElementById('edit-options-button')
)
Expand All @@ -649,7 +653,7 @@ describe('list-sortable', () => {
'<button id="edit-options-button">Re-order</button>' +
'<button id="add-option-button">Add item</button>' +
list1HTML
SetupPreview.ListSortable()
SetupPreview(PreviewTypeEnum.ListSortable)
const reorderButton = /** @type {HTMLElement} */ (
document.getElementById('edit-options-button')
)
Expand Down Expand Up @@ -690,7 +694,7 @@ describe('list-sortable', () => {
'<button id="edit-options-button">Re-order</button>' +
'<button id="add-option-button">Add item</button>' +
list1HTML
SetupPreview.ListSortable()
SetupPreview(PreviewTypeEnum.ListSortable)
const reorderButton = /** @type {HTMLElement} */ (
document.getElementById('edit-options-button')
)
Expand Down Expand Up @@ -911,15 +915,15 @@ describe('list-sortable', () => {
'<button id="add-option-button">Add item</button>' +
listEmptyHTML
const preview = /** @type {ListSortableQuestion} */ (
SetupPreview.ListSortable()
SetupPreview(PreviewTypeEnum.ListSortable)
)
expect(preview.listElementObjects).toHaveLength(0)
document.body.innerHTML =
'<button id="edit-options-button">Done</button>' +
'<button id="add-option-button">Add item</button>' +
list1HTML
const preview2 = /** @type {ListSortableQuestion} */ (
SetupPreview.ListSortable()
SetupPreview(PreviewTypeEnum.ListSortable)
)
preview.resyncPreviewAfterReorder()
expect(preview2.listElementObjects).toHaveLength(4)
Expand Down
14 changes: 8 additions & 6 deletions designer/client/src/javascripts/preview/list.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ListQuestion } from '@defra/forms-model'
import { ListQuestion, PreviewTypeEnum } from '@defra/forms-model'

import { list1HTML } from '~/src/javascripts/preview/__stubs__/list'
import { questionDetailsStubPanels } from '~/src/javascripts/preview/__stubs__/question.js'
Expand Down Expand Up @@ -141,7 +141,7 @@ describe('list', () => {
it('should setup', () => {
document.body.innerHTML = list1HTML
const preview = /** @type {ListSortableQuestion} */ (
SetupPreview.ListSortable()
SetupPreview(PreviewTypeEnum.ListSortable)
)
expect(preview.renderInput.fieldset?.legend.text).toBe('Question')
})
Expand Down Expand Up @@ -192,7 +192,7 @@ describe('list', () => {
describe('editPanelListeners', () => {
it('should update the List class when listeners are called', () => {
const preview = /** @type {ListQuestion} */ (
SetupPreview.ListSortable()
SetupPreview(PreviewTypeEnum.ListSortable)
)
const listEventListeners = new ListEventListeners(
preview,
Expand Down Expand Up @@ -331,7 +331,7 @@ describe('list', () => {

it('should highlight', () => {
const preview = /** @type {ListSortableQuestion} */ (
SetupPreview.ListSortable()
SetupPreview(PreviewTypeEnum.ListSortable)
)
preview.highlight = `${baronListItemId}-hint`
expect(preview.list[3]).toMatchObject({
Expand All @@ -341,7 +341,7 @@ describe('list', () => {

it('should handle edge cases', () => {
const preview = /** @type {ListSortableQuestion} */ (
SetupPreview.ListSortable()
SetupPreview(PreviewTypeEnum.ListSortable)
)
expect(preview.list).toEqual(expectedList)
preview.updateValue(undefined, 'new-value')
Expand All @@ -359,7 +359,9 @@ describe('list', () => {

describe('editFieldHasFocus', () => {
it('should return true for list text field', () => {
const preview = /** @type {ListQuestion} */ (SetupPreview.ListSortable())
const preview = /** @type {ListQuestion} */ (
SetupPreview(PreviewTypeEnum.ListSortable)
)
const listEventListeners = new ListEventListeners(
preview,
questionElements,
Expand Down
4 changes: 2 additions & 2 deletions designer/client/src/javascripts/preview/phone-number.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PhoneNumberQuestion } from '@defra/forms-model'
import { ComponentType, PhoneNumberQuestion } from '@defra/forms-model'

import {
questionDetailsLeftPanelHTML,
Expand All @@ -13,7 +13,7 @@ describe('phone number', () => {
it('should create class', () => {
document.body.innerHTML =
questionDetailsLeftPanelHTML + questionDetailsPreviewHTML
const res = SetupPreview.TelephoneNumberField()
const res = SetupPreview(ComponentType.TelephoneNumberField)
expect(res).toBeInstanceOf(PhoneNumberQuestion)
expect(res).toBeDefined()
expect(res.renderInput).toEqual({
Expand Down
10 changes: 6 additions & 4 deletions designer/client/src/javascripts/preview/question.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { PreviewTypeEnum } from '@defra/forms-model'

import {
questionDetailsLeftPanelHTML,
questionDetailsPreviewHTML
Expand Down Expand Up @@ -74,7 +76,7 @@ describe('question', () => {
it('should create class', () => {
document.body.innerHTML =
questionDetailsLeftPanelHTML + questionDetailsPreviewHTML
const res = SetupPreview.Question()
const res = SetupPreview(PreviewTypeEnum.Question)
expect(res).toBeDefined()
expect(res.renderInput).toEqual({
id: expect.stringContaining('inputField'),
Expand All @@ -101,7 +103,7 @@ describe('question', () => {
it('should handle changed values', () => {
document.body.innerHTML =
questionDetailsLeftPanelHTML + questionDetailsPreviewHTML
const res = SetupPreview.Question()
const res = SetupPreview(PreviewTypeEnum.Question)
expect(res.titleText).toBe('Which quest would you like to pick?')
expect(res.question).toBe('Which quest would you like to pick?')
expect(res.hintText).toBe('Choose one adventure that best suits you.')
Expand All @@ -118,7 +120,7 @@ describe('question', () => {
it('should handle missing values', () => {
document.body.innerHTML =
questionDetailsLeftPanelHTML + questionDetailsPreviewHTML
const res = SetupPreview.Question()
const res = SetupPreview(PreviewTypeEnum.Question)
res.question = ''
expect(res.titleText).toBe('Question')
res.hintText = ''
Expand All @@ -141,7 +143,7 @@ describe('question', () => {
})

it('should highlight', () => {
const preview = SetupPreview.Question()
const preview = SetupPreview(PreviewTypeEnum.Question)
preview.highlight = `hintText`
expect(preview).toMatchObject({
hint: { text: 'Hint text' }
Expand Down
Loading
Loading