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