Preface:
In everyday WEB project development using VUE, there is often a need to submit forms. We can use a component library like iView or Element to do this; But we often ignore the implementation logic, if you want to understand the implementation details, this article from 0 to 1, teach you to encapsulate your own Form component! Example code github.com/zhengjunxia…
Form component overview
There are many form-like components, such as Input, Radio, Checkbox, and so on. Data validation is also often used when working with forms. It would be inefficient to write validators to validate the input values of each Form, so you need a component that validates the underlying Form controls, which is the Form component that we’ll do in this section. The Form component is divided into two parts, one is the outer Form field component. A set of Form controls has only one Form, and the inner contains multiple FormItem components, each Form control is wrapped by a FormItem. The basic structure looks like this:
<! -- ./src/views/Form.vue -->
<iForm ref="form" :model="formData" :rules="rules">
<iFormItem label="Name:" prop="name">
<iInput v-model="formData.name" ></iInput>
</iFormItem>
<iFormItem label="Email:" prop="mail">
<iInput v-model="formData.mail"></iInput>
</iFormItem>
<button @click="handleSubmit">submit</button>
<button @click="handleReset">reset</button>
</iForm>
Copy the code
Forms require input validation and a validation prompt in the corresponding FormItem. We use an open source library called Async-Validator for validation. The rules are as follows:
[{required: true.message: 'Cannot be empty'.trigger: 'blur' },
{ type: 'email'.message: 'Format is not correct'.trigger: 'blur'}]Copy the code
Required indicates a mandatory item, message indicates a message indicating a verification failure, trigger indicates a verification condition, and its values include blur and change indicating a loss of focus and verification during input. If the first verification rule meets the requirements, perform the second verification. Type indicates the verification type, email indicates that the verification input value is in the email format, and user-defined verification rules are supported. See its documentation for more details on its use.
Initialize the project
Create a project using Vue CLI 3 and download the Async-Validator library.
After initializing the project, create a new form folder under SRC/Components and initialize the two components form.vue and FormItem. vue and an input.vue, and configure the route as you wish. After initializing the project, the items in the SRC directory are as follows:
The. / SRC ├ ─ ─ App. Vue ├ ─ ─ assets │ └ ─ ─ logo. The PNG ├ ─ ─ components │ ├ ─ ─ form │ │ ├ ─ ─ form. The vue │ │ └ ─ ─ formItem. Vue │ └ ─ ─ Input. Vue ├ ─ ─ main. Js ├ ─ ─ mixins │ └ ─ ─ emitter. Js ├ ─ ─ the router, js └ ─ ─ views └ ─ ─ Form. The vueCopy the code
Interface implementation
The interface for the component comes from three parts: props, slots, and Events. The Form and FormItem components are used to verify input data. Events are not used. Form slots are a series of formItems, and FormItem slots are concrete forms such as
.
In the Form component, define two props:
- model: Data object bound to the form control that can be accessed during verification or reset. The type is
Object
. - rules: Form validation rules, as described above
async-validator
The verification rule used, of typeObject
.
In the FormItem component, we also define two props:
- label: Label text for a single form component, similar to native
<label>
Element of typeString
. - prop: corresponds to the form field
Form
componentmodel
To access the data bound to the form component during validation or reset, of typeString
.
Once defined, the code to call the page looks like this:
<template>
<div class="home"</h3> <iForm ref="form" :model="formData" :rules="rules">
<iFormItem label="Name:" prop="name">
<iInput v-model="formData.name"></iInput>
</iFormItem>
<iFormItem label="Email:" prop="mail">
<iInput v-model="formData.mail"></iInput>
</iFormItem>
</iForm>
</div>
</template>
<script>
// @ is an alias to /src
import iForm from '@/components/form/form.vue'
import iFormItem from '@/components/form/formItem.vue'
import iInput from '@/components/input.vue'
export default {
name: 'home',
components: { iForm, iFormItem, iInput },
data() {
return {
formData: { name: ' ', mail: ' ' },
rules: {
name: [{ required: true, message: 'Cannot be empty', trigger: 'blur'}],
mail: [
{ required: true, message: 'Cannot be empty', trigger: 'blur'},
{ type: 'email', message: 'Email format is not correct', trigger: 'blur'}
]
}
}
}
}
</script>
Copy the code
Implementation details of the iForm, iFormItem, and iInput components in the code are covered later.
So far, the code for the iForm and iFormItem components looks like this:
<! -- ./src/components/form/form.vue --> <template> <div> <slot></slot> </div> </template> <script>export default {
name: 'iForm'.data() {
return { fields: [] }
},
props: {
model: { type: Object },
rules: { type: Object }
},
created() {
this.$on('form-add', field => {
if (field) this.fields.push(field);
});
this.$on('form-remove', field => {
if (field.prop) this.fields.splice(this.fields.indexOf(field), 1);
})
}
}
</script>
Copy the code
<! -- ./src/components/form/formItem.vue --> <template> <div> <label v-if="label">{{ label }}</label>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'iFormItem',
props: {
label: { type: String, default: ' ' },
prop: { type: String }
}
}
</script>
Copy the code
The fields array is set in the iForm component to save the form instance in the component, which is convenient to obtain the form instance to judge the verification of each form. We bind two listener events, form-add and form-remove, in the Created life cycle to add and remove form instances.
Now we’re going to implement the bind event, but before we do that we’re going to have to imagine, how are we going to call the bind event method? In vue.js 1.x, there was a this.$dispatch method to bind custom events, but it was deprecated in vue.js 2.x. But we can implement a similar method called this.dispatch with $less to differentiate it from the old API. This method can be written to emitters. Js and referenced by mixins in components for code reuse. Create the mixins folder in SRC and create Emitters. Js in it as follows:
<! -- ./src/mixins/emitter.js -->export default {
methods: {
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root;
let name = parent.$options.name;
while(parent && (! name || name ! == componentName)) { parent = parent.$parent;if (parent) name = parent.$options.name;
}
if(parent) parent.$emit.apply(parent, [eventName].concat(params)); }}}Copy the code
As you can see, the Dispatch method compares the component’s $parent. Name with the componentName parameter. When the target parent component is found, it invokes $emit from the parent component to trigger the eventName binding event.
Formitem. vue then introduces the dispatch method via mixins to trigger the binding events form-add and form-remove as follows:
<! -- ./src/components/form/formItem.vue --> <template> <div> <label v-if="label">{{ label }}</label>
<slot></slot>
</div>
</template>
<script>
import Emitter from '@/mixins/emitter.js';
export default {
name: 'iFormItem',
mixins: [ Emitter ],
props: {
label: { type: String, default: ' ' },
prop: { type: String }
},
mounted() {
if (this.prop) {
this.dispatch('iForm'.'form-add', this); }}, // Remove the instance from the Form cache before the component is destroyedbeforeDestroy () {
this.dispatch('iForm'.'form-remove', this);
},
}
</script>
Copy the code
Formitem. vue: Formitem. vue: formitem. vue: formItem.vue: formItem.vue: formItem.vue
- in
Form.vue
中rules
Object throughprops
To pass toiForm
Component, then we can iniForm
Component throughprovide
To export the component instance and make it available to child componentsprops
In therules
Object; - Child components
formItem
Can be achieved byinject
To inject the instance that needs to be accessed.
The code is as follows:
<! -- ./src/components/form/form.vue --> ...export default {
name: 'iForm'.data() {
return { fields: [] }
},
props: {
model: { type: Object },
rules: { type: Object }
},
provide() {
return { form: this }
},
created() {
this.$on('form-add', field => {
if (field) this.fields.push(field);
});
this.$on('form-remove', field => {
if (field.prop) this.fields.splice(this.fields.indexOf(field), 1);
})
}
}
</script>
Copy the code
<! -- ./src/components/form/formItem.vue --> ... import Emitter from'@/mixins/emitter.js';
export default {
name: 'iFormItem',
mixins: [ Emitter ],
inject: [ 'form' ],
props: {
label: { type: String, default: ' ' },
prop: { type: String }
},
mounted() {
if (this.prop) {
this.dispatch('iForm'.'form-add', this); }}, // Remove the instance from the Form cache before the component is destroyedbeforeDestroy () {
this.dispatch('iForm'.'form-remove', this);
},
}
</script>
Copy the code
Now we can retrieve the rule object in formItem via this.form.rules; After having the rule object, you can set the specific verification method;
- SetRules: set specific events to be monitored and trigger verification;
- GetRules: Obtain the verification rules corresponding to the form;
- GetFilteredRule: filters out rules that meet requirements.
- Validate: The specific verification process; .
The specific code is as follows:
<! -- ./src/components/form/formItem.vue --> <template> <div> <label :for="labelFor" v-if="label" :class="{'label-required': isRequired}">{{label}}</label>
<slot></slot>
<div v-if="isShowMes" class="message">{{message}}</div>
</div>
</template>
<script>
import AsyncValidator from 'async-validator';
import Emitter from '@/mixins/emitter.js';
export default {
name: 'iFormItem',
mixins: [ Emitter ],
inject: [ 'form' ],
props: {
label: { type: String, default: ' ' },
prop: { type: String }
},
data() {
return {
isRequired: false, isShowMes: false, message: ' ', labelFor: 'input' + new Date().valueOf()
}
},
mounted() {
if (this.prop) {
this.dispatch('iForm'.'form-add', this); // set the initialValue this.initialValue = this.fieldValue; this.setRules(); }}, // Remove the instance from the Form cache before the component is destroyedbeforeDestroy () {
this.dispatch('iForm'.'form-remove', this);
},
computed: {
fieldValue() {
return this.form.model[this.prop]
}
},
methods: {
setRules() {
let rules = this.getRules();
if (rules.length) {
rules.forEach(rule => {
if(rule.required ! == undefined) this.isRequired = rule.required }); } this.$on('form-blur', this.onFieldBlur);
this.$on('form-change', this.onFieldChange);
},
getRules() {
let formRules = this.form.rules;
formRules = formRules ? formRules[this.prop] : [];
returnformRules; }, // filter out rules that meet the requirements getFilteredRule (trigger) {const rules = this.getrules ();returnrules.filter(rule => ! rule.trigger || rule.trigger.indexOf(trigger) ! = = 1); }, /** * validates form data * @param trigger validates type * @param callback */ validate(trigger, cb) {let rules = this.getFilteredRule(trigger);
if(! rules || rules.length === 0)return true; // Use async-validator const validator = new AsyncValidator({[this.prop]: rules});let model = {[this.prop]: this.fieldValue};
validator.validate(model, { firstFields: true }, errors => {
this.isShowMes = errors ? true : false;
this.message = errors ? errors[0].message : ' ';
if(cb) cb(this.message); })},resetField () {
this.message = ' ';
this.form.model[this.prop] = this.initialValue;
},
onFieldBlur() {
this.validate('blur');
},
onFieldChange() {
this.validate('change');
}
}
}
</script>
<style>
.label-required:before {
content: The '*';
color: red;
}
.message {
font-size: 12px;
color: red;
}
</style>
Copy the code
Note: in addition to the increase of the specific verification method, there are error message display logic
With the formItem component we can use it to wrap the Input component:
- in
input
Component through@input
和@blur
These two events are triggeredformItem
The component’sform-change
和form-blur
The listening method of.Special attention is requiredIn:handleInput
Is required to callthis.$emit('input', value)
,input
Entered in thevalue
Passed to the instance call pageformData
, the code is as follows:
<! <template> <div class=./ SRC /views/ form. vue --"home"</h3> <iForm ref="form" :model="formData" :rules="rules">
<iFormItem label="Name:" prop="name">
<iInput v-model="formData.name"></iInput>
</iFormItem>
<iFormItem label="Email:" prop="mail">
<iInput v-model="formData.mail"></iInput>
</iFormItem>
</iForm>
</div>
</template>
<script>
// @ is an alias to /src
import iForm from '@/components/form/form.vue'
import iFormItem from '@/components/form/formItem.vue'
import iInput from '@/components/input.vue'// The data in formData is bound by the test of the V-model. // Call this in the input component.$emit('input', value) passes the data to formDataexport default {
name: 'home',
components: { iForm, iFormItem, iInput },
data() {
return {
formData: { name: ' ', mail: ' ' }
}
}
}
</script>
Copy the code
- And in the component
watch
The input of thevalue
Value, assigned toinput
Components;
The implementation code is as follows:
<! -- ./src/components/input.vue --> <template> <div> <input ref="input" :type="type" :value="currentValue" @input="handleInput" @blur="handleBlur" />
</div>
</template>
<script>
import Emitter from '@/mixins/emitter.js';
export default {
name: 'iInput',
mixins: [ Emitter ],
props: {
type: { type: String, default: 'text'},
value: { type: String, default: ' '}
},
watch: {
value(value) {
this.currentValue = value
}
},
data() {
return { currentValue: this.value, id: this.label }
},
mounted () {
if (this.$parent.labelFor) this.$refs.input.id = this.$parent.labelFor;
},
methods: {
handleInput(e) {
const value = e.target.value;
this.currentValue = value;
this.$emit('input', value);
this.dispatch('iFormItem'.'form-change', value);
},
handleBlur() {
this.dispatch('iFormItem'.'form-blur', this.currentValue);
}
}
}
</script>
Copy the code
This completes the input component, and we can now proceed to validate all forms and reset the used forms when the form component implements form submission:
<! -- ./src/components/form/form.vue --> <template> <div> <slot></slot> </div> </template> <script>export default {
name: 'iForm'.data() {
return { fields: [] }
},
props: {
model: { type: Object },
rules: { type: Object }
},
provide() {
return { form: this }
},
methods: {
resetFields() {
this.fields.forEach(field => field.resetField())
},
validate(cb) {
return new Promise(resolve => {
let valid = true, count = 0;
this.fields.forEach(field => {
field.validate(' ', error => {
if (error) valid = false;
if (++count === this.fields.length) {
resolve(valid);
if (typeof cb === 'function') cb(valid); }})})})}},created() {
this.$on('form-add', field => {
if (field) this.fields.push(field);
});
this.$on('form-remove', field => {
if (field.prop) this.fields.splice(this.fields.indexOf(field), 1);
})
}
}
</script>
Copy the code
- Validate: Obtain the verification results of all forms and perform corresponding logical processing;
- ResetFields: Resets all forms;
Now let’s go back to the original call page./ SRC /views/ form.vue and add two buttons for submitting and resetting the Form:
<! -- ./src/views/Form.vue --> <template> <div class="home"</h3> <iForm ref="form" :model="formData" :rules="rules">
<iFormItem label="Name:" prop="name">
<iInput v-model="formData.name"></iInput>
</iFormItem>
<iFormItem label="Email:" prop="mail">
<iInput v-model="formData.mail"></iInput>
</iFormItem>
<button @click="handleSubmit"> submit </button> < button@click ="handleReset"Reset > < / button > < / iForm > < / div > < / template > < script > / / @ is the analias to /src
import iForm from '@/components/form/form.vue'
import iFormItem from '@/components/form/formItem.vue'
import iInput from '@/components/input.vue'
export default {
name: 'home',
components: { iForm, iFormItem, iInput },
data() {
return {
formData: { name: ' ', mail: ' ' },
rules: {
name: [{ required: true, message: 'Cannot be empty', trigger: 'blur'}],
mail: [
{ required: true, message: 'Cannot be empty', trigger: 'blur'},
{ type: 'email', message: 'Email format is not correct', trigger: 'blur'}
]
}
}
},
methods: {
handleSubmit() {
this.$refs.form.validate((valid) => {
if (valid) console.log('Submitted successfully');
else console.log('Verification failed'); })},handleReset() { this.$refs.form.resetFields() }
}
}
</script>
Copy the code
At this point, the basic functions of the Form component are complete. Although it is just a few simple Form controls, it already implements verification and prompt functions.
Example code: github.com/zhengjunxia…
The conclusion can further understand vue.js components by encapsulating them, such as provide/Inject and dispatch communication scenarios. It is of great help to the future development.