- Criado por Rafael Cesar Borges, última alteração por Adriano De Avila em 19 set, 2024
CONTEÚDO
- Introdução
- Visão geral
- O que é a personalização
- O que é a customização
- Diferenças da personalização/customização progress para o PO-UI
- Requisitos gerais para o desenvolvimento
Consulta de programas passiveis de personalização e customização
Consulta de programas progress que foram migrados para PO-UI
Consulta de documentações de programas que permitem customização
- Personalização
Template padrão API Rest com pontos de UPC
Guia de Padrões de desenvolvimento de API para o produto Datasul
Como personalizar - Documento de referencia
Como customizar - Documento de referencia
01. Introdução
Neste manual vamos falar sobre como funciona a personalização e customização de telas html PO-UI, vamos falar sobre as diferenças entre personalizar e customizar,
em que momento poderá ser utilizado a personalização ou customização, com exemplos e possíveis cenários de implementação.
02. Visão Geral
Este manual irá ajudar a entender para que serve a personalização e customização, entender em que momento cada um poderá ser utilizado e como efetuar a implementação nas telas PO-UI em conjunto com o back-end progress.
03. O que é a personalização
A personalização se trata de alterações simples na tela, como:
- Alteração de cor
- Apresentação da ordem do campo no qual será visualizado na tela
- Validações de interface
- Mascaras de apresentação de campo
- Alterar label/opções de um checkbox
- Alterar um label de um campo
- Transformar um campo em somente leitura
- Entre outras alterações não especificas. Se a necessidade se encaixar em alguma das opções acima ou algo que seja uma alteração equiparada as opções supra citadas, a melhor opção é utilizar a personalização. Para maiores informações de como personalizar consultar a pagina/tópico "Como personalizar".
- A personalização low-code hoje se aplica apenas ao PO-DYNAMIC-FORM e PO-DYNAMIC-VIEW, para utilizar PO-PAGE-DYNAMIC-TABLE, Deve-se também implementar endpoint em Progress, o qual será utilizado como fonte de dados para os campos personalizados.
04. O que é a customização
A customização já contempla uma alteração mais especifica das telas e do back-end progress.
- sendo possível incluir/eliminar campos da tela
- incluir ações nas telas
- customizar a massa de dados no beck-end que será apresentado no front-end
- incluir validações para mostrar/salvar a massa de dados
- Para alterações dos componentes em tela já não é possível fazer de forma low-code. A maior parte das alterações será feita em progress, para informar a tela quais os campos serão ou não apresentados e quais os dados que vão aparecer nesses campos (consulta) ou que será armazenado (cadastro).
05. Diferenças da personalização/customização progress para o PO-UI
Existem algumas diferenças na customização de telas quando se comparado o progress com as novas telas html, exemplo.:
Progress | PO-UI |
---|---|
Incluir botão em tela progress | No html não tem o evento no botão e sim uma ação |
No progress há eventos de tela | No html não há eventos de tela, mas é possível adicionar "eventos" na BO. |
Regra de negocio junto com a tela | Regra de negocio separada da tela (BO) |
No progress era possível personalizar/customizar qualquer tela | No html somente tela com componentes dinâmicos |
No progress era possível chamar uma tela a partir de outra tela | No html não é possivel chamar novas telas |
06. Requisitos gerais para o desenvolvimento
Tecnico:
- Conhecimento do guia de padrões de desenvolvimento de API para o produto datasul
- Angular
- Typescript
- PO-UI - https://po-ui.io/documentation
Produto:
Personalização:
- Programa em THF - PO-UI utilizando componentes dinâmicos
- Programa deve ser cadastrado no cadastro de programas (men012aa), com as flags "visualiza menu" e "permite personalização" marcadas.
Customização:
- Programa em PO-UI preparado para ser customizado:
- Tela construída com componentes dinâmicos
- Beckend progress preparado com a técnica de EPC/UPC rest.
07. Como saber os programas passiveis de personalização e customização
Para identificar todas as telas que já estão permitindo a personalização/customização, entrar no produto e utilizar a visão de dados "Programas Customizados (vdProgramasDatasulUPC)".
Nota
- Para utilização desta funcionalidade, é necessário que a visão tenha sido previamente importada no Smart View.
- Funcionalidade disponível a partir da release 12.1.2411 ou patch: 12.1.2407.1.
08. Como saber quais programas progress que foram migrados para PO-UI
Para consultar as telas progress que ja foram migradas para PO-UI, dentro do produto utilizar a visão de dados "De-Para Programa HTML x Progress (vdDeParaProgsHTML)". Esta visão de dados ira mostrar todas as telas progress que já foram migradas para PO-UI.
Nota
- Para utilização desta funcionalidade, é necessário que a visão tenha sido previamente importada no Smart View.
- Funcionalidade disponível a partir da release 12.1.2411 ou patch: 12.1.2407.1.
09. Como identificar o que pode ser personalizado ou customizado nos programas
Para consultar os programas que permitem a personalização/customização, basta apertar o F1 para chamar a ajuda da tela e conferir na documentação se a tela já esta preparada para personalização ou customização.
Estamos sempre em evolução!
Estas documentações estão em evolução, talvez nem toda tela tenha ainda a documentação dela e a customização vai depender da equipe responsável pelo programa em aplicar a técnica de customização e assim documentar o que poderá ser customizado.
10. Personalização
EXEMPLO DE UTILIZAÇÃO - Produto Datasul
Cadastro de campos personalizados
A seguir são apresentados as telas necessárias para a realização do cadastro dos campos personalizados.
Ao localizar no menu o programa Campos personalizados (html.personalization-metadata
), é apresentada a tela em formato de lista que conterá todos os campos (metadados) cadastrados no produto Datasul. Para cadastrar um campo que será utilizado na personalização, basta clicar no botão +Adicionar.
A tela a seguir apresenta o cadastro do metadado relacionado a um campo que pode ser apresentado no programa como personalizado
Para alguns tipos de campo, é possível informar uma lista de opções apresentadas em tela, para isso basta informar os dados (chave, valor).
Campo | Descrição | Obrigatório | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Código Programa Datasul | Código do programa base que podem ser aplicadas as técnicas de personalização Nota É possível cadastrar os campos somente em programas que permitem a personalização. Programas que permitem personalização, devem ser cadastrados no "Cadastro de programa" com o "template": "programa THF" e possuir a flag "permite personalização" marcada. | Sim | ||||||||||||||||||||||||||||
Identificador Campo | Identificador único do campo (por programa), necessário para a geração da tela personalizada (código do campo) | Sim | ||||||||||||||||||||||||||||
Nome Campo | Nome do campo que será apresentado na tela (label do campo) Caso o campo não seja informado, o nome do campo apresentado será o informado no identificador. | Não | ||||||||||||||||||||||||||||
Tipo Campo | Tipo do campo cadastrado Caso o campo não seja informado, será considerado que o campo é do tipo Tipos de campos permitidos:
| Não | ||||||||||||||||||||||||||||
Lista Opções | Informa a lista de opções a serem apresentadas em conjunto com o tipo do componente especificado. Informação Esta opção está disponível somente para os tipos Radio Group, Checkbox Group, Select e Multiselect, | |||||||||||||||||||||||||||||
Somente Leitura | Opção para que o campo seja apresentado como somente leitura (torna o campo readOnly) | Sim | ||||||||||||||||||||||||||||
Habilita personalização | Opção para habilitar ou desabilitar a apresentação da personalização por campo (não envia o campo para ser renderizado no fron-end). | Sim |
Após cadastrar o campo, o mesmo é apresentado na tela inicial onde é possível realizar filtros sobre seus resultados, bem como efetuar ações de edição de campos, inclusão de atributos, habilitar ou desabilitar todos os campos por programa e exclusão do campo.
Ao clicar na opção de editar, não será possível modificar o código do programa Datasul vinculado e também seu identificador. Os demais campos estão habilitados para edição.
Atributos de campos personalizados
Com a configuração de atributos dos campos personalizados, é possível adicionar outras características tais como:
- Alteração de cor;
- Apresentação da ordem do campo no qual será visualizado em tela;
- Validações de interface;
- Máscara de apresentação do campo, entre muitos outros.
Ao clicar no botão Atributos apresentado na grid principal (Opções), uma tela de parametrizações é apresentada para que seja possível adicionar as novas características.
Ao renderizar os campos personalizados em tela, esses atributos serão inclusos no campo personalizado e enviados para tela.
Atributos do PO-UI
Os nomes dos atributos devem ser os mesmos que estão documentados nas propriedades do componente no PO-UI.
Exemplo: Para personalizar um campo no formato CPF, criamos o campo COD_CPF e adicionamos um atributo do tipo mask que conterá o formato 999.999.999-99.
O atributo mask corresponde a uma propriedade do componente PO-INPUT.
11. Template padrão API Rest com pontos de UPC (Customização)
Abaixo um exemplo de uma API Rest com a mínimas definições e com entradas de upc nos contextos web de get, post, put e delete (Na pagina Customização PO-UI é possível encontrar exemplos da utilização).
/*------------------------------------------------------------------------ File : templateExemplo.p Purpose : demonstração de definições API REST . Syntax : Description : API REST Author(s) : Created : Notes : ----------------------------------------------------------------------*/ /* *************************** Definitions ************************** */ //Definição da ut-api padrão das apis rest {utp/ut-api.i} //Definição da ut-api-action padrão das apis rest com os contextos web: get, post, put e delete {utp/ut-api-action.i pFindAll GET /~* } {utp/ut-api-action.i pUpdateById PUT /~* } {utp/ut-api-action.i pGetMetadata POST /metadata/~* } {utp/ut-api-action.i pValidateForm POST /validateForm/~* } {utp/ut-api-action.i pValidateField POST /validateField/~* } {utp/ut-api-action.i pCreate POST /~* } {utp/ut-api-action.i pDeleteById DELETE /~* } //Definição da ut-api-notfound que trata erros caso o a procedure do metodo chamado nao exista {utp/ut-api-notfound.i} // Definição de temp table /* *************************** Definitions end ************************** */ /* *************************** Main Block *************************** */ // a definição da include/i-epcrest.i pode ser feita nas procedures conforme a necessidade de manipular/validar os dados // conforme o exemplo, sera chamada a template de upc do exemplo abaixo {include/i-epcrest.i &endpoint=create &event=afterCreate &jsonVar=oBody}
Abaixo um template de programa de UPC rest.
/*------------------------------------------------------------------------ File : templateExemploUpc.p Purpose : demonstração de definições upc REST . Syntax : Description : Exemplo de programa Upc sendo chamado pela API rest com pontos de epc Author(s) : Created : Notes : ----------------------------------------------------------------------*/ /* *************************** Definitions ************************** */ //deifinições obrigatorias para poder manipular as informações USING PROGRESS.json.*. USING PROGRESS.json.ObjectModel.*. //Definições obrigatorias para receber os parametros passados para a include "include/i-epcrest.i" define input parameter pEndPoint as char no-undo. define input parameter pEvent as char no-undo. define input parameter pProgram as char no-undo. define input-output parameter pObjParam as JsonObject no-undo. /* *************************** Definitions end ************************** */ /* *************************** Main Block *************************** */ if pEndPoint = "create" and pEvent = "afterCreate" then do: message "pEndPoint: " pEndPoint skip "pEvent: " pEvent skip "pProgram: " pProgram skip "method: " pObjParam:getCharacter("method") skip "URI: " pObjParam:getCharacter("uri") view-as alert-box. end. return "OK".
12. Guia de Padrões de desenvolvimento de API para o produto Datasul
Introdução
O desenvolvimento de APIs permite a exposição e o consumo de dados com o objetivo da integração front-end (portais, customizações, etc) ao back-end do produto Datasul, de maneira segura e padronizada.
A estrutura de integração de APIs Datasul suporta o envio de requisições no estilo de arquitetura REST com o desenvolvimento da regra de negócio em Progress.
Abaixo o fluxo das requisições via HTTP (DATASUL-REST) e formato de execução via Progress:
Esta funcionalidade está disponível para utilização conforme apresentado no quadro abaixo:
Matriz de Evolução
Contextos
Existem 2 contextos definidos que se aplicam tanto para a Antiga arquitetura (Jboss) quanto para o DTS4THF (Tomcat).
Estes contextos tem o objetivo de definir o acesso das requisições, ou seja, se são contextos de acessos internos (via menu) pelo contexto DTS/DATASUL-REST ou acessos externos (via APPs por exemplo) pelo contexto /API.
IMPORTANTE!
O contexto /api foi criado com autenticação via "Basic Authentication", para atender o guia de API TOTVS, sendo possível conexão com qualquer API que anteriormente era "dts/datasul-rest/resources/prg".
Formato URL
O Guia de Implementação de API TOTVS define que no formato das URIs dos endpoints deve conter:
- o nome do produto;
- o módulo;
- a versão da API;
- o recurso alvo da funcionalidade em questão.
Modelo de acesso aos Contextos:
/prg
Tomando como exemplo o endpoint de integração do recurso de "Usuários" do aplicativo "Foundation" do produto "Datasul", a URI básica deste serviço deve ser: /prg/sec/v1/users
Para o produto Datasul, o serviço responsável é implementado no contexto /dts/datasul-rest/
Desta forma, a URL final do serviço exemplo acima seria composta da seguinte maneira:
http://host:port/dts/datasul-rest/resources/prg/sec/v1/users |
---|
A URL definida pelo Padrão de API TOTVS segue o seguinte formato: /dts/datasul-rest/resouces/prg/<módulo>/<versão API>/<recurso>/ Informações que forem passadas após o recurso, serão tratadas como parâmetros PATH. |
/api
Para exemplo do funcionamento /api vamos utilizar o mesmo recurso de integração de usuário exemplificado anteriormente.
A URI deste serviço deve ser: /api/sec/v1/users
No geral o funcionamento do /api é igual ao /prg, com a ressalva de que o endpoint /api não esta dentro do contexto /dts/datasul-rest/
http://host:port/api/sec/v1/users |
---|
A URL definida pelo Padrão de API TOTVS segue o seguinte formato: /api/<módulo>/<versão API>/<recurso>/ Informações que forem passadas após o recurso, serão tratadas como parâmetros PATH. |
Serviço Progress
Para "publicar" a funcionalidade Progress ABL basta criar o programa (.p) com o seguinte caminho: sec/api/v1/users.p (<módulo>/api/<versão API>/<recurso>.p).
O arquivo *.p é o fonte original de criação da API, porém para sua real utilização é preciso compilá-lo para se transformar em um arquivo *.r.
A sub-pasta "api" passa então a concentrar todas as funcionalidades de integração do módulo em questão:
Os programas Progress disponibilizados, deverão seguir o padrão de localização abaixo e devem estar compilados, ou seja, é necessário o *.r:
<módulo>/api/<versão API>/<recurso>.r |
---|
OBS: Outros caminhos e parâmetros podem ser adicionados a URL, mas sempre de acordo com o Guia de Implementação de APIs.
O Guia de Implementação de API TOTVS define também que a troca de mensagens é feita (impreterivelmente) no formato JSON, e por conta disso, a troca de mensagens com as funcionalidades Progress também devem ser feitas nesse formato, mais especificamente por meio de um parâmetro de entrada e outro de saída do tipo LONGCHAR que devem ser devidamente tratados (parseados e formatados) pela funcionalidade utilizando as includes utilitárias disponibilizadas:
Include | Parâmetros da Include | Descrição |
---|---|---|
utp/ut-api.i | Não há. | Faz o parser do parâmetro LONGCHAR de entrada e cria um objeto JsonObject chamado jsonInput. |
utp/ut-api-action.i |
| Faz o roteamento do objeto jsonInput para uma procedure interna especificada pelo desenvolvedor. |
utp/ut-api-notfound.i | Não há. | Caso nenhuma procedure interna tenha sido encontrada, retorna uma mensagem "Method not found" com HTTP Status 400. |
Para garantir o controle de erros, controle de chamada EPC e padronização no retorno das mensagens, foram criadas includes para executar os principais Endpoints, além de permitir a chamada de Endpoints específicos de negócio:
Include | Parâmetros da Include | Endpoint | Descrição | Parâmetros enviado para PI Interna |
---|---|---|---|---|
utp/ut-api-exec-metadata.i |
| Metadata | Devolve metadata para geração das telas. |
|
utp/ut-api-exec-vld-form.i |
| VldForm | Validação de formulário. |
|
utp/ut-api-exec-vld-field.i |
| VldField | Validação de campo. |
|
utp/ut-api-exec-get.i |
| Get | Busca um registro. |
|
utp/ut-api-exec-query.i |
| Query | Busca vários registros. |
|
utp/ut-api-exec-create.i |
| Create | Criação de registro. |
|
utp/ut-api-exec-update.i |
| Update | Alteração de registro. |
|
utp/ut-api-exec-patch.i |
| Patch | Alteração de registro. |
|
utp/ut-api-exec-delete.i |
| Delete | Eliminação de registro. |
|
utp/ut-api-exec-delete-list.i |
| DeleteList | Eliminação em lote. |
|
utp/ut-api-exec-custom.i |
| Custom (NEG) | Tratativa especifica de negócio. | Para Tipo de Retorno "Object":
Para Tipo de Retorno "Array":
|
Observações
- O programa de negócio informado será executado de forma persistente e executada a PI informada;
- A Include irá utilizar a classe utilitária JsonAPIExecution para realizar a execução do programa de negócio;
- As informações que serão envidas para a EPC, podem ser consultadas aqui.
>>>>>Abaixo um exemplo de recurso desenvolvido em Progress ABL para ser utilizado junto ao serviço de API<<<<<<
No início do código estão todas as includes necessárias. O que vale ressaltar neste trecho é referente a include ut-api-action:
- Nesta include são declaradas as procedures utilizadas na API, por exemplo: pi-send, pi-update.
- Na mesma declaração é definido qual o método http aplicado, por exemplo: GET, POST, entre outros.
- Em seguida é definido como o recurso será acessado pela URI, por exemplo: /~*/SEND
- Isso significa que ao acessar a URI teremos algo como: http://host:port/dts/datasul-rest/resources/prg/sec/v1/users/send onde:
{utp/ut-api.i} {utp/ut-api-action.i pi-send GET /~*/SEND by=email,address=~* } {utp/ut-api-action.i pi-update POST /~* } {utp/ut-api-action.i pi-find GET /~* } {utp/ut-api-action.i pi-default GET } {utp/ut-api-notfound.i} {utp/ut-api-exec-custom.i pi-send btb/progNeg.p pi-send-v1 "Array" "sendEmail"} {utp/ut-api-exec-update.i pi-update btb/progNeg.p pi-update-v1} {utp/ut-api-exec-get.i pi-find btb/progNeg.p pi-find-v1} {utp/ut-api-exec-custom.i pi-default btb/progNeg.p pi-default-v1 "Object" "default"}
PROCEDURE pi-send-v1: DEFINE INPUT PARAM oInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAM aResult AS JsonArray NO-UNDO. DEFINE OUTPUT PARAM lHasNext AS LOGICAL NO-UNDO. DEFINE OUTPUT PARAM TABLE FOR RowErrors. DEFINE VARIABLE oObject AS JsonObject NO-UNDO. aResult = NEW JsonArray(). oObject = NEW JsonObject(). oObject:ADD("teste", "teste"). oObject:ADD("teste1", "teste1"). oObject:ADD("teste2", "teste2"). aResult:ADD(oObject). oObject = NEW JsonObject(). oObject:ADD("teste", "teste"). oObject:ADD("teste1", "teste1"). oObject:ADD("teste2", "teste2"). aResult:ADD(oObject). ASSIGN lHasNext = NO. END. PROCEDURE pi-update-v1: DEFINE INPUT PARAM oInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAM oOutput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAM TABLE FOR RowErrors. oOutput = oInput. END. PROCEDURE pi-find-v1: DEFINE INPUT PARAM oInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAM oOutput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAM cExpandables AS CHARACTER NO-UNDO. DEFINE OUTPUT PARAM TABLE FOR RowErrors. oOutput = NEW JsonObject(). oOutput:ADD("method", "GET"). oOutput:ADD("procedure", "pi-find"). oOutput:ADD("description", "Test"). ASSIGN cExpandables = "tabFilha1,tabFilha2". END. PROCEDURE pi-default-v1: DEFINE INPUT PARAM oInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAM oOutput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAM TABLE FOR RowErrors. oOutput = NEW JSONObject(). oOutput:ADD("method", "GET"). oOutput:ADD("procedure", "pi-default"). oOutput:ADD("description", "Test"). END.
No exemplo acima, temos as seguintes includes utilitárias:
Algumas considerações sobre o uso da include de roteamento (ut-api-action):
- Os roteamentos devem ser definidos do mais específico (detalhado) para o mais genérico (simples);
- O utilitário faz uso da função MATCHES do Progress, que basicamente permite o uso do ponto "." (ponto) como coringa de uma determinada posição (1 caractere apenas) e o "*" (asterisco) para um conjunto de caracteres variáveis;
- O caracter de escape "~" deve ser utilizado sempre que necessário, antecedendo caracteres especiais que comprometam a compilação do código progress;
- Para definir mais de um parâmetro de pesquisa, utilize "," (vírgula) como separador. O processamento de mais de um parâmetro de pesquisa será sempre traduzido para usar o operador AND.
- Permite o uso de todos métodos HTTP suportados pelo API Manager (GET, POST, PUT, DELETE, PATCH, ...)
A include ut-api.i precisa ser adicionada obrigatoriamente no início do programa Progress, visto que esta include faz uso da instrução USING para importação de classes. Portanto, devido a esta caraterística do Progress ABL, somente será possível adicionar outras includes depois da adição da ut-api.i e ut-api-notfound.i, respectivamente.
Classes utilitárias
Com o objetivo de facilitar a manipulação dos objetos JsonObject recebidos e enviados pela API Progress foram desenvolvidas algumas classes de utilitários:
- JsonAPIRequestParser - Extrai informações do objeto JSON recebido como parâmetro da requisição;
- JsonAPIResponse - Trata a criação do objeto JSON de response da requisição;
- JsonAPIResponseBuilder - Trata a criação do objeto JSON de response da requisição através de um builder;
- JsonAPIUtils - Utilitário com métodos que facilitam a manipulação de informações relacionados a API;
- JsonAPIExecution - Classe utilizada para executar as API-REST de negócio, encapsulando toda tratativa de EPC, controle de ERRO e padronizando o retorno das informações para o FrontEnd;
- JsonAPIQueryUtils - Classe utilizada montagem de Querys dinâmicas, considerando o padrão TotvsAPI (filtro simples, complexo (odata), ordenação, etc...).
Formato Mensagem JSON
O objeto JsonObject, recebido pela requisição no programa Progress conterá informações completas da requisição, desde informações do:
- HEADER;
- QUERY PARAMs;
- PATH PARAMs;
- o próprio PAYLOAD e;
- arquivos MULTIPART.
Com esta mensagem, o desenvolvedor poderá efetuar os devidos filtros e classes utilitárias necessárias.
Exemplo de mensagem:
{ uri: valor, method: GET, headers: {}, pathParams: [ "param1", "param2" ], queryParams: { query1: [valor1, valor2], query2: [valor1]}, payload: { }, multyPartFile: [ {file: ...}, {file: ...}] }
Acesso a Diferentes Empresas
O cliente pode fazer acesso a diferentes empresas via BASIC AUTHENTICATION. Para realizar esse processo é necessário primeiramente o login no sistema, e em seguida descobrir todas as empresas do produto, selecionando um dos links a seguir:
- (JBoss ou Tomcat): http://<host>:<port>/api/btb/v1/companies
- (JBoss): http://<host>:<port>/dts/datasul-rest/resources/btb/v1/companies
- (Tomcat): http://<host>:<port>/dts/datasul-rest/resources/prg/btb/v1/companies
Atenção
Os links acima retornam todas as empresas do produto, então se faz necessário verificar que a empresa utilizada realmente possui vínculo com o usuário logado no sistema, uma vez que não existe controle de vinculo entre empresa e usuário.
Se atente aos campos host e port no link, esses deverão ser modificados pelo usuário de acordo com o ambiente utilizado.
Em novas implementações, de preferência ao endpont com o contexto /api.
A partir do momento que se sabe qual empresa se deseja enviar na requisição e que a mesma está associada ao usuário, é possível fazer a chamada da API que desejar, passando o código como parâmetro dentro do atributo companyId, esse especificado dentro do Header.
GET /api/btb/v1/companies HTTP/1.1 Host: host:port companyId: 10 Authorization: Basic MTM6MTM=
Informação
A requisição para a API pode ser feita pela ferramenta Postman ou por outra ferramenta que de suporte a requisições feitas pela API.
Login
Na Antiga Arquitetura (Jboss) para realizar o login no contexto DTS/DATASUL-REST (apesar de existir BASIC AUTHENTICATION com o contexto /api), ainda é possível passar como parâmetro usuário e senha para na seguinte URL.
URL para Login
http://localhost:8180/dts/datasul-rest/resources/login?username=<USER>&password=<PASSWORD>
Aviso importante!
Caso a sua aplicação/portal esteja em um servidor diferente do JBOSS Datasul, será necessário informar a propriedade portal.java.naming.security.datasulurl no arquivo datasul_framework.properties conforme exemplo abaixo:
portal.java.naming.security.datasulurl=http://<servidor JBoss Datasul>:<porta>
Isso é necessário pois o login deverá ser feito no servidor JBOSS do Datasul e não no servidor da aplicação/portal.
Para passagem da senha deve ser em Lower Case e é necessário converte-la para SHA1 (utilizando Byte Array), com este resultado realizar a conversão para BASE64. Tendo em vista que a conversão para BASE64 irá trazer caracteres incoerentes para passagem da URL, será necessário fazer a conversão para URL-ENCODE.
- Senha: testPassword
- BASE64(SHA1): i7YRj4/Wk1rQh2o740pxfTJwj/0=
- URL-ENCODE: i7YRj4%2FWk1rQh2o740pxfTJwj%2F0%3D
Observe que por trazer os caracteres " / e = " na conversão de SHA1 e BASE64, é necessário o URL-ENCODE.
Sugestão para URL-ENCODE: https://meyerweb.com/eric/tools/dencoder/
DEFINE VARIABLE testPass AS CHARACTER NO-UNDO. testPass = BASE64-ENCODE(SHA1-DIGEST(LC("testPassword"))). // Utilize uma lógica de conversão URL-ENCODE para variavel testPass
Aviso importante!
Para realizar esse encode NÃO pode utilizar codificação HEXADECIMAL, caso que acontece no Java (processo interno da linguagem), então é preciso utilizar o Byte Array!
Exemplo de URL final
http://localhost:8180/dts/datasul-rest/resources/login?username=teste&password=i7YRj4%2FWk1rQh2o740pxfTJwj%2F0%3D
Para o servidor de aplicação JBOSS é possível realizar a autenticação via BASIC authentication através de duas maneiras 1) por endpoint REST ou 2) pelo componente JOSSO.
ATENÇÃO
Em caso de erro no login por troca do HTTP METHOD de POST para GET ou vice e versa. Será obrigatório o uso do login via componente JOSSO explicado abaixo!
A chamada com header http BASIC no produto Datasul é possível ser feita diretamente na chamada do endpoint. Basta informar corretamente o HEADER e realizar a request.
EXEMPLO-1: chamando o endpoint http://host:port/dts/datasul-rest/resources/btb/v1/companies
GET dts/datasul-rest/resources/btb/v1/companies HTTP/1.1 Host: host ... Origin: http://host:port Authorization: BASIC Z3VpdGE6Z3VpdGE=
EXEMPLO-2: chamando o endpoint http://host:port/api/btb/v1/companies
GET api/btb/v1/companies HTTP/1.1 Host: host ... Origin: http://host:port Authorization: BASIC Z3VpdGE6Z3VpdGE=
Em ambo os caso a codificação BASIC é construída com a notação BASE64(username:password)
A chamada com header http BASIC no produto Datasul através do componente JOSSO é feita diretamente na chamada do endpoint de autenticação. Basta informar corretamente o HEADER e realizar a request.
EXEMPLO: chamando o endpoint de autenticação http://host:port/josso/signon/auth.do com BASIC
GET josso/signon/auth.do HTTP/1.1 Host: host ... Origin: http://host:port Authorization: BASIC Z3VpdGE6Z3VpdGE=
{ "status":"200", "description":"Login Authorized - Token Generated", "JOSSO_SESSIONID":"C7C4669F68BB35995C4BE13142769FA3", "JWT":"" }
Neste caso basta captura o JSON de retorno acima e utilizar o JOSSO_SESSIONID nas demais requisições. Lembrando que a codificação BASIC é construída com a notação BASE64(username:password)
Para acessar o produto DTS4THF com apis devemos utilizar o formato BASIC do http. Para saber mais clique aqui
Logout
Para a arquitetura do JBOSS é possível realizar o logout via as chamadas REST. Isso pode ser feito para que os contextos de trabalho com o josso sejam liberados e que a session seja invalidada no fechamento dos APPs.
Desta maneira, existe a seguinte url para realizar o logout no produto via chamada REST.
Contexto api http://host:port/api/logout Contexto dts/datasul-rest http://host:port/dts/datasul-rest/resources/logout
13. Como personalizar - Documento de referencia
- 01. INTRODUÇÃO/OBJETIVO
- 02. VISÃO GERAL
- 03. PRÉ-REQUISITOS
- 04. TÉCNICAS
- Endpoint Progress do Framework
- Endpoint Progress que devem ser implementados na "área de negócio"
- Utilitário facilitador no Progress
- Implementação pela área de negócio a interface em PO-DYNAMIC-FORM e PO-DYNAMIC-VIEW.
- Atributos de campos personalizados
- Componentes em PO-UI
- Endpoint em Progress para a área de negócio
- Tela do componente HTML com o resultado da personalização
- 05. LINKS ÚTEIS
- 06. CONCLUSÃO
01. INTRODUÇÃO/OBJETIVO
Temos como objetivo implementar técnicas para facilitar a personalização de telas TOTVS - Linha Datasul de forma lowcode, apenas com cadastro de campos por parte do cliente.
02. VISÃO GERAL
A partir da release 12.1.31 são disponibilizados as técnicas e cadastros para implementar a personalização em telas HTML da linha Datasul.
Nesta técnica de personalização, o desenvolvedor deverá realizar o cadastro dos campos a serem personalizados e criar alguns componentes em PO-UI que utilizem os componentes: PO-DYNAMIC-FORM, PO-DYNAMIC-VIEW e PO-PAGE-DYNAMIC-TABLE (este último somente se for necessário). Deve-se também implementar endpoint em Progress, o qual será utilizado como fonte de dados para os campos personalizados.
03. PRÉ-REQUISITOS
Para utilização desta técnica será necessário possuir conhecimento de desenvolvimento com: APIs REST em Progress, Angular, TypeScript e PO-UI.
04. TÉCNICAS
A Técnica de personalização de telas HTML com PO-UI contempla os seguintes objetos:
Endpoint Progress do Framework
Retorna a lista de campos personalizados que devem ser previamente cadastrados na tela de Personalização em HTML.
Neste item, deverá ser utilizado o endpoint Progress /api/btb/v1/personalizationView/metadata/ + código_do_programa ,onde deve ser passado o Código do Programa Datasul que conterá a lista de campos personalizados.
Exemplo: Implementação de personalização para o programa pedido-execucao-monitor, deve-se utilizar o endpoint "/api/btb/v1/personalizationView/metadata/pedido-execucao-monitor":
{ "fields": [ { "visible": true, "gridColumns": 6, "disable": false, "property": "cod_idioma", "label": "Idioma", "type": "string" }, { "visible": true, "gridColumns": 6, "disable": false, "property": "cod_idiom_padr", "label": "Idioma Padrão", "type": "string" }, { "visible": true, "gridColumns": 6, "disable": false, "property": "des_idioma", "label": "Descrição", "type": "string" }, { "visible": false, "property": "id", "type": "number", "key": true } ] }
Nota!
Atributo KEY: pode-se marcar como "Key":true
o que faz parte da chave/identificador do registro.
Assim é possível ter vários campos marcados como Key, independente do nome da propriedade.
Exemplo:
Caso dois campos (empresa=10 e filial=100) sejam marcados com "key":true
e concatenar os valores.
Ao buscar o registro completo, se tem a url: <URL>/customers/10|100
Endpoint Progress que devem ser implementados na "área de negócio"
Deve retornar os dados que serão apresentados nos campos personalizados;
A tabela ao lado demonstra os componentes dinâmicos do PO-UI com suas respectivas requisições à API REST.
Componente PO-UI | Endpoint | Tipo Requisição | Descrição |
---|---|---|---|
po-dynamic-view po-dynamic-form | /byid/nome_do_programa/id | GET | Retorna um registro único. Recebe no PathParms o "nome do programa" e o "id". |
po-page-dynamic-table | /nome_do_programa | GET | Retorna uma lista de registros. Recebe no PathParams o "nome do programa". |
po-dynamic-form | /validateForm/nome_do_programa | POST | Valida o formulário. Recebe no PathParams o "nome do programa". |
po-dynamic-form | /nome_do_programa | POST | Efetua a criação de um novo registro. Recebe no PathParams o "nome do programa". |
po-dynamic-form | /nome_do_programa/id | PUT | Efetua a alteração do registro. Recebe no PathParams o "nome do programa" e o "id". |
po-dynamic-form | /nome_do_programa/id | DELETE | Efetua a eliminação do registro. Recebe no PathParams o "nome do programa" e o "id". |
Aviso
Em todas os componentes dinâmicos da tabela apresentada, efetuará uma requisição para obter a lista de campos personalizados na API REST do Framework, com a utilização da requisição:
"/api/btb/v1/personalizationView/metadata/" + código_do_programa
Utilitário facilitador no Progress
Foi implementado no Progress o utilitário - btb/personalizationUtil.p - com seu include btb/personalizationUtil.i, que deve ser utilizado para retornar à área de negócio a lista de campos personalizáveis de um determinado programa, cujo o intuito é facilitar a implementação para que seja enviado somente os valores dos campos personalizáveis.
Devido a característica do PO-UI dinâmico, caso seja enviado os dados de campos que não estão na lista de campos, o PO-UI irá apresentar o valor do campo com uma label com o mesmo nome do campo. Com a obtenção da lista de campos pode-se evitar o envio de informações que estão fora da lista de campos personalizados.
- Include btb/personalizationUtil.i
DEFINE TEMP-TABLE ttPersonalization NO-UNDO FIELD codProgDtsul AS CHARACTER FIELD codField AS CHARACTER FIELD codType AS CHARACTER FIELD codLabel AS CHARACTER FIELD codValid AS CHARACTER FIELD logReadOnly AS LOGICAL INITIAL FALSE FIELD logEnable AS LOGICAL INITIAL TRUE INDEX codigo IS PRIMARY codProgDtsul codField.
- Procedures disponíveis no programa btb/personalizationUtil.p
Procedure | Parâmetros | Descrição/Exemplo |
---|---|---|
piGetTTPersonalization | INPUT cProg AS CHARACTER OUTPUT TABLE ttPersonalization | Retorna a temp-table Exemplo:
Exemplo de chamada do utilitário btb/personalizationUtil.p Expandir origem
{ btb/personalizationUtil.i } DEFINE VARIABLE hPers AS HANDLE NO-UNDO. RUN btb/personalizatinUtil.p PERSISTENT SET hPers. RUN piGetTTPersonalization IN hPers ("codigo_do_programa", OUTPUT TABLE ttPersonalization). DELETE PROCEDURE hPers. FOR EACH ttPersonalization: DISPLAY ttPersonalization. END. |
piGetFieldList | INPUT cProg AS CHARACTER OUTPUT cList AS CHARACTER OUTPUT cTypeList AS CHARACTER | Retorna duas listas Observação: As listas usam como separador a vírgula ",". Exemplo:
Exemplode chamada de procedure Expandir origem
DEFINE VARIABLE hPers AS HANDLE NO-UNDO. DEFINE VARIABLE cList AS CHARACTER NO-UNDO. DEFINE VARIABLE cTypeList AS CHARACTER NO-UNDO. RUN btb/personalizatinUtil.p PERSISTENT SET hPers. RUN piGetFieldList IN hPers ("codigo_do_programa", OUTPUT cList, OUTPUT cTypeList). DELETE PROCEDURE hPers. MESSAGE cList SKIP cTypeList VIEW-AS ALERT-BOX |
Implementação pela área de negócio a interface em PO-DYNAMIC-FORM e PO-DYNAMIC-VIEW.
Todos os componentes dinâmicos do PO-UI realizam, no mínimo, duas requisições REST, uma para obter a lista de campos personalizáveis e outra para obter os dados a serem apresentados nesses campos.
Abaixo temos o papel de cada componente dinâmico que podemos utilizar:
PO-DYNAMIC-FORM
: Para a edição e criação de um novo registro personalizado;
PO-DYNAMIC-VIEW
: Para a visualização do registro personalizado.
Dica
Pode-se implementar também um componente PO-PAGE-DYNAMIC-TABLE
, para a navegação dos registros e permitir a visualização e edição dos mesmos.
Em nossa técnica, em todas as requisições REST, será enviado:
- Para buscar a lista de campos personalizados utilizando o endpoint Progress fornecido pelo framework, que é o /api/btb/v1/personalizationView/metadata/ + codigo_do_programa;
- O código do programa personalizado e também um id do registro corrente para obtenção dos valores, necessários para buscar os valores dos dados a serem apresentados.
Atributos de campos personalizados
Com a configuração de atributos dos campos personalizados, é possível adicionar outras características tais como:
- Alteração de cor;
- Apresentação da ordem do campo no qual será visualizado em tela;
- Validações de interface;
- Máscara de apresentação do campo, entre muitos outros.
Ao clicar no botão Atributos apresentado na grid principal (Opções), uma tela de parametrizações é apresentada para que seja possível adicionar as novas características.
Ao renderizar os campos personalizados em tela, esses atributos serão inclusos no campo personalizado e enviados para tela.
Atributos do PO-UI
Os nomes dos atributos devem ser os mesmos que estão documentados nas propriedades do componente no PO-UI.
Exemplo: Para personalizar um campo no formato CPF, criamos o campo COD_CPF e adicionamos um atributo do tipo mask que conterá o formato 999.999.999-99.
O atributo mask corresponde a uma propriedade do componente PO-INPUT.
Componentes em PO-UI
A seguir, são apresentados exemplos de códigos para a implementação com HTML e TypeScript:
Componente HTML (personalization-detail.component.html)
<po-loading-overlay [hidden]="!showLoading"> </po-loading-overlay> <po-page-detail p-title="Detalhe do Idioma" [p-breadcrumb]="breadcrumb" (p-edit)="editClick()" (p-back)="goBackClick()">
<!-- INICIO CODIGO PERSONALIZAVEL --> <po-dynamic-view [p-fields]="fields" [p-value]="record"> </po-dynamic-view> <!-- FINAL CODIGO PERSONALIZAVEL -->
</po-page-detail
Componente TypeScript (personalization-detail.component.ts)
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { PoBreadcrumb, PoBreadcrumbItem } from '@po-ui/ng-components'; import { PersonalizationService } from '../personalization.service'; @Component({ selector: 'app-personalization-detail', templateUrl: './personalization-detail.component.html', styleUrls: ['./personalization-detail.component.css'] })
export class PersonalizationDetailComponent implements OnInit { public static cProg = 'html.aplicativos-eai'; // definicao das variaveis utilizadas public currentId: string; public fields: Array<any> = []; public record = {}; public showLoading = false; public breadcrumb: PoBreadcrumb = { items: [] }; public breadcrumbItem: PoBreadcrumbItem;
// construtor com os servicos necessarios constructor( private service: PersonalizationService, private activatedRoute: ActivatedRoute, private route: Router ) { }
// load do componente public ngOnInit(): void { this.activatedRoute.params.subscribe(pars => { this.showLoading = true; this.record = {}; // Carrega o registro pelo ID // tslint:disable-next-line:no-string-literal this.currentId = pars['id']; //---BUSCA OS VALORES DOS CAMPOS PERSONALIZADOS DA AREA DE NEGOCIO ---- // busca os valores dos dados a serem apresentados this.service.loadValuesById(this.currentId).subscribe(resp => { Object.keys(resp).forEach((key) => this.record[key] = resp[key]); // ---BUSCA OS CAMPOS PERSONALIZADOS DO PROGRAMA ---- // carrega a lista de campos personalizados somente apos receber os dados a serem apresentados this.service.loadMetadata().subscribe(metadata => { // tslint:disable-next-line:no-string-literal this.fields = metadata['fields']; this.showLoading = false; }); }); }); this.setBreadcrumb(); } private setBreadcrumb(): void { this.breadcrumbItem = { label: 'Home', link: '/' }; this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem); this.breadcrumbItem = { label: 'Listagem de Idiomas' , link: '/personalization' }; this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem); this.breadcrumbItem = { label: 'Detalhe do Idioma' }; this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem); } }
// Redireciona quando clicar no botao Edit public editClick(): void { this.route.navigate(['/personalization', 'edit', this.currentId]); } // Redireciona quando clicar no botao Voltar public goBackClick(): void { this.route.navigate(['/personalization']); } }
Componente HTML (personalization-edit.component.html)
<po-loading-overlay [hidden]="!showLoading"> </po-loading-overlay> <po-page-edit [p-title]="cTitle" [p-breadcrumb]="breadcrumb" [p-disable-submit]="formEdit.form.invalid" (p-cancel)="cancelClick()" (p-save)="saveClick()">
<!-- INICIO CODIGO PERSONALIZAVEL --> <po-dynamic-form #formEdit p-auto-focus="string" [p-fields]="fields" [p-validate]="validationUrl" [p-value]="record"> </po-dynamic-form> <!-- FINAL CODIGO PERSONALIZAVEL -->
</po-page-edit>
Componente TypeScript (personalization-edit.component.ts)
import { Component, OnInit, ViewChild } from '@angular/core'; import { NgForm } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { PoBreadcrumb, PoBreadcrumbItem, PoDialogService, PoNotificationService } from '@po-ui/ng-components'; import { PersonalizationService } from './../personalization.service'; @Component({ selector: 'app-personalization-edit', templateUrl: './personalization-edit.component.html', styleUrls: ['./personalization-edit.component.css'] })
export class PersonalizationEditComponent implements OnInit { // Define as variaveis a serem utilizadas public cTitle: string; public currentId: string; public record = {}; public fields: Array<any> = []; public isUpdate = false; public showLoading = false; public validationUrl = this.service.getUrlAreaValidation(); public breadcrumb: PoBreadcrumb = { items: [] }; public breadcrumbItem: PoBreadcrumbItem; // Obtem a referencia do componente HTML @ViewChild('formEdit', { static: true }) formEdit: NgForm;
// Construtor da classe com os servicos necessarios constructor( private service: PersonalizationService, private activatedRoute: ActivatedRoute, private route: Router, private poDialog: PoDialogService, private poNotification: PoNotificationService ) { }
// Load do componente public ngOnInit(): void { this.isUpdate = false; this.showLoading = true; // Carrega o registro pelo ID this.activatedRoute.params.subscribe(pars => { // tslint:disable-next-line:no-string-literal this.currentId = pars['id']; // Se nao tiver o ID definido sera um CREATE if (this.currentId === undefined) { this.isUpdate = false; this.cTitle = 'Novo Idioma'; } else { this.isUpdate = true; this.cTitle = 'Edição do Idioma'; } // Atualiza o breadcrumb de acordo com o tipo de edicao this.setBreadcrumb(); // Se for uma alteracao, busca o registro a ser alterado if (this.isUpdate) { // ----- BUSCA OS VALORES DOS CAMPOS PERSONALIZADOS DA AREA DE NEGOCIO ----- this.service.loadValuesById(this.currentId).subscribe(resp => { Object.keys(resp).forEach((key) => this.record[key] = resp[key]); // ----- BUSCA OS CAMPOS PERSONALIZADOS DO PROGRAMA ----- // Em alteracao temos que receber o registro para depois buscar a lista de campos this.getMetadata(); }); } else { // Se for create, pega a lista de campos this.getMetadata(); } }); } private setBreadcrumb(): void { this.breadcrumbItem = { label: 'Home', action: this.beforeRedirect.bind(this) }; this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem); this.breadcrumbItem = { label: 'Listagem de Idiomas', action: this.beforeRedirect.bind(this) }; this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem); this.breadcrumbItem = { label: this.cTitle }; this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem); } // Retorna a lista de campos private getMetadata() { this.service.loadMetadata().subscribe(metadata => { // tslint:disable-next-line:no-string-literal this.fields = metadata['fields']; this.showLoading = false; }); } // Redireciona via breadcrumb private beforeRedirect(itemBreadcrumbLabel) { if (this.formEdit.valid) { this.route.navigate(['/']); } else { this.poDialog.confirm({ title: 'Cancelamento de edição', message: 'Os dados ainda não foram gravados, confirma redirecinamento ?', confirm: () => this.route.navigate(['/']) }); } }
// Grava o registro quando clicado no botao Salvar public saveClick(): void { this.showLoading = true; if (this.isUpdate) { // Altera um registro ja existente this.service.update(this.currentId, this.record).subscribe(resp => { this.poNotification.success('Dados atualizados com sucesso'); this.showLoading = false; this.route.navigate(['/personalization']); }); } else { // Cria um registro novo this.service.create(this.currentId, this.record).subscribe(resp => { this.poNotification.success('Dados criados com sucesso'); this.showLoading = false; this.route.navigate(['/personalization']); }); } } // Cancela a edicao e redireciona ao clicar no botao Cancelar public cancelClick(): void { this.poDialog.confirm({ title: 'Confirmar cancelamento', message: 'Voce deseja realmente cancelar a edição?', confirm: () => this.route.navigate(['/personalization']) }); } }
// Cancela a edicao e redireciona ao clicar no botao Cancelar public cancelClick(): void { this.poDialog.confirm({ title: 'Confirmar cancelamento', message: 'Voce deseja realmente cancelar a edição?', confirm: () => this.route.navigate(['/personalization']) }); } }
Componente HTML (personalization-list.component.html)
<po-loading-overlay [hidden]="!showLoading"> </po-loading-overlay>
<!-- INICIO CODIGO PERSONALIZAVEL --> <po-page-dynamic-table p-auto-router p-title="Listagem de Idiomas" [p-actions]="actions" [p-breadcrumb]="breadcrumb" [p-fields]="fields" [p-service-api]="serviceApi"> </po-page-dynamic-table> <!-- FINAL CODIGO PERSONALIZAVEL -->
Componente TypeScript (personalization-list.component.ts)
import { Component, OnInit } from '@angular/core'; import { PoBreadcrumb, PoBreadcrumbItem } from '@po-ui/ng-components'; import { PoPageDynamicTableActions } from '@po-ui/ng-templates'; import { PersonalizationService } from './../personalization.service'; @Component({ selector: 'app-personalization-list', templateUrl: './personalization-list.component.html', styleUrls: ['./personalization-list.component.css'] })
export class PersonalizationListComponent implements OnInit { // Definicao das variaveis utilizadas public serviceApi: string; public fields: Array<any> = []; public showLoading = false; public literals; public readonly actions: PoPageDynamicTableActions = { new: '/personalization/create', detail: '/personalization/detail/:id', edit: '/personalization/edit/:id', remove: true }; public breadcrumb: PoBreadcrumb = { items: [] }; public breadcrumbItem: PoBreadcrumbItem;
// Construtor da classe constructor( private service: PersonalizationService ) { }
// Load do componente public ngOnInit(): void { this.fields = []; this.serviceApi = this.service.getUrlArea(); this.showLoading = true; // ------- BUSCA OS CAMPOS PERSONALIZADOS DO PROGRAMA -------- this.service.loadMetadata().subscribe(metadata => { // tslint:disable-next-line:no-string-literal this.fields = metadata['fields']; this.showLoading = false; }); this.setBreadcrumb(); } private setBreadcrumb(): void { this.breadcrumbItem = { label: 'Home', link: '/' }; this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem); this.breadcrumbItem = { label: 'Listagem de Idiomas' }; this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem); } }
Componente de serviço (personalization.service.ts)
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { PoNotificationService } from '@po-ui/ng-components'; @Injectable({ providedIn: 'root' })
export class PersonalizationService { public progCode = 'html.aplicativos-eai'; // Endpoint progress do framework para obtencao da lista de campos personalizados private urlMetadata = '/api/btb/v1/personalizationView/metadata/'; // Endpoint progress da area de negocio para obtencao dos valores dos campos personalizados private urlArea = '/api/trn/v1/idiomaValues/';
// Construtor constructor( private http: HttpClient, private poNotification: PoNotificationService, ) { }
// ------- BUSCA OS CAMPOS PERSONALIZADOS DO PROGRAMA -------- public loadMetadata() { return this.http.post<any[]>(this.urlMetadata + this.progCode).pipe(); } // ------- BUSCA OS VALORES DOS CAMPOS PERSONALIZADOS DA AREA DE NEGOCIO -------- public loadValuesById(cId) { // tslint:disable-next-line:whitespace return this.http.get<any[]>(this.urlArea + 'byid/' + this.progCode + '/' + cId).pipe(); }
public loadAllValues() { return this.http.get<any[]>(this.urlArea + this.progCode + '/').pipe(); } public create(cId, record) { return this.http.post<any[]>(this.urlArea + this.progCode + '/' + cId, record).pipe(); } public update(cId, record) { return this.http.put<any[]>(this.urlArea + this.progCode + '/' + cId, record).pipe(); } public getUrlArea(): string { return this.urlArea + this.progCode + '/'; } public getUrlAreaValidation(): string { return this.urlArea + 'validateForm/' + this.progCode; } }
Endpoint em Progress para a área de negócio
A seguir, são apresentados exemplos de endpoint em Progress, que deverão ser implementados pela área de negócio para obtenção dos valores a serem apresentados nos campos personalizados.
Obtenção dos valores dos campos personalizados (idiomaValues.p)
{utp/ut-api.i} {utp/ut-api-action.i pGetDataById GET /byid/~* } {utp/ut-api-action.i pGetAll GET /~* } {utp/ut-api-action.i pValidateForm POST /validateForm/~* } {utp/ut-api-action.i pCreate POST /~* } {utp/ut-api-action.i pUpdate PUT /~* } {utp/ut-api-action.i pDelete DELETE /~* } {utp/ut-api-notfound.i} {btb/personalizationUtil.i} DEFINE VARIABLE oRequest AS JsonAPIRequestParser NO-UNDO. DEFINE VARIABLE oResponse AS JsonAPIResponse NO-UNDO. DEFINE VARIABLE oObj AS JsonObject NO-UNDO. DEFINE VARIABLE oList AS JsonArray NO-UNDO. DEFINE VARIABLE oBody AS JsonObject NO-UNDO. DEFINE VARIABLE cId AS CHARACTER NO-UNDO. DEFINE VARIABLE cProg AS CHARACTER NO-UNDO. DEFINE VARIABLE cList AS CHARACTER NO-UNDO. DEFINE VARIABLE cTypeList AS CHARACTER NO-UNDO. DEFINE VARIABLE cFld AS CHARACTER NO-UNDO. DEFINE VARIABLE cType AS CHARACTER NO-UNDO. DEFINE VARIABLE ix AS INTEGER NO-UNDO. DEFINE VARIABLE hTab AS HANDLE NO-UNDO. DEFINE VARIABLE hQry AS HANDLE NO-UNDO. DEFINE VARIABLE hPers AS HANDLE NO-UNDO. DEFINE VARIABLE rTab AS RECID NO-UNDO.
// ** Procedure que retorna os valores ** PROCEDURE pGetDataById: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. oObj = NEW JsonObject(). // Le os parametros enviados pela interface HTML oRequest = NEW JsonAPIRequestParser(oJsonInput). // Obtem o programa e o codigo do registro corrente cProg = oRequest:getPathParams():getCharacter(2) NO-ERROR. cId = oRequest:getPathParams():getCharacter(3) NO-ERROR. LOG-MANAGER:WRITE-MESSAGE("pGetDataById - cProg = " + cProg, ">>>>>"). LOG-MANAGER:WRITE-MESSAGE("pGetDataById - cId = " + cId, ">>>>>"). // retorna a lista de campos a serem personalizados EMPTY TEMP-TABLE ttPersonalization. RUN btb/personalizationUtil.p PERSISTENT SET hPers. RUN piGetTTPersonalization IN hPers (cProg, OUTPUT table ttPersonalization). DELETE PROCEDURE hPers NO-ERROR. // se houver algum campo personalizado, busca as informacoes FIND FIRST ttPersonalization NO-LOCK NO-ERROR. IF AVAILABLE ttPersonalization THEN DO: FIND idioma WHERE RECID(idioma) = integer(cId) NO-LOCK NO-ERROR. IF AVAILABLE idioma THEN DO: oObj:add("id", RECID(idioma)). // deve somente alimentar os campos que serao personalizados hTab = BUFFER idioma:HANDLE. FOR EACH ttPersonalization: ASSIGN cFld = ttPersonalization.codField. oObj:add(cFld, hTab:BUFFER-FIELD(cFld):buffer-value()) NO-ERROR. END. hTab:BUFFER-RELEASE(). DELETE OBJECT hTab NO-ERROR. END. LOG-MANAGER:WRITE-MESSAGE("pGetDataById - oObj = " + String(oObj:getJsonText()), ">>>>>"). END. oResponse = NEW JsonAPIResponse(oObj). oJsonOutput = oResponse:createJsonResponse(). END PROCEDURE.
// ** Procedure que retorna os valores ** PROCEDURE pGetAll: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. oList = NEW JsonArray(). // Le os parametros enviados pela interface HTML oRequest = NEW JsonAPIRequestParser(oJsonInput). // Obtem o programa e o codigo do registro corrente cProg = oRequest:getPathParams():getCharacter(1) NO-ERROR. LOG-MANAGER:WRITE-MESSAGE("pGetAll - cProg = " + cProg, ">>>>>"). // retorna a lista de campos a serem personalizados EMPTY TEMP-TABLE ttPersonalization. RUN btb/personalizationUtil.p PERSISTENT SET hPers. RUN piGetTTPersonalization IN hPers (cProg, OUTPUT table ttPersonalization). DELETE PROCEDURE hPers NO-ERROR. // se houver algum campo personalizado, busca as informacoes FIND FIRST ttPersonalization NO-LOCK NO-ERROR. IF AVAILABLE ttPersonalization THEN DO: FOR EACH idioma NO-LOCK: oObj = NEW JsonObject(). oObj:add("id", RECID(idioma)). // deve somente alimentar os campos que serao personalizados hTab = BUFFER idioma:HANDLE. FOR EACH ttPersonalization: ASSIGN cFld = ttPersonalization.codField. oObj:add(cFld, hTab:BUFFER-FIELD(cFld):buffer-value()) NO-ERROR. END. hTab:BUFFER-RELEASE(). DELETE OBJECT hTab NO-ERROR. oList:add(oObj). END. END. oObj = NEW JSonObject(). oObj:add("items", oList). LOG-MANAGER:WRITE-MESSAGE("pGetAll - oObj = " + String(oObj:getJsonText()), ">>>>>"). oResponse = NEW JsonAPIResponse(oObj). oJsonOutput = oResponse:createJsonResponse(). END PROCEDURE.
// ** Procedure que cria um novo registro na tabela ** PROCEDURE pCreate: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. DEFINE VARIABLE cCodIdioma AS CHARACTER NO-UNDO. DEFINE VARIABLE cDesIdioma AS CHARACTER NO-UNDO. DEFINE VARIABLE cCodIdiomPadr AS CHARACTER NO-UNDO. DEFINE VARIABLE rIdioma AS RECID NO-UNDO. DEFINE VARIABLE lCreated AS LOGICAL NO-UNDO INITIAL FALSE. // Le os parametros e os dados enviados pela interface HTML oRequest = NEW JsonAPIRequestParser(oJsonInput). oBody = oRequest:getPayload(). rTab = ?. // Obtem o programa e o codigo do registro corrente cProg = oRequest:getPathParams():getCharacter(1) NO-ERROR. cId = oRequest:getPathParams():getCharacter(2) NO-ERROR. LOG-MANAGER:WRITE-MESSAGE("pCreate - cProg = " + cProg, ">>>>>"). LOG-MANAGER:WRITE-MESSAGE("pCreate - cId = " + cId, ">>>>>"). // retorna a lista de campos a serem personalizados EMPTY TEMP-TABLE ttPersonalization. RUN btb/personalizationUtil.p PERSISTENT SET hPers. RUN piGetTTPersonalization IN hPers (cProg, OUTPUT table ttPersonalization). DELETE PROCEDURE hPers NO-ERROR. // se houver algum campo personalizado, busca as informacoes FIND FIRST ttPersonalization NO-LOCK NO-ERROR. IF AVAILABLE ttPersonalization THEN DO TRANSACTION ON ERROR UNDO, LEAVE: CREATE idioma. // faz a gravacao dos dados onde os campos personalizados estao com o mesmo nome dos campos da tabela hTab = BUFFER idioma:HANDLE. RUN piSetPersonalizationData (hTab, oBody). rTab = hTab:RECID. hTab:BUFFER-RELEASE() NO-ERROR. DELETE OBJECT hTab NO-ERROR. END. // Retorna o ID e se foi criado com sucesso oBody = NEW JsonObject(). oBody:add('id', rTab). oBody:add('created', (IF lCreated THEN 'OK' ELSE 'NOK')). // Retorna o oBody montado para a interface HTML oResponse = NEW JsonAPIResponse(oBody). oJsonOutput = oResponse:createJsonResponse(). END PROCEDURE.
/** Procedure que atualiza o conteudo do registro pelo ID **/ PROCEDURE pUpdate: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. DEFINE VARIABLE lUpdated AS LOGICAL NO-UNDO INITIAL FALSE. DEFINE VARIABLE oIdioma AS JsonObject NO-UNDO. DEFINE VARIABLE oId AS JsonObject NO-UNDO. // Le os parametros e os dados enviados pela interface HTML oRequest = NEW JsonAPIRequestParser(oJsonInput). oBody = oRequest:getPayload(). oObj = NEW JsonObject(). cProg = oRequest:getPathParams():getCharacter(1) NO-ERROR. cId = oRequest:getPathParams():getCharacter(2) NO-ERROR. LOG-MANAGER:WRITE-MESSAGE("pUpdate - cProg = " + cProg, ">>>>>"). LOG-MANAGER:WRITE-MESSAGE("pUpdate - cId = " + cId, ">>>>>"). // retorna a lista de campos a serem personalizados EMPTY TEMP-TABLE ttPersonalization. RUN btb/personalizationUtil.p PERSISTENT SET hPers. RUN piGetTTPersonalization IN hPers (cProg, OUTPUT table ttPersonalization). DELETE PROCEDURE hPers NO-ERROR. // se houver algum campo personalizado, busca as informacoes FIND FIRST ttPersonalization NO-LOCK NO-ERROR. IF AVAILABLE ttPersonalization THEN DO TRANSACTION ON ERROR UNDO, LEAVE: FIND idioma WHERE RECID(idioma) = integer(cId) EXCLUSIVE-LOCK NO-ERROR. IF AVAILABLE idioma THEN DO: oObj:add("id", RECID(idioma)). // faz a gravacao dos dados onde os campos personalizados estao com o mesmo nome dos campos da tabela hTab = BUFFER idioma:HANDLE. RUN piSetPersonalizationData (hTab, oBody). hTab:BUFFER-RELEASE() NO-ERROR. DELETE OBJECT hTab NO-ERROR. lUpdated = TRUE. END. END. // Retorna o ID e se foi atualizado com sucesso oBody = NEW JsonObject(). oBody:add('id', cId). oBody:add('updated', (IF lUpdated THEN 'OK' ELSE 'NOK')). // Retorna o oBody montado para a interface HTML oResponse = NEW JsonAPIResponse(oBody). oJsonOutput = oResponse:createJsonResponse(). END PROCEDURE.
// ** Procedure que atualiza o conteudo do registro pelo ID ** PROCEDURE pDelete: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. DEFINE VARIABLE lDeleted AS LOGICAL NO-UNDO INITIAL FALSE. // Le os parametros enviados pela interface HTML oRequest = NEW JsonAPIRequestParser(oJsonInput). // Le os parametros e os dados enviados pela interface HTML oRequest = NEW JsonAPIRequestParser(oJsonInput). oBody = oRequest:getPayload(). oObj = NEW JsonObject(). cProg = oRequest:getPathParams():getCharacter(1) NO-ERROR. cId = oRequest:getPathParams():getCharacter(2) NO-ERROR. LOG-MANAGER:WRITE-MESSAGE("pDelete - cProg = " + cProg, ">>>>>"). LOG-MANAGER:WRITE-MESSAGE("pDelete - cId = " + cId, ">>>>>"). // retorna a lista de campos a serem personalizados EMPTY TEMP-TABLE ttPersonalization. RUN btb/personalizationUtil.p PERSISTENT SET hPers. RUN piGetTTPersonalization IN hPers (cProg, OUTPUT table ttPersonalization). DELETE PROCEDURE hPers NO-ERROR. // somente elimina o registro se houverem campos personalizaveis FIND FIRST ttPersonalization NO-LOCK NO-ERROR. IF AVAILABLE ttPersonalization THEN DO TRANSACTION ON ERROR UNDO, LEAVE: FIND idioma WHERE RECID(idioma) = integer(cId) EXCLUSIVE-LOCK NO-ERROR. IF AVAILABLE idioma THEN DO: DELETE idioma. ASSIGN lDeleted = TRUE. END. END. // Retorna o ID e se foi criado com sucesso oObj = NEW JsonObject(). oObj:add('id', cId). oObj:add('deleted', (IF lDeleted THEN 'OK' ELSE 'NOK')). // Retorna o oBody montado para a interface HTML oResponse = NEW JsonAPIResponse(oObj). oJsonOutput = oResponse:createJsonResponse(). END PROCEDURE.
PROCEDURE pValidateForm: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. DEFINE VARIABLE cProp AS CHARACTER NO-UNDO. DEFINE VARIABLE oValue AS JsonObject NO-UNDO. DEFINE VARIABLE cValue AS CHARACTER NO-UNDO. DEFINE VARIABLE oNewValue AS JsonObject NO-UNDO. DEFINE VARIABLE oNewFields AS JsonArray NO-UNDO. DEFINE VARIABLE cFocus AS CHARACTER NO-UNDO. DEFINE VARIABLE oRet AS JsonObject NO-UNDO. DEFINE VARIABLE oMessages AS JsonArray NO-UNDO. oRequest = NEW JsonAPIRequestParser(oJsonInput). oBody = oRequest:getPayload(). // obtem o nome da propriedade que ocorreu o LEAVE para validacao cProp = oBody:getCharacter("property") NO-ERROR. oValue = oBody:getJsonObject("value") NO-ERROR. cValue = STRING(oValue:GetCharacter(cProp)) NO-ERROR. cId = oValue:getCharacter("id") NO-ERROR. /* Recebemos do HTML o JSON abaixo { "property": "codAcoes", "value": { "codIdiomPadr": "01 Português", "codIdioma": "12345678", "desIdioma": "12345678901234567890", "id": 6, } } */ // Novas Acoes sobre os campos da tela // oNewValue guarda os valores a serem especificados para os campos ASSIGN oNewValue = NEW JsonObject(). // oNewFields guarda a lista de campos que serao alterados/modificados ASSIGN oNewFields = NEW JsonArray(). // cFocus especifica em qual campo sera feito o focus ASSIGN cFocus = cProp. // oMessages guarda as mensagens de retorno formato // { code: '00', message: 'texto', detailedMessage: 'detalhes da mensagem' } ASSIGN oMessages = NEW JsonArray(). // // adicinar a logica de validacoes dos campos aqui // ASSIGN oRet = NEW JsonObject(). // value -> contem todos os valores dos campos de tela oRet:add('value', oNewValue). // fields -> contem a lista de campos com suas novas propriedades oRet:add('fields', oNewFields). // focus -> especifica em qual campo o cursor vai ficar posicionado oRet:add('focus', cFocus). // _messages -> contem uma lista de mensagens que vao aparecer como notificacoes oRet:add('_messages', oMessages). oResponse = NEW JsonAPIResponse(oRet). oJsonOutput = oResponse:createJsonResponse(). END PROCEDURE.
PROCEDURE piSetPersonalizationData: DEFINE INPUT PARAMETER hTab AS HANDLE NO-UNDO. DEFINE INPUT PARAMETER oBody AS JsonObject NO-UNDO. FOR EACH ttPersonalization: ASSIGN cFld = ttPersonalization.codField cType = ttPersonalization.codType. CASE cType: WHEN "string" THEN hTab:BUFFER-FIELD(cFld):buffer-value() = oBody:getCharacter(cFld) NO-ERROR. WHEN "number" THEN hTab:BUFFER-FIELD(cFld):buffer-value() = oBody:getInteger(cFld) NO-ERROR. WHEN "currency" THEN hTab:BUFFER-FIELD(cFld):buffer-value() = oBody:getDecimal(cFld) NO-ERROR. WHEN "boolean" THEN hTab:BUFFER-FIELD(cFld):buffer-value() = oBody:getLogical(cFld) NO-ERROR. WHEN "datetime" THEN hTab:BUFFER-FIELD(cFld):buffer-value() = oBody:getDatetime(cFld) NO-ERROR. WHEN "date" THEN hTab:BUFFER-FIELD(cFld):buffer-value() = oBody:getDate(cFld) NO-ERROR. END CASE. END. END PROCEDURE. /* fim */
Envio de valores para PO-UI
Caso sejam enviados valores da área de negócio que não estejam cadastrados como campos personalizados, o PO-UI, o padrão adicionará essa informação extra na tela, onde será apresentado em formato String sem uma label válida.
Tela do componente HTML com o resultado da personalização
Tela de Listagem
Tela de Criação/Edição
Tela de Detalhe/Visualização
Dica
Os fontes de exemplo para a personalização também estão disponíveis em nosso GIT (fwk-totvs-jille) em LINKS ÚTEIS.
05. LINKS ÚTEIS
Documentação API Datasul:
Desenvolvimento de APIs para o produto DatasulPO-UI:
Migração THF PO-UI (https://po-ui.io/guides/migration-thf-to-po-ui)
Dynamic-View (https://po-ui.io/documentation/po-dynamic-view);
I18N (https://po-ui.io/documentation/po-i18n)
PO-UI (https://po-ui.io/documentation);
06. CONCLUSÃO
A ideia era apresentar uma técnica de construção para a personalização de um programa TOTVS da Linha Datasul, de forma segura e simples.
Contamos com seu feedback e sugestões para manter a melhoria continua da documentação.
14. Como customizar - Documento de referencia
CONTEÚDO
- 01. INTRODUÇÃO / OBJETIVO
- 02. VISÃO GERAL
- 03. PRÉ-REQUISITOS
- 04. TÉCNICAS
- 05. EXEMPLO DE UTILIZAÇÃO
- 06. VALIDAÇÃO DE COMPONENTES
- 07. VALIDAÇÃO DE FORMULÁRIOS
- 08. FACILITADORES PROGRESS
- 09. TÉCNICA PARA TRADUÇÃO
- 10. COMO DOCUMENTAR A TELA QUE PERMITE CUSTOMIZAÇÃO
- 11. DICAS PARA DESENVOLVER UMA CUSTOMIZAÇÃO
- 12. LINKS ÚTEIS
- 13. CONCLUSÃO
01. INTRODUÇÃO / OBJETIVO
Temos como objetivo desta técnica, apresentar um MVP de como customizar as telas HTML, através de intervenções em API-REST no back-end Progress. O código de API abaixo pode possuir diferenças de estrutura comparado com a forma utilizada pelas equipes de negócio.
Para que caso surja a necessidade do cliente final customizar o resultado de uma tela, ele possa fazer isso de forma dinâmica e em tempo de renderização.
Para isso vamos utilizar o Framework PO-UI como front-end e seus componentes dinâmicos, comunicando com back-end Datasul Progress.
Esta documentação pode ser usada tanto pelos desenvolvedores internos a fim de disponibilizar a customização no produto padrão, quanto pelos desenvolvedores externos que necessitam criar customizações nessas telas HTML. Porém, em algumas seções da documentação podem conter links disponíveis apenas para desenvolvedores internos.
02. VISÃO GERAL
No PO-UI, uma tela dinâmica trabalha recebendo uma lista de informações que serão utilizadas para apresentar os componentes da tela, e uma outra lista contendo os dados que serão apresentados nestes componentes.
Nesta técnica, vamos apresentar como fornecer estas duas listas para o PO-UI, onde poderemos customizá-las de acordo com a necessidade.
Este guia será divido basicamente em duas partes, como vamos trabalhar no back-end Progress e acessar esses dados através do front-end PO-UI.
Abaixo temos um fluxo das informações do PO-UI até a UPC em Progress:
IMPORTANTE
IMPORTANTE: Esta técnica está disponível a partir da versão 12.1.29 do Framework da Linha Datasul.
03. PRÉ-REQUISITOS
Temos como pré-requisito para execução da técnica citada abaixo:
- API-REST desenvolvida no último padrão divulgado pelo Framework, utilizando a técnica de construção de APIs (Desenvolvimento de APIs para o produto Datasul);
- Utilização do Framework PO-UI na última versão disponível;
- Utilização do Framework Tomcat Datasul;
- Utilização da técnica de customização com UPC na API do programa a ser customizado;
- Tela preparada para a customização;
04. TÉCNICAS
Técnica Back-End Progress:
Introdução:
A técnica back-end Progress é formada pelos passos abaixo:
Construção de API-REST para tela customizada:
Para que possamos customizar uma tela HTML construída em PO-UI, necessitamos que o back-end nos retorne qual o metadado e os valores da tela em questão através de uma API-REST.
Sendo assim essa API deve conter no mínimo dois endpoints básicos:
- Endpoint que retornará o metadados da tela;
- Endpoint para retornar os valores da tela;
Cadastro da API-REST no Cadastro de Programas (men012aa) com a respectiva UPC:
Tendo criado a API-REST que retorna os dados básicos para a tela, partimos para o segundo passo, que é a preparação do endpoint da API para a customização.
Esta API deverá ser inserida no cadastro de programas (MEN012AA), onde poderemos também especificar a UPC que será utilizada.
Na técnica de construção de APIs REST é informado sobre a utilização da include "utp/ut-api.i", pois esta include identificará se a API possui uma UPC cadastrada ou não.
IMPORTANTE
IMPORTANTE: A UPC para APIs REST possui um formato diferenciado das UPCs Padrões e de Ponto Estratégico, pois um dos parâmetros utilizados é um JsonObject.
Técnica de customização com UPC
Utilizar a include "include/i-epcrest.i" para chamada UPC na API-REST :
Enfim para chamarmos um programa de customização, criamos uma include que fará esta chamada. Abaixo segue mais detalhes sobre esta include.
Ela encontra-se na pasta include e possui o nome i-epcrest.i, conforme o exemplo abaixo:
{include/i-epcrest.i &endpoint=<nome_end_point> &event=<nome_do_evento> &jsonVar=<variável_jsonObject_com_conteúdo>}
IMPORTANTE
IMPORTANTE: Não é permitido misturar tipos diferentes de UPCs no mesmo programa, pois as assinaturas são incompatíveis e poderão ocorrer erros de parâmetros.
Pré-Processadores da include i-epcrest.i:
Abaixo temos a lista de pré-processadores que devem ser passados para a include i-epcrest.i:
Preprocessador | Descrição |
---|---|
endpoint | Especifica o endpoint que esta sendo chamado pelo HTML. Uma API-REST deve possuir 1 ou mais endpoints. |
event | É o nome do evento que esta ocorrendo antes de chamar a UPC. Exemplo: beforeCreate, validateForm, afterFindAll, etc. |
jsonVar | É a variável do tipo JsonObject que será passada como INPUT-OUTPUT para a UPC. |
Lista de Endpoint, Eventos e Parâmetros padrões:
Abaixo temos a lista de Endpoints, Eventos e Parâmetros que devem ser utilizados conforme cada Endpoint da API:
ENDPOINT API | DESCRIÇÃO | ENDPOINT EPC | BEFORE | AFTER |
---|---|---|---|---|
Metadata | Devolve o metadata | getMetaData | event: NÃO HÁ. jonVar: NÃO HÁ. | event: list, new, edit, copy ou detail (conforme parâmetro recebido do Front). jonVar: Objeto contendo:
|
VldForm | Validação de formulário | validateForm | event: NÃO HÁ. jonVar: NÃO HÁ. | event: validateForm jonVar: Objeto contendo:
|
VldField | Validação de campo | validateField | event: NÃO HÁ. jonVar: NÃO HÁ. | event: validateField jonVar: Objeto contendo:
|
Get | Busca um registro | findById | event: beforeFindById jonVar: Objeto contendo:
| event: afterFindById jonVar: Objeto devolvido pela API-REST (oOutput). |
Query | Busca vários registros | findAll | event: beforeFindAll jonVar: Objeto contendo:
| event: afterFindAll jonVar: Objeto contendo:
|
Create | Criação de registro | create | event: beforeCreate jonVar: Objeto de Payload recebido na requisição. | event: afterCreate jonVar: Objeto devolvido pela API-REST (oOutput). |
Update | Alteração de registro | update | event: beforeUpdate jonVar: Objeto contendo:
| event: afterUpdate jonVar: Objeto contendo:
|
Patch | Alteração de registro | update | event: beforeUpdate jonVar: Objeto contendo:
| event: afterUpdate jonVar: Objeto contendo:
|
Delete | Eliminação de registro | delete | event: beforeDelete jonVar: Objeto contendo:
| event: afterDelete jonVar: Objeto contendo:
|
DeleteList | Eliminação em lote | deleteList | event: beforeDeleteList jonVar: Objeto contendo:
| event: afterDeleteList jonVar: Objeto contendo:
|
Negócios | Tratativa de negócio | <NEG>* | event: before<NEG>* JonVar: Objeto contendo:
| event: after<NEG>* JonVar: Objeto contendo:
|
(*) Definido pelo negócio, deverá estar descrito na documentação específica da tela.
Parâmetros recebidos na UPC da API-REST:
Parametro | Tipo | Tipo de Dados | Descrição |
---|---|---|---|
pEndPoint | INPUT | CHARACTER | Contém o nome do endpoint que está sendo executado. |
pEvent | INPUT | CHARACTER | Contém o nome do evento que está sendo executado. |
pAPI | INPUT | CHARACTER | Contém o nome da API que está sendo executada. |
jsonIO | INPUT-OUTPUT | JsonObject | Contém o JSON com os dados (campos ou valores) que poderão ser customizados. |
IMPORTANTE
IMPORTANTE: Todas as UPCs de API-REST deverão importar os seguintes pacotes:
USING PROGRESS.json.*.
USING PROGRESS.json.ObjectModel.*.
USING com.totvs.framework.api.*.
Front-End PO-UI:
Introdução:
Para termos uma tela dinâmica, de acordo com o que o back-end retorna, precisamos utilizar os componentes dinâmicos ou as templates do PO-UI sendo eles:
Componentes:
Dynamic-Form;
Dynamic-View.
Templates:
- Page-Dynamic-Detail;
- Page-Dymic-Edit;
- Page-Dynamic-Search;
- Page-Dynamic-Table.
Comunicando com o Back-End Progress:
Basicamente para comunicar com o back-end teremos que ter dois serviços que irão alimentar as informações para tela:
- Metadado
- Serviço que irá retornar os campos e as propriedades da tela;
- Values
- Serviço que irá retornar os valores dos campos;
05. EXEMPLO DE UTILIZAÇÃO
Back-End Progress
Introdução:
Para exemplificar a técnica citada acima, criamos uma API-REST que irá retornar os dados da tabela de idiomas, chamando uma UPC que acrescenta algumas informações da tabela usuar_mestre.
Cadastro da UPC:
Primeiramente temos que cadastrar a API-REST no cadastro de programas (MEN012AA) e também especificar a UPC a ser utilizada, conforme o exemplo abaixo:
Atenção:
- Ao cadastrar uma API e sua UPC, o nome externo da API/ENDPOINT NÃO deve possuir extensão.
- Caso o PASOE esteja em um servidor Linux, no campo "Programa UPC" deve-se utilizar "/"(barra) entre os diretórios no lugar de "\"(contrabarra), ou o programa não será encontrado causando erro na execução.
- Caso seja utilizado um caminho relativo, exemplo "upc/nome_upc.p", deve ser ajustado o propath do PASOE para conter o diretório raiz, pois o programa upc será executado no PASOE.
Na aba Opções, teremos que especificar o Template como "API REST", conforme o exemplo abaixo:
Acessar a opção "Grupo de segurança" e configurar os grupos de segurança que terão acesso à API que está sendo cadastrada.
API-REST com chamada de UPC:
Abaixo temos um exemplo de API-REST com suas procedures que são:
- pGetMetaData que retorna os metadados das telas;
- pFindAll que retorna os valores dos campos em questão;
- pFindById que retorna um registro de um determinado ID;
- pCreate que cria um novo registro;
- pUpdateById que altera um registro de um determinado ID;
- pDeleteById que elimina 1 ou mais registros.
Como podemos verificar todas as procedures possuem chamadas para o programa de UPC:
Atenção
O código de API apresentado a seguir pode possuir diferenças de estrutura comparado com a forma utilizada pelas equipes de negócio. Algumas equipes, por exemplo, criam um programa para as regras de negócio e fazem chamadas a esse programa nos métodos das APIs.
O importante aqui é atentar-se às técnicas utilizadas para disponibilizar os pontos de acesso à UPC, para que seja possível customizar a tela.
{utp/ut-api.i} {utp/ut-api-action.i pGetMetadata GET /metadata/~* } {utp/ut-api-action.i pIdiomas GET /translations/~* } {utp/ut-api-action.i pFindById GET /~*/ } {utp/ut-api-action.i pFindAll GET /~* } {utp/ut-api-action.i pUpdateById PUT /~* } {utp/ut-api-action.i pValidateForm POST /validateForm/~* } {utp/ut-api-action.i pValidateField POST /validateField/~* } {utp/ut-api-action.i pCreate POST /~* } {utp/ut-api-action.i pDeleteById DELETE /~* } {utp/ut-api-notfound.i} DEFINE TEMP-TABLE ttIdiomas NO-UNDO FIELD cod_idioma LIKE idioma.cod_idioma SERIALIZE-NAME "codIdioma" FIELD des_idioma LIKE idioma.des_idioma SERIALIZE-NAME "desIdioma" FIELD cod_idiom_padr LIKE idioma.cod_idiom_padr SERIALIZE-NAME "codIdiomPadr" FIELD dat_ult_atualiz LIKE idioma.dat_ult_atualiz SERIALIZE-NAME "datUltAtualiz" FIELD hra_ult_atualiz LIKE idioma.hra_ult_atualiz SERIALIZE-NAME "hraUltAtualiz". /** Procedure que retorna o metadata **/ PROCEDURE pGetMetadata: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. DEFINE VARIABLE oRequest AS JsonAPIRequestParser NO-UNDO. DEFINE VARIABLE oResponse AS JsonAPIResponse NO-UNDO. DEFINE VARIABLE c-evento AS CHAR init "list" NO-UNDO. DEFINE VARIABLE c-versao AS CHAR NO-UNDO. /*Realiza a busca dos parametro enviados na URL da requisicao: - type: Tipo do metadado que deve ser retornado (list,new,copy,edit,detail). - version: Versao do metadado (se existir)*/ ASSIGN oRequest = NEW JsonAPIRequestParser(oJsonInput). IF oRequest:getQueryParams():has("type") AND oRequest:getQueryParams():getJsonArray("type") NE ? THEN ASSIGN c-evento = oRequest:getQueryParams():getJsonArray("type"):getCharacter(1). IF oRequest:getQueryParams():has("version") AND oRequest:getQueryParams():getJsonArray("version") NE ? THEN ASSIGN c-versao = oRequest:getQueryParams():getJsonArray("version"):getCharacter(1). /* Busca o metadata e executa o ponto de UPC conforme o queryParam "type" */ CASE c-evento: WHEN "list":U THEN DO: //Chamar o metodo que retorna o metadado de listagem RUN pGetMetadataList(INPUT oJsonInput, OUTPUT oJsonOutput). // Realiza a chamada da UPC Progress {include/i-epcrest.i &endpoint=getMetaData &event=list &jsonVar=oJsonOutput} END. WHEN "new":U OR WHEN "copy":U THEN DO: //Chamar o metodo que retorna o metadado de criacao /*RUN pGetMetadataNew(INPUT oJsonInput, OUTPUT oJsonOutput).*/ IF c-evento = "copy" THEN DO: //Seta a chave do registro no objeto que vai para a UPC RUN setMetadataEntityKey(INPUT oRequest, INPUT-OUTPUT oJsonOutput). END. {include/i-epcrest.i &endpoint=getMetaData &event=new &jsonVar=oJsonOutput} END. WHEN "edit":U THEN DO: //Chamar o metodo que retorna o metadado de edicao /*RUN pGetMetadataEdit(INPUT oJsonInput, OUTPUT oJsonOutput).*/ //Seta a chave do registro no objeto que vai para a UPC RUN setMetadataEntityKey(INPUT oRequest, INPUT-OUTPUT oJsonOutput). {include/i-epcrest.i &endpoint=getMetaData &event=edit &jsonVar=oJsonOutput} END. WHEN "detail":U THEN DO: //Chamar o metodo que retorna o metadado de detalhe /*RUN pGetMetadataDetail(INPUT oJsonInput, OUTPUT oJsonOutput).*/ //Seta a chave do registro no objeto que vai para a UPC RUN setMetadataEntityKey(INPUT oRequest, INPUT-OUTPUT oJsonOutput). {include/i-epcrest.i &endpoint=getMetaData &event=detail &jsonVar=oJsonOutput} END. END CASE. // Retorna a colecao de campos customizados ou nao para a interface HTML oResponse = NEW JsonAPIResponse(oJsonOutput). oJsonOutput = oResponse:createJsonResponse(). END PROCEDURE. PROCEDURE setMetadataEntityKey: /*No caso da edicao, copia e detalhe e preciso receber a chave do registro como pathParam no endPoint metadata e repassá-la para a UPC. Isso para caso o cliente queira alterar propriedades dos campos com base no registro, visto que o validateForm não é executado na abertura da tela. Adicionar a chave recebida no pathParams no atributo 'entityKey' do objeto enviado para a upc */ DEFINE INPUT PARAMETER oRequest AS JsonAPIRequestParser NO-UNDO. DEFINE INPUT-OUTPUT PARAMETER oObj AS JsonObject NO-UNDO. DEFINE VARIABLE c-key AS CHAR NO-UNDO. ASSIGN c-key = oRequest:getPathParams():GetCharacter(2). IF oObj <> ? THEN oObj:ADD('entityKey',c-key). END PROCEDURE. PROCEDURE pGetMetadataList: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. DEFINE VARIABLE oIdiomas AS JsonArray NO-UNDO. DEFINE VARIABLE oOpts AS JsonArray NO-UNDO. DEFINE VARIABLE oObj AS JsonObject NO-UNDO. DEFINE VARIABLE oOpt AS JsonObject NO-UNDO. ASSIGN oIdiomas = NEW JsonArray(). /* Define a lista de campos a serem apresentados no HTML */ ASSIGN oObj = NEW JsonObject(). oObj:add('property', 'codIdioma'). oObj:add('label', "~{~{language~}~}"). oObj:add('visible', TRUE). oObj:add('disable', TRUE). oObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('character')). oObj:add('gridColumns', 6). oIdiomas:add(oObj). ASSIGN oObj = NEW JsonObject(). oObj:add('property', 'desIdioma'). oObj:add('label', '~{~{description~}~}'). oObj:add('visible', TRUE). oObj:add('required', TRUE). oObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('character')). oObj:add('gridColumns', 6). oIdiomas:add(oObj). ASSIGN oObj = NEW JsonObject(). oObj:add('property', 'codIdiomPadr'). oObj:add('label', '~{~{defaultLanguage~}~}'). oObj:add('visible', TRUE). oObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('character')). oObj:add('gridColumns', 6). oIdiomas:add(oObj). ASSIGN oObj = NEW JsonObject(). oObj:add('property', 'datUltAtualiz'). oObj:add('label', '~{~{lastUpdate~}~}'). oObj:add('visible', TRUE). oObj:add('format', 'dd/MM/yyyy'). oObj:add('disable', TRUE). oObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('date')). oObj:add('gridColumns', 6). oIdiomas:add(oObj). ASSIGN oObj = NEW JsonObject(). oObj:add('property', 'hraUltAtualiz'). oObj:add('label', '~{~{hourLastUpdate~}~}'). oObj:add('visible', TRUE). oObj:add('disable', TRUE). oObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('character')). oObj:add('gridColumns', 6). oIdiomas:add(oObj). // acoes de tela para testes de validacao do formulario ASSIGN oOpts = NEW JsonArray(). ASSIGN oOpt = NEW JsonObject(). oOpt:add('label', '~{~{languageDefaultFocus~}~}'). oOpt:add('value', 'focoCodIdiomPadr'). oOpts:add(oOpt). ASSIGN oOpt = NEW JsonObject(). oOpt:add('label', '~{~{descriptionFocus~}~}'). oOpt:add('value', 'FocoDesIdioma'). oOpts:add(oOpt). ASSIGN oOpt = NEW JsonObject(). oOpt:add('label', '~{~{languageDefaultDisable~}~}'). oOpt:add('value', 'DesabilitaCodIdiomaPadrao'). oOpts:add(oOpt). ASSIGN oOpt = NEW JsonObject(). oOpt:add('label', '~{~{languageDefaultEnable~}~}'). oOpt:add('value', 'HabilitaCodIdiomaPadrao'). oOpts:add(oOpt). ASSIGN oOpt = NEW JsonObject(). oOpt:add('label', '~{~{cpfMask~}~}'). oOpt:add('value', 'MascaraCPF'). oOpts:add(oOpt). ASSIGN oOpt = NEW JsonObject(). oOpt:add('label', '~{~{cnpjMask~}~}'). oOpt:add('value', 'MascaraCNPJ'). oOpts:add(oOpt). ASSIGN oOpt = NEW JsonObject(). oOpt:add('label', '~{~{changeValueLanguage~}~}'). oOpt:add('value', 'TrocaValorDesIdioma'). oOpts:add(oOpt). ASSIGN oOpt = NEW JsonObject(). oOpt:add('label', '~{~{languageHide~}~}'). oOpt:add('value', 'EsconderDesIdioma'). oOpts:add(oOpt). ASSIGN oOpt = NEW JsonObject(). oOpt:add('label', '~{~{languageShow~}~}'). oOpt:add('value', 'AparecerDesIdioma'). oOpts:add(oOpt). ASSIGN oOpt = NEW JsonObject(). oOpt:add('label', '~{~{errorMessageShow~}~}'). oOpt:add('value', 'showErrorMessage'). oOpts:add(oOpt). ASSIGN oOpt = NEW JsonObject(). oOpt:add('label', '~{~{languageLabelChange~}~}'). oOpt:add('value', 'mudaLabelDesIdioma'). oOpts:add(oOpt). ASSIGN oObj = NEW JsonObject(). oObj:add('property', 'codAcoes'). oObj:add('label', '~{~{screenActions~}~}'). oObj:add('visible', TRUE). oObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('character')). oObj:add('options', oOpts). oObj:add('gridColumns', 12). oObj:add('validate', '/api/trn/v1/idiomas/validateField'). oIdiomas:add(oObj). // Informacoes de Tipo de Pessoa ASSIGN oOpts = NEW JsonArray(). ASSIGN oOpt = NEW JsonObject(). oOpt:add('label', '~{~{PF~}~}'). oOpt:add('value', 'f'). oOpts:add(oOpt). ASSIGN oOpt = NEW JsonObject(). oOpt:add('label', '~{~{PJ~}~}'). oOpt:add('value', 'j'). oOpts:add(oOpt). ASSIGN oObj = NEW JsonObject(). oObj:add('property', 'tipUsuario'). oObj:add('label', '~{~{userType~}~}'). oObj:add('visible', TRUE). oObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('character')). oObj:add('options', oOpts). oObj:add('gridColumns', 6). oObj:add('validate', '/api/trn/v1/idiomas/validateField'). oIdiomas:add(oObj). ASSIGN oObj = NEW JsonObject(). oObj:add('property', 'codCpfCnpj'). oObj:add('label', '~{~{documentOptions~}~}'). oObj:add('visible', TRUE). oObj:add('mask', '999.999.999-99'). oObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('character')). oObj:add('gridColumns', 6). oIdiomas:add(oObj). // Adiciona o campo ID na lista de campos para a interface HTML // Isso facilitara o gerenciamento do registro na interface HTML oIdiomas:add(JsonAPIUtils:getIdField()). // Adiciona o JsonArray de campos a propriedade "fields" de um JsonObject //para enviar para a UPC oObj = NEW JsonObject(). oObj:add('fields', oIdiomas). oJsonOutput = oObj. END PROCEDURE. /** Procedure que retorna os valores **/ PROCEDURE pFindAll: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. DEFINE VARIABLE oResponse AS JsonAPIResponse NO-UNDO. DEFINE VARIABLE oIdiomas AS JsonArray NO-UNDO. DEFINE VARIABLE oObj AS JsonObject NO-UNDO. DEFINE VARIABLE oId AS JsonObject NO-UNDO. EMPTY TEMP-TABLE ttIdiomas. // Monta a lista de valores dos campos FOR EACH idioma NO-LOCK BY idioma.cod_idioma: CREATE ttIdiomas. BUFFER-COPY idioma TO ttIdiomas. END. // Obtem um jsonArray com base no conteudo da temp-table oIdiomas = JsonAPIUtils:convertTempTableToJsonArray(TEMP-TABLE ttIdiomas:HANDLE). // Adiciona o JsonArray em um JsonObject para enviar para a UPC oObj = NEW JsonObject(). oObj:add('root', oIdiomas). // Realiza a chamada da UPC Progress {include/i-epcrest.i &endpoint=findAll &event=afterFindAll &jsonVar=oObj} // Recupera o JsonArray de dentro do JsonObject retornado pela UPC oIdiomas = oObj:getJsonArray('root'). // Retorna a colecao de dados customizados ou nao para a interface HTML oResponse = NEW JsonAPIResponse(oIdiomas). oJsonOutput = oResponse:createJsonResponse(). END PROCEDURE. /** Procedure que retorna 1 registro pelo ID **/ PROCEDURE pFindById: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. DEFINE VARIABLE oRequest AS JsonAPIRequestParser NO-UNDO. DEFINE VARIABLE oResponse AS JsonAPIResponse NO-UNDO. DEFINE VARIABLE oIdioma AS JsonObject NO-UNDO. DEFINE VARIABLE oId AS JsonObject NO-UNDO. DEFINE VARIABLE cId AS CHARACTER NO-UNDO. DEFINE VARIABLE iId AS INTEGER NO-UNDO. EMPTY TEMP-TABLE ttIdiomas. // Le os parametros enviados pela interface HTML oRequest = NEW JsonAPIRequestParser(oJsonInput). // Obtem o ID cId = oRequest:getPathParams():getCharacter(1). // Localiza o registro na tabela IDIOMA pela chave FIND FIRST idioma WHERE idioma.cod_idioma = cId NO-LOCK NO-ERROR. IF AVAILABLE idioma THEN DO: BUFFER-COPY idioma TO ttIdiomas. END. // Obtem um jsonArray com base no conteudo da temp-table oIdioma = JsonAPIUtils:convertTempTableFirstItemToJsonObject(TEMP-TABLE ttIdiomas:HANDLE). // Realiza a chamada da UPC Progress {include/i-epcrest.i &endpoint=findById &event=afterFindById &jsonVar=oIdioma} // Retorna o registro customizado ou nao para a interface HTML oResponse = NEW JsonAPIResponse(oIdioma). oJsonOutput = oResponse:createJsonResponse(). END PROCEDURE. /** Procedure que cria um novo registro na tabela **/ PROCEDURE pCreate: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. DEFINE VARIABLE oBody AS JsonObject NO-UNDO. DEFINE VARIABLE oRequest AS JsonAPIRequestParser NO-UNDO. DEFINE VARIABLE oResponse AS JsonAPIResponse NO-UNDO. DEFINE VARIABLE cCodIdioma AS CHARACTER NO-UNDO. DEFINE VARIABLE cDesIdioma AS CHARACTER NO-UNDO. DEFINE VARIABLE cCodIdiomPadr AS CHARACTER NO-UNDO. DEFINE VARIABLE lCreated AS LOGICAL NO-UNDO INITIAL FALSE. // Le os parametros e os dados enviados pela interface HTML oRequest = NEW JsonAPIRequestParser(oJsonInput). oBody = oRequest:getPayload(). // Obtem os demais dados cCodIdioma = oBody:getCharacter("codIdioma") NO-ERROR. cDesIdioma = oBody:getCharacter("desIdioma") NO-ERROR. cCodIdiomPadr = oBody:getCharacter("codIdiomPadr") NO-ERROR. // Cria o registro na tabela IDIOMA DO TRANSACTION ON ERROR UNDO, LEAVE: //Antes de executar a regra de negocio chama o ponto UPC beforeCreate {include/i-epcrest.i &endpoint=create &event=beforeCreate &jsonVar=oBody} /*Caso a UPC retorne NOK significa que a execucao de ser abortada Nesse caso, o objeto retornado pela UPC deve constar uma mensagem de erro para ser retornado no corpo da requisicao. O statusCode nesse caso sera 500. Deve ser realizado o tratamento abaixo para interrompes o processamento.*/ IF RETURN-VALUE = "NOK":U THEN DO: ASSIGN oJsonOutput = JsonApiResponseBuilder:ok(oBody, 500). RETURN. END. FIND FIRST idioma WHERE idioma.cod_idioma = cCodIdioma NO-LOCK NO-ERROR. IF NOT AVAILABLE idioma THEN DO: CREATE idioma. ASSIGN idioma.cod_idioma = cCodIdioma idioma.des_idioma = cDesIdioma idioma.cod_idiom_padr = cCodIdiomPadr idioma.dat_ult_atualiz = TODAY idioma.hra_ult_atualiz = STRING(TIME,"HH:MM:SS") lCreated = TRUE. // Realiza a chamada da UPC Progress para a criacao do // registro customizado. Nao utilizaremos o retorno da UPC // neste caso. {include/i-epcrest.i &endpoint=create &event=afterCreate &jsonVar=oBody} RELEASE idioma. END. END. // Retorna o ID e se foi criado com sucesso oBody = NEW JsonObject(). oBody:add('created', (IF lCreated THEN 'OK' ELSE 'NOK')). // Retorna o oBody montado para a interface HTML oResponse = NEW JsonAPIResponse(oBody). oJsonOutput = oResponse:createJsonResponse(). END PROCEDURE. /** Procedure que atualiza o conteudo do registro pelo ID **/ PROCEDURE pUpdateById: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. DEFINE VARIABLE oBody AS JsonObject NO-UNDO. DEFINE VARIABLE oRequest AS JsonAPIRequestParser NO-UNDO. DEFINE VARIABLE oResponse AS JsonAPIResponse NO-UNDO. DEFINE VARIABLE oIdioma AS JsonObject NO-UNDO. DEFINE VARIABLE oId AS JsonObject NO-UNDO. DEFINE VARIABLE cId AS CHARACTER NO-UNDO. DEFINE VARIABLE cCodIdioma AS CHARACTER NO-UNDO. DEFINE VARIABLE cDesIdioma AS CHARACTER NO-UNDO. DEFINE VARIABLE cCodIdiomPadr AS CHARACTER NO-UNDO. DEFINE VARIABLE datUltAtualiz AS DATE NO-UNDO. DEFINE VARIABLE hraUltAtualiz AS CHARACTER NO-UNDO. DEFINE VARIABLE lUpdated AS LOGICAL NO-UNDO INITIAL FALSE. // Le os parametros e os dados enviados pela interface HTML oRequest = NEW JsonAPIRequestParser(oJsonInput). oBody = oRequest:getPayload(). // Obtem a chave do registro cId = oRequest:getPathParams():getCharacter(1). // Obtem os demais dados cCodIdioma = oBody:getCharacter("codIdioma") NO-ERROR. cDesIdioma = oBody:getCharacter("desIdioma") NO-ERROR. cCodIdiomPadr = oBody:getCharacter("codIdiomPadr") NO-ERROR. // Atualiza o registro na tabela IDIOMA pela chave DO TRANSACTION ON ERROR UNDO, LEAVE: //Antes de executar a regra de negocio chama o ponto UPC beforeUpdate {include/i-epcrest.i &endpoint=update &event=beforeUpdate &jsonVar=oBody} /*Caso a UPC retorne NOK significa que a execucao de ser abortada Nesse caso, o objeto retornado pela UPC deve constar uma mensagem de erro para ser retornado no corpo da requisicao. O statusCode nesse caso sera 500. Deve ser realizado o tratamento abaixo para interrompes o processamento.*/ IF RETURN-VALUE = "NOK":U THEN DO: ASSIGN oJsonOutput = JsonApiResponseBuilder:ok(oBody, 500). RETURN. END. FIND FIRST idioma WHERE idioma.cod_idioma = cId EXCLUSIVE-LOCK NO-ERROR. IF AVAILABLE idioma THEN DO: ASSIGN idioma.des_idioma = cDesIdioma idioma.cod_idiom_padr = cCodIdiomPadr idioma.dat_ult_atualiz = TODAY idioma.hra_ult_atualiz = STRING(TIME,"HH:MM:SS") lUpdated = TRUE. // Realiza a chamada da UPC Progress para atualizar o registro // na tabela cutomizada ou nao. Nao utilizaremos o retorno da UPC // neste caso. {include/i-epcrest.i &endpoint=update &event=afterUpdate &jsonVar=oBody} END. END. // Retorna o ID e se foi atualizado com sucesso oBody = NEW JsonObject(). oBody:add('id', cId). oBody:add('updated', (IF lUpdated THEN 'OK' ELSE 'NOK')). // Retorna o oBody montado para a interface HTML oResponse = NEW JsonAPIResponse(oBody). oJsonOutput = oResponse:createJsonResponse(). END PROCEDURE. /** Procedure que atualiza o conteudo do registro pelo ID **/ PROCEDURE pDeleteById: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. DEFINE VARIABLE oObj AS JsonObject NO-UNDO. DEFINE VARIABLE oRequest AS JsonAPIRequestParser NO-UNDO. DEFINE VARIABLE oResponse AS JsonAPIResponse NO-UNDO. DEFINE VARIABLE oArray AS JsonArray NO-UNDO. DEFINE VARIABLE oIdioma AS JsonObject NO-UNDO. DEFINE VARIABLE oId AS JsonObject NO-UNDO. DEFINE VARIABLE cId AS CHARACTER NO-UNDO. DEFINE VARIABLE lDeleted AS LOGICAL NO-UNDO INITIAL FALSE. DEFINE VARIABLE ix AS INTEGER NO-UNDO. // Le os parametros enviados pela interface HTML oRequest = NEW JsonAPIRequestParser(oJsonInput). // Eliminacao de registro individual IF oRequest:getPathParams():length > 0 THEN DO: // Obtem o ID cId = oRequest:getPathParams():getCharacter(1). RUN piDeleteRecord (INPUT cId, OUTPUT oObj). IF RETURN-VALUE = "NOK":U THEN DO: ASSIGN oJsonOutput = JsonApiResponseBuilder:ok(oObj, 500). RETURN. END. ASSIGN lDeleted = (RETURN-VALUE = "OK"). END. ELSE DO: // Eliminacao de registros em lote // Obtem a lista de IDs diretamente do oJsonInput onde vem um JsonArray // oArray = oJsonInput:getJsonArray('payload'). oArray = oRequest:getPayloadArray(). DO ix = 1 TO oArray:length: oObj = oArray:getJsonObject(ix). cId = STRING(oObj:getInteger('id')). RUN piDeleteRecord (INPUT cId, OUTPUT oObj). IF RETURN-VALUE = "NOK":U THEN DO: ASSIGN oJsonOutput = JsonApiResponseBuilder:ok(oObj, 500). RETURN. END. IF lDeleted = FALSE THEN ASSIGN lDeleted = (RETURN-VALUE = "OK"). END. END. // Retorna o ID e se foi criado com sucesso oObj = NEW JsonObject(). IF oRequest:getPathParams():length > 0 THEN DO: oObj:add('id', cId). END. oObj:add('deleted', (IF lDeleted THEN 'OK' ELSE 'NOK')). // Retorna o oBody montado para a interface HTML oResponse = NEW JsonAPIResponse(oObj). oJsonOutput = oResponse:createJsonResponse(). END PROCEDURE. PROCEDURE piDeleteRecord: DEFINE INPUT PARAMETER cId AS CHARACTER NO-UNDO. DEFINE OUTPUT PARAMETER oObj AS JsonObject NO-UNDO. DEFINE VARIABLE lDeleted AS LOGICAL NO-UNDO INITIAL FALSE. LOG-MANAGER:WRITE-MESSAGE("Eliminando registro -> " + cId, ">>>>>"). // Elimina o registro na tabela IDIOMA pela chave DO TRANSACTION ON ERROR UNDO, LEAVE: // Monta um objeto com a chave para enviar para UPC // poder eliminar o registro da tabela customizada oObj = NEW JsonObject(). oObj:add('entityKey', cId). //Antes de executar a regra de negocio chama o ponto UPC beforeDelete {include/i-epcrest.i &endpoint=delete &event=beforeDelete &jsonVar=oObj} /*Caso a UPC retorne NOK significa que a execucao de ser abortada Nesse caso, o objeto retornado pela UPC deve constar uma mensagem de erro para ser retornado no corpo da requisicao. O statusCode nesse caso sera 500. Deve ser realizado o tratamento abaixo para interromper o processamento.*/ IF RETURN-VALUE = "NOK":U THEN RETURN "NOK". FIND FIRST idioma WHERE idioma.cod_idioma = cId EXCLUSIVE-LOCK NO-ERROR. IF AVAILABLE idioma THEN DO: DELETE idioma. ASSIGN lDeleted = TRUE. END. // Realiza a chamada da UPC Progress para a eliminacao do // registro customizado. {include/i-epcrest.i &endpoint=delete &event=afterDelete &jsonVar=oObj} END. RETURN (IF lDeleted THEN "OK" ELSE "NOK"). END PROCEDURE. PROCEDURE pValidateForm: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. DEFINE VARIABLE oRequest AS JsonAPIRequestParser NO-UNDO. DEFINE VARIABLE oResponse AS JsonAPIResponse NO-UNDO. DEFINE VARIABLE oBody AS JsonObject NO-UNDO. DEFINE VARIABLE cProp AS CHARACTER NO-UNDO. DEFINE VARIABLE oValue AS JsonObject NO-UNDO. DEFINE VARIABLE cValue AS CHARACTER NO-UNDO. DEFINE VARIABLE cId AS CHARACTER NO-UNDO. DEFINE VARIABLE oNewValue AS JsonObject NO-UNDO. DEFINE VARIABLE oNewFields AS JsonArray NO-UNDO. DEFINE VARIABLE cFocus AS CHARACTER NO-UNDO. DEFINE VARIABLE oRet AS JsonObject NO-UNDO. DEFINE VARIABLE oObj AS JsonObject NO-UNDO. DEFINE VARIABLE oMessages AS JsonArray NO-UNDO. oRequest = NEW JsonAPIRequestParser(oJsonInput). oBody = oRequest:getPayload(). // obtem o nome da propriedade que ocorreu o LEAVE para validacao cProp = oBody:getCharacter("property") NO-ERROR. oValue = oBody:getJsonObject("value") NO-ERROR. cValue = STRING(oValue:GetCharacter(cProp)) NO-ERROR. cId = oValue:getCharacter("id") NO-ERROR. /* Recebemos do HTML o JSON abaixo { "property": "codAcoes", "value": { "codIdiomPadr": "01 Portugues", "codIdioma": "12345678", "desIdioma": "12345678901234567890", "hraUltAtualiz": "", "datUltAtualiz": null, "id": 6, "codAcoes": "FocoDesIdioma" } } */ // Novas Acoes sobre os campos da tela // oNewValue guarda os valores a serem especificados para os campos ASSIGN oNewValue = NEW JsonObject(). // oNewFields guarda a lista de campos que serao alterados/modificados ASSIGN oNewFields = NEW JsonArray(). // cFocus especifica em qual campo sera feito o focus ASSIGN cFocus = cProp. // oMessages guarda as mensagens de retorno formato // { code: '00', message: 'texto', detailedMessage: 'detalhes da mensagem' } ASSIGN oMessages = NEW JsonArray(). CASE cProp: WHEN "codAcoes" THEN DO: CASE cValue: WHEN 'focoCodIdiomPadr' THEN DO: // setamos o focus para o campo desejado ASSIGN cFocus = 'codIdiomPadr'. END. WHEN 'FocoDesIdioma' THEN DO: // setamos o focus para o campo desejado ASSIGN cFocus = 'desIdioma'. END. WHEN 'DesabilitaCodIdiomaPadrao' THEN DO: // criamos um novo field para desabilitar ASSIGN oObj = NEW JsonObject(). oObj:add('property', 'codIdiomPadr'). oObj:add('disabled', TRUE). oNewFields:add(oObj). END. WHEN 'HabilitaCodIdiomaPadrao' THEN DO: // criamos um novo field para habilitar ASSIGN oObj = NEW JsonObject(). oObj:add('property', 'codIdiomPadr'). oObj:add('disabled', FALSE). oNewFields:add(oObj). END. WHEN 'MascaraCPF' THEN DO: // IMPORTANTE: // Quando alteramos o valor do radio-set tipUsuario por aqui, // O value-changed dele que especifica qual a mascara // sera utilizada NAO sera disparado, pois ele e dinamico e // estamos validando o campo codAcoes, sendo necessario // fazermos a formatacao da mascara aqui tambem. // A mesma regra e valida para o CNPJ // mudamos os valores dos campos desejados oNewValue:add('tipUsuario', 'f'). oNewValue:add('codCpfCnpj', FILL('0',11)). // criamos um novo field para mudar a mascara ASSIGN oObj = NEW JsonObject(). oObj:add('property', 'codCpfCnpj'). oObj:add('mask', '999.999.999-99'). oNewFields:add(oObj). END. WHEN 'MascaraCNPJ' THEN DO: // IMPORTANTE: // Quando alteramos o valor do radio-set tipUsuario por aqui, // O value-changed dele que especifica qual a mascara // sera utilizada NAO sera disparado, pois ele e dinamico e // estamos validando o campo codAcoes, sendo necessario // fazermos a formatacao da mascara aqui tambem. // A mesma regra e valida para o CPF // alteramos os valores dos campos desejados oNewValue:add('tipUsuario', 'j'). oNewValue:add('codCpfCnpj', FILL('0',15)). // criamos um novo field para mudar a mascara ASSIGN oObj = NEW JsonObject(). oObj:add('property', 'codCpfCnpj'). oObj:add('mask', '99.999.999/9999-99'). oNewFields:add(oObj). END. WHEN 'TrocaValorDesIdioma' THEN DO: // alteramos o conteudo de um campo qualquer oNewValue:add('desIdioma', "Valor retornado do back-end de validacao"). END. WHEN 'EsconderDesIdioma' THEN DO: // criamos um novo field para tornar invisivel o campo ASSIGN oObj = NEW JsonObject(). oObj:add('property', 'desIdioma'). oObj:add('visible', FALSE). oNewFields:add(oObj). END. WHEN 'AparecerDesIdioma' THEN DO: // criamos um novo field para tornar visivel o campo ASSIGN oObj = NEW JsonObject(). oObj:add('property', 'desIdioma'). oObj:add('visible', TRUE). oNewFields:add(oObj). END. WHEN 'mudaLabelDesIdioma' THEN DO: // criamos um novo field para mudar o label ASSIGN oObj = NEW JsonObject(). oObj:add('property', 'desIdioma'). oObj:add('label', 'Label alterado da descricao'). oNewFields:add(oObj). END. WHEN 'showErrorMessage' THEN DO: // criamos uma mensagem de erro ASSIGN oObj = NEW JsonObject(). oObj:add('code', '33'). oObj:add('message', 'A Descricao do idioma nao foi preenchida corretamente'). oObj:add('detailedMessage', 'Detalhe da mensagem de erro'). oMessages:add(oObj). END. END CASE. END. WHEN "tipUsuario" THEN DO: // setamos o focus para o campo desejado ASSIGN cFocus = 'codCpfCnpj'. // criamos um field para mudar o informar o campo e a nova mascara ASSIGN oObj = NEW JsonObject(). oObj:add('property', "codCpfCnpj"). IF cValue = "j" THEN DO: //definido um novo valor para o CNPJ oNewValue:add('codCpfCnpj', FILL('0',15)). //alterado o formato da mascara de edicao oObj:add('mask', '99.999.999/9999-99'). END. IF cValue = "f" THEN DO: //definido um novo valor para o CPF oNewValue:add('codCpfCnpj', FILL('0',11)). //alterado o formato da mascara de edicao oObj:add('mask', '999.999.999-99'). END. oNewFields:add(oObj). END. END CASE. ASSIGN oRet = NEW JsonObject(). // value -> contem todos os valores dos campos de tela oRet:add('value', oNewValue). // fields -> contem a lista de campos com suas novas propriedades oRet:add('fields', oNewFields). // focus -> especifica em qual campo o cursor vai ficar posicionado oRet:add('focus', cFocus). // _messages -> contem uma lista de mensagens que vao aparecer como notificacoes oRet:add('_messages', oMessages). // encapsulamos o retorno para enviar para a UPC oObj = NEW JsonObject(). oObj:add("property", cProp). oObj:add("originalValues", oValue). oObj:add("root", oRet). // Realiza a chamada da UPC Progress {include/i-epcrest.i &endpoint=validateForm &event=validateForm &jsonVar=oObj} // obtem o retorno customizado, onde o mesmo foi alterado e retornado na tag root oRet = oObj:getJsonObject("root"). /* JSON de retorno para o HTML value: { desIdioma: 'teste de escrita', hraUltAtualiz: '17:18:19' }, fields: [ { property: 'codCpfCnpj', mask: '99.999.999/9999-99' } ], focus: 'hraUltAtualiz', _messages: [ { code: '01', message: 'Mensagem do erro que aconteceu', detailedMessage: 'detalhes do erro acontecido' } ] */ // Retorna a colecao de campos customizados ou nao para a interface HTML oResponse = NEW JsonAPIResponse(oRet). oJsonOutput = oResponse:createJsonResponse(). END PROCEDURE. PROCEDURE pValidateField: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. DEFINE VARIABLE oRequest AS JsonAPIRequestParser NO-UNDO. DEFINE VARIABLE oResponse AS JsonAPIResponse NO-UNDO. DEFINE VARIABLE oBody AS JsonObject NO-UNDO. DEFINE VARIABLE cProp AS CHARACTER NO-UNDO. DEFINE VARIABLE oValue AS JsonObject NO-UNDO. DEFINE VARIABLE cValue AS CHARACTER NO-UNDO. DEFINE VARIABLE cId AS CHARACTER NO-UNDO. DEFINE VARIABLE oNewValue AS JsonObject NO-UNDO. DEFINE VARIABLE oNewField AS JsonObject NO-UNDO. DEFINE VARIABLE lFocus AS LOGICAL NO-UNDO INITIAL FALSE. DEFINE VARIABLE oRet AS JsonObject NO-UNDO. DEFINE VARIABLE oObj AS JsonObject NO-UNDO. DEFINE VARIABLE oMessages AS JsonArray NO-UNDO. oRequest = NEW JsonAPIRequestParser(oJsonInput). oBody = oRequest:getPayload(). // obtem o nome da propriedade que ocorreu o LEAVE para validacao cProp = oBody:getCharacter("property") NO-ERROR. /* Recebemos do HTML o JSON abaixo { "property": "codIdioma", "value": "" } */ // oNewField guarda o objeto que sera alterado/modificado ASSIGN oNewField = NEW JsonObject() oRet = NEW JsonObject(). // oMessages guarda as mensagens de retorno formato // { code: '00', message: 'texto', detailedMessage: 'detalhes da mensagem' } ASSIGN oMessages = NEW JsonArray(). IF cProp = "codIdioma" THEN DO: ASSIGN cValue = oBody:getCharacter("value") lFocus = TRUE. IF cValue = '' THEN DO: oNewField:add('property', cProp). //alterado o campo para obrigatorio oNewField:add('required', true). //alterado o campo para apresentar a informacao que ìe obrigatorio oNewField:add('showRequired', true). ASSIGN oObj = NEW JsonObject(). oObj:add('code', '99'). oObj:add('message', 'O campo codigo do idioma ìe obrigatorio.'). oObj:add('detailedMessage', 'Insira uma informacao valida para o campo codigo do idioma.'). oMessages:add(oObj). // value -> valor que deve ser repassado para o campo oRet:add('value', cValue). // field -> contem os novos atributos do campo atual oRet:add('field', oNewField). // focus -> especifica se o campo recebe o focu oRet:add('focus', lFocus). // _messages -> contem uma lista de mensagens que vao aparecer como notificacoes oRet:add('_messages', oMessages). END. END. // encapsulamos o retorno para enviar para a UPC oObj = CAST(oBody:clone(), jsonObject). oObj:add("root", oRet). // Realiza a chamada da UPC Progress {include/i-epcrest.i &endpoint=validateField &event=validateField &jsonVar=oObj} // obtem o retorno customizado, onde o mesmo foi alterado e retornado somente // o conteudo da tag return oRet = oObj:getJsonObject("root"). /* JSON de retorno para o HTML value: '', field: { property: 'codIdioma' required: true, showRequired: true }, focus: true, _messages: [ { code: '99', message: 'O campo codigo do idioma e obrigatorio.', detailedMessage: 'Insira uma informacao valida para o campo codigo do idioma.' } ] */ // Retorna a colecao de campos customizados ou nao para a interface HTML oResponse = NEW JsonAPIResponse(oRet). oJsonOutput = oResponse:createJsonResponse(). END PROCEDURE. /** Recupera as literais */ PROCEDURE pIdiomas: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. // Realiza a chamada da UPC Progress {include/i-epcrest.i &endpoint=i18n &event=i18n &jsonVar=oJsonInput} ASSIGN oJsonOutput = oJsonInput. END PROCEDURE. /* fim */
Programa UPC:
Abaixo temos um exemplo de uma UPC criada para a API-REST:
/************************************************************************** ** upc-idioma.p - Exemplo de upc para Endpoints REST ** 18/05/2020 - Menna - Criado exemplo ***************************************************************************/ USING PROGRESS.json.*. USING PROGRESS.json.ObjectModel.*. USING com.totvs.framework.api.*. DEFINE INPUT PARAMETER pEndPoint AS CHARACTER NO-UNDO. DEFINE INPUT PARAMETER pEvent AS CHARACTER NO-UNDO. DEFINE INPUT PARAMETER pAPI AS CHARACTER NO-UNDO. DEFINE INPUT-OUTPUT PARAMETER jsonIO AS JSONObject NO-UNDO. DEFINE VARIABLE jObj AS JsonObject NO-UNDO. DEFINE VARIABLE oOriginalValues AS JSonObject NO-UNDO. DEFINE VARIABLE oReturn AS JSonObject NO-UNDO. DEFINE VARIABLE oValues AS JSonObject NO-UNDO. DEFINE VARIABLE oFieldObj AS JSonObject NO-UNDO. DEFINE VARIABLE aFields AS JSonArray NO-UNDO. DEFINE VARIABLE aMessages AS JSonArray NO-UNDO. DEFINE VARIABLE ix AS INTEGER NO-UNDO. DEFINE VARIABLE iTot AS INTEGER NO-UNDO. DEFINE VARIABLE cCodIdioma AS CHARACTER NO-UNDO. DEFINE VARIABLE cCodUsuario AS CHARACTER NO-UNDO. DEFINE VARIABLE cNomUsuario AS CHARACTER NO-UNDO. DEFINE VARIABLE cCodDialet AS CHARACTER NO-UNDO. DEFINE VARIABLE cProp AS CHARACTER NO-UNDO. DEFINE VARIABLE cFocus AS CHARACTER NO-UNDO. DEFINE VARIABLE cOriginalValue AS CHARACTER NO-UNDO. DEFINE VARIABLE cValue AS CHARACTER NO-UNDO. DEFINE VARIABLE lFocus AS LOGICAL NO-UNDO INITIAL FALSE. /* *************************** Main Block *************************** */ LOG-MANAGER:WRITE-MESSAGE("UPC EndPoint = " + pEndPoint, ">>>>"). LOG-MANAGER:WRITE-MESSAGE("UPC Event = " + pEvent, ">>>>"). // Carrega as definicoes dos campos customizados da tabela IF pEndPoint = "getMetaData" AND pEvent = "list" THEN DO ON STOP UNDO, LEAVE: RUN piGetMetaDataList. END. // Carrega as definicoes dos campos customizados da tela de inclusão IF pEndPoint = "getMetaData" AND pEvent = "new" THEN DO ON STOP UNDO, LEAVE: //RUN piGetMetaDataNew. END. // Carrega as definicoes dos campos customizados da tela de edição IF pEndPoint = "getMetaData" AND pEvent = "edit" THEN DO ON STOP UNDO, LEAVE: //RUN piGetMetaDataEdit. END. // Carrega as definicoes dos campos customizados da tela de detalhe IF pEndPoint = "getMetaData" AND pEvent = "detail" THEN DO ON STOP UNDO, LEAVE: //RUN piGetMetaDataDetail. END. // Carrega os valores dos campos customizados das tabelas IF pEndPoint = "findAll" AND pEvent = "afterFindAll" THEN DO ON STOP UNDO, LEAVE: RUN piAfterFindAll. END. IF pEndPoint = "findById" AND pEvent = "afterFindById" THEN DO ON STOP UNDO, LEAVE: RUN piAfterFindById. END. IF pEndPoint = "create" AND pEvent = "beforeCreate" THEN DO ON STOP UNDO, LEAVE: RUN piBeforeCreate. END. IF pEndPoint = "create" AND pEvent = "afterCreate" THEN DO ON STOP UNDO, LEAVE: RUN piAfterCreate. END. IF pEndPoint = "update" AND pEvent = "beforeUpdate" THEN DO ON STOP UNDO, LEAVE: RUN piBeforeUpdate. END. IF pEndPoint = "update" AND pEvent = "afterUpdate" THEN DO ON STOP UNDO, LEAVE: RUN piAfterUpdate. END. IF pEndPoint = "delete" AND pEvent = "beforeDelete" THEN DO ON STOP UNDO, LEAVE: RUN piBeforeDelete. END. IF pEndPoint = "delete" AND pEvent = "afterDelete" THEN DO ON STOP UNDO, LEAVE: RUN piAfterDelete. END. IF pEndPoint = "validateForm" AND pEvent = "validateForm" THEN DO ON STOP UNDO, LEAVE: RUN piValidateForm. END. IF pEndPoint = "validateField" AND pEvent = "validateField" THEN DO ON STOP UNDO, LEAVE: RUN piValidateField. END. IF pEndPoint = "i18n" AND pEvent = "i18n" THEN DO ON STOP UNDO, LEAVE: RUN piI18N. END. RETURN "OK". PROCEDURE piGetMetaDataList: // Obtem a lista de campos e valores ASSIGN aFields = jsonIO:getJsonArray('fields'). // Cria os novos campos na lista ASSIGN jObj = NEW JsonObject(). jObj:add('divider', "Itens da UPC"). jObj:add('property', 'codUsuario'). jObj:add('label', '~{~{user~}~}'). jObj:add('visible', TRUE). jObj:add('required', TRUE). jObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('character')). jObj:add('gridColumns', 6). aFields:add(jObj). ASSIGN jObj = NEW JsonObject(). jObj:add('property', 'nomUsuario'). jObj:add('label', '~{~{name~}~}'). jObj:add('visible', TRUE). jObj:add('required', TRUE). jObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('character')). jObj:add('gridColumns', 6). aFields:add(jObj). ASSIGN jObj = NEW JsonObject(). jObj:add('property', 'codDialet'). jObj:add('label', '~{~{dialect~}~}'). jObj:add('visible', FALSE). // <- Remove o item da tela de todos seus correspondentes (Form, View, Table) jObj:add('required', TRUE). jObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('character')). jObj:add('gridColumns', 6). aFields:add(jObj). ASSIGN jObj = NEW JsonObject(). jObj:add('property', 'testeValidacaoRegEx'). jObj:add('label', '~{~{regexTestValidation~}~}'). jObj:add('gridColumns', 6). jObj:add('pattern', "[0-9]~{2~}"). // <- Validacao RegEx jObj:add('errorMessage', 'Obrigatório mínimo 2 n£meros consecutivos.'). aFields:add(jObj). ASSIGN jObj = NEW JsonObject(). jObj:add('property', 'numberRangeValidate'). jObj:add('label', '~{~{cpfMaskApply~}~}'). jObj:add('mask', '999.999.999-99'). // <-- Mascara CPF jObj:add('visible', TRUE). jObj:add('required', FALSE). jObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('character')). jObj:add('gridColumns', 6). aFields:add(jObj). ASSIGN jObj = NEW JsonObject(). jObj:add('property', 'numberValidate'). jObj:add('label', '~{~{onlyNumbers~}~}'). jObj:add('visible', TRUE). jObj:add('required', FALSE). jObj:add('minValue', 1). jObj:add('maxValue', 9). jObj:add('errorMessage', 'Somente números de 1 a 9'). // <- Mensagem de erro 1-9 jObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('integer')). // <- Restringe a digitacao somente numeros jObj:add('gridColumns', 6). aFields:add(jObj). // Retorna a nova lista com os campos customizados jsonIO:Set("fields", aFields). END PROCEDURE. PROCEDURE piAfterFindAll: // Obtem a lista de campos e valores ASSIGN aFields = jsonIO:getJsonArray('root'). LOG-MANAGER:WRITE-MESSAGE("UPC FINDALL", ">>>>"). FIND FIRST usuar_mestre NO-LOCK NO-ERROR. // Armazena o tamanho da lista em variavel para evitar LOOP devido a adicionar novos itens na lista ASSIGN iTot = aFields:length. DO ix = 1 TO iTot: ASSIGN jObj = aFields:GetJsonObject(ix). // Alimenta os novos dados IF AVAILABLE usuar_mestre THEN DO: jObj:add('codUsuario', usuar_mestre.cod_usuario) NO-ERROR. jObj:add('nomUsuario', usuar_mestre.nom_usuario) NO-ERROR. jObj:add('codDialet', usuar_mestre.cod_dialet) NO-ERROR. END. // Atualiza o objeto na lista aFields:set(ix, jObj). FIND NEXT usuar_mestre NO-LOCK NO-ERROR. END. // Retorna o json ROOT a lista nova com novos dados customizados jsonIO:Set("root", aFields). END PROCEDURE. PROCEDURE piAfterFindById: // Obtem as informacoes necessarias da API para retornar dados cCodIdioma = jsonIO:getCharacter("codIdioma"). // chave estrangeira LOG-MANAGER:WRITE-MESSAGE("UPC FINDBYID cod_idioma= " + cCodIdioma, ">>>>"). // Adiciona os valores da tabela customizada no retorno FIND FIRST usuar_mestre NO-LOCK NO-ERROR. IF AVAILABLE usuar_mestre THEN DO: jsonIO:add('codUsuario', usuar_mestre.cod_usuario) NO-ERROR. jsonIO:add('nomUsuario', usuar_mestre.nom_usuario) NO-ERROR. jsonIO:add('codDialet', usuar_mestre.cod_dialet) NO-ERROR. END. END PROCEDURE. PROCEDURE piBeforeCreate: /*Permite realizar validações e bloquear a criação do registro.*/ cCodUsuario = jsonIO:getCharacter("codUsuario") NO-ERROR. //Exemplo de validação IF NOT CAN-FIND(FIRST usuar_mestre WHERE usuar_mestre.cod_usuario = cCodUsuario) THEN DO: ASSIGN jObj = NEW JsonObject() aMessages = NEW JsonArray(). //Cria a mensagem de erro e acrescenta ao objeto retornado pela UPC jObj:add('code', '02'). jObj:add('message', 'Não encontrado registro para o usuário informado.'). jObj:add('detailedMessage', 'Não é possível criar o registro.'). aMessages:add(jObj). jsonIO = NEW JsonObject(). jsonIO:Add("_messages",jObj). //Retorna NOK para que a API faça o tratamento de retornar o statusCode:500 //juntamente com a mensagem no response da requisição. RETURN "NOK". END. RETURN "OK". END PROCEDURE. PROCEDURE piAfterCreate: // Obtem as informacoes necessarias da API para criacao do registro cCodIdioma = jsonIO:getCharacter("codIdioma") NO-ERROR. // chave estrangeira cCodUsuario = jsonIO:getCharacter("codUsuario") NO-ERROR. cNomUsuario = jsonIO:getCharacter("nomUsuario") NO-ERROR. cCodDialet = jsonIO:getCharacter("codDialet") NO-ERROR. LOG-MANAGER:WRITE-MESSAGE("UPC CREATE cod_idioma= " + cCodIdioma, ">>>>"). LOG-MANAGER:WRITE-MESSAGE("UPC CREATE cod_usuario= " + cCodUsuario, ">>>>"). // logica de CREATE /* Em comentario a logica para nao criar registros desnecessariamente FIND FIRST usuar_mestre WHERE usuar_mestre.cod_usuario = cCodUsuario EXCLUSIVE-LOCK NO-ERROR. IF NOT AVAILABLE usuar_mestre THEN DO: ASSIGN usuar_mestre.nom_usuario = cNomUsuario usuar_mestre.cod_dialet = cCodDialet. END. */ END PROCEDURE. PROCEDURE piBeforeCreate: /*Permite realizar validações e bloquear a alteração do registro.*/ cCodUsuario = jsonIO:getCharacter("codUsuario") NO-ERROR. //Exemplo de validação IF NOT CAN-FIND(FIRST usuar_mestre WHERE usuar_mestre.cod_usuario = cCodUsuario) THEN DO: ASSIGN jObj = NEW JsonObject() aMessages = NEW JsonArray(). //Cria a mensagem de erro e acrescenta ao objeto retornado pela UPC jObj:add('code', '02'). jObj:add('message', 'Não encontrado registro para o usuário informado.'). jObj:add('detailedMessage', 'Não é possível alterar o registro.'). aMessages:add(jObj). jsonIO = NEW JsonObject(). jsonIO:Add("_messages",jObj). //Retorna NOK para que a API faça o tratamento de retornar o statusCode:500 //juntamente com a mensagem no response da requisição. RETURN "NOK". END. RETURN "OK". END PROCEDURE. PROCEDURE piAfterUpdate: // Obtem as informacoes necessarias da API para atualizacao cCodIdioma = jsonIO:getCharacter("codIdioma") NO-ERROR. // chave estrangeira cCodUsuario = jsonIO:getCharacter("codUsuario") NO-ERROR. cNomUsuario = jsonIO:getCharacter("nomUsuario") NO-ERROR. cCodDialet = jsonIO:getCharacter("codDialet") NO-ERROR. LOG-MANAGER:WRITE-MESSAGE("UPC UPDATE cod_idioma= " + cCodIdioma, ">>>>"). LOG-MANAGER:WRITE-MESSAGE("UPC UPDATE cod_usuario= " + cCodUsuario, ">>>>"). // logica de UPDATE /* Em comentario a logica para nao alterar tabelas desnecessariamente FIND FIRST usuar_mestre WHERE usuar_mestre.cod_usuario = cCodUsuario EXCLUSIVE-LOCK NO-ERROR. IF AVAILABLE usuar_mestre THEN DO: ASSIGN usuar_mestre.nom_usuario = cNomUsuario usuar_mestre.cod_dialet = cCodDialet. END. */ END PROCEDURE. PROCEDURE piBeforeCreate: /*Permite realizar validações e bloquear a eliminação do registro.*/ cCodUsuario = jsonIO:getCharacter("codUsuario") NO-ERROR. //Exemplo de validação IF NOT CAN-FIND(FIRST usuar_mestre WHERE usuar_mestre.cod_usuario = cCodUsuario) THEN DO: ASSIGN jObj = NEW JsonObject() aMessages = NEW JsonArray(). //Cria a mensagem de erro e acrescenta ao objeto retornado pela UPC jObj:add('code', '02'). jObj:add('message', 'Não encontrado registro para o usuário informado.'). jObj:add('detailedMessage', 'Não é possível alterar o registro.'). aMessages:add(jObj). jsonIO = NEW JsonObject(). jsonIO:Add("_messages",jObj). //Retorna NOK para que a API faça o tratamento de retornar o statusCode:500 //juntamente com a mensagem no response da requisição. RETURN "NOK". END. RETURN "OK". END PROCEDURE. PROCEDURE piAfterDelete: // obtem as informacoes necessarias da API para eliminacao cCodIdioma = jsonIO:getCharacter("codIdioma"). // chave estrangeira LOG-MANAGER:WRITE-MESSAGE("UPC DELETE cod_idioma= " + cCodIdioma, ">>>>"). // logica de DELETE /* Em comentario a logica para nao eliminar o registro desnecessariamente FIND FIRST usuar_mestre WHERE usuar_mestre.cod_usuario = cCodUsuario EXCLUSIVE-LOCK NO-ERROR. IF AVAILABLE usuar_mestre THEN DO: delete usuar_mestre. END. */ END PROCEDURE. PROCEDURE piValidateForm: cProp = jsonIO:getCharacter("property") NO-ERROR. // o cProp contem o nome da propriedade que esta sendo validada oOriginalValues = jsonIO:getJsonObject("originalValues") NO-ERROR. // obtem os valores dos campos que vieram da tela html LOG-MANAGER:WRITE-MESSAGE("UPC ValidateForm property= " + cProp, ">>>>"). oReturn = jsonIO:getJsonObject("root") NO-ERROR. // obtem o retorno que sera enviado para a tela html oValues = oReturn:getJsonObject("value") NO-ERROR. // obtem os valores dos campos ja ajustados aFields = oReturn:getJsonArray("fields") NO-ERROR. // obtem as propriedades dos campos a serem alteradas cFocus = oReturn:getCharacter("focus") NO-ERROR. // obtem o campo de focus a ser retornado para a tela html aMessages = oReturn:getJsonArray("_messages") NO-ERROR. // obtem as mensagens a serem retornados para a tela html /* Exemplo de JSON que veio para a UPC { property: 'codAcao', originalValues: { "codIdiomPadr": "01 Português", "codIdioma": "12345678", "desIdioma": "12345678901234567890", "hraUltAtualiz": "", "datUltAtualiz": null, "id": 6, "codAcoes": "FocoDesIdioma" }, root: { value: { desIdioma: 'teste de escrita', hraUltAtualiz: '17:18:19' }, fields: [ { property: 'codCpfCnpj', mask: '99.999.999/9999-99' } ], focus: 'hraUltAtualiz', _messages: [ { code: '01', message: 'Mensagem do erro que aconteceu', detailedMessage: 'detalhes do erro acontecido' } ] } } */ IF cProp = "desIdioma" THEN DO: cCodIdioma = oOriginalValues:getCharacter("codIdioma"). // chave estrangeira IF cCodIdioma = "12345678" THEN DO: oValues:add("desIdioma", "Valor customizado na UPC"). oValues:add("hraUltAtualiz", "17:18:19"). // criamos um novo field para desabilitar ASSIGN jObj = NEW JsonObject(). jObj:add('property', 'codIdiomPadr'). jObj:add('disabled', TRUE). aFields:add(jObj). ASSIGN cFocus = "desIdioma". ASSIGN jObj = NEW JsonObject(). jObj:add('code', '44'). jObj:add('message', 'A UPC alterou algumas caracteristica da tela.'). jObj:add('detailedMessage', 'Na execução da UPC, houveram alterações nos campos de tela.'). aMessages:add(jObj). END. END. /* Exemplo de JSON de retorno para o HTML value: { desIdioma: 'Valor customizado na UPC' hraUltAtualiz: '17:18:19' }, fields: [ { property: 'codCpfCnpj', mask: '99.999.999/9999-99' } ], focus: 'hraUltAtualiz', _messages: [ { code: '01', message: 'Mensagem do erro que aconteceu', detailedMessage: 'detalhes do erro acontecido' } ] */ // atribui os valores de volta para a tela HTML jsonIO = NEW JSonObject(). oReturn = NEW JSonObject(). oReturn:add("value", oValues). // seta os valores dos campos ja ajustados oReturn:add("fields", aFields). // seta as propriedades dos campos a serem alteradas oReturn:add("focus", cFocus). // seta o campo de focus a ser retornado para a tela html oReturn:add("_messages", aMessages). // seta as mensagens a serem retornadas para a tela html jsonIO:add("root", oReturn). END PROCEDURE. PROCEDURE piValidateField: cProp = jsonIO:getCharacter("property") NO-ERROR. // o cProp contem o nome da propriedade que esta sendo validada LOG-MANAGER:WRITE-MESSAGE("UPC ValidateField property= " + cProp, ">>>>"). oReturn = jsonIO:getJsonObject("root"). // obtem o retorno que sera enviado para a tela html oFieldObj = oReturn:getJsonObject("field"). // obtem as propriedades dos campos a serem alteradas lFocus = oReturn:getLogical("focus"). // obtem se o focus ficara sobre o mesmo campo ao retornar para a tela html aMessages = oReturn:getJsonArray("_messages"). // obtem as mensagens a serem retornados para a tela html /* Exemplo de JSON que veio para a UPC { property: 'codIdioma', value: '', root: { value: '', field: { property: 'codIdioma', required: true, showRequired: true }, focus: true, _messages: [ { code: '99', message: 'O campo código do idioma é obrigatório.', detailedMessage: 'Insira uma informação válida para o campo código do idioma.' } ] } } */ IF cProp = "codIdioma" THEN DO: oFieldObj:add('label', 'Novo label'). oFieldObj:add('required', TRUE). cValue = oReturn:getCharacter("value"). // pega o novo valor do campo ASSIGN lFocus = TRUE cValue = "FocoDesIdioma". ASSIGN jObj = NEW JsonObject(). jObj:add('code', '44'). jObj:add('message', 'A UPC alterou algumas caracteristica da tela.'). jObj:add('detailedMessage', 'Na execução da UPC, houveram alterações nos campos de tela.'). IF aMessages = ? THEN aMessages = NEW JsonArray(). aMessages:add(jObj). oReturn = NEW JSonObject(). oReturn:add("value", cValue). // mantem o valor seta os valores dos campos ja ajustados oReturn:add("field", oFieldObj). // seta as propriedades dos campos a serem alteradas oReturn:add("focus", lFocus). // seta o focus a ser retornado para a tela html oReturn:add("_messages", aMessages). // seta as mensagens a serem retornadas para a tela html END. /* Exemplo de JSON de retorno para o HTML value: 'FocoDesIdioma', field: { required: true, showRequired: true, label: 'Novo Label', }, focus: true, _messages: [ { code: '99', message: 'O campo código do idioma é obrigatório.', detailedMessage: 'Insira uma informação válida para o campo código do idioma.' }, { code: '44', message: 'A UPC alterou algumas caracteristica da tela.', detailedMessage: 'Na execução da UPC, houveram alterações nos campos de tela.' } ] */ // atribui os valores de volta para a tela HTML jsonIO = NEW JSonObject(). jsonIO:add("root", oReturn). END PROCEDURE. PROCEDURE piI18N: DEFINE VARIABLE oParser AS JsonAPIRequestParser NO-UNDO. DEFINE VARIABLE oQueryParams AS JsonObject NO-UNDO. DEFINE VARIABLE pIdioma AS CHARACTER NO-UNDO. ASSIGN oParser = NEW JsonAPIRequestParser(jsonIO) oQueryParams = oParser:GetQueryParams() pIdioma = oQueryParams:GetJsonArray("language"):GetCharacter(1). IF (pIdioma = "pt-BR") THEN DO: jsonIO = NEW JsonObject(). jsonIO:Add("user", "Usuário"). jsonIO:Add("name", "Nome"). jsonIO:Add("regexTestValidation", "Teste Validação REGEX"). jsonIO:Add("cpfMaskApply", "Aplicação Máscara CPF"). jsonIO:Add("onlyNumbers", "Somente Números"). END. ELSE IF (pIdioma = "en-US") THEN DO: jsonIO = NEW JsonObject(). jsonIO:Add("user", "User"). jsonIO:Add("name", "Name"). jsonIO:Add("regexTestValidation", "REGEX Test Validation"). jsonIO:Add("cpfMaskApply", "CPF Apply Mask"). jsonIO:Add("onlyNumbers", "Only Numbers"). END. END PROCEDURE. /* fim */
Resultado ao chamar à API tendo uma UPC cadastrada:
Ao fazer as requisições, virão os seguintes resultados na UPC.
Busca do METADADOS de listagem onde foram adicionados os novos campos codUsuario, nomUsuario e codDialet: GET - http://localhost:8180/dts/datasul-rest/resources/prg/trn/v1/idiomas/metadata?type=list { "fields": [ { "visible": true, "gridColumns": 6, "disable": true, "property": "codIdioma", "label": "{{language}}", "type": "string" }, { "visible": true, "gridColumns": 6, "property": "desIdioma", "label": "{{description}}", "type": "string", "required": true }, { "visible": true, "gridColumns": 6, "property": "codIdiomPadr", "label": "{{defaultLanguage}}", "type": "string" }, { "visible": true, "gridColumns": 6, "disable": true, "property": "datUltAtualiz", "format": "dd/MM/yyyy", "label": "{{lastUpdate}}", "type": "date" }, { "visible": true, "gridColumns": 6, "disable": true, "property": "hraUltAtualiz", "label": "{{hourLastUpdate}}", "type": "string" }, { "visible": true, "gridColumns": 12, "property": "codAcoes", "options": [ { "label": "{{languageDefaultFocus}}", "value": "focoCodIdiomPadr" }, { "label": "{{descriptionFocus}}", "value": "FocoDesIdioma" }, { "label": "{{languageDefaultDisable}}", "value": "DesabilitaCodIdiomaPadrao" }, { "label": "{{languageDefaultEnable}}", "value": "HabilitaCodIdiomaPadrao" }, { "label": "{{cpfMask}}", "value": "MascaraCPF" }, { "label": "{{cnpjMask}}", "value": "MascaraCNPJ" }, { "label": "{{changeValueLanguage}}", "value": "TrocaValorDesIdioma" }, { "label": "{{languageHide}}", "value": "EsconderDesIdioma" }, { "label": "{{languageShow}}", "value": "AparecerDesIdioma" }, { "label": "{{errorMessageShow}}", "value": "showErrorMessage" }, { "label": "{{languageLabelChange}}", "value": "mudaLabelDesIdioma" } ], "label": "{{screenActions}}", "type": "string", "validate": "/api/trn/v1/idiomas/validateField" }, { "visible": true, "gridColumns": 6, "property": "tipUsuario", "options": [ { "label": "{{PF}}", "value": "f" }, { "label": "{{PJ}}", "value": "j" } ], "label": "{{userType}}", "type": "string", "validate": "/api/trn/v1/idiomas/validateField" }, { "visible": true, "gridColumns": 6, "property": "codCpfCnpj", "label": "{{documentOptions}}", "type": "string", "mask": "999.999.999-99" }, { "visible": false, "property": "id", "type": "number", "key": true }, { "visible": true, "gridColumns": 6, "divider": "Itens da UPC", "property": "codUsuario", "label": "{{user}}", "type": "string", "required": true }, { "visible": true, "gridColumns": 6, "property": "nomUsuario", "label": "{{name}}", "type": "string", "required": true }, { "visible": false, "gridColumns": 6, "property": "codDialet", "label": "{{dialect}}", "type": "string", "required": true }, { "gridColumns": 6, "property": "testeValidacaoRegEx", "pattern": "[0-9]{2}", "errorMessage": "Obrigatório mínimo 2 n£meros consecutivos.", "label": "{{regexTestValidation}}" }, { "visible": true, "gridColumns": 6, "property": "numberRangeValidate", "label": "{{cpfMaskApply}}", "type": "string", "required": false, "mask": "999.999.999-99" }, { "minValue": 1, "visible": true, "gridColumns": 6, "maxValue": 9, "property": "numberValidate", "errorMessage": "Somente números de 1 a 9", "label": "{{onlyNumbers}}", "type": "number", "required": false } ] } Busca dos dados onde foram adicionados novos valores: GET - http://localhost:8180/dts/datasul-rest/resources/prg/trn/v1/idiomas { "total": 4, "hasNext": false, "items": [ { "codIdiomPadr": "02 Inglês", "codDialet": "", "codIdioma": "eng", "codUsuario": "", "desIdioma": "English", "hraUltAtualiz": "", "datUltAtualiz": null, "nomUsuario": "Teste Branco - teste", "id": 13084566 }, { "codIdiomPadr": "03 Espanhol", "codDialet": "pt", "codIdioma": "esp", "codUsuario": "testeteste", "desIdioma": "Espanhol", "hraUltAtualiz": "", "datUltAtualiz": null, "nomUsuario": "teste", "id": 13456727 }, { "codIdiomPadr": "01 Português", "codDialet": "Pt", "codIdioma": "por", "codUsuario": "alunox", "desIdioma": "portugues", "hraUltAtualiz": "", "datUltAtualiz": null, "nomUsuario": "Adm Datasul EMS", "id": 493863 }, { "codIdiomPadr": "99 Outros", "codDialet": "Pt", "codIdioma": "spa", "codUsuario": "Amarildo", "desIdioma": "espanhol", "hraUltAtualiz": "", "datUltAtualiz": null, "nomUsuario": "Amarildo", "id": 8335959 } ] }
Front-End PO-UI
Introdução:
Para este exemplo vamos criar um CRUD com template dinâmico, onde serão mostrados os dados de acordo com o que o back-end retornar.
O desenvolvimento do front-end utilizando este campo componente se divide basicamente em três partes:
- Routes:
- Na definição da rota é onde vamos definir todos os caminhos dos componentes;
- HTML
- No HTML basta colocarmos os componentes, pois o metadados irá retornar o que precisamos para renderizar o componente;
- TypeScript
- No Typescript do componente vamos realizar uma pequena lógica para o tratamento dos dados de acordo com metadado;
Abaixo vamos mostrar como ficaram a parte de Listagem, Edição e Detalhe do nosso CRUD dinâmico.
Routes:
Abaixo segue exemplo de como ficará o arquivo de rotas de nossa aplicação CRUD.
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { IdiomaDetailComponent } from './idioma/detail/idioma-detail.component'; import { IdiomaEditComponent } from './idioma/edit/idioma-edit.component'; import { IdiomaListComponent } from './idioma/list/idioma-list.component'; const routes: Routes = [ { path: 'idiomas/create', component: IdiomaEditComponent }, { path: 'idiomas/edit/:id', component: IdiomaEditComponent }, { path: 'idiomas/detail/:id', component: IdiomaDetailComponent }, { path: 'idiomas', component: IdiomaListComponent }, { path: '', redirectTo: '/idiomas', pathMatch: 'full' }, { path: '**', component: IdiomaListComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
Listagem:
É a tela inicial da nossa aplicação e mostra a lista de dados da tabela Idioma, onde foram adicionados através de customização três campos da tabela usuar_mestre. Esta tela dará acesso às outras funcionalidades como edição e detalhamento.
<po-loading-overlay [hidden]="!showLoading"> </po-loading-overlay> <po-page-dynamic-table p-auto-router [p-title]="cTitle" [p-actions]="actions" [p-breadcrumb]="breadcrumb" [p-fields]="fields" [p-service-api]="serviceApi"> </po-page-dynamic-table>
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { PoBreadcrumb } from '@po-ui/ng-components'; import { PoPageDynamicTableActions } from '@po-ui/ng-templates'; import { IdiomaService } from './../resources/idioma.service'; @Component({ selector: 'app-idioma-list', templateUrl: './idioma-list.component.html', styleUrls: ['./idioma-list.component.css'] }) export class IdiomaListComponent implements OnInit { // Definicao das variaveis utilizadas public cTitle = 'Manutenção de Idiomas'; public serviceApi: string; public fields: Array<any> = []; public showLoading = false; public readonly actions: PoPageDynamicTableActions = { new: '/idiomas/create', detail: '/idiomas/detail/:id', edit: '/idiomas/edit/:id', remove: true, removeAll: true }; public readonly breadcrumb: PoBreadcrumb = { items: [ { label: 'Home', link: '/' }, { label: 'Idiomas'} ] }; // Construtor da classe constructor( private service: IdiomaService, private route: Router ) { } // Load do componente public ngOnInit(): void { this.fields = []; this.serviceApi = this.service.getUrl(); this.showLoading = true; this.service.getMetadata().subscribe(resp => { this.fields = resp['items']; this.service.setFieldList(this.fields); this.showLoading = false; }); } }
Edição:
Esta tela permite a inclusão de um novo registro na tabela Idioma e também a alteração de registros já existentes.
<po-loading-overlay [hidden]="!showLoading"> </po-loading-overlay> <po-page-edit [p-title]="cTitle" [p-breadcrumb]="breadcrumb" [p-disable-submit]="formEdit.form.invalid" (p-cancel)="cancelClick()" (p-save)="saveClick()"> <po-dynamic-form #formEdit p-auto-focus="string" [p-fields]="fields" p-validate="/api/trn/v1/idiomas/validateForm" [p-validate-fields]="metadata?.validateFields" [p-value]="record"> </po-dynamic-form> </po-page-edit>
import { Component, OnInit, ViewChild } from '@angular/core'; import { NgForm } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { PoBreadcrumb, PoDialogService, PoNotificationService } from '@po-ui/ng-components'; import { IdiomaService } from './../resources/idioma.service'; @Component({ selector: 'app-idioma-edit', templateUrl: './idioma-edit.component.html', styleUrls: ['./idioma-edit.component.css'] }) export class IdiomaEditComponent implements OnInit { // Define as variaveis a serem utilizadas public cTitle: string; public currentId: string; public record = {}; public fields: Array<any> = []; public isUpdate = false; public showLoading = false; public breadcrumb: PoBreadcrumb; // Obtem a referencia do componente HTML @ViewChild('formEdit', { static: true }) formEdit: NgForm; // Construtor da classe com os servicos necessarios constructor( private service: IdiomaService, private activatedRoute: ActivatedRoute, private route: Router, private poDialog: PoDialogService, private poNotification: PoNotificationService ) { } // Load do componente public ngOnInit(): void { this.isUpdate = false; this.showLoading = true; // Carrega o registro pelo ID this.activatedRoute.params.subscribe(pars => { this.currentId = pars['id']; // Se nao tiver o ID definido sera um CREATE if (this.currentId === undefined) { this.isUpdate = false; this.cTitle = 'Inclusão de Idioma'; } else { this.isUpdate = true; this.cTitle = 'Alteração de Idioma'; } // Atualiza o breadcrumb de acordo com o tipo de edicao this.breadcrumb = { items: [ { label: 'Home', action: this.beforeRedirect.bind(this) }, { label: 'Idiomas', action: this.beforeRedirect.bind(this) }, { label: this.cTitle } ] }; // Se for uma alteracao, busca o registro a ser alterado if (this.isUpdate) { this.service.getById(this.currentId).subscribe(resp => { Object.keys(resp).forEach((key) => this.record[key] = resp[key]); // Em alteracao temos que receber o registro para depois buscar a lista de campos this.getMetadata(); }); } else { // Se for create, pega a lista de campos this.getMetadata(); } }); } // Retorna a lista de campos private getMetadata() { let fieldList: Array<any> = []; // Carrega a lista de campos, trabalhando com um cache da lista de campos fieldList = this.service.getFieldList(this.isUpdate); if (fieldList === null || fieldList.length === 0) { this.service.getMetadata().subscribe(resp => { this.service.setFieldList(resp['items']); this.fields = this.service.getFieldList(this.isUpdate); this.showLoading = false; }); } else { this.fields = fieldList; this.showLoading = false; } } // Redireciona via breadcrumb private beforeRedirect(itemBreadcrumbLabel) { if (this.formEdit.valid) { this.route.navigate(['/']); } else { this.poDialog.confirm({ title: `Confirma o redirecionamento para ${itemBreadcrumbLabel}`, message: `Existem dados que não foram salvos ainda. Você tem certeza que quer sair ?`, confirm: () => this.route.navigate(['/']) }); } } // Grava o registro quando clicado no botao Salvar public saveClick(): void { this.showLoading = true; if (this.isUpdate) { // Altera um registro ja existente this.service.update(this.currentId, this.record).subscribe(resp => { this.poNotification.success('Idioma alterado com sucesso'); this.showLoading = false; this.route.navigate(['/idiomas']); }); } else { // Cria um registro novo this.service.create(this.record).subscribe(resp => { this.poNotification.success('Idioma criado com sucesso'); this.showLoading = false; this.route.navigate(['/idiomas']); }); } } // Cancela a edicao e redireciona ao clicar no botao Cancelar public cancelClick(): void { this.poDialog.confirm({ title: 'Confirma cancelamento', message: 'Existem dados que não foram salvos ainda. Você tem certeza que quer cancelar ?', confirm: () => this.route.navigate(['/']) }); } }
Detalhe:
Esta tela apresenta os detalhes de um registro de Idioma, com suas customizações.
<po-loading-overlay [hidden]="!showLoading"> </po-loading-overlay> <po-page-detail [p-title]="cTitle" [p-breadcrumb]="breadcrumb" (p-edit)="editClick()" (p-back)="goBackClick()"> <po-dynamic-view [p-fields]="fields" [p-value]="record"> </po-dynamic-view> </po-page-detail>
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { PoBreadcrumb } from '@po-ui/ng-components'; import { IdiomaService } from './../resources/idioma.service'; @Component({ selector: 'app-idioma-detail', templateUrl: './idioma-detail.component.html', styleUrls: ['./idioma-detail.component.css'] }) export class IdiomaDetailComponent implements OnInit { // definicao das variaveis utilizadas public cTitle = 'Detalhe do Idioma'; public currentId: string; public fields: Array<any> = []; public record = {}; public showLoading = false; public readonly breadcrumb: PoBreadcrumb = { items: [ { label: 'Home', link: '/' }, { label: 'Idiomas', link: '/idiomas' }, { label: 'Detail' } ] }; // construtor com os servicos necessarios constructor( private service: IdiomaService, private activatedRoute: ActivatedRoute, private route: Router ) { } // load do componente public ngOnInit(): void { this.activatedRoute.params.subscribe(pars => { this.showLoading = true; // carrega o registro pelo ID this.currentId = pars['id']; this.service.getById(this.currentId).subscribe(resp => { Object.keys(resp).forEach((key) => this.record[key] = resp[key]); // carrega a lista de campos somente apos receber o registro a ser apresentado this.fields = this.service.getFieldList(false); if (this.fields === null || this.fields.length === 0) { this.service.getMetadata().subscribe(data => { this.fields = data['items']; this.service.setFieldList(this.fields); this.showLoading = false; }); } this.showLoading = false; }); }); } // Redireciona quando clicar no botao Edit public editClick(): void { this.route.navigate(['/idiomas', 'edit', this.currentId]); } // Redireciona quando clicar no botao Voltar public goBackClick(): void { this.route.navigate(['/idiomas']); } }
06. VALIDAÇÃO DE COMPONENTES
Para os itens a seguir, são apresentados algumas formas de interação com os componentes presentes na interface, bem como possíveis validações sobre os mesmos.
Esconder ou visualizar os campos
Como a geração da tela dinâmica é automática, para esconder determinados campos basta setar o atributo visible para FALSE na montagem do JsonObject de retorno do metadados.
... ASSIGN jObj = NEW JsonObject(). jObj:add('property', 'codDialet'). jObj:add('label', 'Dialeto'). jObj:add('visible', FALSE). // <- Remove o item da tela de todos seus correspondentes (Form, View, Table) jObj:add('required', TRUE). jObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('character')). jObj:add('gridColumns', 6). jObj:add('validate', '/api/trn/v1/idiomas/validateField'). jAList:add(jObj). ...
Validação de componentes na interface
Uma boa prática em desenvolvimento de telas é a validação de alguns campos na própria interface, cujo intuito é reduzir requisições desnecessárias ao 'back-end'. As funcionalidades apresentadas a seguir podem ser utilizadas em conjunto com a validação do próprio Form ([p-disable-submit]="formEdit.form.invalid"), no qual pode desabilitar o botão de confirmação enquanto houver campos inválidos.
Utilização do pattern (RegEx)
Para a validação de campos textos, pode ser utilizado o atributo pattern qualquer expressão regular, caso não atenda ao RegEx, uma mensagem de erro definida em errorMessage é apresentada em tela.
... ASSIGN jObj = NEW JsonObject(). jObj:add('property', 'testeValidacaoRegEx'). jObj:add('label', 'Teste Validação RegEx'). jObj:add('gridColumns', 6). jObj:add('pattern', "[0-9]~{2~}"). // <- Validacao RegEx jObj:add('errorMessage', 'Obrigatório mínimo 2 números consecutivos.'). jAList:add(jObj). ...
Utilização de limites em numeração
Com a utilização dos atributos minValue e maxValue, é possível efetuar a restrição de períodos da numeração que pode ser utilizado em conjunto com o type para restringir a digitação em somente números. Caso houver números inválidos, a mensagem definida em errorMessage é apresentada na tela.
... ASSIGN jObj = NEW JsonObject(). jObj:add('property', 'numberValidate'). jObj:add('label', 'Somente números'). jObj:add('visible', TRUE). jObj:add('required', FALSE). jObj:add('minValue', 1). jObj:add('maxValue', 9). jObj:add('errorMessage', 'Somente números de 1 a 9'). // <- Mensagem de erro 1-9 jObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('integer')). // <- Restringe a digitacao somente numeros jObj:add('gridColumns', 6). jAList:add(jObj). ...
Utilização de máscaras para os campos
Quando é definida uma máscara mask, ocorre a restrição de digitação no próprio campo. Para o exemplo abaixo, é permitido digitar somente números e ao efetuar a digitação, a máscara será aplicada automaticamente.
... ASSIGN jObj = NEW JsonObject(). jObj:add('property', 'numberRangeValidate'). jObj:add('label', 'Aplicação de máscara CPF'). jObj:add('mask', '999.999.999-99'). // <-- Mascara CPF jObj:add('visible', TRUE). jObj:add('required', FALSE). jObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('character')). jObj:add('gridColumns', 6). jAList:add(jObj). ...
Validações no back-end
O po-dynamic-form permite dois tipos de validações, a validação do formulário completo ou por campo.
A validação do formulário completo vamos detalhar mais a frente.
A validação por campo é feito através da validação campo-a-campo, onde você conseguirá alterar algumas características do campo que esta sendo validado.
Nas validações de campos é possível somente alterar as características do próprio campo validado, onde não é permitido alterar nas características de outros campos.
... ASSIGN jObj = NEW JsonObject(). jObj:add('property', 'nomCampo'). jObj:add('label', 'Label do campo'). jObj:add('visible', TRUE). jObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('character')). jObj:add('validate', '/api/trn/v1/idiomas/validateField' jObj:add('gridColumns', 6). jAList:add(jObj). ...
A adição da tag validate na definição do campo, onde deverá ser especificado a URL de validação, fará com que sempre que o campo for alterado pelo usuário, será enviado uma requisição para o back-end para realizar a validação desse campo.
JSon que recebemos da tela HTML
O componente PO-DYNAMIC-FORM, quando ocorre alguma alteração em seus campos, no LEAVE do campo, ele enviará um JSon com o seguinte formato para o back-end:
Tag | Tipo | Descrição |
---|---|---|
property | Character | Contêm o nome do campo que houve a alteração para ser validado. |
value | Any | Contêm o valor atual do campo para ser validado. O tipo é o conforme o campo validado. |
Onde o back-end receberá o seguinte JSon:
{ "property": "campoValidado", "value": "valorAtualDoCampoValidado" //Exemplo de uma propriedade de tipo caractere } ou { "property": "campoValidado", "value": true //Exemplo de uma propriedade de tipo lógico }
JSon que retornamos para a tela HTML
A validação do campo, aguarda o seguinte formato de JSon:
Tag | Tipo | Descrição |
---|---|---|
value | Any | Contêm o novo valor para o campo. O tipo é o conforme o campo validado. |
field | JSonObject | Contêm uma lista de propriedades que serão alteradas. OBS: Estas propriedades tem efeito somente sobre o campo que está sendo validado. |
focus | Logical | Informa se o campo validado deverá ou não receber o focus. |
_messages | JSonArray | Contêm uma lista de mensagens "de erro" que podem ser apresentadas ao voltar para o HTML. |
O back-end, após processar e realizar a validação necessária, retornará para o front-end o seguinte JSon:
{ value: 'novoValorDoCampoValidado', field: { mask: '99.999.999/9999-99', required: true }, focus: true, _messages: [ { code: '01', message: 'Mensagem do erro que aconteceu', detailedMessage: 'detalhes do erro acontecido' } ] }
Para utilizarmos a UPC de customização, tivemos que encapsular o JSon recebido no back-end, dentro da procedure pValidateField, e também o JSon a ser enviado para a tela HTML, conforme o exemplo abaixo:
// Realizamos a cópia do objeto recebido por parâmetro oObj = CAST(oBody:clone(), jsonObject). // adicionamos a propriedade root para encapsular o retorno e enviar para a UPC oObj:add("root", oRet). // Realiza a chamada da UPC Progress {include/i-epcrest.i &endpoint=validateField &event=validateField &jsonVar=oObj} // obtêm o retorno customizado, onde o mesmo foi alterado e retornado na tag root // Portanto o resultado da customização deve estar dentro da propriedade root oRet = oObj:getJsonObject("root").
Neste nosso exemplo, nós dividimos o JSon a ser enviado para UPC em três partes, que são:
Tag | Tipo | Descrição |
---|---|---|
property | Character | Contém o nome do campo que esta sendo validado. |
value | Any | Contém o valor atual do campo para ser validado. O tipo é o conforme o campo validado. |
root | JSonObject | Contém um JSonObject com que será retornado para o HTML e que poderá ser customizado na UPC. Tudo que for customizado deverá estar dentro desta tag. |
Exemplo de JSon recebido pela UPC:
{ property: 'nomeDoCampoValidado', value: '', root: { value: '', field: { mask: '99.999.999/9999-99' }, focus: false, _messages: [ { code: '01', message: 'Mensagem do erro que aconteceu', detailedMessage: 'detalhes do erro acontecido' } ] } }
Após a customização pela UPC, será devolvida para a API-REST apenas a tag root, com as customizações necessárias, conforme o exemplo abaixo onde temos o resultado de uma customização:
{ value: '', field: { mask: '99.999.999/9999-99', label: 'Novo Label' }, focus: false, _messages: [ { code: '01', message: 'Mensagem do erro que aconteceu', detailedMessage: 'detalhes do erro acontecido' } ] }
07. VALIDAÇÃO DE FORMULÁRIOS
O quê deve ser alterado no componente PO-DYNAMIC-FORM
Para validarmos um formulário, temos que configurar primeiro o nosso componente po-dynamic-form para ficar apto a enviar as ocorrências de validações. Para isso temos que especificar algumas propriedades no componente po-dynamic-form, são elas:
- p-validate: onde informamos a URL que fará a validação do formulário, neste exemplo será "/api/trn/v1/idiomas/validateForm".
- p-validate-fields: somente o p-validate não fará com que as requisições de validação sejam executadas, é preciso definir a propriedade p-validate-fields que recebe um array de strings (Ex.: ['codIdioma','desIdioma']) com os campos que irão disparar a requisição ao serviço indicado na propriedade validate, quando estes forem alterados. Essa informação deve vir no metadata de edição para que seja possível customizar as validações de formulário.
<po-dynamic-form #formEdit p-auto-focus="string" [p-fields]="fields" p-validate="/api/trn/v1/idiomas/validateForm" [p-validate-fields]="metadata?.validateFields" [p-value]="record"> </po-dynamic-form>
As validações de formulário validam somente os campos já existentes no formulário, sejam eles campos padrões ou inseridos nas customizações, não é permitido adicionar novos campos através da validação de formulário. Para adicionar novos campos deve-se utilizar a inserção no back-end alterando os retornos dos endPoints de metadata e dados da API, seja alterando o produto padrão ou via customização.
JSon que recebemos da tela HTML
O componente PO-DYNAMIC-FORM, quando ocorre alguma alteração nos campos indicados no validateFields e ao sair destes campos, realizará uma requisição ao endPoint de validateForm e enviará um JSon com o seguinte formato para o back-end:
Tag | Tipo | Descrição |
---|---|---|
property | Character | Contém o nome do campo que houve a alteração. |
value | JSonObject | Contém os valores atuais de todos os atributos utilizados pelos campos do formulário. |
Exemplo de Json:
{ "property": "codAcoes", "value": { "codIdiomPadr": "01 Português", "codIdioma": "12345678", "desIdioma": "12345678901234567890", "hraUltAtualiz": "", "datUltAtualiz": null, "id": 6, "codAcoes": "FocoDesIdioma" } }
JSon que teremos que retornar para a tela HTML
As validações do formulário, aguardam o seguinte formato de JSon:
Tag | Tipo | Descrição |
---|---|---|
value | JSonObject | Objeto com os valores que devem ser alterados. Todos os campos que devem ter seu valor alterado no formulário devem ser informados nesse objeto, aqueles que não devem ser alterados não precisam estar referenciados. |
fields | JSonArray | Contêm uma lista de campos com as suas propriedades que serão alteradas. Se não for alterada nenhuma propriedade de nenhum campo, não é necessário informar essa tag. |
focus | Character | Contêm o campo que receberá o foco ao voltar para a tela HTML. Informar o atributo property do campo. |
_messages | JSonArray | Contêm uma lista de mensagens "de erro" que deverão ser apresentadas ao voltar para o HTML. |
Para retornar as informações para o PO-UI, temos que devolver o seguinte JSon:
{ value: { desIdioma: 'teste de escrita', hraUltAtualiz: '17:18:19' }, fields: [ { property: 'codCpfCnpj', mask: '99.999.999/9999-99' } ], focus: 'hraUltAtualiz', _messages: [ { code: '01', message: 'Mensagem do erro que aconteceu', detailedMessage: 'detalhes do erro acontecido' } ] }
Para utilizarmos a UPC de customização, tivemos que encapsular o JSon recebido no back-end, dentro da procedure pValidateForm, e tambem o JSon a ser enviado para a tela HTML, conforme o exemplo abaixo:
// encapsulamos o retorno para enviar para a UPC oObj = NEW JsonObject(). oObj:add("property", cProp). oObj:add("originalValues", oValue). oObj:add("root", oRet). // Realiza a chamada da UPC Progress {include/i-epcrest.i &endpoint=validateForm &event=validateForm &jsonVar=oObj} // obtem o retorno customizado, onde o mesmo foi alterado e retornado na tag root oRet = oObj:getJsonObject("root").
Neste nosso exemplo, nós dividimos o JSon a ser enviado para UPC em três partes, que são:
Tag | Tipo | Descrição |
---|---|---|
property | Character | Comtêm o nome do campo que esta sendo validado. |
originalValues | JsonObject | Contêm um JSonObject com propriedades contendo os nomes dos campos e os seus respectivos valores. |
root | JSonObject | Contêm um JSonObject com que será retornado para o HTML e que poderá ser customizado na UPC. Tudo que for customizado deverá estar dentro desta tag. |
Exemplo de JSon recebido pela UPC:
{ property: 'codAcao', originalValues: { "codIdiomPadr": "01 Português", "codIdioma": "12345678", "desIdioma": "12345678901234567890", "hraUltAtualiz": "", "datUltAtualiz": null, "id": 6, "codAcoes": "FocoDesIdioma" }, root: { value: { desIdioma: 'teste de escrita', hraUltAtualiz: '17:18:19' }, fields: [ { property: 'codCpfCnpj', mask: '99.999.999/9999-99' } ], focus: 'hraUltAtualiz', _messages: [ { code: '01', message: 'Mensagem do erro que aconteceu', detailedMessage: 'detalhes do erro acontecido' } ] } }
Após a customização pela UPC, será devolvido para a API-REST apenas o conteúdo da propriedade root, com as customizações necessárias, conforme o exemplo abaixo onde temos o resultado de uma customização:
{ "value": { "desIdioma": "Valor customizado na UPC" }, "fields": [ { "property": "codIdiomPadr", "disabled": true } ], "focus": "desIdioma", "_messages": [ { "detailedMessage": "Na execução da UPC, houveram alterações nos campos de tela.", "code": "44", "message": "A UPC alterou algumas caracteristica da tela." } ] }
Trabalhando com vários formulários (Dynamic Form)
Em algumas situações precisamos utilizar vários componentes Dynamic Form em uma tela, quando precisamos agrupar campos seja utilizando um po-tabs ou po-accordion, por exemplo.
Nesse caso, há uma questão com relação a validação de formulários. Quando um campo dispara uma validação que altera características de um campo que está em outro formulário, essa validação não é realizada, sendo necessário realizar um tratamento diferente no front-end para o correto funcionamento.
A técnica consiste em atribuir uma função typescript ao p-validate do formulário, nessa função realizar a requisição ao back-end para o endpoint de validação e com o resultado dessa validação atualizar as variáveis de modelo utilizadas em todos os formulários.
Para facilitar esse tratamento foi criada a classe ValidateService no pacote de utilitários Dts-Backoffice-Utils. Você pode utilizar esse pacote para facilitar o desenvolvimento, ou caso prefira, pode copiar o tratamento realizado para o seu projeto.
Segue abaixo os links do projeto:
- NPM: https://www.npmjs.com/package/dts-backoffice-util
- Documentação da Classe: https://github.com/ModernizaDatasul/dts-backoffice-util/blob/master/projects/dts-backoffice-util/README.md#validateservice
- Código da Classe: https://github.com/ModernizaDatasul/dts-backoffice-util/blob/master/projects/dts-backoffice-util/src/lib/services/validate.service.ts
08. FACILITADORES PROGRESS
Criamos facilitadores para auxiliar no desenvolvimento das API's, ficam localizados na classe Progress "com.totvs.framework.api.JsonAPIUtils":
Método | Descrição | Assinatura/Exemplo |
---|---|---|
convertAblTypeToHtmlType | Converte os tipos nativos do Progress para os tipos esperados pelo PO-UI | Assinatura: convertAblTypeToHtmlType (INPUT cType AS CHARACTER) Exemplo: ASSIGN cType = JsonAPIUtils:convertAblTypeToHtmlType ("integer"). O retorno no cType será "number", que é um formato reconhecido pelo PO-UI. |
convertToCamelCase | Converter os nomes dos campos lidos da tabela, normalmente com "_", para "camel case", que é o mais comum utilizado em Json's. | Assinatura: convertToCamelCase (INPUT cKey AS CHARACTER) Exemplo: ASSIGN cField= JsonAPIUtils:convertToCamelCase ("cod_e_mail_usuar"). O retorno no cField será "codEMailUsuar", que é o campo em Camel Case. |
getIdField | Retorna um campo do tipo ID para ser adicionado na lista de campos do Metadata. Este campo serve como chave do registro nos tratamentos de CRUD na parte HTML. | Assinatura: getIdField() Exemplo: oIdiomas:add( JsonAPIUtils:getIdField() ). |
09. TÉCNICA PARA TRADUÇÃO
A técnica para tradução de label, possui como base as recomendações de i18n do PO UI (https://po-ui.io/documentation/po-i18n) com algumas características adicionais. A seguir serão apresentadas alguns trechos de código que representam a utilização desta técnica de tradução em conjunto com formulários dinâmicos.
Para diferenciar a label que devem ser traduzida, deve-se inserir a key de tradução entre os caracteres chaves. Exemplo: { key }
... ASSIGN jObj = NEW JsonObject(). jObj:add('divider', "Itens da UPC"). jObj:add('property', 'codUsuario'). jObj:add('label', '~{~{user~}~}'). jObj:add('visible', TRUE). jObj:add('required', TRUE). jObj:add('type', JsonAPIUtils:convertAblTypeToHtmlType('character')). jObj:add('gridColumns', 6). oFields:add(jObj). ...
Conforme a arquitetura de customização apresentada anteriormente, deve-se aguardar o resultado da requisição ao serviço metadata e posteriormente, efetuar os seus devidos tratamentos de tradução.
... this.service.getMetadata().subscribe(resp => { this.service.setFieldList(resp['items']); this.fields = this.service.getFieldList(false, this.literals); this.showLoading = false; }); ...
Para que o ponto de tradução seja único e compatível com as técnicas recomendadas pelo PO UI, deve-se efetuar o tratamento no front-end. Recomenda-se que seja realizado na função que retorna a lista de fields pois neste momento, são carregados todos os campos correspondentes à tela.
... public getFieldList(update, literals) { // ajusta alista de campos para habilitar ou nao a chave primaria se for CREATE let fields: Array<any> = []; if (this.fieldList.length > 0) { this.fieldList.forEach((data) => { if (data['label'] !== undefined) { const key = data['label'].replace('{{', '').replace('}}', ''); if (literals[key] !== undefined) { data['label'] = literals[key]; } } if (data['options'] !== undefined) { let options = data['options']; options.forEach((option) => { const key = option['label'].replace('{{', '').replace('}}', ''); if (literals[key] !== undefined) { option['label'] = literals[key]; } }); } fields.push(data); }); } return fields; } ...
A arquitetura de tradução do PO UI cita: "... Existe também a possibilidade de utilizar ambos, onde será feito a busca das literais nas constantes e depois efetua a busca no serviço ...". Portanto pode-se configurar os arquivos de tradução com um serviço (URL) que retorna as literais adicionais desenvolvidas no "back-end", sendo assim, um complemento ao arquivo.
Observação: O exemplo da URL abaixo não segue as recomendações do PO UI (api/translations/idiomas). Foi desenvolvido em uma estrutura diferente para facilitar os códigos a conceito de POC.
... const i18nConfig: PoI18nConfig = { default: { language: 'pt-BR', context: 'general', cache: false }, contexts: { general: { 'pt-BR': generalPt, 'en-US': generalEn, url: 'api/trn/v1/idiomas/translations' } } }; ...
Para o endpoint de tradução, pode ser utilizado uma chamada UPC conforme trecho de código a seguir:
... {utp/ut-api-action.i pIdiomas GET /translations/~* } ... /** Recupera as literais */ PROCEDURE pIdiomas: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. // Realiza a chamada da UPC Progress {include/i-epcrest.i &endpoint=i18n &event=i18n &jsonVar=oJsonInput} ASSIGN oJsonOutput = oJsonInput. END PROCEDURE.
A Procedure correspondente ao serviço (URL) citado anteriormente, deve retornar um objeto no formato JSON de acordo com o idioma enviado por parâmetro.
Nota
O exemplo a seguir efetua o tratamento somente do parâmetro language. Segundo a documentação do PO UI, outros parâmetros (context, literals) podem ser enviados, sendo necessária uma futura implementação de seu recebimento no back-end.
... IF pEndPoint = "i18n" AND pEvent = "i18n" THEN DO ON STOP UNDO, LEAVE: RUN piI18N. END. ... PROCEDURE piI18N: DEFINE VARIABLE oParser AS JsonAPIRequestParser NO-UNDO. DEFINE VARIABLE oQueryParams AS JsonObject NO-UNDO. DEFINE VARIABLE pIdioma AS CHARACTER NO-UNDO. ASSIGN oParser = NEW JsonAPIRequestParser(jsonIO) oQueryParams = oParser:GetQueryParams() pIdioma = oQueryParams:GetJsonArray("language"):GetCharacter(1). IF (pIdioma = "pt-BR") THEN DO: jsonIO = NEW JsonObject(). jsonIO:Add("user", "Usuário"). jsonIO:Add("name", "Nome"). jsonIO:Add("regexTestValidation", "Teste Validação REGEX"). jsonIO:Add("cpfMaskApply", "Aplicação Máscara CPF"). jsonIO:Add("onlyNumbers", "Somente Números"). END. ELSE IF (pIdioma = "en-US") THEN DO: jsonIO = NEW JsonObject(). jsonIO:Add("user", "User"). jsonIO:Add("name", "Name"). jsonIO:Add("regexTestValidation", "REGEX Test Validation"). jsonIO:Add("cpfMaskApply", "CPF Apply Mask"). jsonIO:Add("onlyNumbers", "Only Numbers"). END. END PROCEDURE.
10. COMO DOCUMENTAR A TELA QUE PERMITE CUSTOMIZAÇÃO
Conteúdo interno!
Abaixo segue o link da documentação que ira auxiliar a documentar a tela que permite a customização e assim manter o padrão de desenvolvimento tanto na parte técnica, quanto na documentação das telas:
Documentação Customização HTML PO-UI
e a pagina com exemplo de como deve ficar a documentação:
11. DICAS PARA DESENVOLVER UMA CUSTOMIZAÇÃO
As informações do que pode ser customizado na tela, em qual API-REST deve ser cadastrada a UPC, quais os pontos e eventos existentes, entre outras informações, podem ser localizadas no documento de customização da tela.
Esse documento geralmente é disponibilizado juntamente com o documento de referência da tela.
Porém, em alguns casos pode ainda não existir essa documentação mesmo algumas telas permitindo alguma customização, então abaixo seguem algumas dicas de como podem ser obtidas algumas informações.
- Acessando a tela, clicando com o botão direito do mouse, pode-se utilizar a opção "Inspecionar" do navegador (F12). Essa opção irá abrir a "ferramenta do desenvolvedor".
- Na opção Rede (Network) podem ser visualizadas as chamadas feitas pela tela e dentre elas estão as requisições para a API-REST. Porém, atente-se ao fato de que, somente serão apresentadas as requisições executadas após o acionamento da funcionalidade de inspeção (F12). Portanto, caso a requisição que você deseja visualizar seja feita na abertura da tela, acione o F12 antes mesmo de executar a tela.
- Uma tela desenvolvida com PO UI que permite customização, geralmente vai executar uma requisição para o endPoint "metadata". Procure por essa chamada na lista apresentada.
- Ao clicar na requisição do "metadata", serão apresentadas as informações dessa requisição. Na aba Cabeçalhos (Headers) será possível verificar a URL da API utilizada.
- Na aba Resposta (Response) é possível verificar o conteúdo retornado pela API com as informações que compõem a tela.
- Cadastre uma UPC para a API-REST conforme apresentado no inicio dessa documentação.
Adicione linhas de logs na sua UPC para que sejam impressos os parâmetros recebidos por ela, conforme exemplo abaixo.
UPCUSING PROGRESS.json.*. USING PROGRESS.json.ObjectModel.*. USING com.totvs.framework.api.*. DEFINE INPUT PARAMETER pEndPoint AS CHARACTER NO-UNDO. DEFINE INPUT PARAMETER pEvent AS CHARACTER NO-UNDO. DEFINE INPUT PARAMETER pAPI AS CHARACTER NO-UNDO. DEFINE INPUT-OUTPUT PARAMETER jsonIO AS JSONObject NO-UNDO. DEFINE VARIABLE jsonContent AS LONGCHAR NO-UNDO. //Repassa o conteúdo do JSON em formato de texto do parâmetro jsonIO para uma variável longchar jsonContent = jsonIO:GetJsonText(). LOG-MANAGER:WRITE-MESSAGE("UPC EndPoint = " + pEndPoint, ">>>>"). LOG-MANAGER:WRITE-MESSAGE("UPC Event = " + pEvent, ">>>>"). LOG-MANAGER:WRITE-MESSAGE("UPC pAPI= " + pAPI, ">>>>"). LOG-MANAGER:WRITE-MESSAGE("UPC jsonIO = " + STRING(jsonContent), ">>>>").
- Diferente de uma UPC de tela Progress que é executada no client e as mensagens são apresentadas em tela, essa UPC será executada pelo servidor PASOE, portanto precisa estar acessível no PROPATH do PASOE e ao executar a tela Web, as mensagens de log serão impressas no log do PASOE. Portanto, é necessário ter acesso ao arquivo de log do PASOE do ambiente testado.
12. LINKS ÚTEIS
Conteúdo interno!
Link da documentação: desenvolvimento HTML Estatico x Dinamico
https://tdninterno.totvs.com/pages/releaseview.action?pageId=834824110
Documentação API Datasul:
Desenvolvimento de APIs para o produto DatasulPO-UI:
- Migração THF PO-UI (https://po-ui.io/guides/migration-thf-to-po-ui)
- Dynamic-Form (https://po-ui.io/documentation/po-dynamic-form);
- Dynamic-View (https://po-ui.io/documentation/po-dynamic-view);
- Page-Dynamic-Detail (https://po-ui.io/documentation/po-page-dynamic-detail);
- Page-Dymic-Edit (https://po-ui.io/documentation/po-page-dynamic-edit);
- Page-Dynamic-Search (https://po-ui.io/documentation/po-page-dynamic-search);
- Page-Dynamic-Table (https://po-ui.io/documentation/po-page-dynamic-table);
- I18N (https://po-ui.io/documentation/po-i18n)
13. CONCLUSÃO
A ideia era apresentar uma técnica para que possibilita-se as áreas de negócio, de forma segura e simples, disponibilizarem pontos de customização em suas API’s REST.
Acreditamos que a técnica apresentada permite que o back-end Progress acompanhe a evolução dos componentes PO-UI.
Com a parte de validação do formulário é possível tratar e validar os campos de acordo com a lógica de negócio que ocorre no back-end, onde sempre será recebido na API-REST o campo que está sendo alterado e todos os valores dos demais campos da tela.
Na parte de validação por campo, é recebido na API-REST somente o nome do campo e o seu valor atual. Acho importante informar que não é enviado o ID do registro, onde teremos que tomar cuidado para não perder o contexto do registro que estamos validando.
Contamos com seu feedback e sugestões para manter a melhoria continua nas documentações.