npm package: @mobx-json/mui-form
Description: create material-ui forms by json & mobx
Author: ThunderMiracle
Latest version: 0.2.2

# mobx-json

[![npm version](https://badge.fury.io/js/%40mobx-json%2Fmui-form.svg)](https://badge.fury.io/js/%40mobx-json%2Fmui-form)
[![dependencies Status](https://david-dm.org/thundermiracle/mobx-json/status.svg?path=packages/mobx-json-mui-form)](https://david-dm.org/thundermiracle/mobx-json?path=mobx-json-mui-form)
[![CircleCI](https://img.shields.io/circleci/build/github/thundermiracle/mobx-json/master)](https://circleci.com/gh/thundermiracle/mobx-json)
[![codecov](https://img.shields.io/codecov/c/github/thundermiracle/mobx-json)](https://codecov.io/gh/thundermiracle/mobx-json)
[![Netlify Status](https://api.netlify.com/api/v1/badges/b9f18e47-19f6-4bd7-b493-92c2099c72dc/deploy-status)](https://app.netlify.com/sites/mobx-json/deploys)

The simplest way to manage your form in React.

State management is charged by mobx, but you can use the materia-ui wrapped one without any knowledge of it.

You can check some examples here: [https://mobx-json.thundermiracle.com](https://mobx-json.thundermiracle.com)

## Table of Contents

* [Installation](#Installation)
* [How to use](#How-to-use)
* [A more useful sample](#A-more-useful-sample)
* [How to define blueprint](#How-to-define-blueprint)
  * [settings](#settings)
    * [widget](#widget)
    * [valueType (optional)](#valueType-optional)
    * [rule (optional)](#rule-optional)
    * [service (optional)](#service-optional)
    * [serviceRouter (optional)](#serviceRouter-optional)
    * [serviceParamFields (optional)](#serviceParamFields-optional)
    * [propRule (optional)](#propRule-optional)
    * [computeRule (optional)](#computeRule-optional)
    * [reloadRule (optional)](#reloadRule-optional)
  * [attrs](#attrs)
  * [Components](#Components)
    * [GridItemContainer](#GridItemContainer)
    * [Checkbox](#Checkbox)
    * [Checkboxes](#Checkboxes)
    * [Data](#Data)
    * [Display](#Display)
    * [Password](#Password)
    * [PasswordOutlined](#PasswordOutlined)
    * [Radio](#Radio)
    * [Select](#Select)
    * [SelectOutlined](#SelectOutlined)
    * [Switch](#Switch)
    * [TextField](#TextField)
    * [TextFieldOutlined](#TextFieldOutlined)
    * [Autocomplete](#Autocomplete)
    * [AutocompleteOutlined](#AutocompleteOutlined)
* [How to use in React](#How-to-use-in-React)
  * [Initialize (optional)](#Initialize-optional)
  * [Generate form](#Generate-form)
    * [useMuiJsonForm's parameters](#useMuiJsonForm's-parameters)

## Installation

```shell
npm i -S @mobx-json/mui-form react react-dom validatorjs

or

yarn @mobx-json/mui-form react react-dom validatorjs
```

## How to use

1. Define your form in JSON or javascript.

    ```json
    {
      "fields": [
        {
          "settings": {
            "widget": "TextField",
            "rule": "required|min:3"
          },
          "attrs": {
            "name": "id",
            "label": "Use Id",
            "placeholder": "input your user id"
          }
        },
        {
          "settings": {
            "widget": "Password",
            "rule": "required|min:8"
          },
          "attrs": {
            "name": "password",
            "label": "Password",
            "placeholder": "at least 8 chars"
          }
        }
      ]
    }
    ```

1. Create the form

    ```js
    import { useMuiJsonForm } from '@mobx-json/form';
    import LoginFormJson from './blueprints/login-form.json';

    const IndexPage = props => {
      const { form, getDataWithCheck } = useMuiJsonForm({
        blueprint: LoginFormJson,
      });

      const handleSubmit = React.useCallback(async () => {
        const data = getDataWithCheck();
        if (data) {
          await tryLogin(data);
        }
      }, [getDataWithCheck]);

      // the following one is material-ui, you can use native ones as well
      return (
        <Container maxWidth="xs">
          <Card>
            <CardContent>{form}</CardContent>
            <CardActions>
              <Button color="primary" onClick={handleSubmit}>
                LOGIN
              </Button>
            </CardActions>
          </Card>
        </Container>
      );
    }
    ```

1. Display

    ![simple](./screenshots/simple.png)

## A more useful sample

[./examples/mui-form](./examples/mui-form)

## How to define blueprint

The blueprint _MUST_ be defined as follows.

```json
{
  "fields": [
    ...
  ]
}
```

Each field should contains 3 parts including [settings](#settings), [attrs](#attrs) and [fields](#fields) (which is only necessary for settings.valueType = container).

* normal component

  ```json
  {
    "fields": [
      {
        "settings": {
          "widget": "TextField",
          "valueType": "string"
          ...
        },
        "attrs": {
          "name": "firstName"
          ...
        }
      }
    ]
  }
  ```

* container

  ```json
  {
    "fields": [
      {
        "settings": {
          "widget": "GridItemContainer",
          "valueType": "container"
          ...
        },
        "attrs": {
          "name": "baseInfoGrid"
          ...
        },
        "fields": [
          {
            "settings": {
              "widget": "TextField",
              "valueType": "string"
              ...
            },
            "attrs": {
              "name": "firstName"
              ...
            }
          }
        ]
      }
    ]
  }
  ```

### settings

To define which component to use and validation, etc.

exp:

```json
{
  "settings": {
    "widget": "TextField", // required
    "valueType": "string", // default: string
    "rule": "required"
  }
}
```

generate: ```TextField``` component and display error if input is empty.

| Property | Required | Type | Default | Description |
|:---------:|:--------:|:----:|:--------|:------------|
| widget | 〇 | string |  | 1. [components](components) contained in the package(TextField, Select...) <br /> 2. component you passed by widgetMap <br /> 3. native component in html(div, span...) |
| valueType | | string | ```string``` | one of ```number, string, array, boolean, container``` |
| rule |  | string | null | using [validatorjs](https://github.com/skaterdav85/validatorjs) as default validation tool, you can check the default validation rules [here](https://github.com/skaterdav85/validatorjs#available-rules) |
| propRule |  | string | null | rule to apply prop dynamically by input values. |
| computeRule |  | string | null | rule to compute value dynamically by target fields' value. |

#### widget

Following basic components are included in this package for quicker start: [Checkbox](#Checkbox), [Checkboxes](#Checkboxes), [Display](#Display), [Password](#Password), [PasswordOutlined](#PasswordOutlined), [Radio](#Radio), [Select](#Select), [SelectOutlined](#SelectOutlined), [Switch](#Switch), [TextField](#TextField), [TextFieldOutlined](#TextFieldOutlined).

#### valueType (optional)

  As every value from e.target.value is converted into string, we have to define a correct valueType to format.

  ※ container: a special valueType which means sub fields are contained

#### rule (optional)

A rule to validate the input.

exp: ```"rule": "required|max:30"``` will throws error if input is empty or over 30.

※ [here for details](https://github.com/skaterdav85/validatorjs)

※ [sample page](https://mobx-json.thundermiracle.com/modifyform)

#### service (optional)

※ See [Autocomplete's example](#Autocomplete)

#### serviceRouter (optional)

Use `get` as default router in service.

※ See [Autocomplete's example](#Autocomplete)

#### serviceParamFields (optional)

Get paramFields' value and pass them as parameters.

※ See [Autocomplete's example](#Autocomplete)

#### propRule (optional)

Apply defined prop to attrs if meets theYou can define multiple propRule and split them by __|__

Format:

```text
propName,propValueWhenMeetCondition,propValueType:targetFieldName,targetFieldValue

* Which means if (targetFieldName.value == targetFieldValue) set propName->propValue
```

exp: ```"propRule": "hidden,true,boolean:firstName,Daniel"``` will hide the component when the _firstName_ equals 'Daniel'.

※ [sample page](https://mobx-json.thundermiracle.com/proprule)

#### computeRule (optional)

Compute by other fields value. You can define multiple computeRule and split them by __|__, but the last rule will win.

Format:

```text
computeMethod:targetFieldName1,targetFieldName2,targetFieldName3...:extraInfo
```

Now supports 3 computeMethods:

* concat

  concat all targetFields' value into one string

  exp: ```"computeRule": "concat:firstName,lastName:, "```
  
  When firstName is _'Daniel'_, lastName is _Wood_, component's value will automatically changed to _'Daniel, Woo'_.

* sum

  sum all targetFields' value include string numbers. return 0 if character is contained.

  exp: ```"computeRule": "sum:profit1,profit2"```
  
  When profit1 is _100_, profit2 is _200_, component's value will automatically changed to _300_.

* format

  get and format targetField's value.

  exp: ```"computeRule": "format:sex", "format": { "type": "items", "itemsSource": "Sex"}```
  
  Transform field [sex]'s value by itemsSource. itemsSource: ```[{ value: 0, label: 'Male'}]``` will get _'Male'_ if value is 0.

  ※ [sample page](https://mobx-json.thundermiracle.com/computerule)

#### reloadRule (optional)

Reload items by calling asyncLoadItems when meets reloadRule. You can define multiple computeRule and split them by __|__, but the last rule will win.

Format:

```text
computeMethodtargetFieldName,compareMethod,targetFieldValue
```

List of compareMethod: `=, >, <, >=, <=, <> (DEFAULT)`

exp: ```"reloadRule": "prefecture"``` means call asyncLoadItems if `prefecture`'s value is not empty & changed

exp: ```"reloadRule": "age>18"``` means call asyncLoadItems if `age`'s value is greater than 18.

### attrs

All props defined in attrs will be passed to each component. And attrs.```name MUST``` be unique in each form.

exp:

```json
{
  "settings": {
    "widget": "TextField",
    "valueType": "string"
  },
  "attrs": {
    "name": "firstName",  // required & MUST be unique
    "label": "First Name",
    "placeholder": "input your first name"
  }
}
```

generates:

```js
<TextField name="firstName" label="First Name" placeholder="input your first name" />
```

Extended props in every component:

| Property | Required | Type | Default | Description |
|:---------:|:--------:|:----:|:--------|:------------|
| grid | | object | ```{ "xs": 12 }``` | responsive grid layout. Automatically add _xs=12_ to every component unless _noGrid_ is ```true```. <br /> See [here for details.](https://material-ui.com/components/grid/#grid-with-breakpoints) |
| noGrid | | boolean | ```false``` | if ```true```, grid will be ignored |
| defaultValue | | any | null | applied to ```value``` if it's undefined |
| value | | any | null | ```value``` for display |
| keepLabelSpace | | boolean | ```false``` | if ```true```, margin-top: 16px |
| icon | | string | | 1. iconsMap MUST be passed in initialization. see [iconsMap example](/examples/mui-form/src/settings/iconsMap.js) and [how to initialize](/examples/mui-form/src/pages/_app.jsx). <br /> 2. icon is one of the keys defined in iconsMap.js. <br /> 3. IconComponent will be passed to each component. |
| itemsSource | | string | | 1. itemsSource MUST be passed in initialization. see [itemsSource example](/examples/mui-form/src/settings/itemsSource.js) and [how to initialize](/examples/mui-form/src/pages/_app.jsx). <br /> 2. itemsSource is one of the keys defined in itemsSource.js. <br /> 3. itemsSource will be converted to items and passed to each component. <br /> 4. Available in [Radio](#Radio), [Checkboxes](#Checkboxes) component |

### Components

Check the example page:

* [Normal Design](https://mobx-json.thundermiracle.com/modifyform).
* [Outlined Design](https://mobx-json.thundermiracle.com/modifyformoutlined).

#### GridItemContainer

rest props are the same with [material-ui's Grid component](https://material-ui.com/api/grid/#props).

```json
{
  "settings": {
    "widget": "GridItemContainer", // required
    "valueType": "container" // required
    ... // others like propRule
  },
  "attrs": {
    "name": "nameGrid", // required
    "divider": false, // show divider in the bottom of the Grid component if true
    "primary": '', // primary text size h4 in Grid
    "primaryProps": {}, // props for primary text's typography
    "secondary": '', // secondary text size body2 under the primary text
    "secondaryProps": {}, // props for secondaryProps text's typography
    "subPrimary": '', // subPrimary text size h6 right of the primary text
    "subPrimaryProps": {}, // props for subPrimaryProps text's typography
    "hidden": false, // hide all sub fields if true (propagate hidden prop to all children)
    "disabled": false, // disable all sub fields if true (propagate disable prop to all children)
    ...rest props
  }
}
```

#### Checkbox

rest props are the same with [material-ui's Grid component](https://material-ui.com/api/checkbox/#props).

```json
{
  "settings": {
    "widget": "Checkbox",
    "valueType": "boolean",
    "rule": "required" // invalid if not checked
  },
  "attrs": {
    "name": "subscription",
    "label": "Subscribe the news letter!"
    ...rest props
  }
}
```

#### Checkboxes

```json
{
  "settings": {
    "widget": "Checkboxes",
    "valueType": "array",
    "rule": "required"
  },
  "attrs": {
    "name": "interest",
    "label": "Your Interests",
    "selectAll": false, // if true, show SelectAll checkbox
    "selectAllLabel": "ALL",  // default label of SelectAll checkbox is ALL
    "icon": "", // get IconComponent from iconsMap
    "itemsSource": "", // get items definition from itemsSource
    "items": [   // items MUST be an array with label&value
      {
        "label": "Movie",
        "value": 1
      },
      {
        "label": "Gaming",
        "value": 2
      },
      {
        "label": "Coding",
        "value": 3
      }
    ]
  }
}
```

#### Data

For keeping data and will not be rendered.

exp:

```json
{
  "settings": {
    "widget": "Data"
  },
  "attrs": {
    "name": "id"
  }
}
```

#### Display

For display and format value. [See examples](https://mobx-json.thundermiracle.com/detailform).

formatters:

* date: yyyy/MM/dd HH:mm:ss
* time: HH:mm
* age: compute age
* digit: toLocaleString() (1234 -> 1,234)

```json
{
  "settings": {
    "widget": "Display"
  },
  "attrs": {
    "name": "birthday",
    "label": "Birthday",
    "icon": "Today",
    "format": {
      "type": "date",
      "template": "yyyy年MM月dd日"
    }
  }
}
```

#### Password

#### PasswordOutlined

#### Radio

#### Select

#### SelectOutlined

#### Switch

#### TextField

#### TextFieldOutlined

#### Autocomplete

 [See examples](https://mobx-json.thundermiracle.com/modifyform).

```json
{
  "settings": {
    "widget": "Autocomplete",
    "rule": "required",
    "service": "filmService", // service passed by serviceContainer,
    "serviceRouter": "get", // default: get
    "serviceParamFields": [], // list of fieldName, get value of these fields and pass to service as { params: {fieldName1: value1, fieldName2: value2} }
  },
  "attrs": {
    "name": "film",
    "label": "FavoriteFilm",
    "freeSolo": true, // false: clear input's value when not selected from suggestions list
    "icon": "MenuBook", // get IconComponent from iconsMap
    "reloadOnInput": true, // true: reload suggestions onChange; false: load suggestions once after mounted;
    "reloadExcludeRegex": "[0-9]", // regex that not trigger reload onChange
    "reloadDelay": 300, // throttle of reloadOnInput
    "loaderText": "Loading...", // text when loading suggestions
    "extraAutocompleteProps": {}, // props applied to Autocomplete(material-ui's)
    "autoHighlight": false,
    "autoComplete": false,
    "autoSelect": false,
    ...               // restProps are the same with TextField
  }
  +
}
```

#### AutocompleteOutlined

## How to use in React

Package is using hook to make the form and return submit functions. React > 16.8 is needed.

### Initialize (optional)

Need if ```icon, itemsSource``` is used, use customized components or display self-defined error messages.

[See example](/examples/mui-form/src/pages/_app.jsx).

| Parameter | Required | Type | Default | Description |
|:---------:|:--------:|:----:|:--------|:------------|
| locale | | string | ```en``` | [validatorjs' locale string](https://github.com/skaterdav85/validatorjs/tree/master/src/lang) |
| messages | | object | {} | key-value object for extended error messages|
| itemsSource | △ | object | {} | key-value object for items definition. <br /> ※ required if ```itemsSource``` is used in blueprint |
| iconsMap | △ | object | {} | key-value object for IconComponent definition. <br /> ※ required if ```icon``` is used in blueprint |
| serviceContainer | △ | object | {} | key-value object for services' definition. pass `asyncLoadItems` to component which returns `items` asynchronously <br /> ※ required if ```service``` is used in blueprint |
| extraWidgetMap | | object | {} | key-value object for extra Widget(component) |

* initialize example:

  ```js
  import { initialize } from '@mobx-json/mui-form';

  initialize({
    locale: 'zh',
    messages,
    itemsSource,
    iconsMap,
    extraWidgetMap
  });
  ```

* itemsSource example:

  ```js
  export default {
    City: [
      {
        label: '東京都',
        value: 'tokyo',
      },
      {
        label: '神奈川県',
        value: 'kanagawa',
      },
      {
        label: '千葉県',
        value: 'chiba',
      },
    ],
    Sex: [
      {
        label: 'Male',
        value: 0,
      },
      {
        label: 'Female',
        value: 1,
      },
      {
        label: 'Other',
        value: -1,
      },
    ],
  }
  ```

* iconsMap example:

  ```js
  import AccountCircle from '@material-ui/icons/AccountCircle';
  import ContactMail from '@material-ui/icons/ContactMail';
  
  export default {
    AccountCircle,
    ContactMail,
  };
  ```

* extraWidgetMap example:

  ```js
  import MyTextField from './MyTextField';

  export default {
    AdvancedTextField: MyTextField,
  };
  ```

### Generate form

Use hook useMuiJsonForm to generate form.

example:

```js
import { useMuiJsonForm } from '@mobx-json/mui-form';

const { form, getDataWithCheck, setData, setBlueprint } = useMuiJsonForm({
  blueprint,
  formUniqName,
  data,
});

// load data after mounted
React.useEffect(async () => {
  const dataFromServer = await readDataFromServer();
  setData(dataFromServer);
}, []);

// save to server if validation pass
const handleSubmit = React.useCallback(async () => {
  const submitData = getDataWithCheck();
  if (submitData) {
    await saveDataToServer(submitData);
  }
}, [getDataWithCheck]);
```

[See example](/examples/mui-form/src/views/SampleForm/SampleForm.jsx).

#### useMuiJsonForm's parameters

| Parameter | Required | Type | Default | Description |
|:---------:|:--------:|:----:|:--------|:------------|
| blueprint | 〇 | object | | blueprint for rendering form. [See details](#How-to-define-blueprint). |
| blueprintExtra | {} | object | | key-value object: `fieldName: { attrs: {}, settings: {}}` for override definitions in blueprint. [See details](#How-to-define-blueprint). |
| formUniqName | △ | string | '' | necessary if define multiple forms in one page, or auto focus when errors occur will fail.  |
| data | | object | {} | data for form in initialization |
| options | | object | ```{ smoothScroll: true, gridContainerProps: {} }``` | 1.  smoothScroll: if true, append style ```scroll-behavior:smooth;``` to the latest ```overflow: auto;``` component. <br /> 2. gridContainerProps: props applied to root grid container |

#### useMuiJsonForm's output

useMuiJsonForm returns an object contains following parameters:

| key | Type | Description |
|:---------:|:--------:|:------------|
| form | React.Element | React form generated by blueprint |
| store | object | mobx JsonFormStore |
| setData | Function | apply ```data object``` to form |
| getData | Function | returns input data without running validation  |
| getDataWithCheck | Function | validate all inputs, if ok, returns input data; if not, returns false and automatically focus the first error field.  |
| setBlueprint | Function | apply a new blueprint. <br /> ※ re-render all fields! |
| clearError | Function | clear all error messages |
| clearData | Function | clear all data, set it to defaultValue if exists |
| clearAll | Function | clearError & clearData |
| revertToInit | Function | clearError & revert inputs to initialized data |
| changeFieldAttrs | Function | change field's props to trigger re-rendering by mobx |

## License

This project is licensed under the terms of the [MIT license](/LICENSE).