Form
======

# 1. 介绍

**注意！所有的回调方法都是用对象解构方式传参数的**

就是这样 ⬇️
```javascript
handleChange({fieldName,fieldBizData,...}){
  // Your code is here.
}
```

该组件从平台Form组件中抽出，内置了如下平台逻辑：
- 表单联动
- 字段依赖（根据字段的值刷新某一字段的数据源）
- 表单验证
    - 同步验证
    - 异步验证

# 2. 可扩展的逻辑

## 2.1 表单元数据加载

### 2.1.1 直接提供表单元数据

当直接给`overrideFormMetaData`传递表单元数据时，表单将跳过网络请求，直接使用提供的元数据。

不提供这个属性时，表单将按照`popOption`中提供的数据加载数据。

### 2.1.2 自定义表单元数据请求地址

实现`getFormViewRequestURL`方法，表单将会使用该URL请求表单元数据。

```javascript
getFormViewRequestURL(){
  return url
}

<Form {...otherProps} getFormViewRequestURL={getFormViewRequestURL}/>
```

### 2.1.3 使用自定义参数加载

请求表单时至少要有这四个基础的参数`app`, `metaObjName`, `viewName`, `formState`，这四个参数。当`formState`需要传入`id`。

表单请求时会带上`additionParamsWhenLoad`里面的参数，保存时会带上`additionParamsWhenSave`里面的参数。

```javascript
const formViewParams = {
  // id: '',
  app: 'BeisenCloudStepAAA',
  metaObjName: 'BeisenCloudStepAAA.liandongzhuanyong',
  viewName: 'BeisenCloudStepAAA.shareddemoform2',
  formState: 'create',
  additionParamsWhenLoad: {
    param1:'param1content',
    param2:'param2content'
  },
  additionParamsWhenSave: {
    param1:'param1content',
    param2:'param2content'
  },
  additionHeadersWhenSave:{
    header1:'header1content',
    header2:'header2content'
  },
  httpMethodWhenSave: 'POST'
  // customSaveURL: YOUR_API
}

<Form {...otherProps} formViewParams={formViewParams}/>
```

### 2.1.4 过滤(修改)表单元数据

当表单元数据从网络加载后，如果存在属性`formMetaDataFilter`，会执行该方法，并将返回的元数据作为展示表单的元数据。

```javascript
formMetaDataFilter({formMetaData}){
  // do some change
  return formMetaData
}

<Form {...otherProps} formMetaDataFilter={formMetaDataFilter} />
```

### 2.1.5 修改表单参数重新加载表单

当`formViewParams`中除`additionParamsWhenSave`以外任何一部分发生变化时，表单会重新加载。

可以通过实现`shouldReloadFormData`方法重写判断是否重新加载表单逻辑。

## 2.2 过滤数据源

当表单数据源加载后，会调用`dataSourceFilter`方法，该方法会传入每个`DataSource`项(`fieldName`、`key`、`dataSourceResults`)，用`return`的`dataSourceResults`作为新的数据源。

## 2.3 替换组件

替换组件的优先级如下：

当渲染该字段时，会首先根据字段元数据的`cmp_data.field_name`（复合表单为`cmp_data.field_name_form_multi`）从`extendedFieldMapByFieldName`中查询。

如果查询不到该组件，会首先根据字段元数据的`cmp_type`从`extendedFieldMap`中查询。

如果查询不到该组件，则会从平台标准组件的`FieldMap`中查询。

如果仍然查询不到，则会默认使用`单行文本`代替。

### 2.3.1 根据字段名替换组件

`props`为字段的渲染前的`props`，`OriginalComponent`为

```javascript
const overrideFields = {
  a1: (props, OriginComponent) => {
    return (
      <div>
        <OriginComponent {...props} />
        <span>自定义自定义</span>
      </div>
    );
  },
  extdanxuansetting_100013_62398125: ()=> {
    return <Hello />
  }
}

<Form {...otherProps} extendedFieldMapByFieldName={overrideFields}/>
```

### 2.3.2 根据字段类型替换组件

支持传入自定义字段组件。传入字段组件需要遵照以下格式：

```javascript
const overrideFields = {
  FIELD_TYPE1: (props, OriginComponent) => {
    return (
      <div>
        <OriginComponent {...props} />
        <span>自定义自定义</span>
      </div>
    );
  },
  FIELD_TYPE2: ()=> {
    return <Hello />
  }
}

<Form {...otherProps} extendedFieldMap={overrideFields}/>
```

## 2.4 表单联动

此处将值改变时处罚联动逻辑的字段成为`控制字段`。

### 2.4.1 设定监听字段

需要将所有的`控制字段`都通过`extendedFormLinkageFields`这个props传入。

这样做的目的是，当触发联动时，可以使用专门的`onFormLinkage`方法处理联动逻辑，减小`onChange`方法的体积。

设想一下，如果联动逻辑依赖于`onChange`方法，那么在`onChange`内做联动逻辑必定会使得`onChange`方法非常大。尤其是涉及到链式联动逻辑时。

将联动逻辑和`onChange`分开，可以得到更好的代码结构。

