feat: update form

pull/291/head 1.1.10-beta
tangjinzhou 2018-12-03 21:33:09 +08:00
parent 69a9649e92
commit 91251798b2
12 changed files with 145 additions and 85 deletions

View File

@ -0,0 +1,7 @@
export default {
// just for tag
install: (Vue, options) => {
Vue.directive('decorator', {
})
},
}

View File

@ -49,7 +49,7 @@ const getSlots = (ele) => {
if (ele.$vnode) { if (ele.$vnode) {
componentOptions = ele.$vnode.componentOptions || {} componentOptions = ele.$vnode.componentOptions || {}
} }
const children = componentOptions.children || [] const children = ele.children || componentOptions.children || []
const slots = {} const slots = {}
children.forEach(child => { children.forEach(child => {
const name = (child.data && child.data.slot) || 'default' const name = (child.data && child.data.slot) || 'default'

View File

@ -1,6 +1,8 @@
import PropTypes from '../_util/vue-types' import PropTypes from '../_util/vue-types'
import classNames from 'classnames' import classNames from 'classnames'
import Vue from 'vue'
import isRegExp from 'lodash/isRegExp' import isRegExp from 'lodash/isRegExp'
import warning from '../_util/warning'
import createDOMForm from '../vc-form/src/createDOMForm' import createDOMForm from '../vc-form/src/createDOMForm'
import createFormField from '../vc-form/src/createFormField' import createFormField from '../vc-form/src/createFormField'
import FormItem from './FormItem' import FormItem from './FormItem'
@ -54,7 +56,7 @@ export const WrappedFormUtils = {
export const FormProps = { export const FormProps = {
layout: PropTypes.oneOf(['horizontal', 'inline', 'vertical']), layout: PropTypes.oneOf(['horizontal', 'inline', 'vertical']),
form: PropTypes.shape(WrappedFormUtils).loose, form: PropTypes.object,
// onSubmit: React.FormEventHandler<any>; // onSubmit: React.FormEventHandler<any>;
prefixCls: PropTypes.string, prefixCls: PropTypes.string,
hideRequiredMark: PropTypes.bool, hideRequiredMark: PropTypes.bool,
@ -110,29 +112,13 @@ export const ValidationRule = {
// validateFirst?: boolean; // validateFirst?: boolean;
// }; // };
export default { const Form = {
name: 'AForm', name: 'AForm',
props: initDefaultProps(FormProps, { props: initDefaultProps(FormProps, {
prefixCls: 'ant-form', prefixCls: 'ant-form',
layout: 'horizontal', layout: 'horizontal',
hideRequiredMark: false, hideRequiredMark: false,
}), }),
// static defaultProps = {
// prefixCls: 'ant-form',
// layout: 'horizontal',
// hideRequiredMark: false,
// onSubmit (e) {
// e.preventDefault()
// },
// };
// static propTypes = {
// prefixCls: PropTypes.string,
// layout: PropTypes.oneOf(['horizontal', 'inline', 'vertical']),
// children: PropTypes.any,
// onSubmit: PropTypes.func,
// hideRequiredMark: PropTypes.bool,
// };
Item: FormItem, Item: FormItem,
@ -146,11 +132,19 @@ export default {
fieldDataProp: FIELD_DATA_PROP, fieldDataProp: FIELD_DATA_PROP,
}) })
}, },
createForm (context, options = {}) {
return new Vue(Form.create({ ...options, templateContext: context })())
},
provide () { provide () {
return { return {
FormProps: this.$props, FormProps: this.$props,
} }
}, },
watch: {
form () {
this.$forceUpdate()
},
},
methods: { methods: {
onSubmit (e) { onSubmit (e) {
const { $listeners } = this const { $listeners } = this
@ -174,6 +168,10 @@ export default {
[`${prefixCls}-hide-required-mark`]: hideRequiredMark, [`${prefixCls}-hide-required-mark`]: hideRequiredMark,
}) })
if (autoFormCreate) { if (autoFormCreate) {
warning(
false,
'`autoFormCreate` is deprecated. please use `form` instead.'
)
const DomForm = this.DomForm || createDOMForm({ const DomForm = this.DomForm || createDOMForm({
fieldNameProp: 'id', fieldNameProp: 'id',
...options, ...options,
@ -214,3 +212,5 @@ export default {
return <form onSubmit={onSubmit} class={formClassName}>{$slots.default}</form> return <form onSubmit={onSubmit} class={formClassName}>{$slots.default}</form>
}, },
} }
export default Form

View File

@ -1,14 +1,15 @@
import intersperse from 'intersperse' import intersperse from 'intersperse'
import PropTypes from '../_util/vue-types' import PropTypes from '../_util/vue-types'
import classNames from 'classnames' import classNames from 'classnames'
import find from 'lodash/find'
import Row from '../grid/Row' import Row from '../grid/Row'
import Col, { ColProps } from '../grid/Col' import Col, { ColProps } from '../grid/Col'
import warning from '../_util/warning' import warning from '../_util/warning'
import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants' import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants'
import { initDefaultProps, getComponentFromProp, filterEmpty, getSlotOptions, getSlots, isValidElement } from '../_util/props-util' import { initDefaultProps, getComponentFromProp, filterEmpty, getSlotOptions, isValidElement, getSlots } from '../_util/props-util'
import getTransitionProps from '../_util/getTransitionProps' import getTransitionProps from '../_util/getTransitionProps'
import BaseMixin from '../_util/BaseMixin' import BaseMixin from '../_util/BaseMixin'
import { cloneElement } from '../_util/vnode' import { cloneElement, cloneVNodes } from '../_util/vnode'
export const FormItemProps = { export const FormItemProps = {
id: PropTypes.string, id: PropTypes.string,
prefixCls: PropTypes.string, prefixCls: PropTypes.string,
@ -47,6 +48,10 @@ export default {
'`Form.Item` cannot generate `validateStatus` and `help` automatically, ' + '`Form.Item` cannot generate `validateStatus` and `help` automatically, ' +
'while there are more than one `getFieldDecorator` in it.', 'while there are more than one `getFieldDecorator` in it.',
) )
warning(
!this.fieldDecoratorId,
'`fieldDecoratorId` is deprecated. please use `v-decorator={id, options}` instead.'
)
}, },
methods: { methods: {
getHelpMessage () { getHelpMessage () {
@ -81,11 +86,8 @@ export default {
if (getSlotOptions(child).__ANT_FORM_ITEM) { if (getSlotOptions(child).__ANT_FORM_ITEM) {
continue continue
} }
const attrs = child.data && child.data.attrs
if (!attrs) {
continue
}
const slots = getSlots(child) const slots = getSlots(child)
const attrs = child.data && child.data.attrs || {}
if (FIELD_META_PROP in attrs) { // And means FIELD_DATA_PROP in child.props, too. if (FIELD_META_PROP in attrs) { // And means FIELD_DATA_PROP in child.props, too.
controls.push(child) controls.push(child)
} else if (slots.default) { } else if (slots.default) {
@ -339,11 +341,34 @@ export default {
</Row> </Row>
) )
}, },
decoratorOption (vnode) {
if (vnode.data && vnode.data.directives) {
const directive = find(vnode.data.directives, ['name', 'decorator']) || {}
return directive.value || null
} else {
return null
}
},
decoratorChildren (vnodes) {
const { FormProps } = this
const getFieldDecorator = FormProps.form.getFieldDecorator
vnodes.forEach((vnode, index) => {
const option = this.decoratorOption(vnode)
if (option && option.id) {
vnodes[index] = getFieldDecorator(option.id, option.options || {})(vnode)
} else if (vnode.children) {
vnode.children = this.decoratorChildren(cloneVNodes(vnode.children))
} else if (vnode.componentOptions && vnode.componentOptions.children) {
vnode.componentOptions.children = this.decoratorChildren(cloneVNodes(vnode.componentOptions.children))
}
})
return vnodes
},
}, },
render () { render () {
const { $slots, decoratorFormProps, fieldDecoratorId, fieldDecoratorOptions = {}} = this const { $slots, decoratorFormProps, fieldDecoratorId, fieldDecoratorOptions = {}, FormProps } = this
const child = filterEmpty($slots.default || []) let child = filterEmpty($slots.default || [])
if (decoratorFormProps.form && fieldDecoratorId && child.length) { if (decoratorFormProps.form && fieldDecoratorId && child.length) {
const getFieldDecorator = decoratorFormProps.form.getFieldDecorator const getFieldDecorator = decoratorFormProps.form.getFieldDecorator
child[0] = getFieldDecorator(fieldDecoratorId, fieldDecoratorOptions)(child[0]) child[0] = getFieldDecorator(fieldDecoratorId, fieldDecoratorOptions)(child[0])
@ -351,9 +376,14 @@ export default {
!(child.length > 1), !(child.length > 1),
'`autoFormCreate` just `decorator` then first children. but you can use JSX to support multiple children', '`autoFormCreate` just `decorator` then first children. but you can use JSX to support multiple children',
) )
this.slotDefault = child
} else if (FormProps.form) {
child = cloneVNodes(child)
this.slotDefault = this.decoratorChildren(child)
} else {
this.slotDefault = child
} }
this.slotDefault = child
const children = this.renderChildren() const children = this.renderChildren()
return this.renderFormItem(children) return this.renderFormItem(children)
}, },

View File

@ -580,7 +580,7 @@ exports[`renders ./components/form/demo/validate-other.vue correctly 1`] = `
</div> </div>
</div> </div>
<div class="ant-row ant-form-item"> <div class="ant-row ant-form-item">
<div class="ant-col-6 ant-form-item-label"><label title="Dragger" class="">Dragger</label></div> <div class="ant-col-6 ant-form-item-label"><label for="dragger" title="Dragger" class="">Dragger</label></div>
<div class="ant-col-14 ant-form-item-control-wrapper"> <div class="ant-col-14 ant-form-item-control-wrapper">
<div class="ant-form-item-control"><span class="ant-form-item-children"><div class="dropbox"><span data-__meta="[object Object]" data-__field="[object Object]" id="dragger" class=""><div class="ant-upload ant-upload-drag"><!----></div><div class="ant-upload-list ant-upload-list-text"></div></span></div></span> <div class="ant-form-item-control"><span class="ant-form-item-children"><div class="dropbox"><span data-__meta="[object Object]" data-__field="[object Object]" id="dragger" class=""><div class="ant-upload ant-upload-drag"><!----></div><div class="ant-upload-list ant-upload-list-text"></div></span></div></span>
<!----> <!---->

View File

@ -10,24 +10,29 @@ Use `setFieldsValue` to set other control's value programmaticly.
<template> <template>
<a-form @submit="handleSubmit" :autoFormCreate="(form)=>{this.form = form}"> <a-form @submit="handleSubmit" :form="form">
<a-form-item <a-form-item
label='Note' label='Note'
:labelCol="{ span: 5 }" :labelCol="{ span: 5 }"
:wrapperCol="{ span: 12 }" :wrapperCol="{ span: 12 }"
fieldDecoratorId="note"
:fieldDecoratorOptions="{rules: [{ required: true, message: 'Please input your note!' }]}"
> >
<a-input /> <a-input
v-decorator="{
id: 'note',
options: {rules: [{ required: true, message: 'Please input your note!' }]}
}"
/>
</a-form-item> </a-form-item>
<a-form-item <a-form-item
label='Gender' label='Gender'
:labelCol="{ span: 5 }" :labelCol="{ span: 5 }"
:wrapperCol="{ span: 12 }" :wrapperCol="{ span: 12 }"
fieldDecoratorId="gender"
:fieldDecoratorOptions="{rules: [{ required: true, message: 'Please select your gender!' }]}"
> >
<a-select <a-select
v-decorator="{
id: 'gender',
options: {rules: [{ required: true, message: 'Please select your gender!' }]}
}"
placeholder='Select a option and change input text above' placeholder='Select a option and change input text above'
@change="this.handleSelectChange" @change="this.handleSelectChange"
> >
@ -50,6 +55,7 @@ export default {
data () { data () {
return { return {
formLayout: 'horizontal', formLayout: 'horizontal',
form: this.$form.createForm(this),
} }
}, },
methods: { methods: {

View File

@ -10,24 +10,32 @@ Perform different check rules according to different situations.
<template> <template>
<a-form :autoFormCreate="(form)=>{this.form = form}"> <a-form :form="form">
<a-form-item <a-form-item
:labelCol="formItemLayout.labelCol" :labelCol="formItemLayout.labelCol"
:wrapperCol="formItemLayout.wrapperCol" :wrapperCol="formItemLayout.wrapperCol"
label='Name' label='Name'
fieldDecoratorId="username"
:fieldDecoratorOptions="{rules: [{ required: true, message: 'Please input your name' }]}"
> >
<a-input placeholder='Please input your name' /> <a-input
v-decorator="{
id: 'username',
options: {rules: [{ required: true, message: 'Please input your name' }]}
}"
placeholder='Please input your name'
/>
</a-form-item> </a-form-item>
<a-form-item <a-form-item
:labelCol="formItemLayout.labelCol" :labelCol="formItemLayout.labelCol"
:wrapperCol="formItemLayout.wrapperCol" :wrapperCol="formItemLayout.wrapperCol"
label='Nickname' label='Nickname'
fieldDecoratorId="nickname"
:fieldDecoratorOptions="{rules: [{ required: checkNick, message: 'Please input your nickname' }]}"
> >
<a-input placeholder='Please input your nickname' /> <a-input
v-decorator="{
id: 'nickname',
options: {rules: [{ required: checkNick, message: 'Please input your nickname' }]}
}"
placeholder='Please input your nickname'
/>
</a-form-item> </a-form-item>
<a-form-item <a-form-item
:labelCol="formTailLayout.labelCol" :labelCol="formTailLayout.labelCol"
@ -64,6 +72,7 @@ export default {
checkNick: false, checkNick: false,
formItemLayout, formItemLayout,
formTailLayout, formTailLayout,
form: this.$form.createForm(this),
} }
}, },
methods: { methods: {

View File

@ -10,25 +10,33 @@ Horizontal login form is often used in navigation bar.
<template> <template>
<a-form layout='inline' @submit="handleSubmit" :autoFormCreate="(form)=>{this.form = form}"> <a-form layout='inline' @submit="handleSubmit" :form="form">
<template v-if="form">
<a-form-item <a-form-item
:validateStatus="userNameError() ? 'error' : ''" :validateStatus="userNameError() ? 'error' : ''"
:help="userNameError() || ''" :help="userNameError() || ''"
fieldDecoratorId="userName"
:fieldDecoratorOptions="{rules: [{ required: true, message: 'Please input your username!' }]}"
> >
<a-input placeholder='Username'> <a-input
placeholder='Username'
v-decorator="{
id: 'userName',
options: {rules: [{ required: true, message: 'Please input your username!' }]}
}"
>
<a-icon slot="prefix" type='user' style="color:rgba(0,0,0,.25)"/> <a-icon slot="prefix" type='user' style="color:rgba(0,0,0,.25)"/>
</a-input> </a-input>
</a-form-item> </a-form-item>
<a-form-item <a-form-item
:validateStatus="passwordError() ? 'error' : ''" :validateStatus="passwordError() ? 'error' : ''"
:help="passwordError() || ''" :help="passwordError() || ''"
fieldDecoratorId="password"
:fieldDecoratorOptions="{rules: [{ required: true, message: 'Please input your Password!' }]}"
> >
<a-input type='password' placeholder='Password'> <a-input
v-decorator="{
id: 'password',
options: {rules: [{ required: true, message: 'Please input your Password!' }]}
}"
type='password'
placeholder='Password'
>
<a-icon slot="prefix" type='lock' style="color:rgba(0,0,0,.25)"/> <a-icon slot="prefix" type='lock' style="color:rgba(0,0,0,.25)"/>
</a-input> </a-input>
</a-form-item> </a-form-item>
@ -41,7 +49,6 @@ Horizontal login form is often used in navigation bar.
Log in Log in
</a-button> </a-button>
</a-form-item> </a-form-item>
</template>
</a-form> </a-form>
</template> </template>
@ -53,7 +60,7 @@ export default {
data () { data () {
return { return {
hasErrors, hasErrors,
form: null, form: this.$form.createForm(this),
} }
}, },
mounted () { mounted () {

View File

@ -1,8 +1,10 @@
import Vue from 'vue' import Vue from 'vue'
import Form from './Form' import Form from './Form'
import ref from 'vue-ref' import ref from 'vue-ref'
import FormDecoratorDirective from '../_util/FormDecoratorDirective'
Vue.use(ref, { name: 'ant-ref' }) Vue.use(ref, { name: 'ant-ref' })
Vue.use(FormDecoratorDirective)
export { FormProps, FormCreateOption, ValidationRule } from './Form' export { FormProps, FormCreateOption, ValidationRule } from './Form'
export { FormItemProps } from './FormItem' export { FormItemProps } from './FormItem'
@ -11,6 +13,7 @@ export { FormItemProps } from './FormItem'
Form.install = function (Vue) { Form.install = function (Vue) {
Vue.component(Form.name, Form) Vue.component(Form.name, Form)
Vue.component(Form.Item.name, Form.Item) Vue.component(Form.Item.name, Form.Item)
Vue.prototype.$form = Form
} }
export default Form export default Form

View File

@ -11,25 +11,23 @@
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| form | 经 `Form.create()` 包装过的组件会自带 `this.form` 属性,直接传给 Form 即可。无需手动设置 | object | 无 | | form | 经 `Form.create()` 包装过的组件会自带 `this.form` 属性,如果使用template语法可以使用this.$form.createForm(this, options) | object | 无 |
| hideRequiredMark | 隐藏所有表单项的必选标记 | Boolean | false | | hideRequiredMark | 隐藏所有表单项的必选标记 | Boolean | false |
| layout | 表单布局 | 'horizontal'\|'vertical'\|'inline' | 'horizontal' | | layout | 表单布局 | 'horizontal'\|'vertical'\|'inline' | 'horizontal' |
| autoFormCreate | 自动执行Form.create建议在template组件下使用并且不可以和`Form.create()`同时使用 |Function(form)| 无|
| options | 对应Form.create(options)的`options` | Object | {} |
### 事件 ### 事件
| 事件名称 | 说明 | 回调参数 | | 事件名称 | 说明 | 回调参数 |
| --- | --- | --- | | --- | --- | --- |
| submit | 数据验证成功后回调事件 | Function(e:Event) | | submit | 数据验证成功后回调事件 | Function(e:Event) |
### autoFormCreate ### createForm
````html ````html
<a-form :autoFormCreate="(form)=>{this.form = form}"> <a-form :form="form">
... ...
</a-form> </a-form>
```` ````
如果使用`template`语法,可以使用`autoFormCreate`开启自动校验和数据收集功能,但每一个`Form.Item`仅仅对其第一个子组件进行`decorator`。更加复杂的功能建议使用`JSX` 如果使用`template`语法,可以使用`this.$form.createForm(this, options)`开启自动校验和数据收集功能
相关示例如下: 相关示例如下:
@ -144,7 +142,7 @@ CustomizedForm = Form.create({})(CustomizedForm);
1. `getFieldDecorator` 不能用于装饰纯函数组件。 1. `getFieldDecorator` 不能用于装饰纯函数组件。
2. `getFieldDecorator` 调用不能位于纯函数组件中 <https://cn.vuejs.org/v2/api/#functional> 2. `getFieldDecorator` 调用不能位于纯函数组件中 <https://cn.vuejs.org/v2/api/#functional>
#### getFieldDecorator(id, options) 参数 #### getFieldDecorator(id, options) \| v-decorator="id, options" 参数
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
@ -162,7 +160,7 @@ CustomizedForm = Form.create({})(CustomizedForm);
注意: 注意:
- 一个 Form.Item 建议只放一个被 getFieldDecorator 装饰过的 child当有多个被装饰过的 child 时,`help` `required` `validateStatus` 无法自动生成。 - 一个 Form.Item 建议只放一个被 getFieldDecorator或v-decorator 装饰过的 child当有多个被装饰过的 child 时,`help` `required` `validateStatus` 无法自动生成。
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
@ -175,8 +173,6 @@ CustomizedForm = Form.create({})(CustomizedForm);
| required | 是否必填,如不设置,则会根据校验规则自动生成 | boolean | false | | required | 是否必填,如不设置,则会根据校验规则自动生成 | boolean | false |
| validateStatus | 校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating' | string | | | validateStatus | 校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating' | string | |
| wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol | [object](/ant-design-vue/components/grid-cn/#Col) | | | wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol | [object](/ant-design-vue/components/grid-cn/#Col) | |
| fieldDecoratorId | 对应`getFieldDecorator(id, options)`的第一个参数`id`,如需收集数据需要设置该字段 | string | 无 |
| fieldDecoratorOptions | 对应`getFieldDecorator(id, options)`的第二个参数`options` | object | {} |
### 校验规则 ### 校验规则

View File

@ -577,9 +577,11 @@ function createBaseForm (option = {}, mixins = []) {
on: $listeners, on: $listeners,
ref: 'WrappedComponent', ref: 'WrappedComponent',
} }
return <WrappedComponent {...wrappedComponentProps}>{$slots.default}</WrappedComponent>
return WrappedComponent ? <WrappedComponent {...wrappedComponentProps}>{$slots.default}</WrappedComponent> : null
}, },
} }
if (!WrappedComponent) return Form
if (Array.isArray(WrappedComponent.props)) { if (Array.isArray(WrappedComponent.props)) {
const newProps = {} const newProps = {}
WrappedComponent.props.forEach((prop) => { WrappedComponent.props.forEach((prop) => {

View File

@ -1,6 +1,6 @@
{ {
"name": "ant-design-vue", "name": "ant-design-vue",
"version": "1.1.9", "version": "1.1.10-beta",
"title": "Ant Design Vue", "title": "Ant Design Vue",
"description": "An enterprise-class UI design language and Vue-based implementation", "description": "An enterprise-class UI design language and Vue-based implementation",
"keywords": [ "keywords": [