@vaadin/hilla-react-crud
Version:
Hilla CRUD utils for React
135 lines • 6.77 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { EndpointError } from '@vaadin/hilla-frontend';
import { ValidationError } from '@vaadin/hilla-lit-form';
import { useForm } from '@vaadin/hilla-react-form';
import { Button } from '@vaadin/react-components/Button.js';
import { ConfirmDialog } from '@vaadin/react-components/ConfirmDialog';
import { FormLayout } from '@vaadin/react-components/FormLayout';
import { VerticalLayout } from '@vaadin/react-components/VerticalLayout.js';
import { useEffect, useMemo, useRef, useState, } from 'react';
import { AutoFormField } from './autoform-field.js';
import css from './autoform.obj.js';
import { getDefaultProperties, ModelInfo } from './model-info.js';
import { registerStylesheet } from './util.js';
registerStylesheet(css);
export const emptyItem = Symbol();
export function AutoForm({ service, model, itemIdProperty, item = emptyItem, onSubmitError, onSubmitSuccess, disabled, layoutRenderer: LayoutRenderer, visibleFields, hiddenFields, formLayoutProps, fieldOptions, style, id, className, deleteButtonVisible, onDeleteSuccess, onDeleteError, }) {
const form = useForm(model, {
onSubmit: async (formItem) => service.save(formItem),
});
const formErrorRef = useRef(null);
const [formError, setFormError] = useState('');
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const modelInfo = useMemo(() => new ModelInfo(model, itemIdProperty), [model]);
const isEditMode = item !== undefined && item !== null && item !== emptyItem;
const showDeleteButton = deleteButtonVisible && isEditMode && modelInfo.idProperty;
const isSubmitDisabled = !!disabled || (isEditMode && !form.dirty);
useEffect(() => {
if (item !== emptyItem) {
form.read(item);
}
else {
form.clear();
}
}, [item]);
useEffect(() => {
formErrorRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' });
}, [formError]);
function handleSubmitError(error) {
if (error instanceof ValidationError) {
const nonPropertyErrorMessages = error.errors
.filter((validationError) => !validationError.property || typeof validationError.property === 'string')
.map((validationError) => {
const property = validationError.property && typeof validationError.property === 'string'
? `${validationError.property}: `
: '';
return `${property}${validationError.validatorMessage ?? validationError.message}`;
});
if (nonPropertyErrorMessages.length > 0) {
setFormError(_jsxs("div", { ref: formErrorRef, children: ["Validation errors:", _jsx("ul", { children: nonPropertyErrorMessages.map((message, index) => (_jsx("li", { children: message }, index))) })] }));
}
}
else if (error instanceof EndpointError) {
if (onSubmitError) {
onSubmitError({ error, setMessage: setFormError });
}
else {
setFormError(error.message);
}
}
else {
throw error;
}
}
async function handleSubmit() {
try {
setFormError('');
const newItem = await form.submit();
if (newItem === undefined) {
throw new EndpointError('No update performed');
}
else if (onSubmitSuccess) {
onSubmitSuccess({ item: newItem });
}
if (!item || item === emptyItem) {
form.clear();
}
}
catch (error) {
handleSubmitError(error);
}
}
function deleteItem() {
setShowDeleteDialog(true);
}
async function confirmDelete() {
const deletedItem = item;
try {
const idProperty = modelInfo.idProperty;
const id = item[idProperty.name];
await service.delete(id);
if (onDeleteSuccess) {
onDeleteSuccess({ item: deletedItem });
}
}
catch (error) {
if (error instanceof EndpointError) {
if (onDeleteError) {
onDeleteError({ error, setMessage: setFormError });
}
else {
setFormError(error.message);
}
}
else {
throw error;
}
}
finally {
setShowDeleteDialog(false);
}
}
function cancelDelete() {
setShowDeleteDialog(false);
}
const handleKeyDown = (event) => {
if (event.target instanceof HTMLTextAreaElement) {
return;
}
if (event.key === 'Enter' && !isSubmitDisabled) {
void handleSubmit();
}
};
function createAutoFormField(propertyInfo) {
const fieldOptionsForProperty = fieldOptions?.[propertyInfo.name] ?? {};
return (_jsx(AutoFormField, { propertyInfo: propertyInfo, form: form, disabled: disabled, options: fieldOptionsForProperty }, propertyInfo.name));
}
let visibleProperties = visibleFields ? modelInfo.getProperties(visibleFields) : getDefaultProperties(modelInfo);
if (hiddenFields) {
visibleProperties = visibleProperties.filter(({ name }) => !hiddenFields.includes(name));
}
const fields = visibleProperties.map(createAutoFormField);
const layout = LayoutRenderer ? (_jsx(LayoutRenderer, { form: form, children: fields })) : (_jsx(FormLayout, { ...formLayoutProps, children: fields }));
return (_jsxs("div", { className: `auto-form ${className ?? ''}`, id: id, style: style, "data-testid": "auto-form", children: [_jsxs(VerticalLayout, { className: "auto-form-fields", onKeyDown: handleKeyDown, children: [layout, formError ? _jsx("div", { style: { color: 'var(--lumo-error-color)' }, children: formError }) : _jsx(_Fragment, {})] }), _jsxs("div", { className: "auto-form-toolbar", children: [_jsx(Button, { theme: "primary", disabled: isSubmitDisabled, onClick: handleSubmit, children: "Submit" }), form.dirty ? (_jsx(Button, { theme: "tertiary", onClick: () => form.reset(), children: "Discard" })) : null, showDeleteButton && (_jsx(Button, { className: "auto-form-delete-button", theme: "tertiary error", onClick: deleteItem, children: "Delete..." }))] }), showDeleteDialog && (_jsx(ConfirmDialog, { opened: true, header: "Delete item", confirmTheme: "error", cancelButtonVisible: true, onConfirm: confirmDelete, onCancel: cancelDelete, children: "Are you sure you want to delete the selected item?" }))] }));
}
//# sourceMappingURL=autoform.js.map