Perguntas frequentes


01. Como forçar o uso do HTTPS no Web Framework?

02. Como adicionar colunas ao processo de permissões?

03. Como alterar o texto da barra de título do navegador?

04. Como habilitar usuários para criar chaves custom na base de Desenvolvimento?

05. Como configurar o sistema para uso de nome e logomarca?

06. Como criar um relatório em árvore?

07. Como estender classes explorer?

08. Como estender o explorer?

09. Como executar códigos na inicialização do Web Framework?

10. Como ligar e desligar o speed fill?

11. Como criar grades detalhe?

12. Como estender a interação showVariables de um layout?

13. Como exportar os dados de um relatório que não usa SimpleLayout?

14. Como funcionam os agrupamentos de campos da grade?

15. Como utilizar a propriedade aggregate do Field?

16. Como utilizar a propriedade canNavigate?

17. Por que os detalhes técnicos da ajuda e a pilha do erro não são exibidos pelo sistema?

18. É possível declarar um processo em um módulo da Union File System?




Como forçar o uso do HTTPS no Web Framework?

Para forçar o uso do HTTPS, deve-se criar um arquivo x-class ou x-view no diretório “/Configuração/Web Framework/Segurança” da Virtual File System.

Nesta classe, pode-se indicar manualmente os servidores que têm certificados SSL instalados e são capazes de atender o protocolo HTTPS, conforme exemplo abaixo:

this.httpsCapable = true; 
this.serverAddress.push('<IP_DO_SERVIDOR>'); 

Caso o protocolo HTTPS seja implementado por um outro serviço, como um balanceador de carga ou um proxy reverso, pode-se utilizar o evento o onGetUseHTTPS para detectar que o tráfego foi originado a partir desse outro serviço e realizar o redirecionamento automático. Abaixo segue um exemplo que detecta o uso do balanceador de carga do Google Cloud Platform:

this.onGetUseHTTPS.set(function() {
  return request.getHeader('via') === '1.1 google' && 
    request.getHeader('x-forwarded-proto') === 'http';
});  

Como adicionar colunas ao processo de permissões?

Durante o processo de desenvolvimento, quando trabalhando em novas funcionalidades para o sistema às vezes surge a necessidade de criar uma permissão específica para uma ação definida nesta funcionalidade. O processo de definição de permissões pertence à licença do time Framework, então para resolver o nosso problema isoladamente, existe uma maneira isolada e simples de criar essa coluna sem alterar esse processo.

O primeiro passo a se dar é criar a nova coluna para a nossa nova permissão na tabela de permissões, por isso vamos à classe Permissões em “Raiz > Dados > Sistema > Permissões”. Com essa classe em foco vamos criar um novo registro contendo os dados do nosso campo.

var fld = this.field("EDITANOMECLIENTE", "boolean");
fld.label = "Permite edição de nomes de clientes em um processo imaginário";
fld.help = "Informe se o usuário ou grupo tem permissão para alterar os nomes dos clientes imaginários no processo imaginário.";
fld.order = 999;
fld.visible = true;
fld.scope.addClass(-123456678);

Você deve ter notado que chamamos o método addClass do propriedade scope que, como o nome já diz, adiciona as classes passadas por parâmetro dentro do escopo deste campo de permissão. A consequência disso é que este campo estará visível no processo de permissões para estas classes e suas descendentes. Essa é a única maneira de alterar a visibilidade dos campos de permissão. Apesar de não recomendarmos, é possível um campo de permissão ter várias classes raízes, basta chamar o método novamente passando uma nova chave. Nos casos em que é necessário a criação de uma nova raiz para o campo de permissão é preferível que se crie um novo campo.

IMPORTANTE: Quando dizemos que a visibilidade dos campos de permissão não deve ser alterada em nenhum lugar, nem mesmo no evento onDefinePermissionsGrid, estamos falando sério. Caso isso aconteça um erro será disparado.

Feito isso, certifique-se de que a coluna está criada no banco de dados e no cache local, e prossiga com o nosso manual.

