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()
           })