É 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 o que ocorre quando você precisa de uma funcionalidade que não foi implementada? Esse foi um problema que aconteceu comigo recentemente e creio que possa já ter ocorrido com você 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 necessá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 eu queria utilizar, já que precisava manter o v-text-field que havia sido aplicado no app inteiro.
Foi aí que surgiu o problema. Ao realizar o teste dele como diretiva, por algum motivo interno ele converteu o meu model para String, me forçando a realizar parse do valor para decimal em todos os lugares, gerando muitos pontos de possíveis erros e manutenção futura.
A solução? Criar o nosso próprio componente de currency, encapsulando o v-text-field em um componente chamado currency-field. A ideia 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, utilizei a lib Numeral.js. Vamos visualizar o código utilizado 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 saída de valores no componente, e a entrada e saída de valores no nosso v-text-field que estamos encapsulando. No text-field utilizamos uma variável 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, porém ficamos com alguns problemas: 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? É aí que as novas funcionalidades do ES6 vem a calhar. Utilizando o Spread Operator, podemos linkar todas as props e attrs passadas para o nosso 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 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.
Quer saber mais sobre esse e outros assuntos e soluções em tecnologia? Continue acompanhando o blog da Redspark e siga nossas redes sociais!