ant-design/components/form/FormItem.tsx

311 lines
8.4 KiB
TypeScript
Raw Normal View History

2016-09-21 11:54:53 +08:00
import React from 'react';
import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import PureRenderMixin from 'rc-util/lib/PureRenderMixin';
import Row from '../grid/row';
import Col, { ColProps } from '../grid/col';
2016-08-22 17:26:14 +08:00
import { WrappedFormUtils } from './Form';
2016-07-07 16:59:47 +08:00
import { FIELD_META_PROP } from './constants';
import warning from '../_util/warning';
2015-10-09 13:53:04 +08:00
export interface FormItemProps {
prefixCls?: string;
2016-08-22 17:26:14 +08:00
id?: string;
2016-10-24 12:04:26 +08:00
label?: React.ReactNode;
labelCol?: ColProps;
wrapperCol?: ColProps;
help?: React.ReactNode;
2017-02-27 10:20:46 +08:00
extra?: React.ReactNode;
validateStatus?: 'success' | 'warning' | 'error' | 'validating';
hasFeedback?: boolean;
className?: string;
required?: boolean;
style?: React.CSSProperties;
2016-08-22 17:26:14 +08:00
colon?: boolean;
}
export interface FormItemContext {
form: WrappedFormUtils;
vertical: boolean;
}
export default class FormItem extends React.Component<FormItemProps, any> {
static defaultProps = {
hasFeedback: false,
prefixCls: 'ant-form',
2016-07-21 15:51:37 +08:00
colon: true,
2016-07-13 11:14:24 +08:00
};
static propTypes = {
prefixCls: PropTypes.string,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
labelCol: PropTypes.object,
help: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]),
validateStatus: PropTypes.oneOf(['', 'success', 'warning', 'error', 'validating']),
hasFeedback: PropTypes.bool,
wrapperCol: PropTypes.object,
className: PropTypes.string,
id: PropTypes.string,
children: PropTypes.node,
colon: PropTypes.bool,
2016-07-13 11:14:24 +08:00
};
static contextTypes = {
form: PropTypes.object,
vertical: PropTypes.bool,
2016-07-13 11:14:24 +08:00
};
2016-08-22 17:26:14 +08:00
context: FormItemContext;
componentDidMount() {
warning(
this.getControls(this.props.children, true).length <= 1,
'`Form.Item` cannot generate `validateStatus` and `help` automatically, ' +
'while there are more than one `getFieldDecorator` in it.',
);
}
shouldComponentUpdate(...args) {
return PureRenderMixin.shouldComponentUpdate.apply(this, args);
}
2016-01-21 16:23:35 +08:00
getHelpMsg() {
const context = this.context;
const props = this.props;
if (props.help === undefined && context.form) {
return this.getId() ? (context.form.getFieldError(this.getId()) || []).join(', ') : '';
2016-01-21 16:23:35 +08:00
}
return props.help;
}
getControls(children, recursively: boolean) {
2016-10-24 12:04:26 +08:00
let controls: React.ReactElement<any>[] = [];
const childrenArray = React.Children.toArray(children);
for (let i = 0; i < childrenArray.length; i++) {
if (!recursively && controls.length > 0) {
break;
}
const child = childrenArray[i] as React.ReactElement<any>;
if (child.type &&
(child.type as any === FormItem || (child.type as any).displayName === 'FormItem')) {
continue;
}
if (!child.props) {
continue;
}
if (FIELD_META_PROP in child.props) {
controls.push(child);
} else if (child.props.children) {
controls = controls.concat(this.getControls(child.props.children, recursively));
}
}
return controls;
}
getOnlyControl() {
const child = this.getControls(this.props.children, false)[0];
return child !== undefined ? child : null;
}
getChildProp(prop) {
2016-08-24 16:09:55 +08:00
const child = this.getOnlyControl() as React.ReactElement<any>;
return child && child.props && child.props[prop];
}
2016-01-28 21:43:45 +08:00
getId() {
return this.getChildProp('id');
2016-01-28 21:43:45 +08:00
}
2016-02-01 10:23:06 +08:00
getMeta() {
2016-07-07 16:59:47 +08:00
return this.getChildProp(FIELD_META_PROP);
2016-02-01 10:23:06 +08:00
}
2015-10-09 13:53:04 +08:00
renderHelp() {
const prefixCls = this.props.prefixCls;
2016-01-21 16:23:35 +08:00
const help = this.getHelpMsg();
2017-09-22 21:47:28 +08:00
return help ? (
<div className={`${prefixCls}-explain`} key="help">
{help}
</div>
) : null;
2015-10-09 13:53:04 +08:00
}
2016-04-25 16:25:57 +08:00
renderExtra() {
const { prefixCls, extra } = this.props;
return extra ? (
<div className={`${prefixCls}-extra`}>{extra}</div>
) : null;
2016-04-25 16:25:57 +08:00
}
2016-01-21 16:23:35 +08:00
getValidateStatus() {
const { isFieldValidating, getFieldError, getFieldValue } = this.context.form;
const fieldId = this.getId();
if (!fieldId) {
return '';
}
if (isFieldValidating(fieldId)) {
2016-01-21 16:23:35 +08:00
return 'validating';
}
if (!!getFieldError(fieldId)) {
2016-01-21 16:23:35 +08:00
return 'error';
}
const fieldValue = getFieldValue(fieldId);
if (fieldValue !== undefined && fieldValue !== null && fieldValue !== '') {
2016-01-21 16:23:35 +08:00
return 'success';
}
return '';
2016-01-21 16:23:35 +08:00
}
2016-01-26 15:40:47 +08:00
renderValidateWrapper(c1, c2, c3) {
let classes = '';
2016-01-21 16:23:35 +08:00
const form = this.context.form;
const props = this.props;
const validateStatus = (props.validateStatus === undefined && form) ?
2016-01-28 21:43:45 +08:00
this.getValidateStatus() :
props.validateStatus;
2016-01-21 16:23:35 +08:00
if (validateStatus) {
classes = classNames(
2015-10-09 13:53:04 +08:00
{
'has-feedback': props.hasFeedback || validateStatus === 'validating',
2016-01-21 16:23:35 +08:00
'has-success': validateStatus === 'success',
'has-warning': validateStatus === 'warning',
'has-error': validateStatus === 'error',
'is-validating': validateStatus === 'validating',
},
2015-10-09 13:53:04 +08:00
);
}
return (
<div className={`${this.props.prefixCls}-item-control ${classes}`}>
2016-01-26 15:38:58 +08:00
{c1}{c2}{c3}
</div>
);
2015-10-09 13:53:04 +08:00
}
renderWrapper(children) {
const { prefixCls, wrapperCol } = this.props;
const className = classNames(
`${prefixCls}-item-control-wrapper`,
wrapperCol && wrapperCol.className,
);
return (
<Col {...wrapperCol} className={className} key="wrapper">
2015-10-09 13:53:04 +08:00
{children}
</Col>
);
2015-10-09 13:53:04 +08:00
}
2016-01-21 16:23:35 +08:00
isRequired() {
2017-01-06 01:33:09 +08:00
const { required } = this.props;
if (required !== undefined) {
return required;
}
2016-01-28 21:43:45 +08:00
if (this.context.form) {
2016-02-01 10:23:06 +08:00
const meta = this.getMeta() || {};
const validate = (meta.validate || []);
return validate.filter((item) => !!item.rules).some((item) => {
2016-01-28 21:43:45 +08:00
return item.rules.some((rule) => rule.required);
});
}
return false;
2016-01-21 16:23:35 +08:00
}
// Resolve duplicated ids bug between different forms
// https://github.com/ant-design/ant-design/issues/7351
onLabelClick = () => {
const id = this.props.id || this.getId();
if (!id) {
return;
}
const controls = document.querySelectorAll(`[id="${id}"]`);
2017-11-27 11:01:06 +08:00
if (controls.length !== 1) {
const control = findDOMNode(this).querySelector(`[id="${id}"]`) as HTMLElement;
if (control && control.focus) {
control.focus();
}
}
}
2015-10-09 13:53:04 +08:00
renderLabel() {
const { prefixCls, label, labelCol, colon, id } = this.props;
const context = this.context;
2017-01-06 01:33:09 +08:00
const required = this.isRequired();
2015-10-09 13:53:04 +08:00
const labelColClassName = classNames(
`${prefixCls}-item-label`,
labelCol && labelCol.className,
);
const labelClassName = classNames({
2017-02-25 18:46:24 +08:00
[`${prefixCls}-item-required`]: required,
});
2017-02-25 18:46:24 +08:00
let labelChildren = label;
// Keep label is original where there should have no colon
2017-02-25 18:46:24 +08:00
const haveColon = colon && !context.vertical;
// Remove duplicated user input colon
if (haveColon && typeof label === 'string' && (label as string).trim() !== '') {
2017-02-25 18:46:24 +08:00
labelChildren = (label as string).replace(/[|:]\s*$/, '');
}
2017-02-25 18:46:24 +08:00
return label ? (
<Col {...labelCol} className={labelColClassName} key="label">
2017-02-25 18:46:24 +08:00
<label
htmlFor={id || this.getId()}
className={labelClassName}
2017-02-25 18:48:44 +08:00
title={typeof label === 'string' ? label : ''}
onClick={this.onLabelClick}
2017-02-25 18:46:24 +08:00
>
{labelChildren}
</label>
</Col>
2015-11-03 13:50:36 +08:00
) : null;
2015-10-09 13:53:04 +08:00
}
renderChildren() {
2016-01-21 16:23:35 +08:00
const props = this.props;
const children = React.Children.map(props.children as React.ReactNode, (child: React.ReactElement<any>) => {
2016-03-01 17:52:07 +08:00
if (child && typeof child.type === 'function' && !child.props.size) {
2016-02-01 10:23:06 +08:00
return React.cloneElement(child, { size: 'large' });
}
2016-02-01 10:23:06 +08:00
return child;
2016-01-28 21:43:45 +08:00
});
2015-10-09 13:53:04 +08:00
return [
this.renderLabel(),
this.renderWrapper(
this.renderValidateWrapper(
2016-01-21 16:23:35 +08:00
children,
2016-01-26 15:38:58 +08:00
this.renderHelp(),
this.renderExtra(),
),
2015-10-09 13:53:04 +08:00
),
];
}
renderFormItem(children) {
const props = this.props;
const prefixCls = props.prefixCls;
const style = props.style;
2015-10-09 13:53:04 +08:00
const itemClassName = {
[`${prefixCls}-item`]: true,
[`${prefixCls}-item-with-help`]: !!this.getHelpMsg(),
2016-07-21 15:51:37 +08:00
[`${prefixCls}-item-no-colon`]: !props.colon,
2016-01-12 14:24:42 +08:00
[`${props.className}`]: !!props.className,
2015-10-09 13:53:04 +08:00
};
return (
<Row className={classNames(itemClassName)} style={style}>
2015-10-09 13:53:04 +08:00
{children}
</Row>
2015-10-09 13:53:04 +08:00
);
}
render() {
const children = this.renderChildren();
return this.renderFormItem(children);
}
}