SoFunction
Updated on 2025-04-05

Vue imitates ElementUI's form instance code

Implementation requirements

Forms that imitate ElementUI are divided into four-layer structures: index component, Form form component, FormItem form item component, Input and CheckBox components. The specific division of labor is as follows:

Index component:

  • Implementation: Introduce Form components, FormItem components, and Input components respectively to realize assembly;

Form form components:

  • Implementation: reserve slots, manage data model models, custom verification rules rules, global verification methods validate;

FormItem form item component:

  • Implementation: reserve slots, display label tags, perform data verification, display verification results;

Input and CheckBox components:

  • Implementation: bind the data model v-model and notify the FormItem component to perform verification;

Input component

The specific implementation is as follows:

1. To implement v-model, custom components, :value and @input must be implemented.

2. When the data in the input box changes, notify the parent component to perform the verification.

3. When the type type bound by the Input component is password, use v-bind="$attrs" inside the component to obtain content outside props.

4. Set inheritAttrs to false to avoid the top-level container inheritance attributes.

Input component implementation:

<template>
 <div>
 <input :value="value" @input="onInput" v-bind="$attrs" />
 </div>
</template>

<script>
export default {
 inheritAttrs: false, // Avoid top-level container inheritance attributes props: {
 value: {
 type: String,
 default: ""
 }
 },
 data() {
 return {};
 },
 methods: {
 onInput(e) {
 // Notify the parent component of the change in the numerical value this.$emit("input", );
 
 // Notify FormItem to perform verification // This writing is not robust, because there may be a generational separation between the Input component and the FormItem component. this.$parent.$emit("validate");
 }
 }
};
</script>

<style scoped></style>

Note: This.$parent is used in the code to dispatch events. This writing method is not robust, and problems will occur when the Input component and the FormItem component are separated by generations. For specific solutions, please refer to the code optimization section at the end of the article.

CheckBox Component

1. Customize the two-way data binding of checkBox, which is similar to input, and must implement :checked and @change.

CheckBox component implementation:

<template>
 <section>
 <input type="checkbox" :checked="checked" @change="onChange" />
 </section>
</template>

<script>

export default {
 props: {
 checked: {
 type: Boolean,
 default: false
 }
 },
 model: {
 prop: "checked",
 event: "change"
 },
 methods: {
 onChange(e) {
 this.$emit("change", );
 this.$parent.$emit("validate");
 }
 }
};
</script>
<style scoped lang="less"></style>

FormItem Component

The specific implementation is as follows:

1. Leave slots for the Input component or CheckBox component.

2. If the user sets the label attribute on the component, the label tag should be displayed.

3. Listen to the verification event and perform verification (usingasync-validator Plugin for verification).

4. If the verification rules do not comply with, the verification result needs to be displayed.

During the development process, we need to think about several issues:

1. How to obtain the data and verification rules that need to be checked within the component?

2. There will be multiple menu items in the Form form, such as: username, password, email..., etc. So how does the FormItem component know which menu is now verified?

FormItem component implementation:

&lt;template&gt;
 &lt;div class="formItem-wrapper"&gt;
 &lt;div class="content"&gt;
 &lt;label v-if="label" :style="{ width: labelWidth }"&gt;{{ label }}:&lt;/label&gt;
 &lt;slot&gt;&lt;/slot&gt;
 &lt;/div&gt;
 &lt;p v-if="errorMessage" class="errorStyle"&gt;{{ errorMessage }}&lt;/p&gt;
 &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
import Schema from "async-validator";

export default {
 inject: ["formModel"],
 props: {
 label: {
 type: String,
 default: ""
 },
 prop: String
 },
 data() {
 return {
 errorMessage: "",
 labelWidth: 
 };
 },
 mounted() {
 // Listen to the verification event and perform verification this.$on("validate", () =&gt; {
 ();
 });
 },
 methods: {
 validate() {
 // Perform component verification // 1. Obtain data const values = [];
 // 2. Obtain verification rules const rules = [];
 // 3. Perform verification const schema = new Schema({
 []: rules
 });

 // Parameter 1 is the value, and meal number 2 is the array of verification error objects // validate returns Promise<Boolean> return ({ []: values }, errors =&gt; {
 if (errors) {
  = errors[0].message;
 } else {
  = "";
 }
 });
 }
 }
};
&lt;/script&gt;

