Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/Visma-Consulting/formula


https://github.com/Visma-Consulting/formula

Last synced: 4 months ago
JSON representation

Awesome Lists containing this project

README

        

# @visma/formula 🏎

React component for configurable forms. Optionally connect to the backend to fetch external config and submit form data to.

## Requirements

1. Material UI v4, MUI v5 and `react-intl` are required. Install and set up if necessary:

```sh
npm i @visma/formula @emotion/styled @emotion/react @mui/x-date-pickers @mui/base @mui/material @material-ui/core @material-ui/styles @material-ui/icons @material-ui/lab react-intl --legacy-peer-deps
```

2. Add Vite / Webpack alias for `@emotion/core`:

```js
// vite.config.js
//...
export default defineConfig({
resolve: {
alias: {
'@emotion/core': '@emotion/react',
},
},
});

// webpack.config.js
module.exports = {
//...
resolve: {
alias: {
'@emotion/core': '@emotion/react',
},
},
};
```
```

## Examples

### Login form

```js
import Formula from '@visma/formula';

console.log(values)}
/>;
```

### Use external config, prefill some fields

```js
import Formula from '@visma/formula';

{
axios.defaults.baseURL = 'https://example.com/formula/api';
axios.defaults.headers.common.Authorization = 'Bearer ';
}}
id="1"
// Assuming form has at least a formGroup with key `customer`, containing
// fields with keys `firstName` & `lastName`.
formData={useMemo(
() => ({
customer: {
firstName: user.firstName,
lastName: user.lastName,
},
}),
[user]
)}
/>;
```

## Components

### ``

#### Props

One of `config`, `id` or `dataId` is required. Rest are optional.

| Name | Type | Description |
|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `config` | [Form](https://visma-consulting.github.io/formula/docs/interfaces/Form.html) | Form config |
| `formData` | `any` | Optional, prefilled form data. Ensure the reference does not change undesirably, e.g. using `useMemo`. |
| `id` | `string` | External form config id |
| `dataId` | `string` | Resume editing |
| `onPreSubmit` | `async (args: Args, event: SubmitEvent) => void \ | boolean \ | [Args, SubmitEvent]` | Run code before submit. Falsy return value prevents the submit. Return `true` or modified args to submit. |
| `onSubmit` | `({ values }) => void` | Override default submit handler |
| `onPostSubmit` | `(dataId, { values }) => void` | Get `dataId` of submitted form data |
| `confirm` | `boolean \ | { title: ReactElement, description: ReactElement }` | Show confirm dialog or use object for other messages. Default: `true` |
| `axios` | `axios => void` | Get access to API client's axios instance e.g. to set defaults |
| `dateFnsLocale` | `Locale` from `date-fns` | Examples:
`import useDateFnsLocale from '@visma/react-app-locale-utils/lib/useDateFnsLocale.js';`
`import { fi } from 'date-fns/locale';` |
| `children` | `ReactElement` | Override default submit button. Set `<>>` (empty React Frament) to render nothing. |
| `review` | `boolean` | Show review after the form has been submitted. Default: `true` |
| `forceReview` | `boolean` | Show review directly. Default: `false` |
| `reviewProps` | `{ actions: ReactNode, showSuccessText: boolean, highlightSuccessText: boolean, hideNotAnswered: boolean }` | actions: Additional action buttons
showSuccessText: show success text and summary in review, default `true`
highlightSuccessText: make summary more noticeable, default `false`
hideNotAnswered: hide not answered fields in review, user can see the full form by unchecking a checkbox, default `false` |
| `fillProps` | `{ actions: ReactNode, disableSteps: boolean, disableResetFormdata: boolean, disableElementButtons: boolean, showScores: boolean, disablePrint: boolean }` | actions: Additional action buttons
disableSteps: disables steps when filling the form, default `false`
disableResetFormdata: disable resetting formData to initial formData in disabled fields, default `false`
disableElementButtons: disable all button elements, default `false`
showScores: show scores if required metadata is available, default `false`
disablePrint: disable print button in ConfirmDialog, default `false` |
| `confirmComponent`, `previewField`, `reviewField` | `component` | [Customize](#customize) |
| `customMessages` | `{ submit: string, reviewSubmitConfirmation: string, confirmDialogTitle: string, confirmDialogConsent: string, confirmDialogPreview: string, confirmDialogSendButton: string, confirmDialogCancelButton: string, error: string }` | Overrides default texts in submit button, confirmation dialog, confirm message and error. |
| `buttonActions` | `object` | Functions for button elements, `{functionKey: (buttonActionProps) => boolean}` |

### ``

Provide options for any `` component in children.

Required to use API hooks.

#### Props

Same as for ``, except:

- Without `config`, `id`, `dataId`
- `children: ReactElement`: App, wrapped forms

### ``

#### Props

`config`, `id`, `dataId` and `children` from ``

## Hooks

See [src/api.js](src/api.js) for all API hooks.

### List forms

```js
import { useForms } from '@visma/formula';

