Skip to content
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

Form validations #189

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
42b3d40
Update `isValid` logic
JeffersonBledsoe May 24, 2023
af25e13
Fix isInvalid logic
JeffersonBledsoe May 24, 2023
822767d
Update error handling for validation of text field
JeffersonBledsoe May 24, 2023
c2cb165
Handle multiple validators
JeffersonBledsoe Jun 23, 2023
33982db
Merge branch 'main' into form-validations
JeffersonBledsoe Dec 11, 2023
ffccf1e
Validation errors from backend
JeffersonBledsoe Dec 12, 2023
609ba71
Display error messages on textwidget
JeffersonBledsoe Dec 13, 2023
cd03674
Update form addon
JeffersonBledsoe Dec 13, 2023
6fa9dc2
Merge branch 'main' into form-validations
JeffersonBledsoe Dec 19, 2023
7467883
Remove aria-invalid attributes
JeffersonBledsoe Dec 21, 2023
e105e70
Fix crash with required select options with no values
JeffersonBledsoe Dec 21, 2023
954193a
Ensure all widgets have a top-level ID
JeffersonBledsoe Dec 21, 2023
f22a88e
Fix editing static text
JeffersonBledsoe Dec 21, 2023
bc7cea3
Unify error message display
JeffersonBledsoe Dec 22, 2023
301c6c5
Add error message to file widget
JeffersonBledsoe Dec 22, 2023
1ef9efe
Update frontend to support tidied error messages
JeffersonBledsoe Dec 22, 2023
a0a98f8
Fix error message lookup when there's multiple validations
JeffersonBledsoe Jan 18, 2024
199fa58
Add aria-invalid and aria-describedby to fields
JeffersonBledsoe Jan 23, 2024
1981273
Fix drag and delete button positioning in volto-form-block
JeffersonBledsoe Jan 30, 2024
0166cc0
comment
JeffersonBledsoe Jan 30, 2024
9fbc479
Fix some React warnings
JeffersonBledsoe Jan 31, 2024
122f40f
Some more warning fixes
JeffersonBledsoe Jan 31, 2024
be06d96
Handle case where validation was performed in the past but now passes
JeffersonBledsoe Jan 31, 2024
2e0e5aa
Fix field validation when error messages contain null
JeffersonBledsoe Jan 31, 2024
4010e7c
Fix old-style prop
JeffersonBledsoe Jan 31, 2024
3c4c4e0
Fix for required validation messages
JeffersonBledsoe Jan 31, 2024
c3fc8e7
Improved labelling
JeffersonBledsoe Mar 28, 2024
4e83d82
Fixed duplicate error message
JeffersonBledsoe Mar 28, 2024
0901cdb
Fix unwanted default values
JeffersonBledsoe Mar 29, 2024
dedf4e5
Fix potential exception
JeffersonBledsoe Mar 29, 2024
4d7be42
Fix no-value option not appearing for required fields
JeffersonBledsoe Apr 2, 2024
1385131
Reduce checkbox computation
JeffersonBledsoe Apr 8, 2024
529a6aa
Fix multi-select default value
JeffersonBledsoe Apr 8, 2024
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
17 changes: 17 additions & 0 deletions src/components/Components/Form/ErrorMessage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function ErrorMessage({ inputId, message }) {
return (
<span
className="nsw-form__helper nsw-form__helper--error"
id={`${inputId}-error-text`}
>
<span
className="material-icons nsw-material-icons"
focusable="false"
aria-hidden="true"
>
cancel
</span>
{message}
</span>
);
}
51 changes: 48 additions & 3 deletions src/customizations/volto-form-block/components/Field.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const Field = (props) => {
widget,
shouldShow = true,
display_values,
errors = {},
} = props;
let { value } = props;
// A value of `null` is a touched field with no value
Expand All @@ -59,13 +60,40 @@ const Field = (props) => {
value !== null &&
value === undefined
) {
value = props.default_value;
if (
!['select', 'single_choice', 'multiple_choice'].includes(field_type) ||
input_values?.includes(props.default_value) || // Single-select includes the value
(Array.isArray(props.default_value) &&
props.default_value?.every((default_value) =>
input_values?.includes(props.default_value),
)) // Multi-select includes the value
) {
value = props.default_value;
}
}
const intl = useIntl();

