skyline/src/components/Form/index.jsx
xusongfu 964209ca85 fix: fix the formRef error in browser console
Fix the `formRef` to `formref` as prop of a DOM element

Change-Id: If55a74e6962e1ca44653d3501bdc39d4734257b3
2022-08-22 18:48:28 +08:00

756 lines
18 KiB
JavaScript

// Copyright 2021 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from 'react';
import { isFunction, has, isObject, isEmpty } from 'lodash';
import Notify from 'components/Notify';
import { Row, Col, Form, Button, Spin, Progress } from 'antd';
import classnames from 'classnames';
import { InfoCircleOutlined } from '@ant-design/icons';
import { isAdminPage, firstUpperCase, unescapeHtml } from 'utils/index';
import { parse } from 'qs';
import FormItem from 'components/FormItem';
import { CancelToken } from 'axios';
import { getPath, getLinkRender } from 'utils/route-map';
import InfoButton from 'components/InfoButton';
import QuotaChart from 'components/QuotaChart';
import styles from './index.less';
export default class BaseForm extends React.Component {
constructor(props, options = {}) {
super(props);
this.options = options;
this.state = {
// eslint-disable-next-line react/no-unused-state
defaultValue: {},
// eslint-disable-next-line react/no-unused-state
formData: {},
isSubmitting: false,
percent: '',
};
this.values = {};
this.response = null;
this.responseError = null;
this.formRef = React.createRef();
this.tipRef = React.createRef();
this.codeError = false;
this.currentFormValue = {};
this.cancel = null;
this.cancelToken = this.hasRequestCancelCallback
? new CancelToken((c) => {
this.cancel = c;
})
: null;
this.init();
}
componentDidMount() {
try {
// this.updateDefaultValue();
this.updateState();
} catch (e) {
// eslint-disable-next-line no-console
console.log(e);
}
}
componentWillUnmount() {
this.unsubscribe && this.unsubscribe();
this.disposer && this.disposer();
this.unMountActions && this.unMountActions();
}
get path() {
const { location: { pathname = '' } = {} } = this.props;
return pathname || '';
}
get disableSubmit() {
return false;
}
get name() {
return '';
}
get title() {
return '';
}
get className() {
return '';
}
get prefix() {
return this.props.match.url;
}
get routing() {
return this.props.rootStore.routing;
}
get params() {
return this.props.match.params || {};
}
get location() {
return this.props.location || {};
}
get locationParams() {
return parse(this.location.search.slice(1));
}
get listUrl() {
return '';
}
get currentUser() {
const { user } = this.props.rootStore || {};
return user || {};
}
get isAdminPage() {
const { pathname = '' } = this.props.location || {};
return isAdminPage(pathname);
}
get hasAdminRole() {
return this.props.rootStore.hasAdminRole;
}
get currentProjectId() {
return this.props.rootStore.projectId;
}
get currentProjectName() {
return this.props.rootStore.projectName;
}
getRouteName(routeName) {
return this.isAdminPage ? `${routeName}Admin` : routeName;
}
getRoutePath(routeName, params = {}, query = {}) {
const realName = this.getRouteName(routeName);
return getPath({ key: realName, params, query });
}
getLinkRender(routeName, value, params = {}, query = {}) {
const realName = this.getRouteName(routeName);
return getLinkRender({ key: realName, params, query, value });
}
get isStep() {
return false;
}
get isModal() {
return false;
}
get labelCol() {
return {
xs: { span: 5 },
sm: { span: 3 },
};
}
get wrapperCol() {
return {
xs: { span: 10 },
sm: { span: 8 },
};
}
get defaultValue() {
return null;
}
get formDefaultValue() {
const { context = {} } = this.props;
const { defaultValue } = this;
return {
...defaultValue,
...context,
};
}
get okBtnText() {
return t('Confirm');
}
get instanceName() {
const { name } = this.values || {};
return name;
}
get successText() {
if (this.instanceName) {
return firstUpperCase(
t('{action} successfully, instance: {name}.', {
action: this.name.toLowerCase(),
name: this.instanceName,
})
);
}
return firstUpperCase(
t('{action} successfully.', {
action: this.name.toLowerCase(),
})
);
}
get errorText() {
if (this.instanceName) {
return t('Unable to {action}, instance: {name}.', {
action: this.name.toLowerCase(),
name: this.instanceName,
});
}
return t('Unable to {action}.', {
action: this.name.toLowerCase(),
});
}
get isSubmitting() {
const { isSubmitting = false } = this.state;
return isSubmitting;
// return (this.store && this.store.isSubmitting) || false;
}
get formItems() {
return [];
}
get validateMessages() {
return [];
}
get tips() {
return '';
}
get showNotice() {
return true;
}
get nameForStateUpdate() {
const typeList = ['radio', 'more'];
return this.formItems
.filter((it) => typeList.indexOf(it.type) >= 0)
.map((it) => it.name);
}
get hasRequestCancelCallback() {
return false;
}
get showQuota() {
return false;
}
get quotaInfo() {
return null;
}
getRightExtraSpan() {
return {
left: 18,
right: 6,
};
}
getSubmitData(data) {
return { ...data };
}
updateContext = (allFields) => {
const { updateContext } = this.props;
updateContext && updateContext(allFields);
};
unescape = (message) => unescapeHtml(message);
getFormInstance = () => this.formRef.current;
// eslint-disable-next-line no-unused-vars
onSubmit = (values) => Promise.resolve();
updateSubmitting = (value) => {
this.setState({
isSubmitting: value || false,
});
};
onOk = (values, containerProps, callback) => {
// eslint-disable-next-line no-console
console.log('onOk', values);
this.values = values;
if (this.codeError) {
return;
}
this.updateSubmitting(true);
if (!this.onSubmit) {
return callback(true, false);
}
const submitData = this.getSubmitData(values);
return this.onSubmit(submitData, containerProps).then(
(response) => {
this.updateSubmitting(false);
!this.isModal && this.routing.push(this.listUrl);
this.response = response;
if (callback && isFunction(callback)) {
callback(true, false);
}
if (response instanceof Array) {
const instanceNameArr = this.instanceName
? this.instanceName.split(', ')
: null;
const failedNames = response
.map((it, idx) => {
if (it.status === 'rejected') {
return {
reason: it.reason,
name: instanceNameArr ? instanceNameArr[idx] : '',
};
}
return null;
})
.filter((it) => !!it);
if (failedNames.length !== 0) {
failedNames.forEach((it) => {
const { response: { data } = {} } = it.reason;
this.showNotice &&
Notify.errorWithDetail(
data,
t('Unable to {action}, instance: {name}.', {
action: this.name.toLowerCase(),
name: it.name,
})
);
});
} else {
this.showNotice && Notify.success(this.successText);
}
} else {
this.showNotice && Notify.success(this.successText);
}
},
(err = {}) => {
this.updateSubmitting(false);
this.responseError = err;
const { response: { data } = {} } = err;
this.showNotice && Notify.errorWithDetail(data, this.errorText);
// eslint-disable-next-line no-console
console.log('err', err, data);
if (callback && isFunction(callback)) {
callback(false, true);
}
}
);
};
onCancel = () => {
if (this.isSubmitting && this.cancel) {
this.cancel();
Notify.success(t('Cancel upload successfully.'));
}
};
getChangedFieldsValue = (changedFields, name) => {
const value = changedFields[name];
if (isObject(value) && value.value) {
return value.value;
}
if (isObject(value) && value.selectedRows) {
return value.selectedRows[0];
}
return value;
};
// eslint-disable-next-line no-unused-vars
onValuesChange = (changedFields, allFields) => {};
// eslint-disable-next-line no-unused-vars
onValuesChangeForm = (changedFields, allFields) => {
// save linkage data to state
const newState = {};
this.currentFormValue = allFields;
this.nameForStateUpdate.forEach((name) => {
if (has(changedFields, name)) {
const value = this.getChangedFieldsValue(changedFields, name);
newState[name] = value;
}
});
if (!isEmpty(newState)) {
this.setState({
...newState,
});
}
this.onValuesChange(changedFields, allFields);
};
checkFormInput = (callback, failCallback) => {
this.formRef.current &&
this.formRef.current.validateFields().then(
(values) => {
callback && callback(values);
this.updateContext(values);
},
({ values, errorFields }) => {
if (errorFields && errorFields.length) {
failCallback && failCallback(values, errorFields);
} else {
// eslint-disable-next-line no-console
console.log('checkFormInput-catch', values, errorFields);
// callback && callback(values);
}
}
);
};
onClickSubmit = (callback, checkCallback, containerProps) => {
if (this.codeError) {
return;
}
this.checkFormInput((values) => {
checkCallback && checkCallback(values);
this.onOk(values, containerProps, callback);
});
};
onClickCancel = () => {
this.onCancel();
if (this.listUrl) {
this.routing.push(this.listUrl);
}
};
updateDefaultValue = () => {
this.resetFormValue();
this.updateContext(this.defaultValue);
};
resetFormValue = (fields) => {
if (this.formRef.current && this.formRef.current.resetFields) {
if (!fields) {
this.formRef.current.resetFields();
} else {
this.formRef.current.resetFields(fields);
}
}
};
updateFormValue = (key, value) => {
this.formRef.current &&
this.formRef.current.setFieldsValue({
[key]: value,
});
};
onUploadProgress = (progressEvent) => {
const { loaded, total } = progressEvent;
const percent = Math.floor((loaded / total) * 100);
this.setState({
percent,
});
};
getUploadRequestConf = () => {
return {
onUploadProgress: this.onUploadProgress,
cancelToken: this.cancelToken,
};
};
checkContextValue() {
const { context } = this.props;
const names = this.nameForStateUpdate;
if (isEmpty(context)) {
return false;
}
const item = names.find((name) => has(context, name));
return !!item;
}
updateState() {
// save linkage data to state
const { context } = this.props;
const names = this.nameForStateUpdate;
if (names.length === 0) {
return;
}
const newState = {};
if (this.checkContextValue()) {
names.forEach((name) => {
newState[name] = this.getChangedFieldsValue(context, name);
});
} else {
names.forEach((name) => {
newState[name] = this.getChangedFieldsValue(this.defaultValue, name);
});
}
this.setState({
...newState,
});
}
init() {
this.store = {};
}
renderTips() {
if (this.tips) {
return (
<div className={styles.tips} ref={this.tipRef} id="tips">
<InfoCircleOutlined className={styles['tips-icon']} />
{this.tips}
</div>
);
}
return null;
}
renderFooterLeft() {
return null;
}
renderFooter() {
if (this.isStep || this.isModal) {
return null;
}
const footerStyle = {};
if (this.tips) {
const height =
((document.getElementById('tips') || {}).clientHeight || 35) + 16;
footerStyle.bottom = height;
}
return (
<div className={styles.footer} style={footerStyle}>
<div className={styles['footer-left']}>{this.renderFooterLeft()}</div>
<div className={classnames(styles.btns, 'footer-btns')}>
<Button
className={styles.cancel}
onClick={this.onClickCancel}
loading={this.isSubmitting}
>
{t('Cancel')}
</Button>
<Button
disabled={this.disableSubmit}
type="primary"
className={styles.submit}
onClick={this.onClickSubmit}
loading={this.isSubmitting}
>
{this.okBtnText}
</Button>
</div>
</div>
);
}
renderFormItems() {
try {
return this.formItems.map((it, index) => {
const { name, display = true, ...rest } = it;
if (!display) {
return '';
}
this.codeError = false;
return (
<Col
span={24 / (it.colNum || 1)}
key={`form-item-col-${index}`}
id={`form-item-col-${name}`}
>
<FormItem
{...rest}
name={name}
key={`form-item-${index}`}
formref={this.formRef}
/>
</Col>
);
});
} catch (e) {
// eslint-disable-next-line no-console
console.log(e);
const name = 'error';
const index = 0;
const it = {
type: 'label',
label: t('Error'),
// if can't submit, go this way to not submit.
// example src/pages/network/containers/VPN/IKEPolicy/actions/Edit.js L60-71
content:
e.message === 'Can Not Submit'
? this.errorText
: t('Unable to render form'),
};
this.codeError = true;
return (
<Col
span={24 / (it.colNum || 1)}
key={`form-item-col-${index}`}
id={`form-item-col-${name}`}
>
<FormItem {...it} key={`form-item-${index}`} formref={this.formRef} />
</Col>
);
// return null;
}
}
renderForms() {
return (
<Form
ref={this.formRef}
labelCol={this.labelCol}
colon={false}
labelAlign="left"
wrapperCol={this.wrapperCol}
name={this.name}
// onFinish={this.onOk}
initialValues={this.formDefaultValue}
onValuesChange={this.onValuesChangeForm}
scrollToFirstError
>
<input type="password" hidden autoComplete="new-password" />
<Row>{this.renderFormItems()}</Row>
</Form>
);
}
renderAbortButton() {
if (!this.isSubmitting || this.isModal) {
return null;
}
return (
<Button className={styles.cancel} onClick={this.onClickCancel}>
{t('Abort Upload')}
</Button>
);
}
renderSubmittingTip() {
if (!this.hasRequestCancelCallback) {
return;
}
const { percent } = this.state;
return (
<div className={styles['submit-tip']}>
{t('Upload progress')}
<div className={styles['progress-wrapper']}>
<Progress percent={percent} size="small" />
</div>
{this.renderAbortButton()}
</div>
);
}
renderQuota() {
if (!this.showQuota) {
return null;
}
let props = {};
if (!this.quotaInfo || !this.quotaInfo.length) {
props.loading = true;
} else {
props = {
loading: false,
quotas: this.quotaInfo,
};
}
return <QuotaChart {...props} />;
}
renderRightTopExtra() {
if (this.isModal) {
return null;
}
const content = this.renderQuota();
if (!content) {
return null;
}
const checkValue = JSON.stringify(this.quotaInfo);
return (
<div className={styles['right-top-extra-wrapper']}>
<InfoButton content={content} checkValue={checkValue} />
</div>
);
}
renderModalRightExtra() {
if (!this.isModal) {
return null;
}
const content = this.renderQuota();
if (!content) {
return null;
}
return <div className={styles['modal-right-extra-wrapper']}>{content}</div>;
}
render() {
const wrapperPadding =
this.listUrl || this.isStep || (this.isModal && this.tips)
? styles['wrapper-page-padding']
: '';
const tips = this.renderTips();
const formStyle = {};
if ((this.listUrl || this.isStep) && this.tips && this.tipRef.current) {
if (this.isStep) {
const tipHeight = this.tipRef.current.clientHeight + 219;
formStyle.height = `calc(100vh - ${tipHeight}px)`;
} else {
const tipHeight = this.tipRef.current.clientHeight + 66;
formStyle.height = `calc(100% - ${tipHeight}px)`;
}
}
const formDiv = (
<Spin spinning={this.isSubmitting} tip={this.renderSubmittingTip()}>
{this.renderRightTopExtra()}
<div className={classnames(styles.form, 'sl-form')} style={formStyle}>
{this.renderForms()}
</div>
{this.renderFooter()}
</Spin>
);
const onlyForm = !this.isModal || (this.isModal && !this.showQuota);
const { left, right } = this.getRightExtraSpan();
const modalInner =
this.isModal && !onlyForm ? (
<Row justify="space-between" align="top">
<Col span={left}>{formDiv}</Col>
<Col span={right}>{this.renderModalRightExtra()}</Col>
</Row>
) : null;
return (
<div
className={classnames(styles.wrapper, wrapperPadding, this.className)}
>
{tips}
{onlyForm && formDiv}
{modalInner}
</div>
);
}
}