Recently I am doing background management system for the company, and I have been used to element component library. One of the most commonly used components is the form form. Today I am trying to realize its basic functions by myself

Project structures,

As always, let’s create a project

It looks something like this

man.js

import Vue from 'vue'
import App from './App.vue'


Vue.config.productionTip = false
// Event bus
Vue.prototype.$bus = new Vue()

new Vue({
  render: h= > h(App)
}).$mount('#app')

Copy the code

App.vue

<template>
  <div id="app">
    <my-form></my-form>
  </div>
</template>

<script>
import MyForm from '@/components/form'

export default ({
  components:{
    MyForm
  }
})
</script>
Copy the code

form/index

<template>
  <div>
    <my-Form :model="model" :rules="rules" ref="loginForm">
      <my-FormItem label="Username" prop="username">
        <my-Input v-model="model.username" placeholder="Please enter user name"></my-Input>
      </my-FormItem>
      <my-FormItem>
        <button @click="submit">submit</button>
      </my-FormItem>
    </my-Form>
  </div>
</template>

<script>
import myInput from "@/components/form/myInput.vue";
import myFormItem from "@/components/form/myFormItem.vue";
import myForm from "@/components/form/myForm.vue";


export default {
  components: {
    myElementForm,
    myFormItem,
    myForm,
  },
  data() {
    return {
      model: {
        username: "tom",},rules: {
        username: [{ required: true.message: "Please enter user name"}],}}; },methods: {
    submit() {
      this.$refs.loginForm.validate((isValid) = > {
        console.log(isValid) }); ,}}};</script>

<style scoped></style>

Copy the code

The next key is the implementation of these form components

Demand analysis

MyInput components

The myInput component is the most basic function, which is to implement custom component bidirectional binding, namely v-Model syntax sugar

<template>
  <div>
    <! -- Custom component double binding: v-model syntax sugar, :value, @input -->
    <input :type="type" :value="value" @input="onInput" v-bind="$attrs">
  </div>
</template>

<script>
  export default {
    inheritAttrs: false.props: {
      value: {
        type: String.default: ' '
      },
      type: {
        type: String.default: 'text'}},methods: {
      onInput(e) {
        this.$emit('input', e.target.value)
      }
    },
  }
</script>

<style lang="scss" scoped>

</style>
Copy the code

Because the outer layer may be written directly to the component using properties such as placeholder, there is no need to declare these properties in props (because the logic in the component is not needed). Instead, use $attrs to transfer the properties directly to the input (which is also a way for the component to pass values).

Results the following

MyFormItem components

The main purpose of this component is to validate form entries and display error messages.

myFormItem.vue

The basic structure


<template>
  <div>
     <label v-if="label">{{label}}</label>
    
     <slot></slot>
     <p v-if="error">{{error}}</p>
  </div>
</template>

<script>
]
  export default {
    props: {
      label: {
        type: String.default: ' '
      },
      prop: {
         type: String.default: ' '}},data() {
      return {
        error: ' '}}},</script>

<style scoped>

</style>
Copy the code

Form item verification

As we know, formItem accepts a prop value and verifies that the value of Model [prop] meets the requirements of rules[prop]. However, there is a problem here. Both model and rules are defined in the my-form component, so how can the formItem be obtained as a sub-component (and the hierarchy here is not necessarily parent and child, the actual project may be separated by several layers). To solve this cross-layer communication problem, provide/ Inject can be adopted.

myForm.vue


<template>
  <div>
    <slot></slot>
  </div>
</template>

<script>
export default {
  provide() {
    return {
      // Provide the current form instance directly
      form: this}; },props: {
    model: {
      type: Object.required: true,},rules: Object,}};</script>

<style lang="scss" scoped></style>


Copy the code

We can test to see if we have that form in the formitem

myFormItem.vue



<template>
  <div>
     <label v-if="label">{{label}}</label>
    
     <slot></slot>
     <p v-if="error">{{error}}</p>

   <! -- This is used to print the checksum value and the corresponding rule -->
    <p>{{form.rules[prop]}}</p>
    <p>{{form.model[prop]}}</p>
  </div>
</template>

<script>
  export default {
    // Inject the form instance
    inject: ['form'].props: {
      label: {
        type: String.default: ' '
      },
      prop: {
        type: String.default: ' '}},data() {
      return {
        error: ' '}}},</script>

<style scoped>

</style>
Copy the code

Very successful. I’ve got the values and the rules, and now I’m ready to check. Validation can be triggered in two ways. One is the global validation of the form, which is handed over to myForm. The other is triggered by the myInput component, such as the change, blur event. But myInput is a slot in formItem, and the listener event cannot be written to slot. Therefore, we can consider using the formItem component to get the myFormItem instance through $parent. Here we demonstrate validation triggered by an input event.

myInput.vue


/ /... Template section

<script>
  export default{,/ /.. props
    methods: {
      onInput(e) {
        this.$emit('input', e.target.value)
        
        // // Trigger verification. The observer mode requires that the listener and the dispatcher must be the same component. The parent component corresponds to the slot, so the listener and dispatcher are implemented on the formItem
        this.$parent.$emit('validate')
      }
    },
  }
</script>

<style lang="scss" scoped>

</style>
Copy the code

myFormItem.vue


<template>
  <div>
     <label v-if="label">{{label}}</label>
    
     <slot></slot>
     <p v-if="error">{{error}}</p>
  </div>
</template>

<script>
  import Schema from 'async-validator'
  export default {
    inject: ['form'].props: {
      label: {
        type: String.default: ' '
      },
      prop: {
        type: String.default: ' '}},data() {
      return {
        error: ' '
      }
    },
    mounted () {
      // Listen for the validate event
      this.$on('validate'.() = > {
        this.validate()
      })
    },
    methods: {
      validate() {
        // Perform validation, async-validator
        console.log('validate');
        // 1. Obtain verification rules
        const rules = this.form.rules[this.prop]
        const value = this.form.model[this.prop]

        // 2. Construct a validator instance
        const validator = new Schema({[this.prop]: rules}) 

        // 3. Perform verification
        return validator.validate({[this.prop]: value}, errors= > {
          // If the errors array exists, there is a check error
          if (errors) {
            this.error = errors[0].message
          } else {
            this.error = ' '}},}</script>

<style scoped>

</style>
Copy the code

We can see that there is a prompt when the username input is null

MyForm components

We’ve already done part of the myForm component to manage form data and rules (including accepting data and providing data to formItem). We also need to implement its global verification function.

Because each formItem has a corresponding validate method, a simple and crude strategy would be to iterate over all formItem components and then execute their checkouts. So how do you get the Form Item in the Form component, you can use $children

myForm.vue

<template>
  <div>
    <slot></slot>
  </div>
</template>

<script>
export default {
  provide() {
    return {
      // Provide the current form instance directly
      form: this}; },props: {
    model: {
      type: Object.required: true,},rules: Object,},methods: {
    validate(cb) {
      // Global validation
      // Execute the validate method for all formItems internally
      // Get an array of promises
      const tasks = this.$children
        .filter(item= > item.prop)
        .map((item) = > item.validate());

      // Check the verification result
      Promise.all(tasks)
        .then(() = > cb(true))
        .catch(() = > cb(false)); ,}}};</script>

<style lang="scss" scoped></style>

Copy the code

Let’s take a look at the results:

After the empty form is submitted, the view prompts, and the following code also prints false on the console

form/index

//....
submit() {
      this.$refs.loginForm.validate((isValid) = > {
        console.log(isValid)
      });
    },
Copy the code

At this point, we have the basic functionality of the Element form, so you can type it all together if you’re interested