const isInvalid = () => {
return !isOnEdit && !valid;
let isInvalid = !isOnEdit && !valid;
// TODO: Re-enable frontend-validations
// if (validations && validations.length > 0) {
// validations.forEach((validation) => {
// const validatorObject = validationObjects[validation.validation_type];
// const validator = validatorObject?.validator;
// if (validator) {
// const validatorProperties = Object.keys(validatorObject.properties)
// isInvalid = !validator({
// value,
// ...pick(validation, validatorProperties),
// });
// }
// })
// }

return isInvalid;
};
const errorList =
typeof errors === 'object' && errors !== null ? Object.values(errors) : {};

if (widget) {
const Widget = widgetMapping[widget];
Expand Down Expand Up @@ -97,24 +125,31 @@ const Field = (props) => {
<Widget
{...props}
id={name}
name={name}
title={label}
value={value}
valueList={valueList}
invalid={isInvalid().toString()}
error={errorList}
{...(isInvalid() ? { className: 'is-invalid' } : {})}
/>
);
}

return (
<MaybeWrap condition={isOnEdit} as="div" inert={isOnEdit ? '' : null}>
<MaybeWrap
condition={isOnEdit}
as="div"
inert={isOnEdit && field_type !== 'static_text' ? '' : null}
>
{field_type === 'text' && (
<TextWidget
id={name}
name={name}
title={label}
description={description}
required={required}
error={errorList}
onChange={onChange}
value={value}
isDisabled={disabled}
Expand All @@ -130,6 +165,7 @@ const Field = (props) => {
title={label}
description={description}
required={required}
error={errorList}
onChange={onChange}
value={value}
rows={10}
Expand All @@ -148,6 +184,7 @@ const Field = (props) => {
getVocabularyTokenTitle={() => {}}
choices={[...(input_values?.map((v) => [v, v]) ?? [])]}
value={value}
error={errorList}
onChange={onChange}
placeholder={intl.formatMessage(messages.select_a_value)}
aria-label={intl.formatMessage(messages.select_a_value)}
Expand All @@ -165,6 +202,7 @@ const Field = (props) => {
description={description}
required={required}
onChange={onChange}
error={errorList}
valueList={[
...(input_values?.map((v) => ({ value: v, label: v })) ?? []),
]}
Expand All @@ -182,6 +220,7 @@ const Field = (props) => {
description={description}
required={required}
onChange={onChange}
error={errorList}
valueList={[
...(input_values?.map((v) => ({ value: v, label: v })) ?? []),
]}
Expand All @@ -198,6 +237,7 @@ const Field = (props) => {
title={label}
description={description}
required={required}
error={errorList}
onChange={onChange}
value={!!value}
isDisabled={disabled}
Expand All @@ -214,6 +254,7 @@ const Field = (props) => {
dateOnly={true}
noPastDates={false}
resettable={false}
error={errorList}
onChange={onChange}
value={value}
isDisabled={disabled}
Expand All @@ -230,6 +271,7 @@ const Field = (props) => {
description={description}
type="file"
required={required}
error={errorList}
invalid={isInvalid().toString()}
isDisabled={disabled}
onChange={onChange}
Expand All @@ -243,6 +285,7 @@ const Field = (props) => {
name={name}
title={label}
description={description}
error={errorList}
required={required}
onChange={onChange}
value={value}
Expand All @@ -258,6 +301,7 @@ const Field = (props) => {
id={name}
name={name}
title={label}
error={errorList}
description={description}
onChange={onChange}
value={value}
Expand All @@ -281,6 +325,7 @@ const Field = (props) => {
value={value}
isDisabled={disabled}
formHasErrors={formHasErrors}
error={errorList}
invalid={isInvalid().toString()}
{...(isInvalid() ? { className: 'is-invalid' } : {})}
/>,
Expand Down
65 changes: 43 additions & 22 deletions src/customizations/volto-form-block/components/FormView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const messages = defineMessages({
},
field_is_required: {
id: 'field_is_required',
defaultMessage: '{fieldLabel} is required',
defaultMessage: 'This field is required',
},
});

Expand All @@ -45,48 +45,59 @@ function useIsClient() {
return isClient;
}

function ErrorMessageBox({ formId, formErrors = [], fields }) {
function ErrorMessageBox({ formId, formErrors = {}, fields }) {
const intl = useIntl();
const allFieldData = React.useMemo(() => {
return fields.reduce((errorData, currentField) => {
const fieldName = getFieldName(currentField.label, currentField.id);
if (formErrors.includes(fieldName)) {
if (formErrors[fieldName]) {
errorData[fieldName] = currentField;
}
return errorData;
}, {});
}, [fields, formErrors]);

if (formErrors.length < 1) {
if (Object.keys(formErrors).length < 1) {
return null;
}

return (
<div
class="nsw-in-page-alert nsw-in-page-alert--error"
className="nsw-in-page-alert nsw-in-page-alert--error"
id={`${formId}-errors`}
>
<span
class="material-icons nsw-material-icons nsw-in-page-alert__icon"
className="material-icons nsw-material-icons nsw-in-page-alert__icon"
focusable="false"
aria-hidden="true"
>
cancel
</span>
<div class="nsw-in-page-alert__content">
<p class="nsw-h5">{intl.formatMessage(messages.error)}</p>
<div className="nsw-in-page-alert__content">
<p className="nsw-h5">{intl.formatMessage(messages.error)}</p>
{/* eslint-disable-next-line jsx-a11y/no-redundant-roles */}
<ul role="list">
{formErrors.map((fieldName) => {
const { label, id } = allFieldData[fieldName];
{Object.keys(formErrors).map((fieldName) => {
const fieldData = allFieldData[fieldName];
if (!fieldData) {
return <React.Fragment key={fieldName}></React.Fragment>;
}
const { label, id, validations = [] } = fieldData;
const name = getFieldName(label, id);
const validationsWithErrors = Object.keys(formErrors[name]);
const validationIdToShow = validations.find((validation) =>
validationsWithErrors.includes(validation),
);
const validationMessageToShow = formErrors[name].required
? 'required'
: validationIdToShow;
const errorMessage = validationMessageToShow
? `${label}: ${formErrors[name][validationMessageToShow]}`
: intl.formatMessage(messages.field_is_required);

return (
<li>
<a href={`#field-${name}`}>
{intl.formatMessage(messages.field_is_required, {
fieldLabel: label,
})}
</a>
<li key={fieldName}>
<a href={`#field-${name}`}>{errorMessage}</a>
</li>
);
})}
Expand All @@ -102,7 +113,7 @@ const FieldRenderWrapper = ({
index,
blockData,
onChangeFormData,
formErrors,
formErrors = {},
isValidField,
FieldSchema,
}) => {
Expand Down Expand Up @@ -197,8 +208,9 @@ Only required if '${targetField.label}' is ${validatorLabel} to '${show_when_to}
value={value}
description={description}
valid={isValidField(name)}
formHasErrors={formErrors?.length > 0}
errors={formErrors[name]}
shouldShow={shouldShow}
formHasErrors={Object.keys(formErrors).length > 0} // TODO: Deprecate legacy prop
/>
</div>
);
Expand All @@ -214,8 +226,9 @@ Only required if '${targetField.label}' is ${validatorLabel} to '${show_when_to}
value={value}
description={description}
valid={isValidField(name)}
formHasErrors={formErrors?.length > 0}
errors={formErrors[name]}
shouldShow={shouldShow}
formHasErrors={Object.keys(formErrors).length > 0} // TODO: Deprecate legacy prop
/>
);
};
Expand All @@ -236,7 +249,7 @@ const FormView = ({
const FieldSchema = config.blocks.blocksConfig.form.fieldSchema;

const isValidField = (field) => {
return formErrors?.indexOf(field) < 0;
return !formErrors.hasOwnProperty(field) || formErrors[field] === null;
};

return (
Expand Down Expand Up @@ -266,7 +279,7 @@ const FormView = ({
) : (
// TODO: The original component has a `loading` state. Is this needed here?
<form id={id} className="nsw-form" onSubmit={onSubmit} method="post">
{formErrors.length > 0 && (
{Object.keys(formErrors).length > 0 && (
<ErrorMessageBox
formId={id}
formErrors={formErrors}
Expand All @@ -288,7 +301,14 @@ const FormView = ({
onChange={() => {}}
disabled
valid={isValidField}
formHasErrors={formErrors?.length > 0}
formHasErrors={!!formErrors[field.name]}
errors={
formErrors[
'static_field_' +
(field.field_id ??
field.name?.toLowerCase()?.replace(' ', ''))
]
}
/>
);
})}
Expand All @@ -299,6 +319,7 @@ const FormView = ({
subblock={subblock}
index={index}
formData={formData}
formErrors={formErrors}
blockData={data}
onChangeFormData={onChangeFormData}
isValidField={isValidField}
Expand Down
Loading