Agora que temos o campo criado e visível, pode ser necessário ainda alguma customização. Como temos acesso aos campos? Simples! É só definir uma implementação do evento onDefinePermissionsGrid na classe que se deseja essas alterações. Segue um exemplo:

this.onDefinePermissionsGrid.set(function (grid) {
  inherited(grid);
  var fld = grid.field('editanomecliente');
  fld.label('lambda');
  fld.help('Permissão para escrever "lambda"!');
});

IMPORTANTE: Depois de ler sobre o evento onDefinePermissionsGrid você pode querer alterar a visibilidade dos campos de permissão utilizando-o. Isto não é possível. A visibilidade dos campos só pode ser alterada utilizando o método o método addClass do propriedade scope do próprio campo.


Como alterar o texto da barra de título do navegador?

Na plataforma nginstack há duas configurações referente ao texto da barra de título do navegador, uma para as telas de login customizadas e outra para o ambiente do sistema que será utilizada após o usuário ser autenticado.

Variáveis disponíveis para a personalização da barra de título do navegador:

  • $DataBase: nome da base de dados.
  • $Port: porta de acesso em que o sistema está sendo executado.
  • $EngineVersion: versão atual do Engine.
  • $ProductVersion: versão atual do sistema.
  • $LoggedUser: usuário logado no sistema.
  • $SystemName: nome do sistema.

Para que possamos alterar o título das telas de login customizadas devemos abrir o processo “Admin > Aparência e Personalização > Tela de login” e clicar no botão “Configurar telas customizadas”. Em seguida, será apresentada uma grade com o título “Telas de login”, nela deve ser selecionada a linha cuja a tela de login desejamos alterar e clicar em “Mudar visão”. Já com a grade no modo tabela será possível ver o campo “Título usuário não autenticado”, este campo é responsável pelo texto da barra de título na tela de login.

Variáveis permitidas: $DataBase, $Port, $EngineVersion, $ProductVersion e $SystemName.

Exemplo:

Título: Sistema - $ProductVersion $DataBase - Engine: $EngineVersion - Porta: $Port.

Resultado: Sistema - 2011.4 BaseDeDados - Engine: 11.1.3.10 - Porta: 80.

A configuração do título do navegador no ambiente do sistema é basicamente a mesma que a da tela de login, existindo apenas algumas variações como: processo utilizado e a adição da variável $LoggedUser para compor o título.

Para que possamos alterar o título do navegador no ambiente do sistema devemos abrir o processo Ambientes que se encontra em “Admin > Aparência e Personalização > Ambientes”.

Ao abri-lo você verá uma grade com o título Ambientes, nela deve ser selecionada a linha cujo o ambiente desejamos alterar e clicar em “Mudar visão”. Já com a grade no modo tabela será possível ver o campo “Título usuário autenticado”, este campo é responsável pelo título do navegador após o login.

Variáveis permitidas: $DataBase, $Port, $EngineVersion, $ProductVersion, $LoggedUser e $SystemName.

Exemplo:

Título: Sistema - $ProductVersion $DataBase$LoggedUser - Engine: $EngineVersion - Porta: $Port. Resultado: Sistema - 2011.4 BaseDeDados\Usuário - Engine: 11.1.3.10 - Porta: 80.


Como habilitar usuários para criar chaves custom na base de Desenvolvimento?

Para habilitar a permissão de um usuário criar chaves custom, acesse “Admin > Customização > Configuração.” Clique em Inserir, digite o login do usuário e salve as alterações.

Para criar uma chave custom pela IDE clique em “Tools > License to create keys > Custom”. Caso não seja possível alterar o código, clique em “Tools > Enable product changes”.


Como configurar o sistema para uso de nome e logomarca?

Para realizar essa configuração é necessário criar um script x-class em “Raiz > Configuração > Inicialização da Sessão.””

