8000 Display validation errors in JSON schema form on change/blur of form fields by kuosandys · Pull Request #4059 · giantswarm/happa · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Display validation errors in JSON schema form on change/blur of form fields #4059

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
312a9dd
Live validate form and only display errors on blur and change
kuosandys Jan 26, 2023
89ef761
Live validate form and only display errors on blur and change - take 2
kuosandys Jan 27, 2023
d903503
Handle validation manuallly instead of live validating
kuosandys Jan 27, 2023
20a1409
Revert "Handle validation manuallly instead of live validating"
kuosandys Jan 27, 2023
0c4e08a
Filter error list and handle error display after submit attempt
kuosandys Jan 27, 2023
9cb0f03
Use formContext prop instead of Context API; replace state hooks with…
kuosandys Jan 28, 2023
9ad852d
Refactor field filtering
kuosandys Jan 31, 2023
4fe7a20
Transform errors: required object properties and string array items
kuosandys Jan 31, 2023
61c39f7
Show error states for hierarchical elements
kuosandys Jan 31, 2023
32e22ae
Handle validation errors from number picker
kuosandys Feb 1, 2023
22cc6ef
Improve display of field errors
kuosandys Feb 1, 2023
b95a548
Handle displaying validation errors for array items on reordering and…
kuosandys Feb 1, 2023
e1f7add
R e f a c t o r
kuosandys Feb 1, 2023
8307eb3
Debounce
kuosandys Feb 1, 2023
b437b47
Revert "Debounce"
kuosandys Feb 1, 2023
2ce1076
Merge branch 'main' into form-validation-errors
kuosandys Feb 2, 2023
d91f3d7
Remove transforming required object field errors - fixed upstream
kuosandys Feb 2, 2023
7b8daa5
Match exact field IDs
kuosandys Feb 2, 2023
3f77bac
Fix description
kuosandys Feb 2, 2023
d01b8be
Update mapping root field errors to field IDs; fix determining hierar…
kuosandys Feb 2, 2023
1db00e2
Refactor
kuosandys Feb 3, 2023
7e470ba
Merge branch 'main' into form-validation-errors
kuosandys Feb 7, 2023
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
22 changes: 20 additions & 2 deletions src/components/UI/JSONSchemaForm/AccordionFormField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,26 @@ interface AccordionFormFieldProps {
label: React.ReactNode;
help?: React.ReactNode;
error?: React.ReactNode;
hasChildErrors?: boolean;
}

