UNPKG

@vaadin/hilla-react-crud

Version:

Hilla CRUD utils for React

135 lines 6.77 kB
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