Null Number em ⭕️ Outsystems quando se precisa do Zero

Diógenes Dauster
9 min readSep 18, 2022

--

Oi seus lindos, esse definitivamente é um assunto bem polêmico na comunidade OutSystems e por acaso me deparei com ele em um dos projetos da OMNUS.

Então porque uma coisa tão trivial que os SGDBs ( Banco de Dados ) atuais tratam muito bem, com uma simples definição de schema, deu tanto o que falar ?

Simples, nos utilizamos Low Code, e as plataformas são responsáveis por administrar o banco de dados, e eu agradeço por isso, pois quem nunca deletou um index ou uma coluna que não parecia tão importante até o telefone tocar e perder um final de semana todo tentando restaurar algo que você achava que era trivial ? Parafraseando um ditado bastante comum “A vida do DEV não é fácil”

Então diz ai seu lindo como tu fez para resolver essa treta ? Já que no forum não tinha nada tão concreto, apenas ideias vagas de como resolver !!

O Inicio

Então vamos direto a ponto de forma simples e fácil e vencer mais uma US no nosso backlog, mas primeiro um pouco de storytelling para dar contexto.

Tudo começou quando cliente falou que o ZERO também era um dado valido para aplicação e estávamos usando como valor NULO / NULL, eu nesse momento puts e agora !?

Primeiro, tivemos a péssima ideia de alterar o campo para TEXTO, que a principio resolveu o problema, contudo a implementação ficou muito custosa, e de fato o campo não era TEXTO e sim DECIMAL, o que não faria sentido para consultas via B.I (BUSINESS INTELIGENT).

Segundo, tentamos usar uma mascara no campo e fazer as conversões na actions quando precisávamos salvar no banco de dados, que também se mostrou muito complicado, ficar convertendo isso toda hora.

Então depois de um combo de sushi e uma coca zero , veio uma epifania. Eu precisava de algo que recebesse um DECIMAL e convertesse para TEXTO com mascara , e que me permite-se usar um valor default como NULO, sendo possível usar o ZERO e fizesse todas as conversões automagicamente como se fosse um BIND. Ai me veio na cabeça uma palavra : MIDDLEWAREEEEE, que convertendo para contexto Low Code Outsystems seria chamado de Block ou Widgets para os mais antigos.

Time to Low Code

Então vamos a implementação, primeiro criei a base da minha ideia. Que foi basicamente um Block que recebesse um parâmetro de entrada com o valor que iremos fazer o BIND, um outro que recebera o valor DEFAULT que será nosso NULO e uma variável Local no formato TEXTO que ira receber o valor convertido.

Depois que fiz isso é hora de criar um input que ira fazer a mágica para mim no Widget Tree.

Então atribui o valor da variável local como BIND do campo e também dei um nome a esse campo, pois precisaremos do ID para algumas inicializações.

Feito tudo isso acima, nossa base estava pronta, então, é hora de LifeCycle, vamos ao primeiro evento que vamos implementar o OnInitialize. Ele será responsável por inicializar nossa variável local e fazer o BIND / Conversão dos valores recebidos via parâmetro de entrada.

Esse evento também vai verificar se o valor DEFAULT Nulo não é igual o valor passado na variável de entrada.

Agora que já temos um Bloco funcional, é hora de aplicarmos a mascara nele para que possamos dar um comportamento do campo TEXTO como NUMERO, para que o usuário não digite valores inválidos que não sejam de fatos números e que estejam no formato que esperamos.

No meu caso eu gosto de usar uma biblioteca JS standalone chamada iMask, vocês podem usar InputMask do próprio Outsystems, mas eu não sou muito fan, pois eu acho que esse componente usa JQuery, o que não faz muito sentido se você está usando Reactive ao invés do Traditional que tem o JQuery embarcado.

Então copie o link CDN cole no seu browser e faça o download do JS para incorpora no Outsystems ao invés de acessar via CDN, pois existem caso que os Apps Outsystems rodam em uma intranet sem acesso a internet e liberar os link CDNS as vezes tem um pouco de burocracia, então melhor evitar. Após baixar o arquivo JS, vá em Scripts, botão direito e importar Script, para ser feliz.

Agora no Bloco na sessão de configurações chamada Required Scripts e adicione o Script da sua biblioteca importada.

Pronto, agora temos nossa lib mágica em JS que ira fazer o trabalho por nós. Para usa-la precisamos apenas inicia-la. Para isso criei um ação chamada InitMask e nela fiz uma chamada JS com as configurações necessárias.

Aqui, algumas coisas que fizemos lá atrás começam a fazer sentido, primeiro o nome que foi dado ao campo será usado para busca-lo via DOM (Domain Object Model) e será passado em tempo de runtime (execução) .

