The first two chapters reviewed the two basic knowledge points needed by custom component, laying a foundation for the study of custom component Form. To write a custom component Form, we need to know the following:

I. Functional requirements of Form components

  • Implement model binding of data
  • Form validation rules
  • Global validation

Analysis of elementUI structure and technical implementation

UI components most people look for directly at Element, so let’s look at elementUI’s form form. First, take a look at Element’s official case:

<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm"> <el-form-item Label =" active name" prop="name"> <el-input V-model =" ruleform. name"></el-input> </el-form-item> <el-form-item label=" Active area" Prop ="region"> <el-select V-model =" ruleform. region" placeholder=" select active region"> <el-option label=" placeholder" Value =" Shanghai "></el-option> <el-option label=" area 2 "value=" Beijing "></el-option> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm('ruleForm')" @click="resetForm('ruleForm')"> reset </el-button> </el-form-item> </el-form> </el-form> <script> export default {data() {return { RuleForm: {name: ", region: ",}, rules: {name: [{required: true, message: 'Please enter the activity name ', trigger: 'blur'}, {min: 3, Max: 5, message: 'length between 3 and 5 characters ', trigger: 'blur'}], region: [{required: true, message:' Please select active region ', trigger: 'change' } ] } }; }, methods: { submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { alert('submit! '); } else { console.log('error submit!! '); return false; }}); }, resetForm(formName) { this.$refs[formName].resetFields(); } } } </script>Copy the code

Take the basic case on Element’s website. There is a clear nesting relationship, with the form in the outermost layer, the formItem in the inner layer, and the Input in the inner layer.

  • Form’s tasks: global management data model, validation rules, and global validation
  • FormItem: displays label -label, performs verification, and displays verification results
  • Input: Collect data (bind data model, inform formItem to perform validation)

Custom Form component logic

1. Environmental technology

  • vue-cli 3.x
  • node.10.x
  • vuejs 2.x

2. Project directory structure

index

form

formItem

qInput

3. Problems encountered

  • How does qInput implement data binding?
  • When does formItem perform validation, and how are the validation data and rules obtained?
  • Qinput is already bound to data, why should form be bound to V-Model?
  • How do forms perform global validation?

4, effects,

Four, custom Form Form code implementation

A. Create A qinput.vue on the home page in the following format:

<div> <! <input :type="type" :value="value" @input="onInput" v-bind="$attrs"> </div>Copy the code

You can’t write type dead, so you double bind it with value @input, which is passed in from the parent formItem, and you listen for the @input event, but you don’t have to do anything except do event forwarding. The code is as follows:

Export default {// 1. Double bind // 2. Name: 'yqInput', inheritAttrs: false, props: {type: {type: String, default: 'text'}, value: {type: String, default: '' } }, methods: { onInput (e) { // this.$emit('input', e.target.value) } } }Copy the code

The emit component does not change the value, but only sends the event. This is a one-way data flow concept. After the parent formItem sends the value, the input component displays the value.

Attrs is used to receive parameters passed by the parent component. Such parameters are undeclared for props. Native features can be attrs and bound with v-bind, which expands attrs. The side effect is that the parent component inherits properties as well, so we need to set inheritAttrs: false to not inherit properties. The effect is as follows:

B. Create an index.vue, import the input component, bind the data as follows:

<template> <div> <h3> <hr> <yq-input V-model ="model.username" placeholder=" placeholder "></yq-input> {{model}} </div> </template> <script> import YqInput from './yqInput' export default { name: 'index.vue', components: { YqInput }, data () { return { model: { username: 'yq', password: '' } } } } </script>Copy the code

The effect is as follows:

C. Create formItem.vue according to the logical layout format originally designed. In formItem, label title and error message need to be displayed, label title and error message need to be displayed as follows:

<template> <div> <! --label--> <label v-if="label">{{label}}</label> <slot></slot> <p v-if="errorMsg">{{errorMsg}}</p> </div> </template> <script> export default { name: 'formItem', props: { label: { type: String, default: '' }, prop: Type: String, default: ''}}, data () {return {errorMsg: ''}}} </script>Copy the code

Also, reference the FormItem component in index as follows:

<template> <div> <h3> form </h3> <hr> <form-item label=" prop "="username"> < yQ-input V-model ="model.username" Placeholder =" placeholder "></yq-input> </form-item> {{model}} </div> </template> <script> import YqInput from './ YqInput ' import FormItem from './formItem' export default { name: 'form.vue', components: { YqInput, FormItem }, data () { return { model: { username: 'yq', password: '' } } } } </script>Copy the code

