Temos como objetivo desta técnica apresentar uma forma de customizar as telas HTML através de intervenções no back-end.
No PO-UI, um objeto dinâmico, 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.
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:
Temos como pré-requisito para execução da técnica citada abaixo:
Introdução:
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.*. |
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. |
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:
Templates:
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 que possuí duas procedures:
Como podemos verificar essas duas procedures possuem chamadas para o programa de UPC:
PROCEDURE pGetAll: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. ... oIdiomas = JsonAPIUtils:convertTempTableToJsonArray(TEMP-TABLE ttIdiomas:HANDLE). oObj = new JsonObject(). oObj:add('root', oIdiomas). /* realiza a chamada da UPC Progress */ {include/i-epcrest.i &endpoint=getAll &event=getAll &jsonVar=oObj} oIdiomas = oObj:getJsonArray('root'). oResponse = NEW JsonAPIResponse(oIdiomas). oJsonOutput = oResponse:createJsonResponse(). ... END PROCEDURE. PROCEDURE pGetMetaData: DEFINE INPUT PARAMETER oJsonInput AS JsonObject NO-UNDO. DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO. ... // monsta-se a lista de campos que aparecerão em tela oObj = new JsonObject(). oObj:add('root', jAList). /* realiza a chamada da UPC Progress */ {include/i-epcrest.i &endpoint=getMetaData &event=getMetaData &jsonVar=oObj} oIdiomas = oObj:getJsonArray('root'). oResponse = NEW JsonAPIResponse(oIdiomas). oJsonOutput = oResponse:createJsonResponse(). ... END PROCEDURE. |
Abaixo temos um exemplo de uma UPC criada para a API REST:
/************************************************************************** ** idiomas_upc.p - Exemplo de epc para Endpoints REST ***************************************************************************/ 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 jAList AS JsonArray NO-UNDO. DEFINE VARIABLE jObj AS JsonObject NO-UNDO. DEFINE VARIABLE hBuf AS HANDLE NO-UNDO. DEFINE VARIABLE ix AS INTEGER NO-UNDO. DEFINE VARIABLE iTot AS INTEGER NO-UNDO. DEFINE VARIABLE cType AS CHARACTER NO-UNDO. // carrega as definicoes dos campos da tabela IF pEndPoint = "getMetaData" AND pEvent = "getMetaData" THEN DO ON STOP UNDO, LEAVE: // obtem a lista de campos e valores ASSIGN jAList = jsonIO:getJsonArray('root'). // cria um buffer da tabela para obter os campos da tabela usuar_mestre CREATE BUFFER hBuf FOR TABLE 'usuar_mestre'. DO ix = 1 TO hBuf:NUM-FIELDS: // ignora os campos que nao estao nesta lista IF NOT CAN-DO("nom_usuario,cod_usuario,cod_dialet", hBuf:BUFFER-FIELD(ix):NAME) THEN NEXT. // monta a formatacao do item ASSIGN jObj = NEW JsonObject(). jObj:add('property', JsonAPIUtils:convertToCamelCase(hBuf:BUFFER-FIELD(ix):NAME)). jObj:add('label', hBuf:BUFFER-FIELD(ix):Label). jObj:add('visible', TRUE). // ajusta o tipo ASSIGN cType = JsonAPIUtils:convertAblTypeToHtmlType(hBuf:BUFFER-FIELD(ix):type). jObj:add('type', cType). // adiciona o objeto na lista jAList:add(jObj). END. hBuf:BUFFER-RELEASE(). DELETE OBJECT hBuf. // retorna a nova lista com os campos adicionados jsonIO:Set("root", jAList). END. // carrega os valores dos campos da tabela IF pEndPoint = "getAll" AND pEvent = "getAll" THEN DO ON STOP UNDO, LEAVE: // obtem a lista de campos e valores ASSIGN jAList = jsonIO:getJsonArray('root'). FIND FIRST usuar_mestre NO-LOCK NO-ERROR. // quardado o tamanho da lista em variavel para evitar LOOP devido a adicionar novos itens na lista ASSIGN iTot = jAList:length. DO ix = 1 TO iTot: ASSIGN jObj = jAList: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. // adiciona o objeto na lista jAList:add(jObj). FIND NEXT usuar_mestre NO-LOCK NO-ERROR. END. // devolve para o json ROOT a lista nova com novos objetos jsonIO:Set("root", jAList). END. IF pEndPoint = "getOne" AND pEvent = "getOne" THEN DO ON STOP UNDO, LEAVE: // nao implementado END. IF pEndPoint = "create" AND pEvent = "afterCreate" THEN DO ON STOP UNDO, LEAVE: // nao implementado END. /* 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, "property": "codIdioma", "label": "Idioma", "type": "string" }, { "visible": true, "property": "desIdioma", "label": "Descrição", "type": "string" }, { "visible": true, "property": "codIdiomPadr", "label": "Idioma Padrão", "type": "string" }, { "visible": true, "property": "codUsuarUltAtualiz", "label": "Usuário Ult Atualiz", "type": "string" }, { "visible": true, "property": "datUltAtualiz", "label": "Última Atualização", "type": "string" }, { "visible": true, "property": "hraUltAtualiz", "label": "Hora Última Atualiz", "type": "string" }, { "visible": true, "property": "codUsuario", "label": "Usuário", "type": "string" }, { "visible": true, "property": "nomUsuario", "label": "Nome", "type": "string" }, { "visible": true, "property": "codDialet", "label": "Dialeto", "type": "string" } ] } 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": [ { "codIdioma": "12345678", "desIdioma": "12345678901234567890", "hraUltAtualiz": "08:35:00", "datUltAtualiz": "2010-02-15", "codUsuario": "super", "nomUsuario": "Super", "codDialet": "Pt" }, { "codIdioma": "ale", "desIdioma": "Alemão", "hraUltAtualiz": "15:33:45", "datUltAtualiz": "2018-10-23", "codUsuario": "Joao", "nomUsuario": "Joao da Silva", "codDialet": "PT" }, { "codIdioma": "ESP", "desIdioma": "Espanhol", "hraUltAtualiz": "12:20:03", "datUltAtualiz": "2004-12-08", "codUsuario": "Manoel", "nomUsuario": "Manoel da Silva", "codDialet": "PT" } ] } |
Para este exemplo vamos utilizar o template "Page-Dynamic-Detail", mostrando os dados de acordo com o que o back-end nos retorna.
O desenvolvimento do front-end utilizando este campo componente se divide basicamente em três partes:
Abaixo segue exemplo de como ficará o arquivo de rotas de nossa aplicação, como podemos perceber temos a tag "data", onde vamos passar os dados da API de Metadados.
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { IdiomaDynamicComponent } from './idioma/idioma-dynamic.component'; const routes: Routes = [ { path: 'idioma', component: IdiomaDynamicComponent, data: { serviceMetadataApi: 'http://localhost:8180/dts/datasul-rest/resources/prg/trn/v1/idiomas/metadata' // endpoint dos metadados serviceLoadApi: 'http://localhost:8180/dts/datasul-rest/resources/prg/trn/v1/idiomas/metadata' // endpoint de customizações dos metadados } }, { path: '', redirectTo: '/idioma', pathMatch: 'full' }, { path: '**', component: IdiomaDynamicComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } |
No HTML devemos inserir a tag do componente dinâmico "po-page-dynamic-detail" para renderizar os dados de acordo com o retorno do back-end.
Ponto de atenção é a tag "[p-service-api]="serviceApi" onde devemos apontar qual a variável do componente que indica a URL da API de Dados.
<div class="po-wrapper"> <po-toolbar p-title="Datasul - Dynamic - Custom"></po-toolbar> <po-page-dynamic-detail p-auto-router p-title="Idiomas" [p-service-api]="serviceApi"> </po-page-dynamic-detail> </div> |
No Typescript devemos informar qual será a API que retornará os dados para a tela através da variável "serviceApi".
import { Component } from '@angular/core'; import { PoMenuItem } from '@po-ui/ng-components'; import { PoBreadcrumb } from '@po-ui/ng-components'; import { PoPageDynamicDetailActions} from '@po-ui/ng-templates'; @Component({ selector: 'app-idioma-dynamic', templateUrl: './idioma-dynamic.component.html', styleUrls: ['./idioma-dynamic.component.css'] }) export class IdiomaDynamicComponent { public readonly serviceApi = 'http://localhost:8180/dts/datasul-rest/resources/prg/trn/v1/idiomas'; public readonly actions: PoPageDynamicDetailActions = { back: '/documentation/po-page-dynamic-table' }; public readonly breadcrumb: PoBreadcrumb = { items: [ { label: 'Home', link: '/' }, { label: 'People', link: '/documentation/po-page-dynamic-table' }, { label: 'Detail' } ] }; } |
Criamos dois novos facilitadores para auxiliar no desenvolvimento das API's dentro da classe "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. |
Documentação API Datasul:
PO-UI:
GIT Projeto:
<!-- 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> |