O Objetivo desta técnica é apresentar uma forma de customizar as telas HTML que foram construídas utilizando o Form Dinâmica do PO-UI.
No PO-UI, a Form Dinâmica trabalha recebendo uma lista de campos que serão apresentados em tela, bem como 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.
CARLOS - Colocar aqui as imagens para o pessoal ter uma visao geral
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 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 informa que é necessario a utilização da include "utp/ut-api.i", pois além do que ela se propõem, ela também está identificando 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:
Componentes:
Templates:
Para facilitar a migração do seu projeto com o THF para o PO UI, disponibilizamos um pacote para fazer esta conversão.
Este pacote, irá passar pelos arquivos do seu projeto alterando as palavras-chaves do THF para a nova nomenclatura do PO UI.
Basta seguir o guia disponibilizado pela equipe do PO-UI:
https://po-ui.io/guides/migration-thf-to-po-ui
Tendo o projeto iniciado conforme documentação acima e adicionado os componentes desejados do PO-UI vamos ao desenvolvimento.
Todo componente dinâmico é divido basicamente em duas partes:
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 essas duas procedures tem chamadas para programas 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: GET - http://localhost:8180/dts/datasul-rest/resources/prg/trn/v1/idiomas/metadados { "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 os exemplos de como ficam estes códigos citados acima.
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: 'https://6a6a8bb8-09be-496b-aeea-faa3a7052052.mock.pstmn.io/api/trn/v1/idiomas/metadados', // endpoint dos metadados serviceLoadApi: 'https://6a6a8bb8-09be-496b-aeea-faa3a7052052.mock.pstmn.io/api/trn/v1/idiomas/metadados' // endpoint de customizações dos metadados } }, { path: '', redirectTo: '/idioma', pathMatch: 'full' }, { path: '**', component: IdiomaDynamicComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } |
<div class="po-wrapper"> <po-toolbar p-title="Datasul - Dynamic - Custom"></po-toolbar> <po-menu [p-menus]="menus"></po-menu> <po-page-default p-title="Idiomas"> <p> </p> <po-dynamic-view [p-fields]="fields" [p-value]="employee"> </po-dynamic-view> <po-page-dynamic-detail p-auto-router p-title="Detail" [p-actions]="actions" [p-breadcrumb]="breadcrumb" [p-fields]="fields" [p-service-api]="serviceApi"> </po-page-dynamic-detail> </po-page-default> </div> |
import { Component } from '@angular/core'; import { PoMenuItem } from '@po-ui/ng-components'; import { PoDynamicViewField } from '@po-ui/ng-components'; import { PoBreadcrumb } from '@po-ui/ng-components'; import { PoPageDynamicDetailActions, PoPageDynamicDetailField } from '@po-ui/ng-templates'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { readonly menus: Array<PoMenuItem> = [ { label: 'Home', action: this.onClick.bind(this) } ]; private onClick() { alert('Clicked in menu item') } public readonly serviceApi = 'https://po-sample-api.herokuapp.com/v1/people'; public readonly serviceMetadataApi: 'http://localhost:3000/v1/metadata'; // endpoint dos metadados //public readonly serviceLoadApi: 'http://localhost:3000/load-metadata' // endpoint de customizações dos metadados 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' } ] }; public readonly fields: Array<PoPageDynamicDetailField> = [ { "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" } ]; employee = { codIdioma: "EN", desIdioma: "Inglês", codDialet: "Pt", codUsuario: "super", nomUsuario: "Super" }; } |
Templates:
<!-- 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> |