1 Code Structure

2. Code implementation

2.1 the index. The vue

Implement test code

<template>
    <div>
        <JForm ref="submitForm" :model="model" :rules="rules">
            <JFormItem label="Username" prop="username">
                 <JInput v-model="model.username" type="text" placeholder="Please enter user name 2"></JInput>
            </JFormItem>
            <JFormItem label="User password" prop="password">
                 <JInput  v-model="model.password"  type="password" placeholder="Please enter user password" ></JInput>
            </JFormItem> 
            <JFormItem>
                 <button @click="onClick">submit</button>
            </JFormItem>
        </JForm>
    </div>
</template>

<script>
 import JForm from "./JForm";
 import JFormItem from "./JFormItem";
 import JInput from "./JInput";
    export default {
        components: {
            JForm,
            JFormItem,
            JInput
        },
        data() {
            return {
                model: {username:' '.password:' ',},rules: {
                    "username" : [{required:true.message:"Please enter user information"}]."password" : [{required:false.message:"Please enter user password"}].}}},methods: {
            onClick() {
                this.$refs.submitForm.validate(isVaild= > {
                    if(isVaild){
                        alert("Verification successful")}else {
                        alert("Verification failed")}}); }}},</script>
Copy the code

2.2 JForm. Vue

Technical point

  • Use provide for cross-component object access
  • Prop Records model form information and rules form verification rules
  • This.$children iterates through the validate method of all child components and returns a Promise object. Returns true if the verification is successful
<template>
    <div>
        <slot></slot>
    </div>
</template>

<script>
    export default {
        name : "JForm",
        provide () {
            return {
                form :this}},props: {
            model: {
                type: Object.required : true
            },
            rules: {
                type: Object.required : false}},methods: {
            //callbackFn is a successful callback from the home page
            validate(callbackFn) {// Validates all formItems
               let list = this.$children.filter(item= > item.prop)
               let taskList = []
               list.forEach(item= > {
                   taskList.push(item.validate())
               })
               // Determine if all promise objects are successful,
               Promise.all(taskList).then(res= > {
                   callbackFn(true)
               }).catch(e= > {
                   callbackFn(false)})}},}</script>
Copy the code

2.3 JFormItem. Vue

Technical point

  • Verification is implemented through Async-Validator
  • Inject: [‘form’], inject the provide form object
  • Object property names {[this.prop] :rules} are shorthand for calculating property methods [this.prop]
<template>
    <div>
        <label v-if="label">{{label}}</label>
        <slot></slot>
        <p class="error">{{error}}</p>
    </div>
</template>

<script>
import Validator from "async-validator";
    export default {
        inject : ['form'].name : "JFormItem".props: {
            label: {
                type: String.default: ' '
            },
            prop: {
                type: String.default: ' '}},data() {
            return {
                error: ' '
            }
        },
        mounted () {
            // Add a listening method for the child component to tell itself to start validation
            this.$on("validate".() = > {
                this.validate()
            })
        },
        methods: {
            // Return a Promise object
            validate() {
                let rules = this.form.rules[this.prop]
                let value = this.form.model[this.prop]
                let validator = new Validator({[this.prop] :rules })
                return validator.validate({[this.prop]:value}, errors= > {
                    if(errors) {
                        this.error =  errors[0].message
                    }else {
                        this.error = ""}},}</script>
<style scoped>
.error {color: red; }</style>
Copy the code

2.4 JInput. Vue

Technical point

  • V-bind =”$attrs” gets all prop passed in
  • InheritAttrs: False does not display prop information where called
  • @input=”onInput” :value=”value
  • This. The parent. The parent. The parent. Emit and father component JFormItem interaction
<template>
    <div>
        <input :type="type" @input="onInput" :value="value" v-bind="$attrs" >
    </div>
</template>

<script>
    export default {
        inheritAttrs:false.name : "JInput" ,
        props: {
            value: {
                type: String.default: ""
            },
            type: {
                type: String.default: ""}},methods: {
            onInput(e) { 
                this.$emit('input',e.target.value)
                // Every edit triggers validation
                this.$parent.$emit('validate')}}}</script>
Copy the code

2.3 Code Optimization

Question:

  • Because $chlidren and $parent are strongly associated with components, it is not easy to refactor and adjust the code later if the parent-child relationship changes.

1. Solve the problem that the parent Form traverses all formItems

Implementation steps

  • Parent component itemList, add listener method “jFROm. addItem”, wait for child component instantiation to add itemList
  • After the child component is instantiated, $Dispatch is called to distribute the itemList that registers itself with the parent component
  • The parent component can directly iterate over all itemList child components

2. JInput calls validate of FormItem

Implementation steps

The child component calls $Dispatch directly and validates the parent component

Code implementation

  • Define $boardcast to support infinitely searching down for the eventName broadcast method in $chLID
  • Define $dispatch to find the eventName corresponding to $parent
  • Determine if a custom componentName is found by matching componentName
// Register global methods
//1. Pass from child up
Vue.prototype.$dispatch = function(componentName,eventName,data) {
  let parent = this.$parent || this.$root
  let name = parent.$options.componentName
  while(parent && (! name ||name ! == componentName)){// Can not find the same parent component name or undefined always look up
      parent = parent.$parent
  } 
  if(parent) {
       parent.$emit.apply(parent,[eventName].concat(data));// The parent component invokes $emit to trigger its own $ON event}}//2. Passing from father to son is temporarily useless
Vue.prototype.$boardcast = function(componentName,eventName,data) {
  boardcast.call(this,componentName,eventName,data);
}
function boardcast(componentName,eventName,data) {  
  this.$children.forEach(child= > {
       let name = child.$options.componentName
       if(name === componentName) {
            child.$emit.apply(child,[eventName].concat(data));
       } else{ boardcast.apply(child,[componentName,eventName].concat(data)); }}); }// New componentName attribute for the corresponding page to find a match
//JForm.vue
export default {
        name : "jFrom" ,
        componentName : "j-from".data() {
            return {
                itemList: [] // Add a collection object for all submodules to be fired at the same time
            }
        },
        created () {
            // Wait for jfromItem to register
            this.$on("jFrom.addItem".(item) = > {
                this.itemList.push(item)
            })
        },
          methods: {
            //callbackFn is a successful callback from the home page
            validate(callbackFn) {// Validates all formItems
               let taskList = []
               this.itemList.forEach(item= > {
                   taskList.push(item.validate())
               })
            }
        },
}

//JFormItem.vue
export default {
        name : "JFormItem" ,
        componentName : "j-from-item",
        mounted () {
            // FormItems loaded are automatically registered into the parent component's itemList
            if(this.prop) {
                this.$dispatch("j-from"."jFrom.addItem"[this]) $dispatch = [this] $dispatch = [this] $dispatch = [this]}}},//JInput.vue
export default { 
         methods: {
            onInput(e) {
                this.$emit('input',e.target.value)
                // this.$parent.$emit("validate") 
                this.$dispatch('j-form-item'.'validate')// Go to the corresponding validate method under el-form-item}}},Copy the code