const AccordionFormField: React.FC<
React.PropsWithChildren<AccordionFormFieldProps>
> = ({ label, help, error, children }) => {
> = ({ label, help, error, hasChildErrors, children }) => {
const [activeIndexes, setActiveIndexes] = useState<number[]>([]);
const isExpanded = activeIndexes.length !== 0;

const handleActive = (indices: number[]) => {
setActiveIndexes(indices);
};

const helpComponent =
typeof help === 'string' ? <Text color='text-weak'>{help}</Text> : help;

return (
<StyledAccordion
activeIndex={activeIndexes}
class="x x-first x-last">setActiveIndexes}
class="x x-first x-last">handleActive}
animate={false}
>
<AccordionPanel
Expand All @@ -75,6 +80,19 @@ const AccordionFormField: React.FC<
</Box>
<StyledLabel weight='bold' margin={{ vertical: 'small' }}>
{label}
{(hasChildErrors || error) && !isExpanded && (
<Text
size='large'
color='status-danger'
margin={{ left: 'small' }}
>
<i
className='fa fa-warning'
role='presentation'
aria-hidden='true'
/>
</Text>
)}
</StyledLabel>
</StyledHeader>
}
Expand Down
113 changes: 104 additions & 9 deletions src/components/UI/JSONSchemaForm/ArrayFieldTemplate/index.tsx
items.map(
Original file line number Diff line number Diff line change
@@ -1,25 +1,120 @@
import {
ArrayFieldTemplateItemType,
ArrayFieldTemplateProps,
RJSFSchema,
} from '@rjsf/utils';
import { Box } from 'grommet';
import React from 'react';
import React, { useMemo } from 'react';
import Button from 'UI/Controls/Button';

import { IFormContext } from '..';
import ArrayFieldItem from '../ArrayFieldItem';

const ArrayFieldTemplate: React.FC<ArrayFieldTemplateProps> = ({
items,
canAdd,
onAddClick,
}) => {
function getArrayItemFieldId(
parentId: string,
idx: number,
idSeparator: string
) {
return `${parentId}${idSeparator}${idx}`;
}
function shouldReorder(fields: string[], field1: string, field2: string) {
return fields.includes(field1) !== fields.includes(field2);
}

const ArrayFieldTemplate: React.FC<
ArrayFieldTemplateProps<RJSFSchema, RJSFSchema, IFormContext>
> = ({ items, canAdd, onAddClick, idSchema, formContext }) => {
const getArrayItemFieldIdFn = useMemo(() => {
return (idx: number) => {
if (!formContext) return '';

return getArrayItemFieldId(
idSchema.$id,
idx,
formContext.idConfigs.idSeparator
);
};
}, [formContext, idSchema.$id]);

const reorderFields = (idx: number, newIdx: number) => {
if (!formContext) return;

const fieldAtIdx = getArrayItemFieldIdFn(idx);
const fieldAtNewIdx = getArrayItemFieldIdFn(newIdx);
if (shouldReorder(formContext.touchedFields, fieldAtIdx, fieldAtNewIdx)) {
formContext.toggleTouchedFields(fieldAtIdx, fieldAtNewIdx);
}
};

const removeField = (idx: number) => {
if (!formContext) return;

const fieldsToToggle = [];
for (let i = idx; i < items.length - 1; i++) {
const fieldAtIdx = getArrayItemFieldIdFn(i);
const fieldAtNextIdx = getArrayItemFieldIdFn(i + 1);

if (
shouldReorder(formContext.touchedFields, fieldAtIdx, fieldAtNextIdx)
) {
fieldsToToggle.push(fieldAtIdx);
}
}

const fieldAtLastIdx = getArrayItemFieldIdFn(items.length - 1);
if (formContext.touchedFields.includes(fieldAtLastIdx)) {
fieldsToToggle.push(fieldAtLastIdx);
}

formContext.toggleTouchedFields(...fieldsToToggle);
};

const handleDrop =
(
idx: number,
onDropidxClick: (event?: React.MouseEvent<HTMLElement>) => void
) =>
(event?: React.MouseEvent<HTMLElement>) => {
removeField(idx);

return onDropidxClick(event);
};

const handleReorder =
(
idx: number,
newIdx: number,
onReorderClick: (event?: React.MouseEvent<HTMLElement>) => void
) =>
(event?: React.MouseEvent<HTMLElement>) => {
reorderFields(idx, newIdx);

return onReorderClick(event);
};

return (
<>
<Box>
{items &&
items.map(({ key, ...itemProps }: ArrayFieldTemplateItemType) => (
<ArrayFieldItem key={key} {...itemProps} />
))}
({
key,
onDropIndexClick,
onReorderClick,
...itemProps
}: ArrayFieldTemplateItemType) => (
<ArrayFieldItem
key={key}
=>
handleDrop(idx, onDropIndexClick(idx))
}
newIdx) =>
handleReorder(oldidx, newIdx, onReorderClick(oldidx, newIdx))
}
{...itemProps}
/>
)
)}
</Box>
{canAdd && (
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const BaseInputTemplate: React.FC<WidgetProps> = ({
readonly,
required,
onChange,
onBlur,
}) => {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange(e.target.value === '' ? options.emptyValue : e.target.value);
Expand All @@ -23,6 +24,10 @@ const BaseInputTemplate: React.FC<WidgetProps> = ({
onChange(!patch.valid ? options.emptyValue : patch.value);
};

const handleBlur = (e: React.ChangeEvent<HTMLInputElement>) => {
onBlur(id, e.target.value === '' ? options.emptyValue : e.target.value);
};

const handleSuggestionSelect = (e: { suggestion: string }) => {
onChange(e.suggestion);
};
Expand All @@ -47,6 +52,7 @@ const BaseInputTemplate: React.FC<WidgetProps> = ({
width: { max: 'small' },
}}
>
>
/>
) : (
<TextInput
Expand All @@ -58,6 +64,7 @@ const BaseInputTemplate: React.FC<WidgetProps> = ({
readOnly={readonly}
required={required}
>
>
>
{...inputProps}
/>
Expand Down
28 changes: 22 additions & 6 deletions src/components/UI/JSONSchemaForm/ErrorListTemplate/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
import { ErrorListProps } from '@rjsf/utils';
import { ErrorListProps, RJSFSchema } from '@rjsf/utils';
import { Box, Paragraph } from 'grommet';
import React from 'react';
import React, { useMemo } from 'react';
import { FlashMessageType } from 'styles';
import FlashMessage from 'UI/Display/FlashMessage';

const ErrorListTemplate: React.FC<ErrorListProps> = ({ errors }) => {
return (
import { IFormContext } from '..';
import { isTouchedField, mapErrorPropertyToField } from '../utils';

const ErrorListTemplate: React.FC<
ErrorListProps<RJSFSchema, RJSFSchema, IFormContext>
> = ({ errors, formContext }) => {
const filteredErrors = useMemo(() => {
if (!formContext || formContext.showAllErrors) return errors;

return errors.filter((e) =>
isTouchedField(
mapErrorPropertyToField(e, formContext.idConfigs),
formContext.touchedFields
)
);
}, [errors, formContext]);

return filteredErrors.length > 0 ? (
<FlashMessage type={FlashMessageType.Danger}>
<Paragraph size='xlarge'>Errors</Paragraph>
<Box>
{errors.map((error, idx) => (
{filteredErrors.map((error, idx) => (
<Paragraph key={idx} fill>
{error.stack}
</Paragraph>
))}
</Box>
</FlashMessage>
);
) : null;
};

export default ErrorListTemplate;
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ const FieldErrorTemplate: React.FC<FieldErrorProps> = ({ errors }) => {
<>
{errors.map((error, idx) => (
<Text key={idx} size='small' color='text-error'>
{error}
{typeof error === 'string'
? `${error[0].toLocaleUpperCase()}${error.slice(1)}`
: error}
</Text>
))}
</>
Expand Down
Loading
0