&lt;style scoped lang="less"&gt;
@labelWidth: 90px;

.formItem-wrapper {
 padding-bottom: 10px;
}
.content {
 display: flex;
}
.errorStyle {
 font-size: 12px;
 color: red;
 margin: 0;
 padding-left: @labelWidth;
}
&lt;/style&gt;

Let's first answer the two questions raised above. Here we will involve passing values ​​between components. You can refer to the previous article "Component value transmission, communication》:
First of all, the data and verification rules of the form are defined inside the index component and mounted on the Form component. The verification terms of the form occur in the FormItem component. First, the passed data is received through props within the Form component, and then passed to descendant components in the FormItem component through provide/inject.

When we use ElementUI for form verification, we will find that a prop attribute will be set on each form that needs to be checked, and the attribute value is consistent with the bound data. The purpose here is to be able to obtain relative verification rules and data objects when performing verification in the FormItem component.

In the FormItem component, form data and verification rules can be obtained by using inject to obtain the injected Form instance and combined with the prop attribute.

// 1. Obtain dataconst values = [];

// 2. Obtain verification rulesconst rules = [];

Use the async-validator plug-in to instantiate a schema object to perform verification. Two parameters need to be passed. Parameter 1 is a key-value pair object composed of the field that needs to be checked and the corresponding rules. Parameter 2 is a callback function to obtain error information (an array). The validate method returns a Promise<Boolean>.

Note: In the validate method of this component, the purpose of using return is to perform global verification usage in the Form component.

Form components

The specific implementation is as follows:

1. Reserve slots for the FormItem component.

2. Pass the Form instance to descendants, such as FormItem to obtain the data and rules for verification.

3. Perform global verification

Form component implementation:

&lt;template&gt;
 &lt;div&gt;
 &lt;slot&gt;&lt;/slot&gt;
 &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
 provide() {
 return {
 formModel: this // Pass a Form instance to descendants, such as FormItem to obtain the data and rules for verification };
 },
 props: {
 model: {
 type: Object,
 required: true
 },
 rules: {
 type: Object
 },
 labelWidth: String
 },
 data() {
 return {};
 },
 methods: {
 validate(cb) {
 // Perform global verification // The result is several Promise arrays const tasks = this.$(item =&gt; ).map(item =&gt; ());
 // All tasks must be successfully verified before they are considered to be passed (tasks)
 .then(() =&gt; {
 cb(true);
 })
 .catch(() =&gt; {
 cb(false);
 });
 }
 }
};
&lt;/script&gt;

&lt;style scoped&gt;&lt;/style&gt;

We use provide to inject the current component object in the Form component to facilitate subsequent generations to obtain data/methods.

When performing global verification, first use filter to filter out components that do not need verification (the prop attribute we set on the FormItem component, as long as this attribute is available, it is required to verify), and then execute the validate method in the component separately (if the return data is not used in the FormItem component, all the obtained components are undefined), and the return is a number of Promise arrays.

A brief introduction to a () method:

The () method receives input from a promise's iterable type (Note: Array, Map, and Set are all of the iterable types of ES6) and only returns a Promise instance. The result of all promise's resolve callbacks in that input is an array. The resolution callback of this Promise is executed when all input promise resolve callbacks end, or there is no promise in the input iterable. Its reject callback execution is that as long as any input reject callback execution of any input promise or the input illegal promise will immediately throw an error, and reject the first thrown error message.

index component

Define model data, verification rules, etc., and introduce Form components, FormItem components, and Input components respectively to realize assembly.

The index component implementation:

