Todo arquivo .war que está dentro do EAR do TOTVS11 é um arquivo passível de ser enxergado pelo menu do HTML Framework. Dentro do war da aplicação, deverá ser respeitada a estrutura de diretórios para que a aplicação seja devidamente reconhecida pelo Menu do HTML Framework. A estrutura deverá ser:
- assets: pasta onde residem os recursos visuais da aplicação, como CSS, imagens, fontes e demais dependências (jQuery, bootstrap, etc)
- html:
- subpasta "menu": obrigatória para que a aplicação seja reconhecida pelo Menu do HTML Framework;
- subpastas das rotas: deverá existir uma página para cada rota definida no arquivo "menu.html". Esta pasta conterá tanto os arquivos HTML como JavaScript. No caso da rota "mapping" citada neste exemplo é necessário haver uma pasta de mesmo nome contendo os arquivos que lidam com as entidades envolvidas. Note, apesar de existir o "list", "view" e "edit" do HTML, não existe um controller para cada HTML, tudo está centralizado no "mapping.js" (vide figura abaixo);
- i18n/uncompressed: deverá conter um arquivo "translations.js" contendo um JSON com as strings de tradução. Vide exemplo mais abaixo.
- jsp: contém o arquivo de login e de customização do erro 404, conforme citado aqui;
- Os "services" e "factories" da aplicação AngularJS deverão estar em alguma pasta referenciada pelo arquivo "menu/menu.js". Nos exemplos citados aqui e aqui, estes arquivos correspondem aos caminhos "/totvs-pnk/js/totvs-pnk-services.js" e "/totvs-pnk/js/totvs-pnk-factories.js" referenciados na cláusula "define";
Definição dos itens e rotas de menu
Dentro do WAR deverá existir uma pasta "html/menu" e dentro da pasta "webapp", com os seguintes arquivos:
- menu.html: arquivo HTML que utiliza diretivas do AngularJS e contém todos os itens de menu da aplicação e suas respectivas rotas;
<div class="well"> <h1>{{'totvs-pnk-menu' | i18n}}</h1> <div class="row" ng-controller="totvs-pnk.menu.menuPnkCtrl"> <ul class="nav nav-sidebar"> <li><a href="#/totvs-pnk/mapping">{{'nav-editor' | i18n}}</a></li> </ul> </div> </div>
- menu.js: arquivo Javascript que instacia a aplicação (no exemplo abaixo "TOTVS PNK"), seu respectivo controller (no exemplo abaixo "totvs-pnk.menu.menuPnkCtrl") e carrega as dependências necessárias na diretiva "define" do RequireJS;
define(['index', '/totvs-pnk/js/totvs-pnk-services.js', '/totvs-pnk/js/totvs-pnk-factories.js'], function(index) { menuPnkCtrl.$inject = ['loadedModules']; function menuPnkCtrl(loadedModules) { loadedModules.startModule("TOTVS PNK"); } // function menuCimatecCtrl(loadedModules) index.register.controller('totvs-pnk.menu.menuPnkCtrl', menuPnkCtrl); });
Ao final, o resultado desta configuração será a tela abaixo. Ao clicar no item de menu "Visual Editor" a aplicação vai ser redirecionada para a URL "totvs-pnk/mapping", que foi informada anteriormente no arquivo "menu.html".
Definição dos serviços
A aplicação deverá registrar serviços para cada entidade acessar as factories (similares a DAO´s no Javascript) de cada entidade. Este registro pode ser feito em somente um arquivo para todo o módulo ou em vários (ex: um por entidade), o importante é registrar a respectiva dependência quando for necessário utilizar determinado serviço.
Cada serviço registrado deverá possuir o método "getFactory" implementado, retornando a factory correspondente, que efetuará as transações no banco de dados.
define(['index', 'framework-services'], function (index) { serviceMapping.$inject = ['html-framework.generic.Service', 'html-framework.generic-quick-search.Service', 'html-framework.generic-typeahead.Service', 'html-framework.generic-zoom.Service', 'totvs-pnk.mapping.Factory', 'html-framework.generic-crud.Service', '$timeout' ]; function serviceMapping(genericService, genericQuickSearch, genericTypeaheadService, genericZoomService, factoryMapping, genericCRUDService, $timeout) { var service = { getFactory: function () { return factoryMapping; }, /** * zoomName - é o nome que aparece no titulo do zoom, normalmente é o nome externo da tabela. */ zoomName: 'l-mappings', quickSearchProperties: 'name', /** * propertyFields - e um array de objetos com as propriedades label e property que definem * os campos que deverão aparecer na opção do filtro do zoom. */ propertyFields: [{ label: 'l-mapping', property: 'id' }, { label: 'l-description', property: 'name' }, { label: 'l-site', property: 'establishmentErpCode' }], /** * tableHeader - é um array de objetos com as propriedades label e property que definem * as colunas que aparecem na tabela do zoom. */ tableHeader: [{ label: 'l-mapping', property: 'id' }, { label: 'l-description', property: 'name' }, { label: 'l-site', property: 'establishmentErpCode' }], /** * returnValue - é um metodo que será chamado na confirmação do registro selecionado no zoom, * esse metodo é opcional, o resultado deste metodo irá ser colocado no model do * campo de zoom. Se o metodo não for definida o valor colocado no model será o * conteudo de 'selected'. */ returnValue: function () { console.info("returnValue", this.selected); return this.selected.id; }, /** * getObjectFromValue - esse metodo é chamado pelo validator do campo de zoom, se não retornar o * objeto o validater com a key "zoom" será inválido. */ getObjectFromValue: function (value) { console.info("getObjectFromValue", value); return factoryMapping.get({ id: value }); }, zoomSelected: function (sel) { console.info("zoomSelected", sel); }, /** * applyFilter - é um metodo de carregar os dados do zoom, recebe como parametro um objeto * com as sequines propriedades: * init - contem o objeto informado no atributo zoom-init do zoom. * selectedFilter - contem o objeto do campo selecionado na pesquisa. * selectedFilterValue - contem o valor informado no campo de pesquisa. * more - valor logico informando se deve carregar mais dados no resultado * ou iniciar uma nova pesquisa. */ applyFilter: function (parameters) { /** * A propriedade zoomResultList deve receber os dados que irão aparecer no zoom. */ var that = this; this.zoomResultList = factoryMapping.query({}, function (result) { /** * A propriedade resultTotal deve receber a quantidade de registros total da pesquisa * não apenas o tamanho atual da lista de resultados. * Utilizar o $timeout, porque o o $timeout atualiza o scopo da tela, e faz o contador * atualizar o valor na tela. $scope não funciona, primeiro porque não pode ser * injetado em um serviço, e neste momento já está ocorrendo uma operação de Digest do * angular. */ $timeout(function () { /** * Como o retorno deste sreviço é um Return (no java) é sempre retornado um objeto. * O transformResponse utilizado no menu, faz que para os serviços sempre seja retornado * a propriedade 'data' do Return e a propriedade 'length' é copiada para o primeiro mapping * da lista que está em data como '$length'. */ that.resultTotal = result[0].$length; }, 0); }); }, cloneMapping: function (subj, callback) { this.getFactory().clone({}, subj, callback); }, publishMapping: function (subj, callback) { this.getFactory().publish(subj, callback); } }; angular.extend(service, genericService); angular.extend(service, genericQuickSearch); angular.extend(service, genericZoomService); angular.extend(service, genericCRUDService); angular.extend(service, genericTypeaheadService); return service; } index.register.service('totvs-pnk.mapping.Service', serviceMapping); });
Definição das factories
A aplicação deverá registrar factories para disponibilizar à aplicação AngularJS os serviços REST das entidades JAVA. Este registro pode ser feito em somente um arquivo para todo o módulo ou em vários (ex: um por entidade), o importante é registrar a respectiva dependência quando for necessário utilizar determinado serviço registrado.
define(['index', 'framework-factories', '/totvs-pnk/js/url-services.js'], function (index) { factoryMapping.$inject = ['$resource', 'html-framework.generic.Factory', 'html-framework.generic-crud.Factory', 'html-framework.generic-quick-search.Factory', 'html-framework.factoryResourceLoader']; function factoryMapping($resource, genericFactory, factoryCrud, factoryQuickSearch, factoryResourceLoader) { var specificResources = { publish: { method: 'POST', isArray: false, params: { url: 'publish' } }, clone: { method: 'PUT', isArray: false, params: { url: 'clone' } } }; var factory = factoryResourceLoader.loadSpecificResources(URL.mappingService + '/:url/:id', specificResources); angular.extend(factory, genericFactory); angular.extend(factory, factoryCrud); angular.extend(factory, factoryQuickSearch); return factory; } // function factoryMapping ($resource, genericFactory, factoryCrud) // ######################################################## // ### Register // ######################################################## // Application Factories index.register.factory('totvs-pnk.mapping.Factory', factoryMapping); });
Arquivo de tradução
A aplicação deverá prover um arquivo contendo um JSON com as strings de tradução para os idiomas suportados. Eis um exemplo:
[{ "t-remove-flow-confirmation": { "pt": "Tem certeza que deseja remover o fluxo?", "en": "Are you sure you want to remove the flow?", "es": "¿Está seguro que desea eliminar el flujo?" }, "l-filtered-by": { "pt": "Filtrado por", "en": "Filtered by", "es": "Filtrado por" }, "l-cell-detail": { "pt": "Detalhe da célula", "en": "Cell detail", "es": "Detalle de la célula" }, "l-sku": { "pt": "SKU", "en": "SKU", "es": "SKU" }, "l-invalid-msg": { "pt": "Os campos obrigatórios só podem ser inteiros maiores que zero.", "en": "Required fields must be integer values greater than zero.", "es": "Los campos obligatorios deben ser enteros mayores que cero." }, "success-update-cell": { "pt": "Célula editada com SUCESSO!", "en": "Cell successfully edited!", "es": "Célula editada con SUCCESO!" } }]