É muito comum utilizar bibliotecas de componentes para facilitar o nosso trabalho do dia a dia, elas costumam ser extremamente dinâmicas, com um layout padronizado e testadas pela comunidade, mas oque acontece quando voce precisa de uma funcionalidade que não foi implementada? Esse foi um problema que aconteceu comigo recentemente e creio que ja tenha ocorrido com voce também.
Em um projeto estávamos utilizando a biblioteca Vuetify como base do nosso layout de formulários, e em vários locais na aplicação era nescessário utilizar o input de valores monetários, na época, o único plugin que existia para isso era o v-money ele fornece um componente de input que funciona perfeitamente, e uma diretiva que era oque eu queria utilizar ja que precisava manter o v-text-field que já estava aplicado no app inteiro. Foi ai que surgiu o problema, quando fui testar ele como diretiva por algum motivo interno ele converte o meu model para String, me forçando a fazer parse do valor para decimal em todos os lugares, gerando muitos pontos de possíveis erros e manutenção futura.
A solução que decidimos? Criar o nosso próprio componente de currency encapsulando o v-text-field em um componente que chamamos de currency-field. A idéia era bem simples capturar qualquer input gerado no v-text-field formatar ele no currency padrão do sistema( $ 0.000,00 ) e devolver o valor em decimal no model do componente. Para não ter que lidar com a parte de formatação eu utilizei a lib Numeral.js, vamos dar uma olhada no código nescessário para isso:
<template>
<v-text-field ref="currencyField" v-model="currencyFieldValue">
</v-text-field>
</template>
<script>
import numeral from 'numeral'
export default {
name: 'CurrencyField',
props: ['value', 'currencyPrefix'],
data() {
return {
fieldValue: null,
prefix: '$',
}
},
created() {
this.fieldValue = this.value
this.prefix = this.currencyPrefix || '$'
},
computed: {
currencyFieldValue: {
get() {
return this.fieldValue == null ?
"" :
numeral(this.fieldValue).format(`${this.prefix}0,0.00`)
},
set(newValue) {
this.currencyFieldValueChanged(newValue)
}
}
},
watch: {
value(newValue) {
if(newValue == this.fieldValue) return
this.fieldValue = newValue
this.updateViewValue(newValue)
},
},
methods: {
currencyFieldValueChanged(newVal) {
if(newVal == this.currencyFieldValue) return
if (!newVal.length) {
this.updateModelValue(null)
return
}
let newParsedValue = parseFloat(newVal.replace(/\D/gi, '')) / 100
this.updateModelValue(newParsedValue)
},
updateViewValue() {
let el = this.$refs.currencyField.$el.getElementsByTagName('input')[0]
el.value = this.currencyFieldValue
el.dispatchEvent(this.createEvent('input'))
},
updateModelValue(modelValue) {
this.fieldValue = modelValue
this.$emit('input', modelValue)
this.updateViewValue()
},
createEvent (name) {
var evt = document.createEvent('Event')
evt.initEvent(name, true, true)
return evt
}
}
}
</script>
Os pontos de atenção neste código são 2, a entrada e saida de valores no componente, e a entrada e saida de valores no nosso v-text-field que estamos encapsulando, no text-field utilizamos uma variavel computed com setter e getter como v-model, no get aplicamos a nossa máscara para para mostrar o dado formatado e no set pegamos o input do usuário. Já para a entrada e saída de valores do nosso componente, nós damos watch na prop value caso o valor tenho sido alterado externamente, e executamos this.$emit(‘input’, modelValue) para informar quem estiver escutando o v-model ou o @input do nosso currency-field.
Com isso nosso componente está pronto, porem ficamos com um problema, e todas as props que tenho no v-text-field? E se eu quiser fazer uma validação com a prop rule, definir um label ou placeholder? É ai que as novas funcionalidades do ES6 vem a calhar, utilizando o Spread Operator podemos linkar todas as props e attrs passadas para o nnosso componente diretamente para o v-text-field
<template>
<v-text-field v-bind="{...$props, ...$attrs}" ref="currencyField" v-model="currencyFieldValue">
</v-text-field>
</template>
Espero que tenham gostado da solução para esse problema, o componente em sí ainda possui muito espaço para melhorias, uma forma de configuração global ou registrar novos formatters do numeral.js ou até mesmo transformar ele em um proxy para o numeral.js e permitir que o usuário passe a máscara na qual ele quer formatar os números.