### 2.4.2 实现回调方法

实现接口方法`onFormLinkage`，该方法会传入：
- `fieldName` 发生值改变的控制字段的字段名
- `fieldBizData` 控制字段的值
- `formBizData` 当前表单（所有字段）的值
- `fieldBizDataBeforeChange` 控制字段的值改变之前的值
- `formLinkageFunctions` 表单联动操作方法集，包含的方法在`2.4.3`介绍
- `commitFormLinkage` 提交表单联动操作

当进行表单联动操作后，必须调用`commitFormLinkage`提交联动操作。这样做的原因是考虑到有一些联动逻辑是异步进行的，因此联动方法也应该是异步的。

请务必确保联动提交。否则，未使用`commitFormLinkage`提交的联动不会生效，还会遗留在联动操作队列中，产生奇怪的问题。

这里要强调，每调用一次`commitFormLinkage`，都会立刻进行联动逻辑。需要考虑数据同步（例如进行设定字段值后进行了`commitFormLinkage`，但一个异步请求还没返回）和性能问题。建议有异步操作的在异步回调中使用`commitFormLinkage`。

实际上，每个表单联动方法做的事情，在表单联动操作队列中添加一条联动记录（也可以认为是一条消息），当调用`commitFormLinkage`时，执行联动操作队列中所有的联动逻辑。

对于设定`setFieldError`和`removeFieldError`这两个显示和移除字段错误信息的联动方法，由于没有传入验证器，错误信息的显示和隐藏需要手动维护。表单进行验证（且失败）时，回调方法`onValidateFailed`的参数`formErrors`会包含联动设置的错误。

### 2.4.3 表单联动操作方法

method              |params                         |description
--------------------|-------------------------------|----------------------------
hideField           |fieldName                      |使字段fieldName隐藏
showField           |fieldName                      |使字段fieldName显示
presenceField       |fieldName                      |使字段fieldName必填
nonPresenceField    |fieldName                      |使字段fieldName非必填
readOnlyField       |fieldName                      |使字段fieldName只读
clearBizData        |fieldName                      |清空字段fieldName的值
setBizData          |fieldName, bizData             |设置字段fieldName的值
setBizDataGroup     |bizDataMap, fieldNamesToInvokeNextLinkageOn | 同时设置多个数据，如果有下一级联动，触发指定字段的联动
updateDataSource    |fieldName, dataSourceResults   |更新字段fieldName的数据源
hideFormPart        |formPartIndex                  |隐藏第formPartIndex个表单区块（从0开始）
showFormPart        |formPartIndex                  |显示第formPartIndex个表单区块（从0开始）
setFieldError       |fieldName, errorMessage        |使字段fieldName显示错误errorMessage
removeFieldError    |fieldName                      |移除字段fieldName上的错误信息（必须是由setFieldError设定的错误信息）

### 2.4.4 代码风格
**请尽量使用对象解构的方式接收参数，尽量使用switch-case来区分fieldName和fieldBizData.value的值，减少if-else的使用。**否则当逻辑较多时，过多的if-else会让代码变得非常乱。

## 2.5 表单验证

### 2.5.1 传入自定义验证器

