First post this article to achieve the function, the function is very simple, is two input boxes, a login button. When the content of the input box changes, the system verifies the information in real time according to the defined verification rules and outputs verification error information. The entire form is validated when the login button is clicked.

In our work, we often use Element’s form for data verification when developing the PC page similar to the management background. We define a model attribute and a Rules attribute in the El-Form component, and a prop attribute in the El-Form-Item component. The EL-Input component uses the V-Model to bind data in both directions, and then there is a button that validates the entire form when clicked. Something like this:

<jd-form :model="userInfo" :rules="rules" ref="loginForm">
   <jd-form-item label="Username" prop="username">
       <jd-input v-model="userInfo.username" placeholder="Please enter user name"></jd-input>
   </jd-form-item>
   <jd-form-item label="Password" prop="password">
       <jd-input v-model="userInfo.password" placeholder="Please enter your password"></jd-input>
   </jd-form-item>
   <! -- Submit button -->
   <jd-form-item>
       <button class="login-button" @click="login">The login</button>
   </jd-form-item>
</jd-form>
Copy the code

There is no deep understanding of why these attributes are passed on components and why they are combined in such a way. For example, the model and rules passed in the el-Form component are for what purpose, when and who will use them, and how to use them. Why does el-Form-item pass a prop property? Why does el-Input, el-Select and other components in the form wrap a layer with El-form-Item instead of using it directly? How to verify the whole form when clicking a button?

Through learning the source code of Element form, this article will realize its encapsulated EL-form, el-form-Item and EL-Input components to complete the same basic functions as Element form and deepen the understanding of the idea of componentization. The implementation also answers all of the questions mentioned above.

Some of the componentization techniques and components to be implemented in this article are as follows:

The following will first introduce the main entry file index.vue, and then introduce the implementation and functions of el-form, el-form-item and el-input components used in index.vue respectively, as well as the organizational communication mode among each component.

The main entry file’s index.vue code is as follows: define a form component JD-form, two input fields jD-input, enter the user name and password respectively, and a login button, and then wrap them with jD-form-item components. Here we use JD-form instead of el-form. In data, we define rules that require responsive data userInfo and verification, and in methods, we define a login method that is used to verify the whole form when the login button is clicked. We’ll make the following code run like Element by implementing JD-form, JD-form-item, and JD-input.

<jd-form :model="userInfo" :rules="rules" ref="loginForm">
   <jd-form-item label="Username" prop="username">
       <jd-input v-model="userInfo.username" placeholder="Please enter user name"></jd-input>
   </jd-form-item>
   <jd-form-item label="Password" prop="password">
       <jd-input v-model="userInfo.password" placeholder="Please enter your password"></jd-input>
   </jd-form-item>
   <! -- Submit button -->
   <jd-form-item>
       <button class="login-button" @click="login">The login</button>
   </jd-form-item>