function ListForms() {
const forms = useForms({ status: 'published', visibility: 'public' });
// ...
}
```

### Form config details

```js
import { useForm } from '@visma/formula';

function FormTitle({ id }) {
const form = useForm(id);

return

{form.title}

;
}
```

### Intercept built-in submit function

```js
import { Formula, useMutations } from '@visma/formula';
// ...
const { submit } = useMutations();

{
try {
return await submit(...args);
} catch (error) {
logger(error);
throw error;
}
}}
// ...
/>;
```

## Customize

### Confirm dialog (`confirmComponent`)

Example:

```js
import {
DialogActions,
DialogContent,
DialogContentText,
} from '@material-ui/core';
import produce, { original } from 'immer';
import { FormattedMessage, useIntl } from 'react-intl';

export function CustomConfirm({ config, formData, children }) {
const intl = useIntl();

// children, the original dialog, is readonly – use produce from immer to make deep immutable changes.
return produce(children, (children) => {
const dialogContentElement = children.props.children.find(
(element) => element && original(element)?.type === DialogContent
);

if (config.meta?.showScoreOnPreview && dialogContentElement) {
dialogContentElement.props.children.splice(
2,
0,



);
}

// Reverse dialog children order 🤪
children.props.children.reverse();

// Reverse dialog action button order
children.props.children
.find((element) => element && original(element)?.type === DialogActions)
?.props.children.reverse();

// If set, override consent message
const consentElement = dialogContentElement?.props.children.find(
(element) => element?.key === 'consent'
);
if (consentElement) {
consentElement.props.label = intl.formatMessage({
defaultMessage:
'Kyllä, haluan lähettää tiedot ja osallistua palkinnon arvontaan 🏆',
});
}
});
}
```

### Preview (`previewField`) & Review Field (`reviewField`)

Example:

```js
import produce from 'immer';
import { sortBy } from 'lodash';

export function CustomPreviewField({ formData, uiSchema, children }) {
const dataElement = children[1];

// children, the original field, is readonly – use produce from immer to make deep immutable changes.
return produce(children, (children) => {
if (uiSchema['ui:options'].element.meta.showScoreOnPreview) {
const highlight = sortBy(
uiSchema['ui:options'].element.meta.highlightColors,
['scoreGreaterThan']
)
.reverse()
.find(({ scoreGreaterThan }) => scoreGreaterThan < formData);

if (highlight) {
children[1] = (


{dataElement}







);
}
}
});
}
```

## Load library dynamically

1. Call `init` from `@visma/formula/lib/dll` before using the API:

```js
import { init } from '@visma/formula/lib/dll';
import App from 'components/App';
import React from 'react';
import ReactDOM from 'react-dom';

async function main() {
await init('https://example.com/formula');

ReactDOM.render(


,
document.getElementById('root')
);
}

main();
```

2. Import the API from `@visma/formula/lib/dll`. Note that all components and hooks are available only using the default export:

```js
import DLL from '@visma/formula/lib/dll';

{
axios.defaults.baseURL = 'https://example.com/formula/api';
axios.defaults.headers.common.Authorization = 'Bearer ';
}}
id="1"
/>;
```


EXAMPLES


App wrapped with FormulaProvider

```js
async function main() {
await init('https://example.com/formula');


ReactDOM.render(
{
axios.defaults.baseURL = 'https://example.com/formula';
}}
>


, document.getElementById('root'));
}
main();
```



After wrapping App with the Provider, Formula component can be used anywhere inside the App

FormulaComponent
```js
import {IntlProvider} from "react-intl";
import DLL from "@visma/formula/lib/dll";
import React from "react";

const FormulaComponent = (props) => {
return (




);
}

export default FormulaComponent;
```


Using FormulaComponent inside App

```js
...

...
```