Árvore de páginas

O arquivo Javascript que agrupa todos os controllers de uma determinada função será o arquivo que irá interagir com o framework do HTML Menu e disponibilizar as funcionalidades para o usuário.
A estrutura dele se divide em:

Definição das dependências

O primeiro comando que o arquivo JS deverá conter é a cláusula "define" do RequireJS listando as dependências do framewoek do HTML Menu que são necessárias para aquela funcionalidade.

 

Exemplo de definição
define(['index', 'components', '/totvs-pnk/js/totvs-pnk-services.js', '/totvs-pnk/js/totvs-pnk-factories.js'], function (index) {
};
O mesmo serve para os controllers. As dependências registradas tanto no framework do HTML Menu como na própria aplicação podem ser injetadas pela sua string de registro.
Exemplo de injeção de dependências
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:

 

Exemplo de roteamento
	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


O registro dos controllers definidos na rota de estados é feito ao final do arquivo Javascript através do comando "register.controller", conforme exemplo abaixo:

 

	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 


Abaixo estão listadas as maneiras pelas quais o controller interage com as funções disponibilizadas pelo framework do HTML Menu ao usuário.

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.

Exemplo de declaração do serviço
	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)


Para efetuar a busca rápida, existem 2 atributos que devem receber valor para que a função "search" do framework efetue a busca rápida.
    • 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;

 

Definição do serviço com a propriedade "quickSearchProperties"
	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".

Método setQuickFilter
		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.

Método applyFilters
		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).

Exemplo de métodos para tratar a deleção
		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.

 

Exemplo de escopo de form
<!-- 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;
		}

 

Caso a validação do form esteja OK, deve ser chamado o método "saveRecord" ou "updateRecord" do serviço da entidade (caso esteja criando ou atualizando, respectivamente). No exemplo abaixo, a função "save" é chamada por uma tela modal que passa como parâmetros a ação ("action") e o objeto a ser persistido ("model").
Exemplo de métodos para tratar a deleção
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>

Variáveis do serviço de zoom
	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


Para exibir telas modais, o controller que a chamará deverá injetar o serviço 'html-framework.generic.Modal' e chamar o método "init" com os respectivos parâmetros. Se for necessário enviar parâmetros para a modal, basta criar o objeto (no exemplo abaixo "params") no controller que chamará a modal (controller origem) e enviar tal objeto para o controller da modal (controller destino). 

 

Exemplo de chamada de tela modal
			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".

 

 

Exemplo de recuperação de parâmetros da modal
<!-- 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);
					});
				}
			}
		};

  • Sem rótulos