</jd-form>
Copy the code
data() {
    return {
      // Response data
      userInfo: {
        username: "".password: ""
      },
      // Check the rule
      rules: {
        username: [{required: true.message: 'Please enter user name'}].password: [{required: true.message: 'Please enter your password'}, {type: 'string'.min: 6.message: 'Password length must not be less than 6 characters'}}}; },methods: {
  // Validates the entire form
  login() {
    this.$refs.loginForm.validate((success) = > {
        if (success) {
            alert('Verification successful ~');
        } else {
            alert('Verification failed ~'); }}); }}Copy the code

The el-form-item component is used as an input component, and the el-select component is used as an input component. The el-form-item component is used as an input component.

The reason here is that in order to achieve high cohesion and low coupling of components, the main function of EL-input and EL-Select is to update data and achieve two-way binding of data, rather than to perform data verification. The function of data verification is entrusted to El-Form-Item, so that the division of responsibilities is clear. El-input is responsible for maintaining data, and el-form-item is responsible for verifying data and displaying error messages. Both el-form-Item and El-Input can be used individually or in combination. If data validation is also done in components such as EL-Input and EL-Select, the high cohesion and low coupling characteristics of the components will be destroyed and the reusability of the components will be reduced.

Jd-input component implementation:

As you can see in the code below, the JD-INPUT component simply implements a two-way binding, defining the V-Model on the component and distributing the input events inside the component. This changes the contents of the internal input element to the userinfo.username. When the content changes, the this.dispatch method is also called to notify the parent to perform verification, which in this case is the JD-form-item component. The implementation of JD-form-item will be explained later, but here we’ll look at JD-input.

For convenience, put the code that references the JD-input component as well:

<jd-form-item label="Username" prop="username">
    <jd-input type="text" v-model="userInfo.username" placeholder="Please enter user name"></jd-input>
</jd-form-item>
Copy the code

Internal code of JD-INPUT component:

<template> <div> <! - v - bind = "$attrs" launch $attrs - > < input type = "type" : value = "value" @ input = "the onInput v - bind" = "$attrs" > < / div > < / template > <script> // Emitter defines the dispatch method import emitter from '.. /.. /mixins/emitter.js'; Export default {inheritAttrs: false, // $inheritAttrs to avoid setting to the root element componentName: 'JD-input ', // The name of the component will be set to this. Codes.com ponentName Mixins: [Emitter], // Mix the dispatch method with props: {value: {// Input elements type: String, default: "}, type: {// input elements type: String, default: 'text'}}, methods: $emit('input', e.target.value); $emit('input', e.target.value); $emit('input', e.target.value); // this.$parent.$emit('validate'); // this. this.dispatch('JdFormItem', 'validate'); } }, } </script>Copy the code

Jd-input has several key function points, $attrs, mixin, and Dispatch, which are described below:

V-bind =”$attrs”, which expands all the non-prop properties defined on the JD-INPUT component to the inner input element, which is useful when you have a lot of properties that need to be uploaded to the inner expansion, where the placeholder will be expanded to the inner input tag, Same thing if you have other properties.

The purpose of mixin is to blend the dispatch method into the METHODS method of the JD-input component instance, and then call the jD-input component directly with the this.dispatch method. Dispatch is used in many components, so pull it out and mix it in as needed. The Dispatch method starts with the current component instance and looks up the parent component level by level, like a prototype chain, until the desired parent component is found and the corresponding method of that parent component instance is called. In this case, this.dispatch(‘ JD-form-item ‘, ‘validate’) is used to check the input of the component named JD-form-item by calling the JD-form-item instance’s validate method.

$emit(‘validate’); $emit(‘validate’); $emit(‘validate’); Jd-form-item is the parent of jD-input. If jD-form-item is the parent of JD-input’s parent, then this.$parent would be a problem.

$parent () uses this.$parent to find the parent instance of the current instance. Use parent. code.componentName to determine if it is the parent. The parent instance will emit the eventName method on $emit. This.$on will listen on the parent instance. In our case, the parent component is JD-form-item.

  export default {
    methods: {
      dispatch(componentName, eventName, params) {
        var parent = this.$parent || this.$root;
        var name = parent.$options.componentName;
  
        while(parent && (! name || name ! == componentName)) { parent = parent.$parent;if(parent) { name = parent.$options.componentName; }}if(parent) { parent.$emit.apply(parent, [eventName].concat(params)); }}};Copy the code

Jd-form-item implementation:

See the following code, jd – form – the function of the item main implementation is defined a validate method in the methods, and will use this. When mounted $on listening his son the validate method provided by the component to it. The dispatch method of its sub-components has been described above as using the dispatch method. The validate method in the JD-form-item is triggered by the update in the JD-input. Then we define an Err field in the data to control the display of error information. If the validate check fails, the display of error information will be displayed.

But if you look closely at the code, you will see that the implementation is not as simple as described above, for example, in describing objectsinjectInjection, used in the templateslotSlot. I used it for validationasync-validatorLibrary, as well as the field name of the validation and the validation method to get, this will correspond to the problem mentioned at the beginning of this article, namelyel-formComponentmodelandrulesWhat it’s for, when and who uses it, how it’s used,el-form-itemWhy pass a componentpropProperty, let’s go into the implementation details of the code in the component

<template>
    <div class="form-item">
        <label v-if="label">{{label}}</label>
        <div class="form-item-content"> <slot></slot> <! -- error message --> <p class="err" v-if="err">{{err}}</p>
        </div>
    </div>
</template>

<script>
    import Schema from 'async-validator';
    export default {
        componentName: 'jd-form-item',
        inject: ['form'],
        props: {
            label: {
                type: String,
                default: ' '
            },
            prop: {
                type: String
            }
        },
        data() {
            return {
                err: ' '}},mounted () {
            this.$on('validate', () => {
                this.validate();
            })
        },
        methods: {
            validate() {
                if(! this.prop)return; // Const rules = this.form.rules[this.prop]; // Const value = this.form.model[this.prop]; Const desc = {[this.prop]: rules}; // Const desc = {[this.prop]: rules}; // Create a Schema instance const Schema = new Schema(desc); // Validate values with schemareturn schema.validate({[this.prop]: value}, errors => {
                    if (errors) {
                        this.err = errors[0].message;
                    } else {
                        this.err = ' ';
                    }
                })
            }
        },
    }
