Vue imitates ElementUI's form example code

Vue imitates ElementUI's form example code

Implementation requirements

The form imitating ElementUI is divided into four layers: 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 the Form component, FormItem component, and Input component respectively to realize assembly;

Form form component:

  • Implementation: reserved slots, management data model model, custom validation rules, global validation method validate;

FormItem form item component:

  • Implementation: reserve slots, display labels, perform data verification, and display verification results;

Input and CheckBox components:

  • Implementation: Bind the data model v-model and notify the FormItem component to perform validation;

Input Component

The specific implementation is as follows:

1. Custom components must implement :value and @input to implement v-model.

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

3. When the type of the Input component is password, use v-bind="$attrs" inside the component to obtain content other than props.

4. Set inheritAttrs to false to prevent the top-level container from inheriting 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 inheriting properties props: {
 value: {
 type: String,
 default: ""
 }
 },
 data() {
 return {};
 },
 methods: {
 onInput(e) {
 // Notify the parent component of the value change this.$emit("input", e.target.value);
 
 // Notify FormItem to perform validation // This writing method is not robust because there may be generations between the Input component and the FormItem component this.$parent.$emit("validate");
 }
 }
};
</script>

<style scoped></style>

Note: The code uses this.$parent to dispatch events. This writing method is not robust and will cause problems when there is generation gap between the Input component and the FormItem component. For specific solutions, see 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. You 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", e.target.checked);
 this.$parent.$emit("validate");
 }
 }
};
</script>
<style scoped lang="less"></style>

FormItem component

The specific implementation is as follows:

1. Reserve a slot 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 for validation events and perform validation (using the async-validator plugin for validation).

4. If the verification rules are not met, the verification results need 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 verified within the component?

2. There will be multiple menu items in the Form form, such as: Username, Password, Email... and so on. How does the FormItem component know which menu is being verified?

FormItem component implementation:

<template>
 <div class="formItem-wrapper">
 <div class="content">
 <label v-if="label" :style="{ width: labelWidth }">{{ label }}:</label>
 <slot></slot>
 </div>
 <p v-if="errorMessage" class="errorStyle">{{ errorMessage }}</p>
 </div>
</template>

<script>
import Schema from "async-validator";

export default {
 inject: ["formModel"],
 props: {
 label: {
 type: String,
 default: ""
 },
 prop: String
 },
 data() {
 return {
 errorMessage: "",
 labelWidth: this.formModel.labelWidth
 };
 },
 mounted() {
 // Listen for validation events and perform validation this.$on("validate", () => {
 this.validate();
 });
 },
 methods: {
 validate() {
 // Execute component verification // 1. Get data const values ​​= this.formModel.model[this.prop];
 // 2. Get verification rules const rules = this.formModel.rules[this.prop];
 // 3. Execute verification const schema = new Schema({
 [this.prop]: rules
 });

 // Parameter 1 is the value, and parameter 2 is an array of validation error objects. // validate returns Promise<Boolean>
 return schema.validate({ [this.prop]: values ​​}, errors => {
 if (errors) {
 this.errorMessage = errors[0].message;
 } else {
 this.errorMessage = "";
 }
 });
 }
 }
};
</script>

<style scoped lang="less">
@labelWidth: 90px;

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

Let's first answer the two questions raised above. This involves value transfer between components. You can refer to the previous article "Component Value Transfer and Communication":
First, the form data and validation rules are defined inside the index component and mounted on the Form component. The form validation items occur in the FormItem component. The Form component first receives the passed data through props, and then passes it to the descendant components in the FormItem component through provide/inject.

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

In the FormItem component, inject is used to obtain the injected Form instance, and combined with the prop attribute, the form data and validation rules can be obtained.

// 1. Get data const values ​​= this.formModel.model[this.prop];

// 2. Get verification rules const rules = this.formModel.rules[this.prop];

Use the async-validator plug-in to instantiate a schema object for validation. schema.validate needs to pass two parameters. Parameter 1 is a key-value pair object consisting of the field currently to be validated and the corresponding rules. Parameter 2 is a callback function used 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 at the end is to perform global validation in the Form component.

Form Component

The specific implementation is as follows:

1. Reserve a slot for the FormItem component.

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

3. Perform global verification

Form component implementation:

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

<script>
export default {
 provide() {
 return {
 formModel: this // Pass the Form instance to descendants, such as FormItem to obtain validation data and rules};
 },
 props: {
 model: {
 type: Object,
 required: true
 },
 rules:
 type: Object
 },
 labelWidth: String
 },
 data() {
 return {};
 },
 methods: {
 validate(cb) {
 //Perform global validation //The map result is an array of Promises const tasks = this.$children.filter(item => item.prop).map(item => item.validate());
 // All tasks must be verified successfully to be considered verified Promise.all(tasks)
 .then(() => {
 cb(true);
 })
 .catch(() => {
 cb(false);
 });
 }
 }
};
</script>

