Skip to content

Commit

Permalink
Add custom required validation text (#1333)
Browse files Browse the repository at this point in the history
* implement custom required message

* attempt at noticing custom required validation messages

* required banner works with custom required message

* custom required message for list

* fix typo

* fix custom required message for nested groups

* add fieldName as variable for custom message

* remove recursive function, fix required-banner for messages using fieldname variable
  • Loading branch information
Magnusrm authored Aug 28, 2023
1 parent b33be4c commit 19a9aae
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 25 deletions.
29 changes: 21 additions & 8 deletions src/components/form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import { ReadyForPrint } from 'src/components/ReadyForPrint';
import { useAppSelector } from 'src/hooks/useAppSelector';
import { useLanguage } from 'src/hooks/useLanguage';
import { GenericComponent } from 'src/layout/GenericComponent';
import { getFieldName } from 'src/utils/formComponentUtils';
import { extractBottomButtons, hasRequiredFields } from 'src/utils/formLayout';
import { useExprContext } from 'src/utils/layout/ExprContext';
import { getFormHasErrors, missingFieldsInLayoutValidations } from 'src/utils/validation/validation';
import type { ITextResourceBindings } from 'src/layout/layout';

export function Form() {
const nodes = useExprContext();
Expand All @@ -21,21 +23,32 @@ export function Form() {
const page = nodes?.current();
const pageKey = page?.top.myKey;

const requiredFieldsMissing = React.useMemo(() => {
if (validations && pageKey && validations[pageKey]) {
return missingFieldsInLayoutValidations(validations[pageKey], langTools);
}

return false;
}, [pageKey, langTools, validations]);

const [mainNodes, errorReportNodes] = React.useMemo(() => {
if (!page) {
return [[], []];
}
return hasErrors ? extractBottomButtons(page) : [page.children(), []];
}, [page, hasErrors]);

const requiredFieldsMissing = React.useMemo(() => {
if (validations && pageKey && validations[pageKey]) {
const requiredValidationTextResources: string[] = [];
page.flat(true).forEach((node) => {
const textResourceBindings = node.item.textResourceBindings as ITextResourceBindings;
const fieldName = getFieldName(textResourceBindings, langTools);
if (node.item.required && textResourceBindings?.requiredValidation) {
requiredValidationTextResources.push(
langTools.langAsString(textResourceBindings?.requiredValidation, [fieldName]),
);
}
});

return missingFieldsInLayoutValidations(validations[pageKey], requiredValidationTextResources, langTools);
}

return false;
}, [validations, pageKey, page, langTools]);

if (!page) {
return null;
}
Expand Down
18 changes: 8 additions & 10 deletions src/layout/LayoutComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,25 +198,23 @@ export abstract class FormComponent<Type extends ComponentTypes>
if (!node.item.required) {
return [];
}
const { langAsString } = langTools;

const formDataToValidate = { ...formData, ...overrideFormData };
const validationObjects: IValidationObject[] = [];

const bindings = Object.entries(node.item.dataModelBindings ?? {});
for (const [bindingKey, field] of bindings) {
const data = formDataToValidate[field];
const textResourceBindings = node.item.textResourceBindings as ITextResourceBindings;

if (!data?.length) {
const fieldName = getFieldName(node.item.textResourceBindings as ITextResourceBindings, langTools, bindingKey);

validationObjects.push(
buildValidationObject(
node,
'errors',
langTools.langAsString('form_filler.error_required', [fieldName]),
bindingKey,
),
);
const fieldName = getFieldName(textResourceBindings, langTools, bindingKey);
const errorMessage = textResourceBindings?.requiredValidation
? langAsString(textResourceBindings?.requiredValidation, [fieldName])
: langAsString('form_filler.error_required', [fieldName]);

validationObjects.push(buildValidationObject(node, 'errors', errorMessage, bindingKey));
}
}
return validationObjects;
Expand Down
7 changes: 6 additions & 1 deletion src/layout/List/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getFieldName } from 'src/utils/formComponentUtils';
import { buildValidationObject } from 'src/utils/validation/validationHelpers';
import type { ExprResolved } from 'src/features/expressions/types';
import type { PropsFromGenericComponent } from 'src/layout';
import type { ITextResourceBindings } from 'src/layout/layout';
import type { SummaryRendererProps } from 'src/layout/LayoutComponent';
import type { IDataModelBindingsForList, ILayoutCompList } from 'src/layout/List/types';
import type { LayoutNodeFromType } from 'src/utils/layout/hierarchy.types';
Expand Down Expand Up @@ -47,6 +48,8 @@ export class List extends FormComponent<'List'> {
return [];
}

const { langAsString } = langTools;
const textResourceBindings = node.item.textResourceBindings as ITextResourceBindings;
const validationObjects: IValidationObject[] = [];

const bindings = Object.values(node.item.dataModelBindings ?? {});
Expand All @@ -60,7 +63,9 @@ export class List extends FormComponent<'List'> {
}
if (listHasErrors) {
const fieldName = getFieldName(node.item.textResourceBindings, langTools, undefined);
const message = langTools.langAsString('form_filler.error_required', [fieldName]);
const message = textResourceBindings?.requiredValidation
? langAsString(textResourceBindings?.requiredValidation, [fieldName])
: langAsString('form_filler.error_required', [fieldName]);
validationObjects.push(buildValidationObject(node, 'errors', message));
}
return validationObjects;
Expand Down
6 changes: 5 additions & 1 deletion src/layout/layout.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,11 @@ export type ITextResourceBindings<T extends ComponentTypes = ComponentTypes> =
| undefined;

export type TextBindingsForSummarizableComponents = 'summaryTitle' | 'summaryDescription' | 'summaryAccessibleTitle';
export type TextBindingsForFormComponents = TextBindingsForSummarizableComponents | 'tableTitle' | 'shortName';
export type TextBindingsForFormComponents =
| TextBindingsForSummarizableComponents
| 'tableTitle'
| 'shortName'
| 'requiredValidation';
export type TextBindingsForLabel = 'title' | 'description' | 'help';

export type ILayout = ExprUnresolved<ILayoutComponentOrGroup>[];
Expand Down
13 changes: 9 additions & 4 deletions src/utils/validation/validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ describe('utils > validation', () => {
id: 'c4Title',
value: 'component_4',
},
{
id: 'c4RequiredValidation',
value: 'Component_4 feltet er påkrevd og må besvares',
},
{
id: 'c5Title',
value: 'component_5',
Expand Down Expand Up @@ -105,6 +109,7 @@ describe('utils > validation', () => {
readOnly: false,
textResourceBindings: {
title: 'c4Title',
requiredValidation: 'c4RequiredValidation',
},
};

Expand Down Expand Up @@ -678,7 +683,7 @@ describe('utils > validation', () => {
},
},
};
const result = validation.missingFieldsInLayoutValidations(validations, mockLangTools);
const result = validation.missingFieldsInLayoutValidations(validations, [], mockLangTools);
expect(result).toBeFalsy();
});
it('should return true when validations contain messages (string) for missing fields', () => {
Expand All @@ -690,7 +695,7 @@ describe('utils > validation', () => {
},
},
};
const result = validation.missingFieldsInLayoutValidations(validations, mockLangTools);
const result = validation.missingFieldsInLayoutValidations(validations, [], mockLangTools);
expect(result).toBeTruthy();
});
it('should return true when validations contain arrays with error message for missing fields', () => {
Expand All @@ -704,8 +709,8 @@ describe('utils > validation', () => {
});
const shallow = 'Første linje\nDu må fylle ut ';
const deep = 'Dette er feil:\nFørste linje\nDu må fylle ut ';
expect(validation.missingFieldsInLayoutValidations(validations(shallow), mockLangTools)).toBeTruthy();
expect(validation.missingFieldsInLayoutValidations(validations(deep), mockLangTools)).toBeTruthy();
expect(validation.missingFieldsInLayoutValidations(validations(shallow), [], mockLangTools)).toBeTruthy();
expect(validation.missingFieldsInLayoutValidations(validations(deep), [], mockLangTools)).toBeTruthy();
});
});
});
11 changes: 10 additions & 1 deletion src/utils/validation/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ export function getHighestIndexOfChildGroup(group: string, repeatingGroups: IRep

export function missingFieldsInLayoutValidations(
layoutValidations: ILayoutValidations,
requiredValidationTextResources: string[],
langTools: IUseLanguage,
): boolean {
let result = false;
Expand All @@ -392,7 +393,15 @@ export function missingFieldsInLayoutValidations(
}

const errors = layoutValidations[component][binding]?.errors;
result = !!(errors && errors.length > 0 && errors.findIndex(lookForRequiredMsg) > -1);

const customRequiredValidationMessageExists = errors?.some((error) =>
requiredValidationTextResources.includes(error),
);

result = !!(
(errors && errors.length > 0 && errors.findIndex(lookForRequiredMsg) > -1) ||
customRequiredValidationMessageExists
);
});
});

Expand Down

0 comments on commit 19a9aae

Please sign in to comment.