In the last section, we laid the technical groundwork for component development. In this section, we will enter the actual field

Let’s start by implementing a set of Form components. If you’ve used any of the popular component libraries on the market, you should know that a set of Form components should contain something like this

So let’s start with the next use case, starting with the use case, and working backwards with the components

<f-form :model="userForm" :rules="rules">
  <f-form-item label="Username" prop="username">
    <f-input v-model="userForm.username" placeholder="Please enter user name" />
  </f-form-item>
  <f-form-item label="Password" prop="password">
    <f-input type="password" v-model="userForm.password" placeholder="Please enter your password" />
  </f-form-item>
</f-form>

<script>
export default {
  data() {
    return {
      userForm: {
        username: ' '.password: ' '
      },
      rules: {
        username: [{required: true.message: 'Please enter a user name'}].password: [{required: true.message: 'Please enter your password'}]}}}}</script>
Copy the code

Implement it from the inside out, starting with input, then form-item, and then form, without further ado

f-input

Start with the input field. You might think it’s simple, but remember we said earlier that child components can’t directly modify the values that prop receives, so throw out your idea of v-model binding directly to prop.

If you don’t turn off alerts you’ll get warnings like this

You need to know that v-model is a syntactic sugar, and these two lines actually do the same thing

1<input v-model="username" />
<br/>
2<input :value="username" @input="username = $event.target.value" />
<br/>
{{username}}
Copy the code

Let’s run it and see what happens

So in F-input you can do this for binding passing

<template>
  <div>
    <input :value="value" @input="inputHandler" />
  </div>
</template>

<script>
  export default {
    props: {
      value: {
        type: String.default: ' '}},methods: {
      inputHandler(e) {
        this.$emit('input', e.target.value)
      }
    }
  }
</script>
Copy the code

At this time to try again, found that it is already ok, the console did not report a warning

And then you see a problem. The placeholder setting isn’t in effect because it’s being added to the F-Input instead of the input inside the F-Input

There is a paragraph in the API documentation on the official website

If this option is enabled, attributes bound to the parent component that are not accepted by props will be stored in attrs. You can use v−bind to attrs. You can use v−bind to attrs on any non-root component. Then we’ll tweak our F-input a little bit

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

<script>
  export default {
    inheritAttrs: true.// Omit some code
</script>
Copy the code

Perfect. You can see that the password input box has been set up

f-form-item

This is a little bit more difficult than the input box up here, but it’s pretty easy, and the point that I need to achieve here is

  • The label text
  • Reserve slots for form components
  • Check and error according to rules

The first two are easy, but you should be more afraid of the third one. There is a ready-made “wheel” called Async-Validator. As a programmer, there is no need to duplicate wheels 😂

Think about the form-item attributes of our previous use case: label and prop, and then configure the parameters to receive

<template>
  <div>
    <label v-if="label">{{label}}</label>
    <slot></slot>
    <p v-if="errorMessage">{{errorMessage}}</p>
  </div>
</template>

<script>
  export default {
    props: {
      label: {
        type: String,
        default: ''
      },
      prop: {
        type: String,
      }
    },
    data() {
      return {
        errorMessage: ''
      }
    },
  }
</script>
Copy the code

With step 1 and Step 2 complete, validate is the next step. The prop is used to get the validation rule from the rules bound to the form component and the value of the current form item from the form bound content. Here you can use provide/inject to pass data

<script> import Schema from 'async-validator' export default { inject: ['form'], props: { label: { type: String, default: '' }, prop: { type: String, } }, data() { return { errorMessage: '' } }, methods: {validate() {const rule = this.form.rules[this.prop] const value = this.form.model[this.prop] // Get the validation rule instance const description = {[this.prop]: Rule} const schema = new schema (description) return schema.validate({[this.prop]: Value}, (error, field) => {if(error) {this.errorMessage = error[0].message console.log(' ${field} failed '); } else { this.errorMessage = '' } }) } }, } </script>Copy the code

Then add a listener that validates the event dynamically when the internal form item value changes

mounted() {
	this.$on('validate', this.validate)
}
Copy the code

Modify the f-input content as well

Methods: {inputHandler(e) {this.$emit('input', e.target.value)}}Copy the code

OK ~ ~ form – item together live

f-form

After completing the Form-item and input components, the Form component is simple. Let’s take a look at what the form does

  • Receive validation rules and form data
  • Reserve slots for form-items
  • Global check method

First we implement the first two steps. It’s very simple. Don’t forget that the form-item uses inject data, which needs to be bound

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

<script>
  export default {
    provide() {
      return {
        form: this
      }
    },
    props: {
      model: {
        type: Object,
        required: true
      },
      rules: {
        type: Object
      }
    }
  }
</script>
Copy the code

Next, the global validation takes a callback that takes a Boolean to indicate whether the validation passes, uses promise. all to execute multiple validators, and returns false if any fail

methods: {
  validate(cb) {
    const validators = this.$children
    .filter(item= > item.prop)
    .map(item= > item.validate())

    Promise.all(validators)
    .then(() = > cb(true))
    .catch(() = > cb(false}})),Copy the code

Then modify the use case by adding a submit button and binding global validation

<template> <div> < F-form :model="userForm" :rules="rules" ref="formRef"> < F-form-item label=" prop "="username"> <f-input v-model=" userform. username" placeholder=" please input username" /> </f-form-item> <f-form-item label=" password" ="password"> <f-input type="password" v-model=" userform. password" placeholder=" please input password" /> </f-form-item> <button </ form> </div> </template> </ script> {submit() {this.$refs.formref.validate (valid => {if(valid) {console.log(' validated '); } else {console.log(' verification failed '); } }) } }, } </script>Copy the code

So far, we have roughly completed a set of form components. Of course, there is more to them than that. This is just a start, but I won’t go into details here

This article is part of the “Gold Nuggets For Free!” Event, click to view details of the event