Diretiva Vue.js para Transformar Texto em HTML com Campos de Entrada Interativos

Neste exemplo, implementaremos uma diretiva personalizada no Vue.js que converte uma string contendo variáveis em elementos HTML enterativos, permitindo a entrada de dados pelo usuário. A solução integra Vuex para gerenciamneto de estado e utiliza componentes do Element UI para os campos de entrada.

Exemplo de Texto com Variáveis:

"Contrato celebrado entre $var<empresa_contratante> (CNPJ: $var<cnpj_empresa>) e $var<nome_cliente> (CPF: $var<cpf_cliente>). Valor total: $var<valor_total> em $var<data_vencimento>."

Configuração do Vuex:

const mutations = { [TIPOS.ATUALIZAR_ESTADO]: (estado, { chave, valor }) => { estado[chave] = valor; }, }; export default mutations;


</details><details><summary>Actions</summary>```
const actions = {
  atualizarValorEstado({ commit }, { chave, valor, retorno }) {
    commit(TIPOS.ATUALIZAR_ESTADO, { chave, valor });
    if (retorno) retorno();
  },
};
export default actions;
<div class="container-texto" v-renderizar-h="{conteudo: item, modoLeitura: false}"></div>

Estilos CSS (ajustes visuais para os campos):

/deep/.container-texto {
  .el-input {
    width: 250px !important;
    .el-input__inner {
      border-left: none !important;
      border-right: none !important;
      border-top: none !important;
      border-bottom: 1px solid #DCDFE6;
      &:hover {
        border-bottom: 1px solid #14DAAF;
      }
    }
    &.campo-readonly {
      .el-input__inner {
        border-bottom: 1px solid #DCDFE6 !important;
      }
    }
  }
}

Implementação da Diretiva Personalizada:

const instalacao = function (Vue) { Vue.directive('renderizarHtml', renderizarHtml); };

if (window.Vue) { window['renderizarHtml'] = renderizarHtml; Vue.use(instalacao); } renderizarHtml.instalacao = instalacao; export default renderizarHtml;


</details><details><summary>Lógica da Diretiva (renderizarHtml.js)</summary>```
import {
  verificarPadrao,
  extrairVariaveis
} from "@/utils/transformacao";
import store from '@/store';
import { Input, DatePicker, Message } from 'element-ui';
import Vue from 'vue';

export default {
  inserido(el, binding, vnode) {
    const { conteudo, modoLeitura } = binding.value;
    const listaVariaveis = extrairVariaveis(conteudo);
    const dadosArmazenados = store.getters?.obterDadosContrato || [];

    let textoProcessado = conteudo;
    listaVariaveis.forEach(variavel => {
      textoProcessado = textoProcessado.replace(variavel.padrao, `--SEPARADOR--${variavel.identificador}--SEPARADOR--`);
    });

    const fragmentos = textoProcessado.split('--SEPARADOR--');
    fragmentos.forEach(fragmento => {
      const itemEncontrado = dadosArmazenados.find(dado => dado.identificador === fragmento);
      if (itemEncontrado) {
        let instanciaComponente = null;
        if (fragmento.includes("texto_") || fragmento.includes("dinheiro_")) {
          instanciaComponente = new Vue({
            render(h) {
              return h(Input, {
                attrs: { id: fragmento },
                class: modoLeitura ? 'campo-readonly' : '',
                props: {
                  placeholder: modoLeitura ? '' : (fragmento.includes("texto_") ? 'Insira texto' : 'Insira valor monetário'),
                  type: 'text',
                  readonly: modoLeitura,
                },
                model: {
                  value: this.valorCampo,
                  callback: this.atualizarValor,
                  expression: 'valorCampo',
                },
                on: { change: this.processarAlteracao },
              });
            },
            data() {
              return {
                valorCampo: itemEncontrado.valor || '',
              };
            },
            methods: {
              atualizarValor(novoValor) {
                this.valorCampo = novoValor;
              },
              processarAlteracao(evento) {
                if (fragmento.includes("dinheiro_")) {
                  const regexMonetario = /^(\d+(\.\d{1,7})?)$/;
                  if (!regexMonetario.test(evento)) {
                    Message.error("Formato de valor inválido");
                    this.valorCampo = '';
                    return;
                  }
                }
                const idCampo = this.$el.querySelector('input')?.getAttribute('id');
                const dadosAtualizados = (store.getters?.obterDadosContrato || []).map(item => {
                  if (item.identificador === idCampo) {
                    item.valor = evento;
                  }
                  return item;
                });
                store.dispatch("atualizarValorEstado", {
                  chave: "dadosContrato",
                  valor: dadosAtualizados,
                });
              },
            },
          }).$mount();
        } else if (fragmento.includes("data_")) {
          instanciaComponente = new Vue({
            render(h) {
              return h(DatePicker, {
                attrs: { id: fragmento },
                class: modoLeitura ? 'campo-readonly' : '',
                props: {
                  placeholder: modoLeitura ? '' : 'Selecione uma data',
                  type: 'date',
                  valueFormat: 'yyyy-MM-dd',
                  readonly: modoLeitura,
                },
                model: {
                  value: this.valorCampo,
                  callback: this.atualizarValor,
                  expression: 'valorCampo',
                },
                on: { change: this.processarAlteracao },
              });
            },
            data() {
              return {
                valorCampo: itemEncontrado.valor || '',
              };
            },
            methods: {
              atualizarValor(novoValor) {
                this.valorCampo = novoValor;
              },
              processarAlteracao(evento) {
                const idCampo = this.$el.querySelector('input')?.getAttribute('id');
                const dadosAtualizados = (store.getters?.obterDadosContrato || []).map(item => {
                  if (item.identificador === idCampo) {
                    item.valor = evento;
                  }
                  return item;
                });
                store.dispatch("atualizarValorEstado", {
                  chave: "dadosContrato",
                  valor: dadosAtualizados,
                });
              },
            },
          }).$mount();
        }
        if (instanciaComponente) {
          el.appendChild(instanciaComponente.$el);
        }
      } else {
        el.appendChild(document.createTextNode(fragmento));
      }
    });
  },
  desvinculado(el) {
    const primeiroFilho = el.firstChild;
    if (primeiroFilho && primeiroFilho.$destroy) {
      primeiroFilho.$destroy();
    }
  },
};
import renderizarHtml from "@/utils/renderizarHtml";
Vue.use(renderizarHtml);

Utilitários para Processamento de Texto:

/**
 * Verifica se uma string contém o padrão de variável.
 * @param {string} texto - Texto a ser verificado.
 * @returns {boolean} Verdadeiro se o padrão for encontrado.
 */
const verificarPadrao = (texto) => {
  const expressaoRegular = /\$var<[a-zA-Z\d_]+>/;
  return expressaoRegular.test(texto);
};

/**
 * Extrai variáveis de uma string com base em um padrão específico.
 * @param {string} texto - Texto contendo variáveis.
 * @returns {Array} Lista de objetos com identificador e padrão da variável.
 */
const extrairVariaveis = (texto) => {
  const variaveis = [];
  const expressaoRegular = /\$var<([^>]+)>/g;
  let correspondencia;
  while ((correspondencia = expressaoRegular.exec(texto)) !== null) {
    variaveis.push({
      identificador: correspondencia[1],
      padrao: correspondencia[0],
      valor: '',
      posicao: correspondencia.index,
    });
  }
  return variaveis;
};

export { verificarPadrao, extrairVariaveis };

Tags: Vue.js Vuex directivas-personalizadas Element-UI javascript

Publicado em 6-17 05:16