CONTEÚDO
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 utilizar o Framework PO-UI como front-end e seus componentes dinâmicos, comunicando com back-end Datasul Progress.
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:
IMPORTANTE: Esta técnica está disponível a partir da versão 12.1.29 do Framework da Linha Datasul. |
Temos como pré-requisito para execução da técnica citada abaixo:
A técnica Back-end Progress é formada pelos passos abaixo:
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:
Tendo criado a API REST que retorna os dados básicos para a tela, partimos para o segundo o passo, que é preparação do endpoint da API para 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 este include identificará se a API possui uma UPC cadastrada ou não.
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. |
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: 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. |
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. |
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.*. |
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. |
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:
Dynamic-Form;
Dynamic-View.
Basicamente para comunicar com o back-end teremos que ter dois serviços que irão alimentar as informações para tela:
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.
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:
Na aba Opções, teremos que especificar o Template como "API REST", conforme o exemplo abaixo:
Abaixo temos um exemplo de API REST com suas procedures que são:
Como podemos verificar todas as procedures possuem chamadas para o programa de UPC:
{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 */ |
Abaixo temos um exemplo de uma UPC criada para a API REST:
/************************************************************************** ** 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 */ |
Ao fazer as requisições, virão os seguintes resultados na UPC.
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 } ] } |
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:
Abaixo vamos mostrar como ficaram a parte de Listagem, Edição e Detalhe do nosso CRUD dinâmico.
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 { } |
É 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; }); } } |
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-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(['/']) }); } } |
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']); } } |
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.
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). ... |
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.
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). ... |
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). ... |
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). ... |
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.
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:
{ "property": "campoValidado", "value": "valorAtualDoCampoValidado" } |
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:
{ 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:
// 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:
{ 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:
{ 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' } ] } |
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".
<po-dynamic-form #formEdit p-auto-focus="string" [p-fields]="fields" p-validate="/api/trn/v1/idiomas/validateForm" [p-value]="record"> </po-dynamic-form> |
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. |
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:
{ "property": "codAcoes", "value": { "codIdiomPadr": "01 Português", "codIdioma": "12345678", "desIdioma": "12345678901234567890", "hraUltAtualiz": "", "datUltAtualiz": null, "id": 6, "codAcoes": "FocoDesIdioma" } } |
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:
{ 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, 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:
{ "_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" } } |
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() ). |
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.
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. |
fwk-tools-jille/DATASUL/customization-poui/ ( https://github.com/totvs/fwk-tools-jille )
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.
<!-- esconder o menu --> <style> div.theme-default .ia-splitter #main { margin-left: 0px; } .ia-fixed-sidebar, .ia-splitter-left { display: none; } #main { padding-left: 10px; padding-right: 10px; overflow-x: hidden; } .aui-header-primary .aui-nav, .aui-page-panel { margin-left: 0px !important; } .aui-header-primary .aui-nav { margin-left: 0px !important; } </style> |