&lt;template&gt;
 &lt;div&gt;
 &lt;Form :model="formModel" :rules="rules" ref="loginForm" label-width="90px"&gt;
 &lt;FormItem label="username" prop="username"&gt;
 &lt;Input v-model=""&gt;&lt;/Input&gt;
 &lt;/FormItem&gt;
 &lt;FormItem label="password" prop="password"&gt;
 &lt;Input type="password" v-model=""&gt;&lt;/Input&gt;
 &lt;/FormItem&gt;
 &lt;FormItem label="Remember the password" prop="remember"&gt;
 &lt;CheckBox v-model=""&gt;&lt;/CheckBox&gt;
 &lt;/FormItem&gt;
 &lt;FormItem&gt;
 &lt;button @click="onLogin"&gt;Log in&lt;/button&gt;
 &lt;/FormItem&gt;
 &lt;/Form&gt;
 &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
import Input from "@/components/form/Input";
import CheckBox from '@/components/form/CheckBox'
import FormItem from "@/components/form/FormItem";
import Form from "@/components/form/Form";

export default {
 data() {
 const validateName = (rule, value, callback) =&gt; {
 if (!value) {
 callback(new Error("Username cannot be empty"));
 } else if (value !== "admin") {
 callback(new Error("Username Error - admin"));
 } else {
 callback();
 }
 };
 const validatePass = (rule, value, callback) =&gt; {
 if (!value) {
 callback(false);
 } else {
 callback();
 }
 };
 return {
 formModel: {
 username: "",
 password: "",
 remember: false
 },
 rules: {
 username: [{ required: true, validator: validateName }],
 password: [{ required: true, message: "Password required" }],
 remember: [{ required: true, message: "Remember password required", validator: validatePass }]
 }
 };
 },
 methods: {
 onLogin() {
 this.$(isValid =&gt; {
 if (isValid) {
 alert("Login successfully");
 } else {
 alert("Login failed");
 }
 });
 }
 },
 components: {
 Input,
 CheckBox,
 FormItem,
 Form
 }
};
&lt;/script&gt;

&lt;style scoped&gt;&lt;/style&gt;

When we click the login button, a global verification method is performed, and we can use this.$ to get the DOM element and component instances.

We also left a small tail above, which is to notify the parent component to perform verification in the Input component. Currently, this.$parent.$emit() is used. This has a disadvantage, that is, when the Input component and the FormItem component are separated by generations, the FormItem component cannot be obtained by using this.$parent.
We can encapsulate a dispatch method, which mainly implements upward loop search for parent elements and dispatch events. The code implementation is as follows:

dispatch(eventName, data) {
 let parent = this.$parent;
 // Find parent element while (parent) {
 // The parent element is triggered with $emit parent.$emit(eventName, data);
 // Recursively search for parent element parent = parent.$parent;
 }
}

This method can be introduced and used by borrowing mixins: mixins/

export default {
 methods: {
 dispatch(eventName, data) {
 let parent = this.$parent;
 // Find parent element while (parent) {
 // The parent element is triggered with $emit parent.$emit(eventName, data);
 // Recursively search for parent element parent = parent.$parent;
 }
 }
 }
};

Modify the Input component:

&lt;template&gt;
 &lt;div&gt;
 &lt;input :value="value" @input="onInput" v-bind="$attrs" /&gt;
 &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
import emmiter from "@/mixins/emmiter";

export default {
 inheritAttrs: false, // Avoid top-level container inheritance attributes mixins: [emmiter],
 props: {
 value: {
 type: String,
 default: ""
 }
 },
 data() {
 return {};
 },
 methods: {
 onInput(e) {
 // Notify the parent component of the change in the numerical value this.$emit("input", );
 
 // Notify FormItem to perform verification // This writing is not robust, because there may be a generational separation between the Input component and the FormItem component. // this.$parent.$emit("validate");
 
 ("validate"); // Use Emmiter's dispatch in mixin to solve cross-level problems }
 }
};
&lt;/script&gt;

&lt;style scoped&gt;&lt;/style&gt;

Summarize

This is the article about Vue imitating ElementUI form form. For more related contents of Vue imitating ElementUI form form, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!