Histórico da Página
CONTEÚDO
Índice maxLevel 2
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.
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.
02. VISÃO GERAL
No PO-UI, uma tela dinâmica trabalha recebendo uma lista de campos, que serão apresentados em tela, e uma outra lista contendo os dados que serão apresentados nestes campos.
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:
Informações | ||
---|---|---|
| ||
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 (
...
...
...
...
...
...
...
- 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 EPC
...
- 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 o passo, que é a preparação do endpoint da API para a customização.
Esta API deverá ser cadastrada 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.
Informações | ||
---|---|---|
| ||
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 EPC
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:
Bloco de código |
---|
{include/i-epcrest.i &endpoint=<nome_end_point> &event=<nome_do_evento> &jsonVar=<variável_jsonObject_com_conteúdo>} |
Informações | ||
---|---|---|
| ||
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, getAll, getMetaData, etc. |
jsonVar | É a variável do tipo JsonObject que será passada como INPUT-OUTPUT para a UPC. |
Informações | ||
---|---|---|
| ||
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.*. |
Parâmetros recebidos na UPC da API REST:
Parametro | Tipo | Tipo de Dados | Descrição |
---|---|---|---|
pEndPoint | INPUT | CHARACTER | Contem o nome do endpoint que está sendo executado. |
pEvent | INPUT | CHARACTER | Contem o nome do evento que está sendo executado. |
pAPI | INPUT | CHARACTER | Contem o nome da API que está sendo executada. |
jsonIO | INPUT-OUTPUT | JSONObject | Contem o JSON com os dados (campos ou valores) que poderão ser customizados. |
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:
Aviso |
---|
Atenção: Ao cadastrar uma API e sua EPC, o nome externo da API/ENDPOINT NÃO deve possuir extensão. |
Na aba Opções, teremos que especificar o Template como "API REST", conforme o exemplo abaixo:
API Rest com chamada de UPC:
Abaixo temos um exemplo de API REST com suas procedures que são:
- pGetMetaData que retorna os metadados do campos em questão;
- 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:
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{utp/ut-api.i}
{utp/ut-api-action.i pFindById GET /byid/~* }
{utp/ut-api-action.i pIdiomas GET /translations/~* }
{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 /~* }
{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"
FIELD id AS RECID.
/** Procedure que retorna a metadata **/
PROCEDURE pGetMetadata:
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 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 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=getMetaData &event=getMetaData &jsonVar=oObj}
// Recupera o JsonArray de dentro do JsonObject retornado pela UPC
oIdiomas = oObj:getJsonArray('root').
// Retorna a colecao de campos customizados ou nao para a interface HTML
oResponse = NEW JsonAPIResponse(oIdiomas).
oJsonOutput = oResponse:createJsonResponse().
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.
// Alimenta o campo ID utilizado pela interface HTML como chave primaria
ASSIGN ttIdiomas.id = RECID(idioma).
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=findAll &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(2).
// Localiza o registro na tabela IDIOMA pelo ID (recid)
FIND FIRST idioma
WHERE RECID(idioma) = INT(cId)
NO-LOCK NO-ERROR.
IF AVAILABLE idioma THEN DO:
BUFFER-COPY idioma TO ttIdiomas.
ASSIGN ttIdiomas.id = RECID(idioma).
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=findById &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 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().
// 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:
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")
rIdioma = RECID(idioma)
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('id', rIdioma).
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 o ID
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 pelo ID (recid)
DO TRANSACTION
ON ERROR UNDO, LEAVE:
FIND FIRST idioma
WHERE RECID(idioma) = INT(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 (cId).
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 (cId).
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 VARIABLE 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 pelo ID (recid)
DO TRANSACTION
ON ERROR UNDO, LEAVE:
FIND FIRST idioma
WHERE RECID(idioma) = INT(cId)
EXCLUSIVE-LOCK NO-ERROR.
IF AVAILABLE idioma THEN DO:
// Monta a chave estrangeira para enviar para UPC
// poder elominar o registro da tabela customizada
oObj = NEW JsonObject().
oObj:add('codIdioma', idioma.cod_idioma).
// Realiza a chamada da UPC Progress para a eliminacao do
// registro customizado. Nao utilizaremos o retorno da UPC
// neste caso.
{include/i-epcrest.i &endpoint=delete &event=beforeDelete &jsonVar=oObj}
DELETE idioma.
ASSIGN lDeleted = TRUE.
END.
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 Português",
"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 backend 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.
cValue = oBody:getCharacter("value") NO-ERROR.
/* Recebemos do HTML o JSON abaixo
{
"property": "codAcoes",
"value": "FocoDesIdioma"
}
*/
// Novas Acoes sobre os campos da tela
// oNewField guarda o objeto que sera alterado/modificado
ASSIGN oNewField = NEW JsonObject().
// oMessages guarda as mensagens de retorno formato
// { code: '00', message: 'texto', detailedMessage: 'detalhes da mensagem' }
ASSIGN oMessages = NEW JsonArray().
IF cProp = "tipUsuario" THEN DO:
ASSIGN lFocus = TRUE.
oNewField:add('property', "codCpfCnpj").
IF cValue = "j" THEN DO:
//alterado o formato da mascara de edicao
oNewField:add('mask', '99.999.999/9999-99').
END.
IF cValue = "f" THEN DO:
//alterado o formato da mascara de edicao
oNewField:add('mask', '999.999.999-99').
END.
ASSIGN oObj = NEW JsonObject().
oObj:add('code', '33').
oObj:add('message', 'A mascara do CPF/CNPJ foi ajustada').
oObj:add('detailedMessage', 'Ocorreu um ajusta na mascara do CPF/CNPJ, favor verificar se os dados estao corretos').
oMessages:add(oObj).
END.
ASSIGN oRet = NEW JsonObject().
// value -> contem todos os valores dos campos de tela
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).
// encapsulamos o retorno para enviar para a UPC
oObj = NEW JsonObject().
oObj:add("property", cProp).
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: 'teste de escrita',
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'
}
]
*/
// 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:
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
/**************************************************************************
** idiomas_upc.p - Exemplo de epc 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 oFields AS JSonArray NO-UNDO.
DEFINE VARIABLE oMessages 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 = "getMetaData" THEN DO ON STOP UNDO, LEAVE:
RUN piGetMetaData.
END.
// Carrega os valores dos campos customizados das tabelas
IF pEndPoint = "findAll"
AND pEvent = "findAll" THEN DO ON STOP UNDO, LEAVE:
RUN piFindAll.
END.
IF pEndPoint = "findById"
AND pEvent = "findById" THEN DO ON STOP UNDO, LEAVE:
RUN piFindById.
END.
IF pEndPoint = "create"
AND pEvent = "afterCreate" THEN DO ON STOP UNDO, LEAVE:
RUN piCreate.
END.
IF pEndPoint = "update"
AND pEvent = "afterUpdate" THEN DO ON STOP UNDO, LEAVE:
RUN piUpdate.
END.
IF pEndPoint = "delete"
AND pEvent = "beforeDelete" THEN DO ON STOP UNDO, LEAVE:
RUN piDelete.
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 piGetMetaData:
// Obtem a lista de campos e valores
ASSIGN oFields = jsonIO:getJsonArray('root').
// 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).
oFields: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).
oFields: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).
oFields: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.').
oFields: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).
oFields: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).
oFields:add(jObj).
// Retorna a nova lista com os campos customizados
jsonIO:Set("root", oFields).
END PROCEDURE.
PROCEDURE piFindAll:
// Obtem a lista de campos e valores
ASSIGN oFields = 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 = oFields:length.
DO ix = 1 TO iTot:
ASSIGN jObj = oFields: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
oFields: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", oFields).
END PROCEDURE.
PROCEDURE piFindById:
// 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 piCreate:
// 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 piUpdate:
// 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 piDelete:
// 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
oFields = 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
oMessages = 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).
oFields: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.').
oMessages: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", oFields). // 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", oMessages). // 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") NO-ERROR. // obtem o retorno que sera enviado para a tela html
cValue = oReturn:getCharacter("value") NO-ERROR. // pega o novo valor do campo atual
oFieldObj = oReturn:getJsonObject("field") NO-ERROR. // obtem as propriedades dos campos a serem alteradas
lFocus = oReturn:getLogical("focus") NO-ERROR. // obtem se o focus ficara sobre o mesmo campo ao retornar para a tela html
oMessages = 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',
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'
}
]
}
}
*/
IF cProp = "codAcoes" THEN DO:
oFieldObj = NEW JsonObject().
oFieldObj:add('label', 'Novo label').
oFieldObj:add('required', TRUE).
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.').
oMessages:add(jObj).
END.
/* Exemplo de JSON de retorno para o HTML
value: 'FocoDesIdioma',
field: {
label: 'Novo Label',
required: true
},
focus: true,
_messages: [
{
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().
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", oMessages). // seta as mensagens a serem retornadas para a tela html
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 ao API tendo uma UPC cadastrada:
Ao fazer as requisições, virão os seguintes resultados na UPC.
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
Busca do METADADOS onde foram adicionados os novos campos codUsuario, nomUsuario e codDialet:
POST - http://localhost:8180/dts/datasul-rest/resources/prg/trn/v1/idiomas/metadata
{
"total": 9,
"hasNext": false,
"items": [
{
"visible": true,
"gridColumns": 6,
"disable": true,
"property": "codIdioma",
"label": "Idioma",
"type": "string"
},
{
"visible": true,
"gridColumns": 6,
"property": "desIdioma",
"label": "Descrição",
"type": "string",
"required": true,
"validate": "/api/trn/v1/idiomas/validateField"
},
{
"visible": true,
"gridColumns": 6,
"property": "codIdiomPadr",
"label": "Idioma Padrão",
"type": "string"
},
{
"visible": true,
"gridColumns": 6,
"disable": true,
"property": "datUltAtualiz",
"format": "dd/MM/yyyy",
"label": "Última Atualização",
"type": "date"
},
{
"visible": true,
"gridColumns": 6,
"disable": true,
"property": "hraUltAtualiz",
"label": "Hora Última Atualização",
"type": "string"
},
{
"visible": false,
"property": "id",
"type": "number",
"key": true
},
{
"visible": true,
"gridColumns": 6,
"property": "codUsuario",
"label": "Usuário",
"type": "string",
"required": true
},
{
"visible": true,
"gridColumns": 6,
"property": "nomUsuario",
"label": "Nome",
"type": "string",
"required": true
},
{
"visible": true,
"gridColumns": 6,
"property": "codDialet",
"label": "Dialeto",
"type": "string",
"required": true
}
]
}
Busca dos dados onde foram adicionados novos valores:
GET - http://localhost:8180/dts/datasul-rest/resources/prg/trn/v1/idiomas
{
"total": 3,
"hasNext": false,
"items": [
{
"codIdiomPadr": "99 Outros",
"codDialet": "Pt",
"codIdioma": "ale",
"codUsuario": "super",
"desIdioma": "Alemão",
"hraUltAtualiz": "",
"datUltAtualiz": null,
"nomUsuario": "Super",
"id": 4580144
},
{
"codIdiomPadr": "99 Outros",
"codDialet": "PT",
"codIdioma": "EN",
"codUsuario": "joao",
"desIdioma": "Ingles",
"hraUltAtualiz": "",
"datUltAtualiz": null,
"nomUsuario": "Joao da Silva",
"id": 194736
},
{
"codIdiomPadr": "03 Espanhol",
"codDialet": "PT",
"codIdioma": "ES",
"codUsuario": "Manoel",
"desIdioma": "Espanhol",
"hraUltAtualiz": "",
"datUltAtualiz": null,
"nomUsuario": "Manoel da Silva",
"id": 2968898
}
]
} |
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 frontend 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.
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
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.
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<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>
|
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
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.
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<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-value]="record">
</po-dynamic-form>
</po-page-edit>
|
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
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.
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<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>
|
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
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.
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
...
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 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.
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
...
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.
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
...
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.
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
...
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.
Aviso |
---|
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. |
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
...
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 | Character | Contêm o valor atual do campo para ser validado. |
Onde o back-end receberá o seguinte JSon:
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
{
"property": "campoValidado",
"value": "valorAtualDoCampoValidado"
} |
JSon que retornamos para a tela HTML
A validação do campo, aguarda o seguinte formato de JSon:
Tag | Tipo | Descrição |
---|---|---|
value | Character | Contêm o novo valor para o campo. |
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:
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
{
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 tambem o JSon a ser enviado para a tela HTML, conforme o exemplo abaixo:
Bloco de código | ||||
---|---|---|---|---|
|
...
| |||||
// encapsulamos o retorno para enviar para a UPC
oObj = NEW JsonObject().
oObj:add("property", cProp).
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 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. |
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:
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{
property: 'nomeDoCampoValidado',
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, sera devolvido 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:
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{
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 no componente po-dynamic-form a tag p-validate, onde informamos a URL que fará a validação do form, neste exemplo será "/api/trn/v1/idiomas/validateForm".
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<po-dynamic-form
#formEdit
p-auto-focus="string"
[p-fields]="fields"
p-validate="/api/trn/v1/idiomas/validateForm"
[p-value]="record">
</po-dynamic-form> |
Aviso |
---|
As validações de formulário validam somente os campos já existentes, onde não é permitido adicionar novos campos. Para adicionar novos campos deve-se utilizar a tag p-load, que é executada logo após a inicialização do componente. |
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 | JSonObject | Contêm uma tag para cada campo da tela com os seus respectivos valores atuais. |
A cada campo que for alterado e ocorrer um LEAVE, será enviado pelo PO-UI um Json que contém o campo que teve o seu valor alterado junto com todos os valores dos demais campos da tela HTML, conforme o exemplo abaixo:
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
{
"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 | Contêm uma tag que representa o nome do campo em tela e o seu novo valor. Se não for especificado um novo valor, não é necessario adicionar a tag referente ao campo |
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. |
_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:
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
{
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:
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
// 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:
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{
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, sera devolvido 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:
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{
"_messages": [
{
"detailedMessage": "Na execução da UPC, houveram alterações nos campos de tela.",
"code": "44",
"message": "A UPC alterou algumas caracteristica da tela."
}
],
"focus": "desIdioma",
"fields": [
{
"property": "codIdiomPadr",
"disabled": true
}
],
"value": {
"desIdioma": "Valor customizado na UPC"
}
} |
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 }
Bloco de código | ||||
---|---|---|---|---|
| ||||
...
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.
Bloco de código | ||||
---|---|---|---|---|
| ||||
...
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.
Bloco de código | ||||
---|---|---|---|---|
| ||||
...
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.
Bloco de código | ||||
---|---|---|---|---|
| ||||
...
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:
Bloco de código | ||||
---|---|---|---|---|
| ||||
...
{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. |
Bloco de código | ||||
---|---|---|---|---|
| ||||
...
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
Aviso | ||
---|---|---|
| ||
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 |
...
...
...
PO-UI:
...
11. LINKS ÚTEIS
Aviso | ||
---|---|---|
| ||
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)
GIT Projeto:
fwk-tools-jille/DATASUL/customization-poui/ ( https://github.com/totvs/fwk-tools-jille )
...
12. 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.
Esta apresentação trata-se de um MVP e poderá ser evoluída nas próximas Sprints.
Com a parte de validação do formulário é possível tratar e validar os campos de acordo cum 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.