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) {
componentOptions = ele.$vnode.componentOptions || {}
}
const children = componentOptions.children || []
const children = ele.children || componentOptions.children || []
const slots = {}
children.forEach(child => {
const name = (child.data && child.data.slot) || 'default'

View File

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

View File

@ -1,14 +1,15 @@
import intersperse from 'intersperse'
import PropTypes from '../_util/vue-types'
import classNames from 'classnames'
import find from 'lodash/find'
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 { initDefaultProps, getComponentFromProp, filterEmpty, getSlotOptions, isValidElement, getSlots } from '../_util/props-util'
import getTransitionProps from '../_util/getTransitionProps'
import BaseMixin from '../_util/BaseMixin'
import { cloneElement } from '../_util/vnode'
import { cloneElement, cloneVNodes } from '../_util/vnode'
export const FormItemProps = {
id: PropTypes.string,
prefixCls: PropTypes.string,
@ -47,6 +48,10 @@ export default {
'`Form.Item` cannot generate `validateStatus` and `help` automatically, ' +
'while there are more than one `getFieldDecorator` in it.',
)
warning(
!this.fieldDecoratorId,
'`fieldDecoratorId` is deprecated. please use `v-decorator={id, options}` instead.'
)
},
methods: {
getHelpMessage () {
@ -81,11 +86,8 @@ export default {
if (getSlotOptions(child).__ANT_FORM_ITEM) {
continue
}
const attrs = child.data && child.data.attrs
if (!attrs) {
continue
}
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.
controls.push(child)
} else if (slots.default) {
@ -339,11 +341,34 @@ export default {
</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 () {
const { $slots, decoratorFormProps, fieldDecoratorId, fieldDecoratorOptions = {}} = this
const child = filterEmpty($slots.default || [])
const { $slots, decoratorFormProps, fieldDecoratorId, fieldDecoratorOptions = {}, FormProps } = this
let child = filterEmpty($slots.default || [])
if (decoratorFormProps.form && fieldDecoratorId && child.length) {
const getFieldDecorator = decoratorFormProps.form.getFieldDecorator
child[0] = getFieldDecorator(fieldDecoratorId, fieldDecoratorOptions)(child[0])
@ -351,9 +376,14 @@ export default {
!(child.length > 1),
'`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()
return this.renderFormItem(children)
},

View File

@ -580,7 +580,7 @@ exports[`renders ./components/form/demo/validate-other.vue correctly 1`] = `
</div>
</div>
<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-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>
<a-form @submit="handleSubmit" :autoFormCreate="(form)=>{this.form = form}">
<a-form @submit="handleSubmit" :form="form">
<a-form-item
label='Note'
:labelCol="{ span: 5 }"
: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
label='Gender'
:labelCol="{ span: 5 }"
:wrapperCol="{ span: 12 }"
fieldDecoratorId="gender"
:fieldDecoratorOptions="{rules: [{ required: true, message: 'Please select your gender!' }]}"
>
<a-select
v-decorator="{
id: 'gender',
options: {rules: [{ required: true, message: 'Please select your gender!' }]}
}"
placeholder='Select a option and change input text above'
@change="this.handleSelectChange"
>
@ -50,6 +55,7 @@ export default {
data () {
return {
formLayout: 'horizontal',
form: this.$form.createForm(this),
}
},
methods: {

View File

@ -10,24 +10,32 @@ Perform different check rules according to different situations.
<template>
<a-form :autoFormCreate="(form)=>{this.form = form}">
<a-form :form="form">
<a-form-item
:labelCol="formItemLayout.labelCol"
:wrapperCol="formItemLayout.wrapperCol"
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
:labelCol="formItemLayout.labelCol"
:wrapperCol="formItemLayout.wrapperCol"
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
:labelCol="formTailLayout.labelCol"
@ -64,6 +72,7 @@ export default {
checkNick: false,
formItemLayout,
formTailLayout,
form: this.$form.createForm(this),
}
},
methods: {

View File

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

View File

@ -1,8 +1,10 @@
import Vue from 'vue'
import Form from './Form'
import ref from 'vue-ref'
import FormDecoratorDirective from '../_util/FormDecoratorDirective'
Vue.use(ref, { name: 'ant-ref' })
Vue.use(FormDecoratorDirective)
export { FormProps, FormCreateOption, ValidationRule } from './Form'
export { FormItemProps } from './FormItem'
@ -11,6 +13,7 @@ export { FormItemProps } from './FormItem'
Form.install = function (Vue) {
Vue.component(Form.name, Form)
Vue.component(Form.Item.name, Form.Item)
Vue.prototype.$form = 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 |
| layout | 表单布局 | 'horizontal'\|'vertical'\|'inline' | 'horizontal' |
| autoFormCreate | 自动执行Form.create建议在template组件下使用并且不可以和`Form.create()`同时使用 |Function(form)| 无|
| options | 对应Form.create(options)的`options` | Object | {} |
### 事件
| 事件名称 | 说明 | 回调参数 |
| --- | --- | --- |
| submit | 数据验证成功后回调事件 | Function(e:Event) |
### autoFormCreate
### createForm
````html
<a-form :autoFormCreate="(form)=>{this.form = form}">
<a-form :form="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` 不能用于装饰纯函数组件。
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 |
| validateStatus | 校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating' | string | |
| 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,
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)) {
const newProps = {}
WrappedComponent.props.forEach((prop) => {

View File

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