D. Create the outermost Form component, add provide (for all descendants to obtain the parameters of the ancestor Form), and receive the model and rules as follows:

<template>
    <div>
     <slot></slot>
    </div>
</template>
<script>
export default {
  name: 'form.vue',
  provide () {
    return {
      form: this
    }
  },
  props: {
    model: {
      type: Object,
      required: true
    },
    rules: {
      type: Object
    }
  },
  data () {
    return {
    }
  }
}
</script>
Copy the code

At the same time, reference the Form component in index, bind the Model and rules properties, add the login button, add the global validation click event, as follows:

<template> <div> < H3 > form </h3> <hr> < YQ-form :model="model" :rules="rules"> <form-item label=" prop "="username"> <yq-input v-model="model.username" placeholder=" please input username" ></yq-input> </form-item> <form-item> <button </button> </form-item> </yq-form> </div> </template> <script> import YqInput from './ YqInput 'import FormItem from './formItem' import YqForm from './form' export default { name: 'index.vue', components: { YqInput, FormItem, YqForm }, data () { return { model: { username: 'yq', password: '' }, rules: { username: [{required: true, message:' Please input user name '}]}}}, methods: {onlogin () {// global validation}}} </script>Copy the code

Inject value into the Form component and validate validate in the formItem component. First, fomeItme injects value into the Form component by injecting value (omits previously written code) as follows:

. export default { name: 'formItem', inject: ['form'], ...Copy the code

FomeItme’s child input notifies validation

$emit('input', e.target.value) {$parent.$emit('validate')}Copy the code
But the parent component is clearly coupled to the parent component, and Element’s official name is Minxins, which are added at the end of the document.

Meanwhile, fomrItem itself listens and executes a validate method as follows:

mounted () { this.$on('validate', () => { this.validate() }) }, methods: { validate () { // 1. Const rules = this.form.rules[this.prop] const value = this.form.model[this.prop]}}Copy the code

To validate rules, you need to install a library called Async-Validator, which makes validation rules. It supports asynchracy very well. Element uses this library. Github.com/tmpfs/async…

npm i -S async-validator
Copy the code

Import this library in formItme,

import Schema from 'async-validator'
Copy the code

An example of creating a Schema in a method is as follows:

Const rules = this.form.rules[this.prop] const value = this.form.model[this.prop] // 2. {username: rules} const Schema = new Schema({[this.prop]: rules}) // 3 Return schema.validate({[this.prop]: value}, (errors) => { if (errors) { this.errorMsg = errors[0].message } else { this.errorMsg = '' } }) }Copy the code

Return To tell peripheral components that the result of an execution is true or false, the validate method returns a promise execution result:

If the element uses ref and has a validate event, add ref to the index component (omit the same code) :

. <yq-form :model="model" :rules="rules" ref="loginForm"> ... $refs['loginForm']. Validate (isValid => {if (isValid) {alert(' valid ')} else {alert(' error ')}} })}...Copy the code

Add a validate event to the form component and receive a callback that fails or succeeds. Not all items need to be validated. The value of the prop attribute is required for validation.

Methods: {validate (cb) {// global check // 1. Not all items need validation, so filter out items that do not have a prop property and return true to leave, perform all validation methods in the leave array, and execute the component instance's validate method, Const tasks = this.$children.filter(item => item.prop). Map (item =>) Then promise.all (tasks).then(() => cb(true)).catch(() => cb(false)) }}Copy the code

Effect:

Promise:es6.ruanyifeng.com/#docs/promi…

Now that the binding data and validation of the basic form form are complete, the rest is up to styling.

Added minxins to fix parent’s high coupling problem:

1. Create a minxins.js file on the home page, which is used to recursively search for components and send events, and use minxins’ mixing syntax to mix methods into Vue’s methods for component expansion. The code is as follows:

export default { methods: {dispatch (the componentName, eventName, params) {/ / component name, the name of the event, the parameters of var parent = this. $parent | | this. $root / / look for the parent component, Var name = parent.$options.__proto__. Name while (parent && (! name || name ! $parent = parent.$parent if (parent) {name = parent. componentName}} If () {$parent = parent (parent) { parent.$emit.apply(parent, [eventName].concat(params)) } } } }Copy the code

2. Import the minxins.js file in main.js

import Mixin from 'components/uiPc/form/mixins'
Vue.mixin(Mixin)
Copy the code

3. Modify the formItem component as follows:

OnInput (e) {this.$emit('input', $emit('validate') this.dispatch('formItem', 'validate', e.target.value)}Copy the code