ant-design-vue/components/form/FormItem.jsx

334 lines
9.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import PropTypes from '../_util/vue-types'
import classNames from 'classnames'
import Row from '../grid/Row'
import Col, { ColProps } from '../grid/Col'
import warning from '../_util/warning'
import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants'
import { initDefaultProps, getComponentFromProp, filterEmpty, getSlotOptions, getSlots } from '../_util/props-util'
import getTransitionProps from '../_util/getTransitionProps'
import BaseMixin from '../_util/BaseMixin'
export const FormItemProps = {
id: PropTypes.string,
prefixCls: PropTypes.string,
label: PropTypes.any,
labelCol: PropTypes.shape(ColProps).loose,
wrapperCol: PropTypes.shape(ColProps).loose,
help: PropTypes.any,
extra: PropTypes.any,
validateStatus: PropTypes.oneOf(['', 'success', 'warning', 'error', 'validating']),
hasFeedback: PropTypes.bool,
required: PropTypes.bool,
colon: PropTypes.bool,
}
export default {
name: 'AFormItem',
__ANT_FORM_ITEM: true,
mixins: [BaseMixin],
props: initDefaultProps(FormItemProps, {
hasFeedback: false,
prefixCls: 'ant-form',
colon: true,
}),
inject: {
FormProps: { default: {}},
},
data () {
return { helpShow: false }
},
mounted () {
warning(
this.getControls(this.$slots.default, true).length <= 1,
'`Form.Item` cannot generate `validateStatus` and `help` automatically, ' +
'while there are more than one `getFieldDecorator` in it.',
)
},
// shouldComponentUpdate(...args: any[]) {
// return PureRenderMixin.shouldComponentUpdate.apply(this, args);
// }
methods: {
getHelpMsg () {
const help = getComponentFromProp(this, 'help')
const onlyControl = this.getOnlyControl()
if (help === undefined && onlyControl) {
const errors = this.getField().errors
return errors ? errors.map((e) => e.message).join(', ') : ''
}
return help
},
getControls (childrenArray = [], recursively) {
let controls = []
for (let i = 0; i < childrenArray.length; i++) {
if (!recursively && controls.length > 0) {
break
}
const child = childrenArray[i]
if (!child.tag && child.text.trim() === '') {
continue
}
if (getSlotOptions(child).__ANT_FORM_ITEM) {
continue
}
const attrs = child.data && child.data.attrs
if (!attrs) {
continue
}
const slots = getSlots(child)
if (FIELD_META_PROP in attrs) { // And means FIELD_DATA_PROP in chidl.props, too.
controls.push(child)
} else if (slots.default) {
controls = controls.concat(this.getControls(slots.default, recursively))
}
}
return controls
},
getOnlyControl () {
const child = this.getControls(this.$slots.default, false)[0]
return child !== undefined ? child : null
},
getChildAttr (prop) {
const child = this.getOnlyControl()
let data = {}
if (child.data) {
data = child.data
} else if (child.$vnode && child.$vnode.data) {
data = child.$vnode.data
}
return data[prop] || data.attrs[prop]
},
getId () {
return this.getChildAttr('id')
},
getMeta () {
return this.getChildAttr(FIELD_META_PROP)
},
getField () {
return this.getChildAttr(FIELD_DATA_PROP)
},
onHelpAnimEnd (_key, helpShow) {
this.setState({ helpShow })
},
renderHelp () {
const prefixCls = this.prefixCls
const help = this.getHelpMsg()
const children = help ? (
<div class={`${prefixCls}-explain`} key='help'>
{help}
</div>
) : null
const transitionProps = getTransitionProps('show-help', {
afterLeave: this.onHelpAnimEnd,
})
return (
<transition
{...transitionProps}
key='help'
>
{children}
</transition>
)
},
renderExtra () {
const { prefixCls } = this
const extra = getComponentFromProp(this, 'extra')
return extra ? (
<div class={`${prefixCls}-extra`}>{extra}</div>
) : null
},
getValidateStatus () {
const onlyControl = this.getOnlyControl()
if (!onlyControl) {
return ''
}
const field = this.getField()
if (field.validating) {
return 'validating'
}
if (field.errors) {
return 'error'
}
const fieldValue = 'value' in field ? field.value : this.getMeta().initialValue
if (fieldValue !== undefined && fieldValue !== null && fieldValue !== '') {
return 'success'
}
return ''
},
renderValidateWrapper (c1, c2, c3) {
const props = this.$props
const onlyControl = this.getOnlyControl
const validateStatus = (props.validateStatus === undefined && onlyControl)
? this.getValidateStatus()
: props.validateStatus
let classes = `${props.prefixCls}-item-control`
if (validateStatus) {
classes = classNames(`${props.prefixCls}-item-control`, {
'has-feedback': props.hasFeedback || validateStatus === 'validating',
'has-success': validateStatus === 'success',
'has-warning': validateStatus === 'warning',
'has-error': validateStatus === 'error',
'is-validating': validateStatus === 'validating',
})
}
return (
<div class={classes}>
<span class={`${props.prefixCls}-item-children`}>{c1}</span>
{c2}{c3}
</div>
)
},
renderWrapper (children) {
const { prefixCls, wrapperCol = {}} = this
const { class: cls, style, id, on, ...restProps } = wrapperCol
const className = classNames(
`${prefixCls}-item-control-wrapper`,
cls,
)
const colProps = {
props: restProps,
class: className,
key: 'wrapper',
style,
id,
on,
}
return (
<Col {...colProps}>
{children}
</Col>
)
},
isRequired () {
const { required } = this
if (required !== undefined) {
return required
}
if (this.getOnlyControl()) {
const meta = this.getMeta() || {}
const validate = meta.validate || []
return validate.filter((item) => !!item.rules).some((item) => {
return item.rules.some((rule) => rule.required)
})
}
return false
},
// Resolve duplicated ids bug between different forms
// https://github.com/ant-design/ant-design/issues/7351
onLabelClick (e) {
const label = getComponentFromProp(this, 'label')
const id = this.id || this.getId()
if (!id) {
return
}
const controls = document.querySelectorAll(`[id="${id}"]`)
if (controls.length !== 1) {
// Only prevent in default situation
// Avoid preventing event in `label={<a href="xx">link</a>}``
if (typeof label === 'string') {
e.preventDefault()
}
const control = this.$el.querySelector(`[id="${id}"]`)
if (control && control.focus) {
control.focus()
}
}
},
renderLabel () {
const { prefixCls, labelCol = {}, colon, id } = this
const label = getComponentFromProp(this, 'label')
const required = this.isRequired()
const { class: labelColClass, style: labelColStyle, id: labelColId, on, ...restProps } = labelCol
const labelColClassName = classNames(
`${prefixCls}-item-label`,
labelColClass,
)
const labelClassName = classNames({
[`${prefixCls}-item-required`]: required,
})
let labelChildren = label
// Keep label is original where there should have no colon
const haveColon = colon && this.FormProps.layout !== 'vertical'
// Remove duplicated user input colon
if (haveColon && typeof label === 'string' && label.trim() !== '') {
labelChildren = label.replace(/[|:]\s*$/, '')
}
const colProps = {
props: restProps,
class: labelColClassName,
key: 'label',
style: labelColStyle,
id: labelColId,
on,
}
return label ? (
<Col {...colProps} >
<label
htmlFor={id || this.getId()}
class={labelClassName}
title={typeof label === 'string' ? label : ''}
onClick={this.onLabelClick}
>
{labelChildren}
</label>
</Col>
) : null
},
renderChildren () {
const { $slots } = this
return [
this.renderLabel(),
this.renderWrapper(
this.renderValidateWrapper(
filterEmpty($slots.default || []),
this.renderHelp(),
this.renderExtra(),
),
),
]
},
renderFormItem (children) {
const props = this.$props
const prefixCls = props.prefixCls
const itemClassName = {
[`${prefixCls}-item`]: true,
[`${prefixCls}-item-with-help`]: !!this.getHelpMsg() || this.helpShow,
[`${prefixCls}-item-no-colon`]: !props.colon,
}
return (
<Row class={classNames(itemClassName)}>
{children}
</Row>
)
},
},
render () {
const children = this.renderChildren()
return this.renderFormItem(children)
},
}