vuejs-tips / vue-the-mask

Tiny (<2k gzipped) and dependency free mask input for Vue.js
https://vuejs-tips.github.io/vue-the-mask/
1.74k stars 213 forks source link

Example of how to use vue-the-mask with antd #168

Open Pacheco95 opened 3 years ago

Pacheco95 commented 3 years ago

I'm adding this example as an issue for those who wants to know how to integrate this awesome lib with ant design for vuejs (antd).

If you go to ant input docs you will find an example of a customized form control. If the link does not scroll to the correct doc section, all you just need to do is to search for "Customized Form Controls".

As you can see, to use antd with 3rd party libraries, there are somre requirements that should be follwed:

Customized or third-party form controls can be used in Form, too. Controls must follow these conventions:

  • It has a controlled property value or other name which is equal to the value of valuePropName-parameters).
  • It has event onChange or an event which name is equal to the value of trigger-parameters).
  • It must be a class component.

Here I'll show you how to create a masked input for brazilian phone input numbers and I'll also use class components and typescript but you can quickly convert the code to "pure" vuejs code. So, let's get our hands dirty!

First of all, you need to create an wrapper component to your custom input like this:

<template>
  <InputMask
    class="ant-input"
    :value="value"
    :mask="['(##) ####-####', '(##) # ####-####']"
    @input="change"
  />
</template>

<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import { Emit, Model } from 'vue-property-decorator'
import { TheMask } from 'vue-the-mask'

@Component({
  components: {
    InputMask: TheMask,
  },
})
export default class BRLPhoneInput extends Vue {
  @Model('change', { type: String })
  readonly value!: string

  @Emit()
  change(value: string) {
    return value
  }
}
</script>

And use it in another component!

<template>
  <div class="container">
    <a-form layout="inline" :form="form" @submit="handleSubmit">
      <a-form-item label="Phone">
        <BRLPhoneInput
          v-decorator="[
            'phone',
            {
              initialValue: value,
              rules: [{ validator: validatePhoneNumber }],
            },
          ]"
        />
      </a-form-item>
      <a-form-item>
        <a-button type="primary" html-type="submit"> Submit </a-button>
      </a-form-item>
    </a-form>
    <div v-show="submittedValue !== null" style="margin-top: 16px">
      <span>Submitted raw phone number:</span>
      <a-tag color="green">{{ submittedValue }}</a-tag>
    </div>
  </div>
</template>

<script lang="ts">
import { Vue, Component } from 'nuxt-property-decorator'
import { WrappedFormUtils } from 'ant-design-vue/types/form/form'
import BRLPhoneInput from '~/components/BRLPhoneInput.vue'

@Component({
  components: {
    BRLPhoneInput,
  },
})
export default class extends Vue {
  form!: WrappedFormUtils
  value = '99999999999'

  submittedValue = null

  beforeCreate() {
    this.form = this.$form.createForm(this, {
      mapPropsToFields: () => ({
        phone: this.$form.createFormField(this.value),
      }),
    })
  }

  handleSubmit(e: Event) {
    e.preventDefault()
    this.form.validateFields((err, values) => {
      if (!err) {
        console.log('Values from form', values)
        this.submittedValue = values.phone
      } else {
        console.error(err)
      }
    })
  }

  validatePhoneNumber(
    _rule: any, // which role is being validated
    value: string | undefined,
    callback: (error?: Error) => void
  ) {
    if (!value) {
      return callback(new Error('Required field'))
    }
    if (value && value.length !== 11) {
      return callback(new Error('BRL phone numbers have 11 digits'))
    }
    callback()
  }
}
</script>

Here, antd will pass initialValue from v-decorator to our BRLPhoneInput component as a prop and everything will work perfectly as expected! See demo below.

gif

Hope it hepls!

Pacheco95 commented 3 years ago

In a more reusable way

// masked-input.vue
<template>
  <InputMask class="ant-input" :value="value" :mask="mask" @input="change" />
</template>

<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import { Emit, Model, Prop } from 'vue-property-decorator'
import { TheMask } from 'vue-the-mask'

@Component({
  components: {
    InputMask: TheMask,
  },
})
export default class MaskedInput extends Vue {
  @Prop({ type: [String, Array], required: true })
  readonly mask!: string | string[]

  @Model('change', { type: String })
  readonly value!: string

  @Emit()
  change(value: string) {
    return value
  }
}
</script>