From e77b78b8094dcbd4599eb6e2424f07490da7195c Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Sat, 23 Jun 2018 17:17:45 +0800 Subject: [PATCH] feat: support form template --- components/form/Form.jsx | 54 +++++++------ components/form/FormItem.jsx | 18 ++--- components/form/demo/coordinated.vue | 91 +++++++++++----------- components/form/demo/dynamic-rule.vue | 82 +++++++++----------- components/form/demo/test.vue | 94 +++++++++++++---------- components/vc-form/src/createBaseForm.jsx | 7 +- 6 files changed, 175 insertions(+), 171 deletions(-) diff --git a/components/form/Form.jsx b/components/form/Form.jsx index 761d55f04..c118eec42 100755 --- a/components/form/Form.jsx +++ b/components/form/Form.jsx @@ -57,7 +57,8 @@ export const FormProps = { // onSubmit: React.FormEventHandler<any>; prefixCls: PropTypes.string, hideRequiredMark: PropTypes.bool, - formRef: PropTypes.func, + autoFormCreate: PropTypes.func, + options: PropTypes.object, } export const ValidationRule = { @@ -144,23 +145,6 @@ export default { fieldDataProp: FIELD_DATA_PROP, }) }, - - // constructor (props) { - // super(props) - - // warning(!props.form, 'It is unnecessary to pass `form` to `Form` after antd@1.7.0.') - // } - - // shouldComponentUpdate(...args) { - // return PureRenderMixin.shouldComponentUpdate.apply(this, args); - // } - - // getChildContext () { - // const { layout } = this.props - // return { - // vertical: layout === 'vertical', - // } - // }, provide () { return { FormProps: this.$props, @@ -179,7 +163,7 @@ export default { render () { const { - prefixCls, hideRequiredMark, layout, onSubmit, $slots, formRef, + prefixCls, hideRequiredMark, layout, onSubmit, $slots, autoFormCreate, options = {}, } = this const formClassName = classNames(prefixCls, { @@ -188,25 +172,45 @@ export default { [`${prefixCls}-inline`]: layout === 'inline', [`${prefixCls}-hide-required-mark`]: hideRequiredMark, }) - if (formRef) { - const NewForm = createDOMForm({ + if (autoFormCreate) { + const saveFormRef = (ref) => { + this.domForm = ref + } + const DomForm = this.DomForm || createDOMForm({ fieldNameProp: 'id', + ...options, fieldMetaProp: FIELD_META_PROP, fieldDataProp: FIELD_DATA_PROP, + templateContext: this.$parent, })({ provide () { return { - NewFormProps: this.$props, + decoratorFormProps: this.$props, + } + }, + data () { + return { + children: $slots.default, + formClassName: formClassName, + submit: onSubmit, } }, mounted () { - formRef(this.form) + autoFormCreate(this.form) }, render () { - return <form onSubmit={onSubmit} class={formClassName}>{$slots.default}</form> + const { children, formClassName, submit } = this + return <form onSubmit={submit} class={formClassName}>{children}</form> }, }) - return <NewForm /> + if (this.domForm) { + this.domForm.children = $slots.default + this.domForm.submit = onSubmit + this.domForm.formClassName = formClassName + } + this.DomForm = DomForm + + return <DomForm wrappedComponentRef={(inst) => saveFormRef(inst)}/> } return <form onSubmit={onSubmit} class={formClassName}>{$slots.default}</form> diff --git a/components/form/FormItem.jsx b/components/form/FormItem.jsx index ad36b65b5..aaa5157cd 100644 --- a/components/form/FormItem.jsx +++ b/components/form/FormItem.jsx @@ -5,7 +5,7 @@ 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 { initDefaultProps, getComponentFromProp, filterEmpty, getSlotOptions, getSlots, isEmptyElement } from '../_util/props-util' import getTransitionProps from '../_util/getTransitionProps' import BaseMixin from '../_util/BaseMixin' export const FormItemProps = { @@ -35,14 +35,14 @@ export default { }), inject: { FormProps: { default: {}}, - NewFormProps: { default: {}}, + decoratorFormProps: { default: {}}, }, data () { return { helpShow: false } }, mounted () { warning( - this.getControls(this.$slots.default, true).length <= 1, + this.getControls(this.slotDefault, true).length <= 1, '`Form.Item` cannot generate `validateStatus` and `help` automatically, ' + 'while there are more than one `getFieldDecorator` in it.', ) @@ -306,10 +306,10 @@ export default { ) : null }, renderChildren () { - // const { $slots, FormProps, NewFormProps, prop } = this + // const { $slots, FormProps, decoratorFormProps, prop } = this // const child = filterEmpty($slots.default || []) - // if (NewFormProps.form && prop && child.length) { - // const getFieldDecorator = NewFormProps.form.getFieldDecorator + // if (decoratorFormProps.form && prop && child.length) { + // const getFieldDecorator = decoratorFormProps.form.getFieldDecorator // const rules = FormProps.rules[prop] || [] // child[0] = getFieldDecorator(prop, { // rules, @@ -344,10 +344,10 @@ export default { }, render () { - const { $slots, NewFormProps, fieldDecoratorId, fieldDecoratorOptions = {}} = this + const { $slots, decoratorFormProps, fieldDecoratorId, fieldDecoratorOptions = {}} = this const child = filterEmpty($slots.default || []) - if (NewFormProps.form && fieldDecoratorId && child.length) { - const getFieldDecorator = NewFormProps.form.getFieldDecorator + if (decoratorFormProps.form && fieldDecoratorId && child.length) { + const getFieldDecorator = decoratorFormProps.form.getFieldDecorator child[0] = getFieldDecorator(fieldDecoratorId, fieldDecoratorOptions)(child[0]) } this.slotDefault = child diff --git a/components/form/demo/coordinated.vue b/components/form/demo/coordinated.vue index 33365f78a..359155f7c 100644 --- a/components/form/demo/coordinated.vue +++ b/components/form/demo/coordinated.vue @@ -9,10 +9,49 @@ Use `setFieldsValue` to set other control's value programmaticly. </us> -<script> -import { Form } from 'vue-antd-ui' +<template> + <a-form @submit="handleSubmit" :autoFormCreate="(form)=>{this.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-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 + placeholder='Select a option and change input text above' + @change="this.handleSelectChange" + > + <a-select-option value='male'>male</a-select-option> + <a-select-option value='female'>female</a-select-option> + </a-select> + </a-form-item> + <a-form-item + :wrapperCol="{ span: 12, offset: 5 }" + > + <a-button type='primary' htmlType='submit'> + Submit + </a-button> + </a-form-item> + </a-form> +</template> -const CoordinatedForm = { +<script> +export default { + data () { + return { + formLayout: 'horizontal', + } + }, methods: { handleSubmit (e) { e.preventDefault() @@ -29,54 +68,10 @@ const CoordinatedForm = { }) }, }, - - render () { - const { getFieldDecorator } = this.form - return ( - <a-form onSubmit={this.handleSubmit}> - <a-form-item - label='Note' - labelCol={{ span: 5 }} - wrapperCol={{ span: 12 }} - > - {getFieldDecorator('note', { - rules: [{ required: true, message: 'Please input your note!' }], - })( - <a-input /> - )} - </a-form-item> - <a-form-item - label='Gender' - labelCol={{ span: 5 }} - wrapperCol={{ span: 12 }} - > - {getFieldDecorator('gender', { - rules: [{ required: true, message: 'Please select your gender!' }], - })( - <a-select - placeholder='Select a option and change input text above' - onChange={this.handleSelectChange} - > - <a-select-option value='male'>male</a-select-option> - <a-select-option value='female'>female</a-select-option> - </a-select> - )} - </a-form-item> - <a-form-item - wrapperCol={{ span: 12, offset: 5 }} - > - <a-button type='primary' htmlType='submit'> - Submit - </a-button> - </a-form-item> - </a-form> - ) - }, } - -export default Form.create()(CoordinatedForm) </script> + diff --git a/components/form/demo/dynamic-rule.vue b/components/form/demo/dynamic-rule.vue index fccac58ca..2d1ee0ff6 100644 --- a/components/form/demo/dynamic-rule.vue +++ b/components/form/demo/dynamic-rule.vue @@ -9,9 +9,39 @@ Perform different check rules according to different situations. </us> -<script> -import { Form } from 'vue-antd-ui' +<template> + <a-form :autoFormCreate="(form)=>{this.form = form}"> + <a-form-item + :formItemLayout="formItemLayout" + label='Name' + fieldDecoratorId="username" + :fieldDecoratorOptions="{rules: [{ required: true, message: 'Please input your name' }]}" + > + <a-input placeholder='Please input your name' /> + </a-form-item> + <a-form-item + :formItemLayout="formItemLayout" + label='Nickname' + fieldDecoratorId="nickname" + :fieldDecoratorOptions="{rules: [{ required: checkNick, message: 'Please input your nickname' }]}" + > + <a-input placeholder='Please input your nickname' /> + </a-form-item> + <a-form-item :formTailLayout="formTailLayout"> + <a-checkbox + :checked="checkNick" + @change="handleChange" + > + Nickname is required + </a-checkbox> + </a-form-item> + <a-form-item :formTailLayout="formTailLayout"> + <a-button type='primary' @click="check">Check</a-button> + </a-form-item> + </a-form> +</template> +<script> const formItemLayout = { labelCol: { span: 4 }, wrapperCol: { span: 8 }, @@ -20,10 +50,12 @@ const formTailLayout = { labelCol: { span: 4 }, wrapperCol: { span: 8, offset: 4 }, } -const DynamicRule = { +export default { data () { return { checkNick: false, + formItemLayout, + formTailLayout, } }, methods: { @@ -43,52 +75,10 @@ const DynamicRule = { }) }, }, - - render () { - const { getFieldDecorator } = this.form - return ( - <div> - <a-form-item {...{ props: formItemLayout }} label='Name'> - {getFieldDecorator('username', { - rules: [{ - required: true, - message: 'Please input your name', - }], - })( - <a-input placeholder='Please input your name' /> - )} - </a-form-item> - <a-form-item {...{ props: formItemLayout }} label='Nickname'> - {getFieldDecorator('nickname', { - rules: [{ - required: this.checkNick, - message: 'Please input your nickname', - }], - })( - <a-input placeholder='Please input your nickname' /> - )} - </a-form-item> - <a-form-item {...{ props: formTailLayout }}> - <a-checkbox - value={this.checkNick} - onChange={this.handleChange} - > - Nickname is required - </a-checkbox> - </a-form-item> - <a-form-item {...{ props: formTailLayout }}> - <a-button type='primary' onClick={this.check}> - Check - </a-button> - </a-form-item> - </div> - ) - }, } - -export default Form.create()(DynamicRule) </script> + diff --git a/components/form/demo/test.vue b/components/form/demo/test.vue index 20b30d0ca..24aabaea1 100644 --- a/components/form/demo/test.vue +++ b/components/form/demo/test.vue @@ -1,57 +1,73 @@ <template> -<div> - <a-form @submit="handleSubmit" :formRef="(form)=>{this.form = form}"> +<a-form layout='inline' @submit="handleSubmit" :autoFormCreate="(form)=>{this.form = form}"> + <template v-if="form"> <a-form-item - label='Note' - :labelCol="{ span: 5 }" - :wrapperCol="{ span: 12 }" - fieldDecoratorId="note" - :fieldDecoratorOptions="{rules: [{ required: true, message: 'Please input your note!' }]}" + :validateStatus="userNameError() ? 'error' : ''" + :help="userNameError() || ''" + fieldDecoratorId="userName" + :fieldDecoratorOptions="{rules: [{ required: true, message: 'Please input your username!' }]}" > - <a-input /> + <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 - label='Gender' - :labelCol="{ span: 5 }" - :wrapperCol="{ span: 12 }" - fieldDecoratorId="gender" - :fieldDecoratorOptions="{rules: [{ required: true, message: 'Please select your gender!' }]}" + :validateStatus="passwordError() ? 'error' : ''" + :help="passwordError() || ''" + fieldDecoratorId="password" + :fieldDecoratorOptions="{rules: [{ required: true, message: 'Please input your Password!' }]}" > - <a-select - placeholder='Select a option and change input text above' - @change="this.handleSelectChange" + <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())" > - <a-select-option value='male'>male</a-select-option> - <a-select-option value='female'>female</a-select-option> - </a-select> - </a-form-item> - <a-form-item - :wrapperCol="{ span: 12, offset: 5 }" - > - <a-button type='primary' htmlType='submit'> - Submit + Log in </a-button> </a-form-item> - </a-form> -</div> + </template> +</a-form> </template> <script> +function hasErrors (fieldsError) { + return Object.keys(fieldsError).some(field => fieldsError[field]) +} export default { data () { return { - formLayout: 'horizontal', - rules: { - test: [{ - type: 'email', message: 'The input is not valid E-mail!', - }, { - required: true, message: 'Please input your E-mail!', - }], - }, + hasErrors, + form: null, } }, + mounted () { + + }, + watch: { + form (val) { + this.$nextTick(() => { + // To disabled submit button at the beginning. + this.form.validateFields() + }) + }, + }, methods: { - handleSubmit (e) { + // Only show error after a field is touched. + userNameError () { + const { getFieldError, isFieldTouched } = this.form + return isFieldTouched('userName') && getFieldError('userName') + }, + // Only show error after a field is touched. + passwordError () { + const { getFieldError, isFieldTouched } = this.form + return isFieldTouched('password') && getFieldError('password') + }, + handleSubmit (e) { e.preventDefault() this.form.validateFields((err, values) => { if (!err) { @@ -59,12 +75,6 @@ export default { } }) }, - handleSelectChange (value) { - console.log(value) - this.form.setFieldsValue({ - note: `Hi, ${value === 'male' ? 'man' : 'lady'}!`, - }) - }, }, } </script> diff --git a/components/vc-form/src/createBaseForm.jsx b/components/vc-form/src/createBaseForm.jsx index 151f5c750..f128fe88c 100644 --- a/components/vc-form/src/createBaseForm.jsx +++ b/components/vc-form/src/createBaseForm.jsx @@ -35,6 +35,7 @@ function createBaseForm (option = {}, mixins = []) { fieldDataProp, formPropName = 'form', props = {}, + templateContext, } = option return function decorate (WrappedComponent) { @@ -327,7 +328,11 @@ function createBaseForm (option = {}, mixins = []) { .reduce((acc, name) => set(acc, name, this.fieldsStore.getField(name)), {}) onFieldsChange(this, changedFields, this.fieldsStore.getNestedAllFields()) } - this.$forceUpdate() + if (templateContext) { + templateContext.$forceUpdate() + } else { + this.$forceUpdate() + } this.$nextTick(() => { callback && callback() })