O objeto uwi.config contém diversas propriedades relativas a configurações do sistema. Quatro delas dizem respeito a nomes e sobremarcas:

  • uwi.config.systemName: é o nome de exibição do sistema. Por exemplo, quando o sistema dispara um email de relatório agendado, este é o nome que o sistema usa como remetente do email.
  • uwi.config.vendorFooterLogo: é a chave do arquivo que contém a logomarca, para exibição no rodapé dos relatórios.
  • uwi.config.vendorFooterLogoTitle: texto descritivo do logotipo no rodapé.
  • uwi.config.vendorName: o nome do fornecedor.
  • uwi.config.vendorURI: é o endereço do site do fornecedor. Quando tem um valor, a logomarca também se comporta como link, apontando para esse endereço.

É importante notar que existe sempre a possibilidade de que sejam implementadas melhorias e alterações relativas a colocação e formatação dos nomes e marcas no sistema. Utilizar esta API garante a estabilidade do sistema quanto ao uso de marcas e nomes independente das melhorias que venham a ser feitas.


Como criar um relatório em árvore?

Árvore é uma estrutura capaz de representar uma hierarquia em modo gráfico, sendo a sua base, a raiz, o primeiro elemento e também a origem de todos os outros elementos. As pontas da árvore são as folhas.

E como eu indico ao sistema que o meu relatório se trata de um relatório em árvore? O Simple Layout vai entender isso através do método sobrecarregado newRecord. A assinatura do método é essa:

newRecord(checkGroup, groupTotalLabel, showLineTop, showLineBottom, treeNodeId, parentTreeNodeId)

Note que os dois últimos parâmetros dizem respeito à estrutura de árvore, sendo o treeNodeId o ID do nó que se quer acrescentar ao Simple Layout e o parentTreeNodeId o pai dele.

O Simple Layout ainda tem atributos especiais para a nossa estrutura em árvore:

  • treeExpansionLevel: Esse atributo é um inteiro que representa até que nível da árvore ela vai estar expandida quando o relatório for aberto.
  • showTreeRoot: Atributo booleano que indica se a raiz da árvore deve ser exibida.

Como estender classes explorer?

O processo Classes Explorer é utilizado para configurar as classes presentes no sistema.

O Classes Explorer é um processo implementado para ser possível configurar as classes existentes no sistema. A execução do processo abrirá uma tela onde mostrará toda a hierarquia de classes do sistema.

É possível estendê-lo para que a hierarquia de classes exibidas, seja mostrado a partir de uma determinada classe base.

Para configuração é necessário criar um processo e alterar a propriedade baseClass para ter a chave da classe que se deseja utilizar como raiz (base). O exemplo do processo segue abaixo:

this.baseClass = -1898187809; /* Usuarios */
__includeOnce(-1898146496 /* /products/Admin/modules/Admin/Classes Explorer.ip */);

Como estender o explorer?

Esta resposta visa mostrar como estender o Explorer.ip para uso em outros processos. O Explorer é um processo utilizado para criação, exclusão e alteração dos registros de quaisquer tabelas de cadastro (filhas da classe Cadastrais) do sistema e também pode ser estendido e customizado.

Os atributos e eventos que podem ser customizados são:

  • viewMode: Define o modo de vizualização da grade na primeira escrita. (Grid.FORMVIEW ou Grid.TABLEVIEW)
  • baseKey: Chave do registro em que a grade deve estar posicionada. (Number)
  • help: ajuda do processo. (String)
  • onDefineExplorerGrid: evento que possibilita a customização da grade de dados.

Um exemplo de um processo simples usando o Explorer:

this.baseClass = -1898187808; /* Tipos de Arquivos */ 
include -1898145512 /* Data Explorer.ip */ 

Podemos também usar propriedades para alterar a grade, no exemplo abaixo estamos mudando o modo de exibição:

this.baseClass = -1898187808; /* Tipos de Arquivos */ 
this.viewMode = Grid.FORMVIEW; 
include -1898145512 /* Data Explorer.ip */ 

Pode-se fazer customizações mais específicas utilizando o evento onDefineExplorerGrid. No exemplo abaixo iremos criar um novo filtro (extensions) para a grande principal:

this.baseClass = -1898187808; /* Tipos de Arquivos */
include -1898145512 /* Data Explorer.ip */

