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

350 lines
9.8 KiB
Vue
Raw Normal View History

2018-05-05 09:00:51 +00:00
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'
2018-06-24 13:18:45 +00:00
import { initDefaultProps, getComponentFromProp, filterEmpty, getSlotOptions, getSlots } from '../_util/props-util'
2018-05-05 09:00:51 +00:00
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,
2018-06-22 13:35:14 +00:00
fieldDecoratorId: PropTypes.string,
fieldDecoratorOptions: PropTypes.object,
2018-05-05 09:00:51 +00:00
}
export default {
name: 'AFormItem',
__ANT_FORM_ITEM: true,
mixins: [BaseMixin],
props: initDefaultProps(FormItemProps, {
hasFeedback: false,
prefixCls: 'ant-form',
colon: true,
}),
inject: {
FormProps: { default: {}},
2018-06-23 09:17:45 +00:00
decoratorFormProps: { default: {}},
2018-05-05 09:00:51 +00:00
},
data () {
return { helpShow: false }
},
mounted () {
warning(
2018-06-23 09:17:45 +00:00
this.getControls(this.slotDefault, true).length <= 1,
2018-05-05 09:00:51 +00:00
'`Form.Item` cannot generate `validateStatus` and `help` automatically, ' +
'while there are more than one `getFieldDecorator` in it.',
)
},
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 () {
2018-06-22 13:35:14 +00:00
const child = this.getControls(this.slotDefault, false)[0]
2018-05-05 09:00:51 +00:00
return child !== undefined ? child : null
},
getChildAttr (prop) {
const child = this.getOnlyControl()
let data = {}
2018-05-06 10:32:40 +00:00
if (!child) {
return undefined
}
2018-05-05 09:00:51 +00:00
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)
},
2018-05-05 13:38:03 +00:00
onHelpAnimEnd () {
this.setState({ helpShow: false })
2018-05-05 09:00:51 +00:00
},
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,
})
2018-05-05 13:38:03 +00:00
if (children) {
this.setState({ helpShow: true })
}
2018-05-05 09:00:51 +00:00
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
2018-05-05 13:38:03 +00:00
for={id || this.getId()}
2018-05-05 09:00:51 +00:00
class={labelClassName}
title={typeof label === 'string' ? label : ''}
onClick={this.onLabelClick}
>
{labelChildren}
</label>
</Col>
) : null
},
renderChildren () {
return [
this.renderLabel(),
this.renderWrapper(
this.renderValidateWrapper(
2018-06-22 13:35:14 +00:00
this.slotDefault,
2018-05-05 09:00:51 +00:00
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 () {
2018-06-23 09:17:45 +00:00
const { $slots, decoratorFormProps, fieldDecoratorId, fieldDecoratorOptions = {}} = this
2018-06-22 13:35:14 +00:00
const child = filterEmpty($slots.default || [])
2018-06-23 09:17:45 +00:00
if (decoratorFormProps.form && fieldDecoratorId && child.length) {
const getFieldDecorator = decoratorFormProps.form.getFieldDecorator
2018-06-22 13:35:14 +00:00
child[0] = getFieldDecorator(fieldDecoratorId, fieldDecoratorOptions)(child[0])
2018-06-24 13:18:45 +00:00
warning(
!(child.length > 1),
'`autoFormCreate` just `decorator` then first children. but you can use JSX to support multiple children',
)
2018-06-22 13:35:14 +00:00
}
2018-06-24 13:18:45 +00:00
2018-06-22 13:35:14 +00:00
this.slotDefault = child
2018-05-05 09:00:51 +00:00
const children = this.renderChildren()
return this.renderFormItem(children)
},
}