361 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Vue
		
	
	
			
		
		
	
	
			361 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Vue
		
	
	
| import intersperse from 'intersperse'
 | ||
| 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, isValidElement } from '../_util/props-util'
 | ||
| import getTransitionProps from '../_util/getTransitionProps'
 | ||
| import BaseMixin from '../_util/BaseMixin'
 | ||
| import { cloneElement } from '../_util/vnode'
 | ||
| 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,
 | ||
|   fieldDecoratorId: PropTypes.string,
 | ||
|   fieldDecoratorOptions: PropTypes.object,
 | ||
| }
 | ||
| 
 | ||
| export default {
 | ||
|   name: 'AFormItem',
 | ||
|   __ANT_FORM_ITEM: true,
 | ||
|   mixins: [BaseMixin],
 | ||
|   props: initDefaultProps(FormItemProps, {
 | ||
|     hasFeedback: false,
 | ||
|     prefixCls: 'ant-form',
 | ||
|     colon: true,
 | ||
|   }),
 | ||
|   inject: {
 | ||
|     FormProps: { default: {}},
 | ||
|     decoratorFormProps: { default: {}},
 | ||
|   },
 | ||
|   data () {
 | ||
|     return { helpShow: false }
 | ||
|   },
 | ||
|   mounted () {
 | ||
|     warning(
 | ||
|       this.getControls(this.slotDefault, true).length <= 1,
 | ||
|       '`Form.Item` cannot generate `validateStatus` and `help` automatically, ' +
 | ||
|       'while there are more than one `getFieldDecorator` in it.',
 | ||
|     )
 | ||
|   },
 | ||
|   methods: {
 | ||
|     getHelpMessage () {
 | ||
|       const help = getComponentFromProp(this, 'help')
 | ||
|       const onlyControl = this.getOnlyControl()
 | ||
|       if (help === undefined && onlyControl) {
 | ||
|         const errors = this.getField().errors
 | ||
|         if (errors) {
 | ||
|           return intersperse(errors.map((e, index) => {
 | ||
|             return isValidElement(e.message) ? cloneElement(e.message, { key: index }) : e.message
 | ||
|           }), ' ')
 | ||
|         } else {
 | ||
|           return ''
 | ||
|         }
 | ||
|       }
 | ||
| 
 | ||
|       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 child.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.slotDefault, false)[0]
 | ||
|       return child !== undefined ? child : null
 | ||
|     },
 | ||
| 
 | ||
|     getChildAttr (prop) {
 | ||
|       const child = this.getOnlyControl()
 | ||
|       let data = {}
 | ||
|       if (!child) {
 | ||
|         return undefined
 | ||
|       }
 | ||
|       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.helpShow = helpShow
 | ||
|       if (!helpShow) {
 | ||
|         this.$forceUpdate()
 | ||
|       }
 | ||
|     },
 | ||
| 
 | ||
|     renderHelp () {
 | ||
|       const prefixCls = this.prefixCls
 | ||
|       const help = this.getHelpMessage()
 | ||
|       const children = help ? (
 | ||
|         <div class={`${prefixCls}-explain`} key='help'>
 | ||
|           {help}
 | ||
|         </div>
 | ||
|       ) : null
 | ||
|       if (children) {
 | ||
|         this.helpShow = !!children
 | ||
|       }
 | ||
|       const transitionProps = getTransitionProps('show-help', {
 | ||
|         afterEnter: () => this.onHelpAnimEnd('help', true),
 | ||
|         afterLeave: () => this.onHelpAnimEnd('help', false),
 | ||
|       })
 | ||
|       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
 | ||
|             for={id || this.getId()}
 | ||
|             class={labelClassName}
 | ||
|             title={typeof label === 'string' ? label : ''}
 | ||
|             onClick={this.onLabelClick}
 | ||
|           >
 | ||
|             {labelChildren}
 | ||
|           </label>
 | ||
|         </Col>
 | ||
|       ) : null
 | ||
|     },
 | ||
|     renderChildren () {
 | ||
|       return [
 | ||
|         this.renderLabel(),
 | ||
|         this.renderWrapper(
 | ||
|           this.renderValidateWrapper(
 | ||
|             this.slotDefault,
 | ||
|             this.renderHelp(),
 | ||
|             this.renderExtra(),
 | ||
|           ),
 | ||
|         ),
 | ||
|       ]
 | ||
|     },
 | ||
|     renderFormItem (children) {
 | ||
|       const props = this.$props
 | ||
|       const prefixCls = props.prefixCls
 | ||
|       const itemClassName = {
 | ||
|         [`${prefixCls}-item`]: true,
 | ||
|         [`${prefixCls}-item-with-help`]: this.helpShow,
 | ||
|         [`${prefixCls}-item-no-colon`]: !props.colon,
 | ||
|       }
 | ||
| 
 | ||
|       return (
 | ||
|         <Row class={classNames(itemClassName)}>
 | ||
|           {children}
 | ||
|         </Row>
 | ||
|       )
 | ||
|     },
 | ||
|   },
 | ||
| 
 | ||
|   render () {
 | ||
|     const { $slots, decoratorFormProps, fieldDecoratorId, fieldDecoratorOptions = {}} = this
 | ||
|     const child = filterEmpty($slots.default || [])
 | ||
|     if (decoratorFormProps.form && fieldDecoratorId && child.length) {
 | ||
|       const getFieldDecorator = decoratorFormProps.form.getFieldDecorator
 | ||
|       child[0] = getFieldDecorator(fieldDecoratorId, fieldDecoratorOptions)(child[0])
 | ||
|       warning(
 | ||
|         !(child.length > 1),
 | ||
|         '`autoFormCreate` just `decorator` then first children. but you can use JSX to support multiple children',
 | ||
|       )
 | ||
|     }
 | ||
| 
 | ||
|     this.slotDefault = child
 | ||
|     const children = this.renderChildren()
 | ||
|     return this.renderFormItem(children)
 | ||
|   },
 | ||
| }
 |