Definição das dependências
define(['index', 'components', '/totvs-pnk/js/totvs-pnk-services.js', '/totvs-pnk/js/totvs-pnk-factories.js'], function (index) { };
controllerMappingListCtrl.$inject = ['loadedModules', 'totvs-pnk.mapping.Service', '$location', 'sessionContext', '$log', 'messageHolder', 'html-framework.generic.Modal', 'html-framework.Helper', 'html-framework.FilterBy']; function controllerMappingListCtrl(loadedModules, mappingResource, $location, sessionContext, $log, messageHolder, modalService, serviceHelper, filterByService) { };
Definição das rotas das entidades
O atributo "stateProvider" do AngularJS define as rotas de funcionalidade, sendo que cada rota aponta para um item de menu. No exemplo acima o item de menu é "Visual Editor" e no arquivo menu.js a rota definida para este item foi "/totvs-pnk/mapping", sendo que "totvs-pnk" é o módulo e "mapping" é a rota.
O framework do HTML menu irá então procurar pelo diretório "mapping", e dentro deste diretório, pelo arquivo "mapping.js" (exatamente com estes nomes). E é justamente dentro do arquivo "mapping/mapping.js" que deverão estar as rotas para todas as operações relacionadas com o item de menu "Visual Editor". Isto é feito através da propriedade "stateProvider" do AngularJS.
Deve ser escolhido um "HMTL de entrada" para ser carregado quando for clicado no item de menu. Essa definição será efetuada pela rota existente no arquivo "menu.js" seguida da string ".start". Ainda na propriedade "stateProvider", deverão ser registradas as demais rotas que estão relacionadas ao item de menu "Visual Edtior", conforme exemplificado abaixo:
index.stateProvider /** * Estado pai, a hierarquia de states é feita através do '.', e todo estado novo tem que * ter um estado pai, que nesse caso é abstrato. Este status precisa apenas de uma * template com o elemento <ui-view> para conter os estados filhos. */ .state('totvs-pnk/mapping', { abstract: true, template: '<br><ui-view/>' }) /** * Estado inicial da tela deve ser o estado pai com o sufixo '.start' este estado será * ativado automaticamente quando a tela for carregada. * a URL deve ser compativel com a tela inicial. * No estado tambem definimos o controller usado na template do estado, e definimos * o nome do controller em 'controllerAs' para ser utilizado na view. * tambem definimos a template ou templateUrl com o HTML da tela da view. * * Para ententer o motivo de utilizar o controllerAs e outras tecnicas neste fonte, * deve ser lido o seguinte artigo: * http://www.technofattie.com/2014/03/21/five-guidelines-for-avoiding-scope-soup-in-angular.html * */ .state('totvs-pnk/mapping.start', { url: '/totvs-pnk/mapping/', controller: 'totvs-pnk.mapping.ListCtrl', controllerAs: 'controller', templateUrl: '/totvs-pnk/html/mapping/mapping.list.html' }) /** * Notar que outros estados tambem são filhos com sufixos conforme o objetivo da tela, assim * como o padrão da URL, controller e template. */ .state('totvs-pnk/mapping.edit', { url: '/totvs-pnk/mapping/edit/:id', controller: 'totvs-pnk.mapping.EditCtrl', controllerAs: 'controller', templateUrl: '/totvs-pnk/html/mapping/mapping.edit.html' });
No exemplo acima, foram definidos 2 controllers ('totvs-pnk.mapping.EditCtrl' e 'totvs-pnk.mapping.ListCtrl') que correspondem a 2 arquivos HTML´s diferentes (/totvs-pnk/html/mapping/mapping.edit.html e /totvs-pnk/html/mapping/mapping.list.html)
Definição dos controllers das entidades
index.register.controller('totvs-pnk.mapping.ListCtrl', controllerMappingListCtrl); index.register.controller('totvs-pnk.mapping.EditCtrl', controllerMappingEditCtrl);
São eles que deverão interagir com o framework do HTML Menu para atualizar a interface de tela. Logo após a sua definição, o controller deverá herdar os métodos dos serviços dependentes injetados dinamicamente (através do comando "angular.extend").
Isso pode ser feito de 2 maneiras para que o framework utilize os serviços:
- Efetuando a herança dos serviços diretamente no controller
function controllerMappingListCtrl(loadedModules, mappingResource, $location, sessionContext, $log, messageHolder, modalService, serviceHelper, filterByService) { var listCtrl = this; angular.extend(this, filterByService); angular.extend(this, mappingResource); }
- Efetuando a herança dos serviços em uma variável "service" do controller
serviceTaskControl.$inject = ['sessionContext', 'html-framework.Helper', 'portal-crm.Legend', 'portal-crm.task.Service', 'portal-crm.task.AdvancedSearch', 'html-framework.FilterBy', 'portal-crm.task.ModalService', 'portal-crm.history.ModalService']; function serviceTaskControl(sessionContext, helperService, legendService, taskService, taskAdvancedSearchService, filterByService, serviceTaskModal, serviceHistoryModal) { var service = {}; angular.extend(service, filterByService); angular.extend(service, taskAdvancedSearchService); angular.extend(service, { legend : legendService, helper : helperService, // Variable listResult : undefined, totalRecords : undefined, quickSearchText : undefined, isAdvancedSearch : undefined, applyFilterUser : true, loadDefaults : function() { var _self = this; _self.listResult = []; _self.totalRecords = 0; _self.quickSearchText = ''; _self.isAdvancedSearch = false; taskService.isOnlyResponsibleExecuteTask(function(result) { _self.isOnlyResponsibleExecuteTask = result; }); taskService.isToOpenHistoryAfterExecute(function(result) { _self.isToOpenHistoryAfterExecute = result; }); } }); return service; } // serviceTaskControl
Interação do controller com o framework
Inicialização do controller
O primeiro comando que o controller deverá executar é o método "startModule" provido pelo framework-services.js do framework do HTML Menu. Caso este comando não seja o primeiro a ser executado, corre-se o risco de sobrescrever o conteúdo de atributos que receberam valor antes da chamada do "startModule".
if (loadedModules.startModule(sessionContext.i18n("mappings"), 'totvs-pnk.mapping.ListCtrl', this)) { mappingResource.findRecords(null, 20, null, listCtrl.listCallback); };
Outra maneira também suportada pelo framework do HTML Menu é armazenar a lista de serviços em uma variável "service" dentro do controller, conforme exemplo abaixo.
serviceAccountControl.$inject = ['sessionContext', 'html-framework.Helper', 'portal-crm.Legend', 'portal-crm.account.Service', 'portal-crm.account.AdvancedSearch', 'html-framework.FilterBy', 'portal-crm.history.ModalService', 'portal-crm.task.ModalService', 'portal-crm.support.ModalService']; function serviceAccountControl(sessionContext, helperService, legendService, accountService, accountAdvancedSearchService, filterByService, serviceHistoryModal, serviceTaskModal, serviceSupportModal) { var service = {}; angular.extend(service, filterByService); angular.extend(service, accountAdvancedSearchService); angular.extend(service, { // Services legend : legendService, helper : helperService, context : sessionContext, service : accountService, // Variable listResult : undefined, totalRecords : undefined, quickSearchText : undefined, isAdvancedSearch : undefined, // Retorna se o usuario tem permissao para remover a conta canRemove : function(account) { if (account.accountType == 1) { return true; } return false; } }); return service;
Busca rápida (quick search)
- quickSearchText: valor digitado pelo usuário que é o argumento da busca a ser realizada;
- quickSearchProperties: lista de atributos da entidade (separados por vírgula) sobre os quais deve ser efetuada a busca;
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' }; angular.extend(service, genericService); angular.extend(service, genericQuickSearch); angular.extend(service, genericZoomService); angular.extend(service, genericCRUDService); angular.extend(service, genericTypeaheadService); return service; }
Lista de dados da consulta e total de registros
O retorno de qualquer consulta feita utilizando o framework do HTML menu será retornada na variável "listResult" que é automaticamente criada e alimentada no objeto que herda de 'html-framework.FilterBy'.
O total de registros lidos na consulta, desconsiderando paginação, será gravado na variável "totalRecords".
Filtro rápido (pré-definido)
O filtro rápido é definido no próprio código e no momento em que o usuário selecioná-lo, o sistema deverá passar as cláusulas de busca para o objeto "searchModel" existente no serviço 'html-framework.FilterBy'. Este objeto segue o modelo "propriedade-valor", sendo que a propriedade deve ser o nome do atributo da entidade a ser pesquisado e o valor deve ser o conteúdo que filtra a busca.
Após isso, o controller deve implementar um método de nome "applyFilters" e migrar os dados do "searchModel" para o array "filterBy" através do método addFilter. Abaixo segue exemplo de um objeto pertencente ao controller ("preSavedFilters") contendo as cláusulas de cada filtro rápido.
listCtrl.preSavedFilters = { 'PUBLISHED': [{ property: 'published', restriction: 'EQUALS', value: true }], 'UNPUBLISHED': [{ property: 'published', restriction: 'EQUALS', value: false }, { property: 'publicationDate', restriction: 'EQUALS', value: undefined }], 'NOTPUBLISHED': [{ property: 'published', restriction: 'EQUALS', value: false }] };
O método "setQuickFilter" é disparado quando o usuário seleciona o filtro rápido. Este método alimenta o "searchModel" e chama o método "search".
this.setQuickFilter = function (quickFilterName) { this.clearDefaultData(false); this.clearSearchModel(); var filterClause = []; if (listCtrl.preSavedFilters) { var filters = listCtrl.preSavedFilters; for (var index in filters) { if (filters.hasOwnProperty(index)) { if (index == quickFilterName) { filterClause = filters[index]; } } } } if (filterClause.length > 0) { angular.forEach(filterClause, function (itemFilter) { listCtrl.searchModel[itemFilter.property] = itemFilter.value; }); } this.search(false, listCtrl); };
O método "search" do framework chama o método "applyFilters" do controllers para recuperar os filtros escolhidos pelo usuário e em seguida realiza a consulta.
this.applyFilters = function () { // Limpa a lista de filtros para refatoramento. this.clearFilter(); // Adiciona os valores do model a lista de filtros var objSearch = listCtrl.searchModel; if (objSearch) { for (var key in objSearch) { if (objSearch.hasOwnProperty(key)) { this.addFilter(key, objSearch[key], 'l-' + key, objSearch[key]); } } } };
Deleção de registros
Quando o usuário clicar no botão de remover, o sistema deverá chamar um método para tratar esta transação (no exemplo abaixo, é o método "remove"). Após isso, o sistema deverá questionar o usuário se realmente ele deseja excluir o registro (através do serviço 'messageHolder' instanciado na variável 'messageHolder' do exemplo abaixo). Se o usuário confirmar que deseja excluir, chamar o método "deleteRecord" do serviço do framework informando uma função de callback para retirar o registro da lista (no exemplo abaixo a função é "afterRemove).
this.afterRemove = function (obj) { var index = listCtrl.listResult.indexOf(obj); listCtrl.listResult.splice(index, 1); listCtrl.totalRecords--; } this.remove = function (model) { messageHolder.showQuestion('t-remove-mapping', 't-remove-mapping-confirmation', 'l-yes', 'l-no', function (result) { if (result === true) { mappingResource.deleteRecord(model.id, listCtrl.afterRemove(model)); } }); };
Criação/Atualização de registros
Quando a transação de criação de registros puder ser efetivada, é necessário primeiro validar os dados do form através do método "validateForm" do serviço 'html-framework.Helper' passando como parâmetro o escopo do form. Para isso, é necessário criar o método "setFormScope" em cada controller que tiver um form e necessitar ser validado.
<!-- Código a ser inserido no HTML --> <form id="mappingForm" name="mappingForm" class="form-horizontal" role="form" data-ng-init="controller.setFormScope(this)"> </form> <!-- Código a ser inserido no Controller --> this.setFormScope = function (scope) { this.formScope = scope; }
this.save = function (action, model) { var canSave = serviceHelper.validateForm(this.formScope.mappingForm); if (canSave) { if (action == 'edit') { mappingResource.updateRecord(model.id, model); } else { model.published = false; mappingResource.saveRecord(model); } } };
Pesquisa rápida e pesquisa avançada
Tanto para a pesquisa rápida como a avançada, deverá ser chamado o método "search" do framework. Para que este método possa executar corretamente ambas as pesquisas, é necessário implementar o método "applyFilters" no controller (tal como exemplificado na seção "Filtro Pré-definido") e o controller deverá injetar o serviço 'html-framework.generic.Service' por causa do método findRecords, que é utilizado internamente.
Zoom - dados selecionáveis
Após a definição do campo que possuirá um zoom correspondente, é necessário configurar a modal de zoom com os dados selecionáveis.
No código HTML, a propriedade "zoom-service" define o serviço registrado pela aplicação para efetuar a busca dos dados a serem mostrados na tela de zoom. Os dados a serem exibidos na tela template de zoom são definidos nas seguintes propriedades da instância do serviço da propriedade "zoom-service".
- "zoomName": é o nome que aparece no titulo do zoom, normalmente é o nome externo da tabela;
- "propertyFields": 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;
- "tableHeader": um array de objetos com as propriedades "label" e "property" que definem as colunas que aparecem na tabela do zoom;
Também é necessário criar um método "applyFilter" para carregar os dados do zoom. Recebe como parâmetro um objeto com as sequines propriedades:
- init: contem o objeto informado no atributo zoom-init do zoom;
- selectedFilter: contém o objeto do campo selecionado na pesquisa;
- selectedFilterValue: contém o valor informado no campo de pesquisa;
- more: valor lógico informando se deve carregar mais dados no resultado ou iniciar uma nova pesquisa;
Zoom - capturar seleção
Para capturar o valor selecionado pelo usuário no zoom e exibi-lo na widget de tela, será necessário informar a tag "ng-model" do AngularJS na widget HTML contendo o nome de uma variável para armazenar o conteúdo selecionado.
Caso seja necessário fazer alguma customização no conteúdo retornado, o controller do zoom poderá implementar a função "returnValue" para tratar isso.
<field type="select" data-ng-model="controller.itemERP" zoom zoom-service="totvs-pnk.item.Service" canclean> <label>{{ 'l-item' | i18n }}</label> <include> <ui-select-match placeholder="{{ 'l-item' | i18n }}">{{ $select.selected.name }}</ui-select-match> <ui-select-choices repeat="item in controller.flowItens track by $index" refresh="controller.searchItems($select.search)" refresh-delay="300"> <span data-ng-bind-html="item.name | highlight: $select.search"></span> </ui-select-choices> </include> </field>
serviceItem.$inject = ['html-framework.generic.Service', 'html-framework.generic-quick-search.Service', 'html-framework.generic-typeahead.Service', 'html-framework.generic-zoom.Service', 'totvs-pnk.item.Factory', 'html-framework.generic-crud.Service', '$timeout', 'html-framework.generic-zoom.Service', 'html-framework.generic-typeahead.Service']; function serviceItem(genericService, genericQuickSearch, genericTypeaheadService, genericZoomService, factoryItem, genericCRUDService, $timeout, genericZoomService, genericTypeaheadService) { var service = { getFactory: function () { return factoryItem; }, /** * zoomName - é o nome que aparece no titulo do zoom, normalmente é o nome externo da tabela. */ zoomName: 'l-items', /** * 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-code', property: 'erpCode' }, { label: 'l-name', property: 'name' }, { label: 'l-description', property: 'description' }], /** * tableHeader - é um array de objetos com as propriedades label e property que definem * as colunas que aparecem na tabela do zoom. */ tableHeader: [{ label: 'l-erp-code', property: 'erpCode' }, { label: 'l-name', property: 'name' }, { label: 'l-description', property: 'description' }], returnValue: function () { return this.zoomResultList[this.selected]; }, /** * 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) { var _self = this; var value = parameters.selectedFilterValue; var startAt = (parameters.more ? _self.zoomResultList.length : 0); var filters = [ { property: parameters.selectedFilter.property, value: value }, ]; this.zoom(startAt, undefined, filters, function (result) { _self.resultTotal = 0; if (!parameters.more) { _self.zoomResultList = []; } angular.forEach(result, function (value) { if (value && value.$length) { _self.resultTotal = value.$length; } _self.zoomResultList.push(value); }); }); } }; angular.extend(service, genericService); angular.extend(service, genericCRUDService); angular.extend(service, genericZoomService); angular.extend(service, genericTypeaheadService); return service; }
Telas modais
var _self = this; this.afterSave = function (status, obj) { if (status == 'ok') { if (_self.relatedAction == 'edit') { var index = serviceHelper.findIndexByKeyValue(listCtrl.listResult, 'id', obj.id); if (index != -1) { listCtrl.listResult[index] = obj; } } else { listCtrl.listResult.unshift(obj); listCtrl.totalRecords++; } } }; var params = { model: mapping, action: this.relatedAction }; var strContext = $location.url().split('/')[1]; modalService.init(params, this.afterSave, _self, '/' + strContext + '/html/mapping/mapping.edit.html', 'totvs-pnk.mapping.EditCtrl');
Para recuperar estes parâmetros dentro da modal (controller destino), deve-se injetar o serviço 'modalParams' (abaixo exemplificado na varíavel "modalParams").
Para fechar a modal, é necessário injetar o serviço '$modalInstance' do AngularJS e criar uma função para tratar o evento do clique do usuário em "Cancelar" (no exemplo abaixo é o método "cancel") que chame o método "dismiss" da "$modalInstance".
Quando o usuário clicar no "OK" da modal, o framework chamará primeiro a função relacionada ao clique no botão e em seguida a função de callback passada como parâmetro para o controller da modal. A primeira está representada no exemplo abaixo pela função "save" do controller da modal. Ela recebe os parâmetros "action" e "model" vindos da modal para efetivar a transação.
Já a função da callback (no exemplo acima representada pela função "afterSave") é disparada pelo evento de "close" da modal e sempre receberá 2 parâmetros: a ação ('ok' ou 'cancel' e o objeto retornado pela transação (que é o parâmetro "result" do método de "close" da "$modalInstance"). Mais detalhes no tópico "Criação/Atualização de Registros".
<!-- Código a ser inserido no HTML da Modal --> <modalfooter> <button class="btn btn-primary" data-ng-click="controller.save(controller.myParams.action, controller.myParams.model);"> {{'btn-save' | i18n}} </button> <button class="btn btn-default" data-ng-click="controller.cancel();"> {{'btn-cancel' | i18n}} </button> </modalfooter> <!-- Código a ser inserido no Controller da Modal --> this.myParams = angular.copy(modalParams); this.cancel = function () { $modalInstance.dismiss('cancel'); }; this.save = function (action, model) { var canSave = serviceHelper.validateForm(this.formScope.mappingForm); if (canSave) { if (action == 'clone') { mappingResource.cloneMapping(model, function (result) { $modalInstance.close(result); }); } else if (action == 'edit') { mappingResource.updateRecord(model.id, model, function (result) { $modalInstance.close(result); }); } else { model.published = false; mappingResource.saveRecord(model, function (result) { $modalInstance.close(result); }); } } };