this.onDefineExplorerGrid.set(function(grid) {
  var filter = grid.process.grid("filter");
  filter.onDefineFields.set(function(grid) {
    inherited(grid);
    var fld = grid.field("extension", "string", 4);
    fld.defaultValue = null;
    fld.saveInputAsDefault = false;
    fld.help = "Extensão do arquivo.";
    fld.onAfterChange.set(function(field) {
      field.parent.process.atualizaFiltroDataSet();
    });
    var fld = grid.field("CLASS");
  });

  grid.process.atualizaFiltroDataSet();
})

this.atualizaFiltroDataSet = function() {
  this.dataSet.filter = this.pegaFiltroDaClasse();
}

this.pegaFiltroDaClasse = function() {
  var filter = this.filter;

  var extension = filter.field("extension").value;

  if (extension) {
    return "javascript:ds.ifileextensions == \"" + extension + "\"";
  } else {
    return "javascript:true";
  }
}

Como executar códigos na inicialização do Web Framework?

Durante o desenvolvimento de alguns produtos, surgiu a necessidade de se executar eventos de interação com o usuário logo após o login no sistema. Para isso desenvolvemos usando um sistema de inicialização para o Framework.

Temos um novo sistema de inicialização que executa os scripts presentes na classe localizada em “/Configuracao/Inicializacao do Web Framework”. Nessa classe está presente a variável global de nome “environment” que é do tipo Environment.

Para sua utilização, deve ser criado um registro novo com uma chave custom na classe -1892603642 (/Configuração/Inicialização do Web Framework). Com a chave custom criada podemos inserir o nosso código diretamente.

var answer = env.confirm('Deseja usar o sistema?', false);
if (!answer) {
  env.exit();
} else {
  env.alert('Bem vindo ao sistema!');
}

Como ligar e desligar o speed fill?

O speed fill é um tipo de preenchimento automático que permite que o sistema sugira um valor para um campo lookup ou combo, completando a digitação do usuário.

À medida que o usuário digita os dados, aparecem palavras ou frases que podem preencher aquele campo (esta ação é chamada speed fill, auto-preenchimento ou preenchimento rápido).

Caso necessário, é possível habilitar ou desabilitar esse comportamento. O uso do speed fill é configurado separadamente para cada agente de usuário.

Para configurar o uso do speed fill no sistema, navegue até “Admin > Explorer” e utilize a classe “Agentes de Usuário”. A classe possui um campo chamado “Tipo de Auto Complete”.

Caso a opção speed-fill esteja selecionada, o recurso será usado para o agente do registro. Para desativá-lo para o agente de usuário sendo editado, selecione outra opção (ou nehuma opção) e salve o registro.


Como criar grades detalhe?

Exemplo 1: Grade simples

this.interaction("main", function () {

  this.ds = connection.cloneLocalCacheByClass(-1898187808/* Tipos de Arquivos */);
  this.ds.indexFieldNames = 'iKey';

  this.detail = dbCache.getTable('iVfs');
  this.detail.indexFieldNames = 'imimeType';

  var grid = this.grid("master", this.ds, -1898187808/* Tipos de Arquivos */);
 
  grid.onDefineFields.set(function(grid){
      var fld = grid.field("detail", "grid");
     
      fld.readOnly = true;
      fld.masterFieldNames = 'iKey';
      fld.detailFieldNames = "imimeType";
      fld.detailIndexFieldNames = "imimeType";
      fld.detailFilter = null;
      fld.masterDetailMaxRecordCount = 10;
      fld.masterDeleteAction = "throw";
     
      fld.onDefineGrid.set(function(field) {
          var grid = field.grid
         
          
          grid.onCreateDataSet.set(function (grid) {
              return grid.process.detail
          })
      });
  });
  grid.onBeforePost.set(function(grid) {
      if (grid.field("detail").ds.isEmpty) {
          grid.process.alert("O DataSet da grade detalhe está vazio");    
      }
  });

  grid.write();

});

Exemplo 2: Utilizando eventos específicos da grade Detalhe.

Neste exemplo, ao invés de utilizarmos as propriedades, iremos sobrescrever os eventos da grade, de forma que ela produzirá o mesmo efeito das propriedades que foram definidas no exemplo 1.

