Cleave.js: Cleave is broken with Vue

Created on 4 Apr 2019  路  6Comments  路  Source: nosir/cleave.js

Cleave is not working with vue/vuetify
https://github.com/vuetifyjs/vuetify/issues/6910#issuecomment-479798524

Most helpful comment

There is really nothing wrong with Cleave or Vuetify. Here is my v-text-field wrapper component which works perfectly with Cleave.

<script>
import Cleave from 'cleave.js';

export default {
  props: {
    value: {
      type: [String, Number],
      required: false,
      default: '',
    },
  },
  data() {
    return {
      currentValue: this.value,
    };
  },
  mounted() {
    this.cleave = new Cleave(this.$el.querySelector('input'), {
      numeral: true,
      onValueChanged: this.onValueChanged.bind(this),
    });

    this.cleave.setRawValue(this.value);
  },
  methods: {
    onValueChanged({ target }) {
      this.$nextTick(() => {
        this.currentValue = target.value;
        this.$emit('input', target.rawValue);
      });
    },
  },
};
</script>

<template>
  <v-text-field
    v-model="currentValue"
    v-bind="$attrs"
    :label="$t(domain)"
    height="22"
    class="caption rbx-text-input"
  />
</template>

I would like to mention two specific lines here.

this.$nextTick(() => { ... } in onValueChanged method

This is the actual trick to prevent entering non-numeric characters to target input. Also, to be able to show Cleave formatted value properly in a Vuetify textarea, you have to use a $nextTick. Without this formatting will be wrong.


this.cleave.setRawValue(this.value); in the mounted hook

This will set the initial value you got from your server so that Cleave can format it properly. Without this line, if you use custom delimiter and numeralDecimalMark in Cleave options, Cleave won't be able to show correctly formatted value.

All 6 comments

There is really nothing wrong with Cleave or Vuetify. Here is my v-text-field wrapper component which works perfectly with Cleave.

<script>
import Cleave from 'cleave.js';

export default {
  props: {
    value: {
      type: [String, Number],
      required: false,
      default: '',
    },
  },
  data() {
    return {
      currentValue: this.value,
    };
  },
  mounted() {
    this.cleave = new Cleave(this.$el.querySelector('input'), {
      numeral: true,
      onValueChanged: this.onValueChanged.bind(this),
    });

    this.cleave.setRawValue(this.value);
  },
  methods: {
    onValueChanged({ target }) {
      this.$nextTick(() => {
        this.currentValue = target.value;
        this.$emit('input', target.rawValue);
      });
    },
  },
};
</script>

<template>
  <v-text-field
    v-model="currentValue"
    v-bind="$attrs"
    :label="$t(domain)"
    height="22"
    class="caption rbx-text-input"
  />
</template>

I would like to mention two specific lines here.

this.$nextTick(() => { ... } in onValueChanged method

This is the actual trick to prevent entering non-numeric characters to target input. Also, to be able to show Cleave formatted value properly in a Vuetify textarea, you have to use a $nextTick. Without this formatting will be wrong.


this.cleave.setRawValue(this.value); in the mounted hook

This will set the initial value you got from your server so that Cleave can format it properly. Without this line, if you use custom delimiter and numeralDecimalMark in Cleave options, Cleave won't be able to show correctly formatted value.

For those that are still having issues binding Cleave to this.$refs.input (or whatever) -- I found a surprisingly easy fix that hasn't been documented yet:

v-model.lazy="foo"

The .lazy modifier makes Vue wait for Cleave to validate by binding to change events (instead of input events).

@fatihacet thanks this works for me.. for other people reference :)
but i was looking to make this as directive much easier to apply and maintain i guess ?

`<template>
    <v-text-field v-model="currentValue" v-bind="$attrs"></v-text-field>
</template>

<script lang="ts">
import { Component, Vue, Prop, Watch } from 'vue-property-decorator'
import Cleave from 'cleave.js'
import { CleaveOptions } from 'cleave.js/options'

@Component
export default class VTextFormat extends Vue {
    @Prop() value!: any
    @Prop(Object) readonly option!: CleaveOptions
    @Prop(Boolean) readonly raw!: boolean

    cleave: Cleave = null
    currentValue = this.value

    mounted () {
        const input = this.$el.querySelector('input')
        this.cleave = new Cleave(input, {
            ...this.option,
            onValueChanged: this.onValueChanged.bind(this)
        })

        this.cleave.setRawValue(this.value)
    }

    onValueChanged ({ target }) {
        this.$nextTick(() => {
            let value = this.raw ? target.rawValue : target.value
            this.currentValue = target.value
            this.$emit('input', value)
        })
    }

    beforeDestroy () {
        if (this.cleave) {
            this.cleave.destroy()
            this.cleave = null
        }
    }
}
</script>`

Or as directive (Vuetify), it is hackish but works for now.

Vue.directive('cleave', {
  bind(el, binding, vnode) {
    const input = el.querySelector('input')
    // @ts-ignore
    input._vCleave = new Cleave(input, binding.value)
  },
  update: (el) => {
    const input = el.querySelector('input')
    const event = new Event('input', {bubbles: true});
    setTimeout(function () {
      input.value = input._vCleave.properties.result
      input.dispatchEvent(event)
    }, 100);
  },
  unbind(el) {
    const input = el.querySelector('input')
    // @ts-ignore
    input._vCleave.destroy()
  }
})

@harlet That works good but keep in mind that you cannot fill form data with a click anymore. The data shows up for a second and disappears. But for now its a decent workaround to the issue. Just wondering if this is gonna get properly fixed any time?!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

YanikCeulemans picture YanikCeulemans  路  3Comments

wcedmisten picture wcedmisten  路  4Comments

d3bgger picture d3bgger  路  4Comments

Jahleel17 picture Jahleel17  路  3Comments

qm3ster picture qm3ster  路  4Comments