自定义验证器`function`格式为[validate.js的自定义验证器格式](http://validatejs.org/#validate)

```javascript
const extendedValidators = {
  validatorName1: validateFunction1,
  validatorName2: validateFunction2,
  validatorName3: validateFunction3,
}

<Form {...otherProps} extendedValidators={extendedValidators} />
```

### 2.5.2 自定义字段的验证器

传入`extendedFieldValidators`属性可以单独定义每个字段的验证器。**替换组件的组件只能使用这种方式定义验证器**。

在`extendedFieldValidators`中，每个`function`的参数是表单元数据带的验证器，需要返回最终决定的该字段的验证器。

```javascript
const extendedFieldValidators = {
  fieldName1: (originalValidators) => {
    return Object.assign({},validators,{presence: {message: "^必填"}})
  },
  fieldName2: () => {
    return Object.assign({presence: {message: "^必填"}})
  }
}

<Form {...otherProps} extendedFieldValidators={extendedFieldValidators}/>
```

### 2.5.3 重写验证逻辑

实现`overrideValidate`方法。

该方法的参数是表单数据`formBizData`和表单元数据`formMetaData`。

该方法需要返回一个`Promise`，值为`validate.js`的错误类型，当验证通过时，应返回可判断为`false`的值（建议返回`null`）。

### 2.5.4 在onChange中手动设定错误信息

当onChange调用时，`functions`参数中有两个方法：`setError(errorMessage)`和`removeError()`，可以通过调用这两个方法给字段设置错误信息。
但需要注意，和联动中的设置字段错误一样，需要手动清除字段错误信息才能通过表单验证（实际上内部实现是一样的，只是绑定了fieldName）。

## 2.6 表单提交

### 2.6.1 重写表单提交逻辑

实现`overrideSubmit`方法。参数是表单的数据`bizData`。

### 2.6.2 自定义提交附带参数

见`2.1.3`。

### 2.6.3 表单提交前

表单验证通过后，开始提交前会回调`preSubmit`方法，该方法需要返回一个`Promise`。当返回`resolve`时，表单会继续进行提交动作，返回`reject`时，会中断提交动作。

这是一个例子，通过做一个前置请求，决定是否提交数据，当这个前置请求返回错误时，弹出错误提示

```javascript
export default class PreSubmitExample extends Component {

  handlePreSubmit = ({ formBizData, functions }) => {
    const _417 = 'https://api.simgenius.cn/simrest/ObjectData/BSDevHelper/OperationResult/4615096d-6752-495f-a32c-ceac05e5beea';
    const _200 = 'https://api.simgenius.cn/simrest/ObjectData/BSDevHelper/OperationResult/6ea7ac57-14da-41bb-b59e-b60903c6db33';
    return new Promise((resolve,reject)=>{
      fetch(_417)
        .then(r => r.json())
        .then((operationResult) => {
          if(operationResult.code !== 200){
            this.formMethods.showTip({title:operationResult.message, infoType:'error', content:[]})
            reject(operationResult)
          }else {
            return functions.showConfirm(operationResult.message)
          }
        }).then(()=>{console.log('confirm'); resolve();},()=>{console.log('cancel');reject()})
    })
  };

  render() {
    const popOptions = {
      formSaveLabel: '想要一个长长的名字，很长很长的，长长长长长长长长的',
      cmpContext: {
        app: 'BeisenCloudDemo',
        'currentViewName': 'BeisenCloudDemo_Sshitu',
        'metaObjName': 'BeisenCloudDemo.123'
      },
      formState: 'create',
    };
    return (
      <FormWithFooter
        formMethods={f=>this.formMethods = f}
        overrideFormMetaData={require('../BugFix__NOT_EXAMPLE/metadata6')}
        BSGlobal={BSGlobal}
        popOptions={popOptions}
        preSubmit={this.handlePreSubmit}
      />
    );
  }
}
```

## 2.7 捕获内部事件

内部事件产生时机见下图的生命周期。

![FormEvent](https://wiki.beisen.co/download/attachments/25559076/FormEvent.png?version=1&modificationDate=1545204835032&api=v2)

实现`onImplicitEvent`方法，参数为`eventName`、`params`。目前`params`没有参数传进来，将来传递参数仍然会通过对象解构方式传递到`params`。

# 3. API

### 3.1 值类型props

name                        |type                   |default    |description
----------------------------|-----------------------|-----------|:-------------------------------
overrideFormMetaData        |MetaData               |none       |当使用这个属性时，表单会直接使用提供的元数据，不再通过网络请求获取表单的元数据。
formLinkageFields           |Array\<string>         |none       |扩展联动逻辑中，控制字段的字段名
extendedFieldMap            |Dictionary<Function>   |none       |传入的自定义组件列表
extendedFieldMapByFieldName |Dictionary<Function>   |none       |传入的自定义组件列表
defaultBizData              |BizData                |none       |表单的初始值
formViewParams              |Object                 |none       |表单的参数，其中: app, metaObjName, viewName, formState必填，编辑时需要填表单数据的id，additionParamsWhenLoad为加载时的额外参数，additionParamsWhenSave为提交时的额外参数
extendedValidators          |Dictionary<Function>   |none       |自定义验证器
extendedFieldValidators     |Dictionary<Function>   |none       |自定义字段的验证器
loadingIsFixedPosition      |boolean                |false      |表单加载的loading是否是fixed定位

### 3.1 function类型props

name                |params                                                         |returns                 |description
--------------------|---------------------------------------------------------------|-----------------------|:---------------
onChange            |fieldName, fieldBizData, formBizData, fieldBizDataBeforeChange, functions |none        |表单数据改变时的回调
onFormLinkage       |fieldName, fieldBizData, formBizData, fieldBizDataBeforeChange, formLinkageFunctions, commitFormLinkage | undefined | 表单联动回调
dataSourceFilter    |fieldName, key, dataSourceResults                              |dataSourceResults      |数据源过滤器
formMetaDataFilter  |formMetaData                                                   |formMetaData           |过滤（修改）表单元数据
overrideValidate    |formBizData, formMetaData                                      |Promise\<form_errors>  |自定义表单验证过程
onValidateFailed    |formErrors                                                     |none                   |表单验证失败时的回调
overrideSubmit      |bizData                                                        |none                   |自定义表单提交过程
preSubmit           |formBizData, dataToSubmit, functions                           |Promise                |表单验证通过后，开始提交前的回调
onSubmitted         |response                                                       |none                   |表单提交成功后的回调
onSubmitFailed      |Promise.reject\<response>                                      |none                   |表单提交失败时的回调
onImplicitEvent     |eventName, params                                              |none                   |隐式事件回调
shouldReloadFormData|prevProps, currentProps, formBizData, formErrors               |boolean                |当formViewParams发生变化时，是否重新加载表单

### 3.3 methods

name      |params     |returns            |description
----------|-----------|-------------------|:---------------
showTip   |tipOption  |none               |弹出Tip
submit    |none       |none               |表单提交
validate  |none       |Promise<FormError> |表单验证
getBizData|none       |BizData            |获取表单数据