</script>
Copy the code

Slot. When writing encapsulated UI components, slots are used a lot. Components reserve a slot in which you can control what needs to be passed. In this article, we need to understand that JD-form-item uses slot to receive incoming content, and jD-form-item itself is only responsible for checking the contents of the incoming component. What is the verification content? Look down at prop properties.

< JD-form-item label=” username” prop=”username”> < JD-form-item label=” username” prop=”username”> < JD-form-item label=” username”> This prop is the key of the property we are checking. How do we get the value of the property from this key?

< JD-form :model=”userInfo” :rules=”rules” ref=”loginForm”> < JD-form :model=”userInfo” :rules=”rules” > In model are the data to be verified, and in rules are the rules to be verified. In JD-form-item component, we already know the property key to be verified through prop, so we just need to get the corresponding value of this key and the verification rules to be verified. The value of this key can be obtained through model[prop], and the verification rule can be obtained through rules[prop]. Jd-form-item {{model}}} {model} {rule}} {jD-form-item {model}} {rule}} {jD-form-item}}

<jd-form :model="userInfo" :rules="rules" ref="loginForm">
    <jd-form-item label="Username" prop="username">
        <jd-input v-model="userInfo.username" placeholder="Please enter user name"></jd-input>
    </jd-form-item>
    <jd-form-item label="Password" prop="password">
        <jd-input v-model="userInfo.password" placeholder="Please enter your password"></jd-input>
    </jd-form-item>
    <! -- Submit button -->
    <jd-form-item>
        <button class="login-button" @click="login">The login</button>
    </jd-form-item>
</jd-form>
Copy the code

To make models and rules available inside jD-forms, we use provide/inject. Inside JD-Forms, we use provide to provide a form attribute. Set the form attribute to the current JD-form component instance (the implementation of jD-Form is described below), then the sub-component only needs to inject: Model [this.prop] and this.form.rules[this.prop] can be used to check data and rules on child component instances. After receiving the data and verification rules, we can perform verification and assign the corresponding verification results to Err to prompt on the page. We use the async-Validator library for verification, which is also used in Element. The specific usage is not introduced here.

So far, jD-input and JD-form-item have been implemented, implementing two-way binding and real-time validation of data, but our validation now is for a single input. When we submit, we need to validate the entire form, which requires JD-Form. Let’s implement the final component of this article, JD-Form.

jd-formComponent implementation

As can be seen from the following code, the main function of JD-form is to receive all elements in the form with slots, obtain the data model model and verification rule rules with props, and provide a form attribute with a value of JD-form instance to its sub-components with provide. Its children can retrieve model and rules by injecting form properties instead of importing them separately. We also define a validate method that validates the entire form when the login is clicked. In this example, we use this.$children to get an array of its child instances. Filter out instances of sub-components that do not contain prop properties (in this case, the JD-form-item component that wraps the login button does not require validation), and then validate each instance by calling its validate method, which returns a Promise object. If all instances pass the validate, If one instance fails, the form fails to pass the verification. See the login method at the beginning of this article to get an instance of JD-form and validate the form.

<template> <div> <slot></slot> </div> </template> <script> export default { provide() { return { form: this } }, props: { model: Object, rules: Object }, methods: {validate(cb) {const task = this.$children.filter (item => item.prop).map(item => item.validate()); Promise.all(task).then(() => {cb(true); }).catch(() => { cb(false); }); } }, } </script>Copy the code

To sum up:

jd-form

  • Specify data and validation rules

jd-form-item:

  • Perform validation
  • Display error message

jd-input:

  • Maintain data

Github code link for this article