this.interaction("main", function () {

  this.ds = connection.cloneLocalCacheByClass(-1898187808/* Tipos de Arquivos */);
  this.ds.indexFieldNames = 'iKey';

  this.detail = dbCache.getTable('iVfs');
  this.detail.indexFieldNames = 'imimeType';

  var grid = this.grid("master", this.ds, -1898187808/* Tipos de Arquivos */);
  grid.onDefineFields.set(function(grid){
      var fld = grid.field("detail", "grid");
      fld.order = 999;
      fld.readOnly = true;
      fld.masterDetailMaxRecordCount = 10;
      fld.onDefineGrid.set(function(field) {
          var grid = field.grid
         
          grid.onCreateDataSet.set(function (grid) {
              return grid.process.detail
          });
         
          grid.onMasterScroll.set(function (grid) {
              var master = grid.master;
              grid.ds.setRange(master.iKey,master.iKey);
          });
         
          grid.onMasterDelete.set(function (grid) {
              if( !grid.ds.isEmpty ) {
                  throw new Error('É necessário apagar os registros da grade detalhe antes de efetuarmos a deleção.');
              }
          });
         
          grid.onMasterInsert.set(function (grid) {
              grid.process.alert('Essa extensão não tem nenhum arquivo associado ainda!');
          });
         
          grid.onMasterPost.set(function(grid) {
              grid.process.alert('Detalhe: A master acabou de ser postada!');
          });
         
      });
  });

  grid.write();
});

Como estender a interação showVariables de um layout?

Todo relatório no sistema é, na verdade, um processo com interactions e activities pré-definidas, de acordo com a seguinte estrutura básica:

  • interaction showVariables
  • interaction run
  • interaction writeLayout

Existem ainda outras interações auxiliares responsáveis pelo envio de e-mail, exportação do layout para texto, dentre outras.

Iremos compreender como estender a interação showVariables, que é primeira interação chamada ao ser aberto um relatório, e que é responsável pela montagem da grade de variáveis visando a execução do layout.

Um caso prático:

this.help = 'Esse layout mostra como é possível estender a interação "showVariables"
           para, por exemplo, alterar os botões ou escrever um texto antes da
           grade principal.';

var fld = vars.field('variavelA', 'int64');
fld.help = '.';
fld = vars.field('variavelB', 'int64');
fld.help = '.';
fld = vars.field('variavelC', 'int64');
fld.help = '.';

this.interaction( 'showVariables', function () {
  var label = this.label(this.help + '<br><br>');
  label.write();

  inherited();

  this.visibleActions = ['run', 'NovaAção', 'printTxt', 'sendReport', 'export'];
});

this.activity('run', function () {});

this.interaction('writeLayout', function () {
  this.write('resultado');
});

this.activity('NovaAção', function () {
  throw new Error('Ação nova executada com sucesso!');
});

O trecho de código destacado em vermelho, mostra a definição (opcional) da interaction showVariables.

  1. Observe que primeiro estamos escrevendo um label com um texto obtido da propriedade help do layout.

    var label = this.label(this.help + '<br><br>');

    label.write();

  2. Em seguida, a instrução inherited(); se encarrega de executar o que por padrão a interação em questão faz: que é escrever a grade de variáveis e definir as ações padrão. Se essa instrução não for executada, a grade de variáveis não será escrita.

    inherited();

  3. Por fim, estamos redefinindo as ações que essa interação irá apresentar, acrescentando às ações já existentes, uma nova ação chamada ‘NovaAção’:

    this.visibleActions = ['run', 'NovaAção', 'printTxt', 'sendReport', 'export'];

É importante atentar para os nomes das ações pré-definidas pelo SimpleLayout. São elas:

  • run
  • printTxt
  • sendReport
  • export

Como exportar os dados de um relatório que não usa SimpleLayout?

iremos mostrar o uso do evento onGetDataExporter, que serve para exportarmos dados em relatórios que não usam o SimpleLayout.

Exemplo:

includeOnce -1898143872; /* .../library/export/DataSetExporter.ijs */