<style scoped></style>

We use provide in the Form component to inject the current component object, making it easier for subsequent descendants to obtain data/methods.

When performing global validation, first use filter to filter out components that do not need to be validated (the prop attribute we set on the FormItem component needs to be validated as long as there is this attribute), and then execute the validate method in the component separately (if return data is not used in the FormItem component, all the data obtained in the end is undefined), and return an array of Promises.

Briefly introduce a Promise.all() method:

The Promise.all() method receives an input of an iterable type of promise (Note: Array, Map, Set all belong to the iterable type of ES6) and returns only one Promise instance. The result of the resolve callback of all the promises of that input is an array. The resolve callback of this Promise is executed when the resolve callbacks of all input promises are completed, or when there are no promises in the input iterable. Its reject callback is executed, as long as any input promise's reject callback is executed or an invalid promise is input, an error will be thrown immediately, and the first error message thrown will be rejected.

index component

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

Index component implementation:

<template>
 <div>
 <Form :model="formModel" :rules="rules" ref="loginForm" label-width="90px">
 <FormItem label="username" prop="username">
 <Input v-model="formModel.username"></Input>
 </FormItem>
 <FormItem label="Password" prop="password">
 <Input type="password" v-model="formModel.password"></Input>
 </FormItem>
 <FormItem label="Remember password" prop="remember">
 <CheckBox v-model="formModel.remember"></CheckBox>
 </FormItem>
 <FormItem>
 <button @click="onLogin">Login</button>
 </FormItem>
 </Form>
 </div>
</template>

<script>
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) => {
 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) => {
 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.$refs.loginForm.validate(isValid => {
 if (isValid) {
 alert("Login successful");
 } else {
 alert("Login failed");
 }
 });
 }
 },
 components:
 Input,
 CheckBox,
 FormItem,
 Form
 }
};
</script>

<style scoped></style>

When we click the login button, the global validation method will be executed, and we can use this.$refs.xxx to get the DOM element and component instance.

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. There is a disadvantage in this way. 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 the upward loop to find the parent element and dispatch events. The code is as follows:

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

This method can be introduced using mixins: mixins/emmiters.js

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

Modify the Input component:

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

<script>
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 value change this.$emit("input", e.target.value);
 
 // Notify FormItem to perform validation // This approach is not robust because there may be generations between the Input component and the FormItem component // this.$parent.$emit("validate");
 
 this.dispatch("validate"); // Use the dispatch of emmiter in mixin to solve the cross-level problem}
 }
};
</script>

<style scoped></style>

Summarize

This is the end of this article about Vue imitating ElementUI's form. For more relevant Vue imitating ElementUI form content, please search 123WORDPRESS.COM's previous articles or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Vue ElementUI Form form validation
  • Vue elementUI form nested validation example code
  • Vue elementUI form validation function array multi-layer nesting
  • Implementation of Vue elementui form validation
  • Vue basics detailed explanation of ElementUI's form

<<:  Detailed installation and use of virtuoso database under Linux system

>>:  Mybatis paging plug-in pageHelper detailed explanation and simple example

Recommend

JavaScript implements simple calculator function

This article shares the specific code of JavaScri...

The difference between hash mode and history mode in vue-router

vue-router has two modes hash mode History mode 1...

mysql8.0 windows x64 zip package installation and configuration tutorial

MySQL 8 Windows version zip installation steps (d...

Detailed explanation of how to view the current number of MySQL connections

1. View the detailed information of all current c...

Detailed explanation of using scp command to copy files remotely in Linux

Preface scp is the abbreviation of secure copy. s...

Working principle and implementation method of Vue instruction

Introduction to Vue The current era of big front-...

Docker container log analysis

View container logs First, use docker run -it --r...

Detailed explanation of PHP+nginx service 500 502 error troubleshooting ideas

Overview When a 500 or 502 error occurs during ac...

How to force vertical screen on mobile pages

I recently wrote a mobile page at work, which was...

WePY cloud development practice in Linux command query applet

Hello everyone, today I will share with you the W...

Nginx reverse proxy springboot jar package process analysis

The common way to deploy a springboot project to ...

...

Solve the problem that ElementUI custom CSS style does not take effect

For example, there is an input box <el-input r...

JavaScript to implement a simple clock

This article example shares the specific code for...