const element = document.getElementById($parameters.WidgetId) // Busca o campo do Bloco

if(IMask) { // Verifica se a lib iMask foi importada com sucesso

if(!element.iMask) { // Verifica se o campo já foi inicializado antes

// inicia o campo com as configurações abaixo

const maskInput = IMask(element,{

mask: Number, // numero

scale: 2, // apenas duas casa decimais

signed: true, // não aceita numero negativos

thousandsSeparator: ‘’, // separador de milhão

padFractionalZeros: false, // espaço entre zeros fracionados

normalizeZeros: false, // normaliza os zeros 10.00

radix: ‘.’, // marcado de decimal visual

mapToRadix: [‘.’], // marcador de decimal após conversão

min: 0, max:100000}) // range de numero aceitos

element.iMask = maskInput; // Variável de controle para verificar em caso de reinicialização

}

}

Agora que já temos essa action desenvolvida, é hora de mais LifeCycle. Dessa vez precisamos coloca o código acima para rodar e o melhor local para isso é quando o DOM já tiver renderizado. O que nos sugere o OnReady, aqui , basta chamar a action da máscara e passar o ID do campo via runtime.

Iremos fazer a mesma coisa para o evento OnRender.

Você deve está me perguntando mais por que ? Simples, OnReady é chamado apenas uma vez, mas existe a possibilidade de ele ser forçado a ser renderizado novamente, e caso isso aconteça, precisamos inicia-lo novamente, é por causa disso que temos um IF dentro do código JS para verificar essa situação.

A imagem abaixo ilustra exatamente o que acontece, lembrando que estamos usando e abusando do LifeCycle, tirando o que a de melhor dele.

Para fechar nosso LifeCycle usaremos o OnParametersChanged, para garantir que caso os parâmetros sejam atualizado nosso campo atualize a variável do BIND do campo com novo valor.

Aqui, chamamos o evento OnInitialize novamente, contudo não se preocupe, ele não vai disparar os outros eventos, pois ele está sendo chamado de forma manual e não através do ciclo de vida. Então caso os parâmetros mude ele ira inicializar novamente as variáveis, fazendo a conversão e o método OnRender fará a inicialização.

Exemplificando o fluxo do LifeCycle (Por favor se estiver errado, deixe um comentário xD)

Ufaaaa, até aqui foi uma longa jornada, vamos rever o que fizemos.

Primeiro implementamos a base do nosso Bloco, depois adicionamos a máscara e utilizamos o LifeCycle para converter o valor inicialmente e iniciar a mascara. Agora falta apenas converter novamente para Number e notificar o consumidor do Bloco quando alteramos os valores no campo.

Para notificarmos o consumidor usaremos eventos que ira dar o Trigger com o valor do campo atual convertido para Number. Também usaremos o evento padrão OnChange do campo para chamar esse gatilho que notificará o consumidor.

Então no campo adicione o evento OnChange.

Em seguida antes de implementar esse evento, crie um evento que ira ser consumido pelo consumidor pai. Chamei esse evento de OnChange , mas poderia ser qualquer nome, também adicionei um parâmetro chamado Value que é o numero convertido.

Tudo certo, é hora de implementar o evento OnChange do campo, aqui adicionei um variável local para fazer a conversão ao invés de faze-la direta na chamado do evento do consumidor, pois gosto de um código mais CLEAN (igual Uncle Bob).

Agora é só chamar o evento que será implementado um nível acima fazendo o BIND do campo TEXTO para NUMBER e ainda considerando o ZERO.

Com isso terminamos nossa jornada que vou ilustrar na próxima imagem.

Não falei quando o método OnRender é chamado quando a variável local é alterada, contudo você podem visualizar no log abaixo esse comportamento.

Primeira Inicialização

Valor Default Nulo — 1
Valor Inicial -1 que convertido igual “” (Branco)

Rerendirazação

Valor Default Nulo — 1
Valor novo digitado 0

(Lembrando que o OnRender depois do OnRender é devido a variável local que foi alterada, então o Bloco o OnRender é chamado novamente, pois o estado atual mudou)

Conclusão

Caso você precise do valor NULL para números, não coloque o campo como TEXTO, isso ira dificultar sua vida no futuro próximo. Existem varias forma de resolver esse problema, fazendo conversões, e essa é uma das soluções possíveis que pode ser implementada evitando complexibilidade no seu código Low Code.

Espero que tenham gostado, pois aqui trabalhamos muito o uso do LifeCycle dentro da plataforma.

Eiiiii me ajuda ai vai !? deixa uma palminhaaaa 👋🏻 e clica em seguir para mais conteúdos legais.

--

--

No responses yet