var fld = vars.field('quantidadeDeRegistros', 'int32');
fld.help = 'Quantidade de registros que serão exportados.';

this.activity('run', function () {
  this.ds = new DataSet();
  this.ds.createField('Nome', 'string', 200);
  this.ds.createField('Valor', 'int32');
  this.ds.create();

  var qtdRegistros = this.quantidadeDeRegistros;
  for (var i = 0; i < qtdRegistros; ++i) {
    this.ds.append(['Registro' + i, i]);
    this.ds.post();
  }
});

this.interaction( 'writeLayout', function () {
  var result = '';
  result += '<br><table>';
  result += '<tr>';
  for (var i = 0; i < this.ds.fieldCount; ++i) {
    result += '<td>';
    result += this.ds.getFieldName(i);
    result += '</td>';
  };
  result += '</tr>';

  for (this.ds.first(); !this.ds.eof; this.ds.next()) {
    result += '<tr>';
    for (var i = 0; i < this.ds.fieldCount; ++i) {
      result += '<td>';
      result += this.ds.getField(i);
      result += '</td>';
    };
    result += '</tr>';
  }
  result += '</table>';

  this.write(result);
});

this.onGetDataToExport.set(function(process) {
  var dataExporter = new DataSetExporter(process.ds);
  dataExporter.title = process.title;

  return dataExporter;
});
  • Incluímos o objeto de exportação de dados do DataSet.
  • Declaramos as variáveis do relatório.
  • Criamos um dataSet com uma massa de dados de teste.
  • Criamos um relatório HTML sem o uso do SimpleLayout.
  • Alteramos o evento onGetDataToExport para retornar um DataSetExporter do dataSet utilizado para gerar o relatório.

Como funcionam os agrupamentos de campos da grade?

No modo formulário, a grade do sistema agrupa automaticamente os campos com mesmo “group” (Field.group). Vejamos o exemplo de definição de campos abaixo, e logo em seguida o resultado da ordenação dos campos.

grid.onDefineFields.set(function(grid) {
  var ordem = 0;

  var fld = grid.field("campoA", "int64");
  fld.group = "Grupo 1";

  var fld = grid.field("campoB", "int64");
  fld.group = "Grupo 2";

  var fld = grid.field("campoC", "int64");
  fld.group = "Grupo 1";
});

Os campos serão exibidos na seguinte ordem:

  • Grupo 1

    CampoA

    CampoC

  • Grupo 2

    CampoB

Observe que o campoB, apesar de ter sido definido entre os campos CampoA e CampoC, ficou isolado pois pertence a um grupo diferente.

Existem duas maneiras de definir a propriedade group de um field:

  • Ela pode ser definida com uma String. Ex.: “Primeiro Agrupamento”
  • Ou pode receber uma instância do objeto FieldGroup, como no exemplo abaixo:
var fieldGroup = new FieldGroup("Grupo Contraido");
// Todos os campos desse agrupamento estarão contraídos no momento da escrita da grade,
// podendo ser visualizados quando o usuário clicar sobre o agrupamento para expandí-lo.
fieldGroup.colapsed = true;

var fld = grid.field("campoF", "string", 30);
fld.group = fieldGroup;

var fld = grid.field("campoG", "string", 30);
fld.group = fieldGroup;

Os campos CampoF e CampoG serão visualizados dentro do “Grupo Contraido” somente após esse ser expandido manualmente


Como utilizar a propriedade aggregate do Field?

Essa propriedade serve para agregar campos que tenham um campo correspondente no DataSet.

Ela permite o uso de funções de:

  • Soma
  • Média
  • Máximo
  • Mínimo
  • Quantidade

O exemplo abaixo irá mostrar como realizamos cada totalização nos campos, é simples:

this.interaction("main", function () {

  this.ds = new DataSet();
  this.ds.createField('soma','number');
  this.ds.createField('media','number');
  this.ds.createField('maximo','number');
  this.ds.createField('minimo','number');
  this.ds.createField('quantidade','number');
  this.ds.create();
 
  this.ds.append([1,1,1,1,1]);
  this.ds.append([2,2,2,2,2]);
  this.ds.append([3,3,3,3,3]);
  this.ds.append([0,0,0,0,0]);
  this.ds.append([0,0,0,0,0]);
  this.ds.append([0,0,0,0,0]);
  //              6,1,3,0,6
  this.ds.post();
 
  var gr = this.grid("Agregacao", this.ds);
  gr.onDefineFields.set(function (grid) {
    var fld = grid.field("soma", "number");
    fld.label = "Soma";
    fld.aggregate = new SumDataSetAggregate();

    var fld = grid.field("media", "number");
    fld.aggregate = new AvgDataSetAggregate();
    fld.label = "Média";

    var fld = grid.field("maximo", "number");
    fld.label = "Máximo";
    fld.aggregate = new MaxDataSetAggregate();

    var fld = grid.field("minimo", "number");
    fld.label = "Mínimo";
    fld.aggregate = new MinDataSetAggregate();

    var fld = grid.field("quantidade", "number");
    fld.label = "Quantidade";
    fld.aggregate = new CountDataSetAggregate();
  });

  gr.write();
});

Como utilizar a propriedade canNavigate?

Ao entrar no Web Framework, o sistema precisa descobrir quais diretórios devem ser exibidos no menu Ir Para. O comportamento atual é que sejam exibidos os diretórios que possuem a propriedade de x-class canNavigate com valor true.

Para descobrir quais diretórios possuem esta propriedade habilitada, o sistema inicia uma exploração da árvore de classes a partir da classe Raiz. Se a classe não configurar esta propriedade, suas filhas são avaliadas. Este processo é executado recursivamente até que se encontre uma classe que defina a propriedade canNavigate.

Visto que habilitados a propriedade canNavigate apenas nas classes que são módulos do sistema, a princípio esta implementação teria um problema de desempenho, pois teríamos que executar praticamente todos os x-class do sistema. Para evitar este comportamento, temos a possibilidade indicar que um ramo não será navegável no menu Ir Para definindo a propriedade canNavigate com false. Desta forma, suas filhas não precisam ser exploradas.

Se observamos a nossa árvore de classes, temos 3 grandes ramos:

  • Dados
  • Configuração
  • products

Os dois primeiros não podem conter processos ou relatórios, portanto são configurados com canNavigate false. Esta configuração garante que todos os x-class dentro destes ramos não serão avaliados desnecessariamente na inicialização do Web Framework. O diretório products não possui esta configuração, pois é nele que estão os processos e relatórios dos módulos.


Por que os detalhes técnicos da ajuda e a pilha do erro não são exibidos pelo sistema?

Os detalhes técnicos nos diálogos de erro e de ajuda podem conter informações privilegiadas que podem ser utilizadas em um ataque, fragilizando a segurança do sistema. Por padrão, essas informações são exibidas apenas para os usuários que participam dos grupos “Administrators” e “Developers”.

Para permitir que outros grupos de usuários visualizem essas informações, basta conceder a esses grupos a permissão ao escopo “security.viewTechnicalInfo”. Essa atribuição deve ser realizada no processo “Admin > Segurança > Grupos, papéis e usuários > Grupos e papéis”. No entanto, considerando aspectos de segurança, deve-se restringir a visualização dessas informações à menor quantidade de usuários possíveis.


É possível declarar um processo em um módulo da Union File System?

Diretamente, não. Os processos precisam ser definidos em arquivos da Virtual File System, pois a chave do arquivo é utilizada como identificador do processo no controle de permissões do sistema e o menu de navegação é construído a partir da estrutura de diretórios da Virtual File System. No entanto, o script da Virtual File System pode delegar a definição do processo para um módulo JavaScript por meio da função loadModule conforme exemplo abaixo:

// Vfs File "/products/custom/modules/Example/Process.ip"

this.loadModule('custom/modules/Example/Process.js');
// Ufs File "/package/custom/modules/Example/Process.js"

/** @param {import('@nginstack/web-framework/lib/process/Process.js')} process */
module.exports = function (process) {

  process.help = 'Example process.';

  process.interaction('main', function () {
    const label = process.label('Hello World from module!');
    label.write();
  });
};