software‎ > ‎módulos‎ > ‎engine‎ > ‎manuais‎ > ‎

Desenvolvendo no Engine


Antes de começar!

Inicialmente, sugerimos que você dê uma olhada nos principais conceitos e filosofias do Engine. A página sobre o Engine tem um bom material conceitual e serve de base a este manual de desenvolvimento. Para acessar essa página, clique aqui.

Preparando o ambiente para o desenvolvimento

O primeiro passo que o desenvolvedor deve realizar é a instalação do Engine, pois o IDE para codificação é um componente do Engine. Para instalar o IDE, acesse o link a seguir e siga as instruções: Instalação do Engine.

Habilitando o modo de DEBUG

Uma ferramenta importante para os desenvolvedores é o Debugger JavaScript do Engine que por padrão é desativado por motivo de desempenho. Para ativa-lo, realize os seguintes passos:
  1. Clique com o botão direito no ícone do Engine na área de notificação e selecione a opção Manage.
  2. Será perguntando o nome do usuário e senha de acesso ao Manage. Informe o usuário iengine e a senha iengine caso seja a primeira vez que utilize o Manage ou se nunca alterou a senha padrão.
  3. Clique em Configuration e em seguida em General
  4. Marque a opção JavaScript Debugger Enabled 
  5. Clique em Save.
  6. Feche a IDE e abra novamente o Engine.
Pronto o modo DEBUG já está habilitado.

Depurando o código

Para inserir um break-point no código JavaScript é necessário usar a palavra reservada debugger. Se durante a execução de um script JavaScript for encontrada a palavra debugger, o programa será interrompido e exibido na janela JavaScript Debugger.  Veja a imagem da janela de debugger:



É recomendado que o debugger seja condicionado ao seu usuário para não afetar outros usuários com as suas depurações. Exemplo:
 
if (session.userKey === 409302 /* desenvolvedor x */){
   debugger;
}

A propriedade userKey do objeto global session armazena a chave do usuário na tabela de usuários. Você poderá descobrir a sua chave de usuário facilmente digitando usuarios. Lembre-se de colocar o .(ponto) depois de usuários para que o popup de auto-complete apareça. Veja a imagem abaixo:

Escolha o usuário pelo nome de usuário e tecla ENTER. A chave do seu usuário irá aparecer.

A linguagem iJavaScript

Visão geral

O iJavaScript é uma linguagem dinâmica desenvolvida pela Bematech, baseada na especificação do ECMAScript. É uma variante da linguagem JavaScript.
Por se tratar de uma linguagem dinâmica, é possível executar diversos comportamentos em tempo de execução. Tais como: criar propriedade em um objeto, mudar o tipo de uma variável, adicionar um novo código, etc.
O desenvolvedor não compila os scripts iJavaScript, entretanto, para melhorar a performance durante a interpretação dos scripts há um compilador que compila código iJavaScript em tempo de execução que é executado automaticamente de forma transparente para o desenvolvedor.
O código do ERP é implementado usando scripts iJavaScript.

Protótipos(Prototype)

A linguagem iJavaScript assim como a linguagem JavaScript é orientada a objeto e utiliza protótipos para estender comportamento de um construtor de objeto. Veja abaixo um exemplo de como criar objetos em iJavaScript:

//Construtor
function Pessoa(nome) {
   this.nome = nome;
}

//Variáveis estática ou variáveis de protótipo(classe)
Pessoa.PARADO = "parado";
Pessoa.ANDANDO = "andando";
Pessoa.CORRENDO = "correndo";

//Campos, propriedades ou atributos privados
Pessoa.prototype._nome = "";
Pessoa.prototype._idade = 0;
Pessoa.prototype._estado = Pessoa.PARADO;

//Set's e Get's
Pessoa.prototype.setNome = function Pessoa_setNome(nome) {
   this._nome = nome;
};

Pessoa.prototype.setIdade = function Pessoa_setIdade(idade) {
   this._idade = idade;
};

Pessoa.prototype.getNome = function Pessoa_getNome() {
   return this._nome;
};

Pessoa.prototype.getIdade = function Pessoa_getIdade() {
   return this._idade;
};

Pessoa.prototype.getEstado = function Pessoa_getEstado() {
   return this._estado;
};


//Métodos
Pessoa.prototype.parar = function Pessoa_parar() {
   this._estado = Pessoa.PARADO;
};

Pessoa.prototype.andar = function Pessoa_andar() {
   this._estado = Pessoa.ANDANDO
};

Pessoa.prototype.correr = function Pessoa_correr() {
   this._estado = Pessoa.CORRENDO
};


//Criando o objeto paulo
var joseh = new Pessoa("José");

//Setando uma propriedade, aqui será invocado o método setIdade
joseh.idade = 21;

//Invocando o método correr()

joseh.correr();



//Criando o objeto maria
var maria = new Pessoa("Maria");

//Setando a propriedade idade

maria.idade = 18;

//Invocando o método andar()

maria.andar();

Include

Irá incluir o script como se fosse um copiar-colar, desta forma as funções ficam disponíveis para serem redefinidas. O mesmo script pode ser incluído mais de uma vez e o __include pode ser dado em qualquer parte do código.
 
__include(-892139821); /* Script de teste */

function Objeto() {
}

Objeto.prototype.funcao = function(scriptAInserir){
    if(scriptAInserir == 1) {
        include 321323 /* Script 1 */
    } else {
        include 325543 /* Script 2 */
    }
};

includeOnce

Como o nome já diz, o script só será inserido uma vez, mesmo que seja dado vários __includeOnce. O compilador do iEngine irá analisar se já foi incluído, para que não inclua uma segunda vez. O __includeOnce só pode ser invocado no início do script, uma vez que a sua chamada faz parte da sintaxe do script que será analisado pelo compilador.
 
//Só será incluído uma vez
__includeOnce(3445667); /* Script de includeOnce */
__includeOnce(3445667); /* Script de includeOnce */

//OBS: O sript: 3423523 pode, também, incluir o script acima: 3445667
__includeOnce(3423523); /* Outro script */

function Teste(){
}

Herança

A herança nada mais é do que um relação de generalização-especialização entre dois prótotipos ou classes: Um protótipo de generalizado(o protótipo pai) e um protótipo especializado(o protótipo filho).
A implementação de uma relação de herança em JavaScript não funciona como nas linguagens baseadas em classe, embora o efeito prático seja semelhante. Vamos ao exemplo para melhor entender como a herança é implementada na prática:


//Necessário incluir, pois implementação do método inherit() está definido aqui
__includeOnce (-1898145852); /* /products/INTEQengine/library/extensions/Function.js */


function Pessoa(nome) {
   this.nome = nome;
   this.estado = "parado";
}

Pessoa.prototype.correr = function Pessoa_correr() {
   this.estado = "correndo";
};


function Cliente() {
}

//Faz a herança
Cliente.inherit(Pessoa);



var cliente = new Cliente();
cliente.correr(); //O método correr está definido em Pessoa
cliente.estado; //A propriedade estado está definida em Pessoa

Objetos nativos do browser sem correspondência no iJavaScript

Segue abaixo uma tabela com a lista de objetos e funções globais que existem no JavaScript que roda no browser, mas não tem correspondente no iJavaScript que roda no servidor:
 
OBJETOS  FUNÇÕES
location parseFloat
window parseInt
navigator --
RegExp --
 document --
event --
history --
screen  --


Objetos nativos do iJavaScript sem correspondência no javascript do browser

Segue abaixo a lista exemplificativa de alguns objetos que estão publicados no embiente iJavaScript mas não estão publicados no JavaScript do browser:
OBJETOS DESCRIÇÃO
Connection Promove toda a comunicação entre a estação e o servidor.
DataSet Possibilidade de criação de datasets em memória, evitando a criação de tabelas temporárias no banco de dados, obtendo assim um alto ganho de performance.
FileSystem Promove todo o controle I/O. Todo o controle de escrita, lida, gravação de arquivos é feito com o fs.
Session Objeto de controle de sessão.
Profiler Permite obter uma grande quantidade de detalhes sobre os processamentos dos scripts, possibilitando assim uma eficiente perícia do desempenho do mesmo.
Log Objeto de log. A qualquer momento de um script é possível logar informações.
Mail Objeto para o envio de email.
Request Objeto que dar acesso ao HTTP header e body da requisição do usuário.
Reponse Objeto que envia resposta para o cliente via HTTP.
Scheduler Permite o agendamento de tarefas (scripts).
Spool Todo o controle de impressão pode ser feito através do Spool.
SerialPortStream Promove toda a comunicação com a porta serial.
Task Tarefas que podem ser empilhadas no Scheduler.
Repository Local onde podem ser armazenados objetos, variáveis etc. Que podem ser acessadas a partir de todas as sessões.
GenericBarCodeScanner Objeto básico para criação de códigos de barras.
Server Objeto que fornece uma série de funções do servidor (local).
Escape Obtém o código ASCII de um carácter que não seja alfanumérico (vem precedido de %). Ex: response.write(escape("@"))
Unscape(string) Retorna o carácter a partir de um valor ASCII (precedido de %). Ex: response.write(unscape("%20"))
Eval Avalia uma expressão. Você pode, por exemplo, ter uma string com um script que você deseja que seja executado. Ex:
var complementExpression = ( ... ? 15 : 20 )
var result = eval('10 * ' + complementExpression )

Objeto Null

O objeto Null é uma particularidade do iJavaScript, ele não existe nas outras implementações da linguagem JavaScript. Não confunda null com undefined. Uma das diferenças é o fato de que ao acessar uma propriedade de uma váriavel que seja null, é retornado null e não um erro. Veja:

var obj = null;
//Retorna null e não um erro
obj.propriedade;



var obj = undefined;
//Ocorre um erro
typeof obj.propriedade;


Getter e Setters implícitos

Ao implementar um getter e setter de uma propriedade, você poderá acessar o valor da propriedade pelo nome da propriedade, sem chamar o método explicitamente. O exemplo abaixo irá ajudar a compreensão:

function Teste(){
}

//Notação de propriedade privada
Teste.prototype._numero = 0;

Teste.prototype.getNumero = function Teste_getNumero(){
    log.write("Invocado pedir o valor da prorpriedade. Exemplo: instancia.numero");
    return this._numero;
};

Teste.prototype.setNumero - function Teste_setNumero(numero){
    log.write("Invocado ao fazer uma atribuição. Exemplo: instancia.numero = 3");
    this._numero = numero;
};

//Execução
var instancia = new Teste();
instancia.numero = 10;

instancia.numero;

No exemplo acima os métodos setNumero() e getNumero() seram chamados implicitamente.


Operações aritméticas com Data

Diferentemente do JavaScript do browser, no iJavaScript é possível fazer soma e subtração em uma data. Isso facilita o cálculo de uma data anterior ou posterior.

var hoje = new Date();
var amanha = hoje + 1;
var ontem = hoje - 1;

Tipo Number unificado com as chaves dos registros do Bematech ERP

Há um recurso no iJavaScript que é chamado vulgarmente por desenvolvedores da Bematech de "Recurso do Pontinho". Este recurso faz com que a partir de uma chave se possa acessar os demais campos de um registro. Vamos ao exemplo para entender melhor:

//O tipo -189874923 é number, porém a propriedade nome não existe no Number padrão. Desta forma //será considerado como uma chave e a busca pela propriedade nome do registro -189874923 será //feita no cache local.
(-189874923).nome;

Tipos de arquivos

O Bematech ERP possui um cadastro de tipos de arquivos com cerca de 90 tipos de arquivos cadastrados. Dentre estes tipos destaco 6 deles:

Extensão Tipo Mime
 Descrição
 ic  application/x-class Usado na configuração do Bematech ERP e na definição de processo, grades e campos. (Seu uso se tornou deprecated)
 model application/x-modelContém as definições do esquema de uma tabela representada por uma classe. Essas definições são responsáveis pela estrutura de tabelas, configurações de campos e regras de negócio.
 view application/x-viewOs arquivos x-view definem uma interface para visualização dos dados.
 ijs  application/x-javascript
Usado para armazenar, na iVFS, código iJavaScript que roda somente no lado servido.
 iejs  application/x-embeded-javascript Usado para amazenar, na iVFS, código HTML+JavaScript com iJavaScript embarcardo. Semelhante ao ASP ou PHP.
 js  text/javascript
Usado para armaenzar, na iVFS, código JavaScript que roda no browser.
 ip  application/x-process Usado para armazenar código iJavaScript de definição de processos.
 il  application/x-layout Usado para armazenar código iJavaScript de definição de relatórios.                    


Neste primeiro momento iremos exemplificar apenas códigos IJS, IEJS e JS.

Scripts IJS's

São scripts escritos em iJavaScript que são executados no servidor. A API formada por objetos nativos do iJavaScript não é a mesma do JavaScript que roda no browser. Há objetos que são comuns aos dois javascripts como objeto Date e o objeto Math, como também há objetos específicos de cada JavaScript. Veja abaixo um exemplo de um script IJS:

response.write("Olá mundo!!");

Se você criar um arquivo na VFS com o código acima e salvá-lo na VFS será gerada um chave para este arquivo, esta chave poderá ser usada para executar o arquivo pelo browser. Para ver as chaves na IDE vá em: Tools>Show iKeys. Supondo que a chave gerado foi 43614350, então o script poderá ser executado pelo browser através da URL: http://127.0.0.1/43614350. Eis o resultado que aparecerá no browser:



Scripts IEJS's

São códigos HTML que podem ter código iJavaScript embarcado. Aliás, a letra E e IEJS vem de embeded. Para quem conhece ASP e PHP irá perceber que é semelhante as estas tecnologias. Veja o exemplo abaixo:

e<html>
<body>

<server>
       //tudo que tiver entre <server> e </server> será executado no servidor.
   //Tudo que tiver fora de <server> e </server> será enviado direto para o browser.
   
    response.write("Olá mundo!!");

</server>

</body>
</html>


Este script, se invocado pelo browser, irá resultar em:

Scripts JS's

São scripts que rodam no navegador. Veja o exemplo abaixo:

//Script: 43614350 - Teste.js
function sayHello() {
   window.alert("Hello world!!");
}



//HTML: 43614351 - Teste.htm
<html>

<!--incluindo o script Teste.js-->

<script src="43614350">
</script>

<script>
  sayHello() //Chamando a função sayHello() definida em Teste.js
</script>

</html>

O resultado de chamar a url http://127.0.0.1/43614351 no browser será:


Guia prático do desenvolvimento sem a camada do Framework

Este tópico tem como propósito quebrar a teoria que vinha sendo ministrada nos tópicos anteriores e partir para a prática. Obviamente que novos conceitos e teorias serão explicados durante os exemplos práticos apresentados neste tópico.
Os exemplos apresentados neste tópico não usaram o WebFramework que é um framework que abstrai a programação client-side dos browsers modernos para construção de telas de cadastro e relatórios, ou seja, aqui iremos criar as telas de cadastro e relatórios com recursos do browser. O leitor deverá depois ler o manual do WebFramework para saber como construir telas de cadastro e relatórios sem necessidade de escrever códigos client-side.

Objetivo

Ao final deste tópico o leitor será capaz de criar uma agenda de contatos simples. Serão criadas duas telas: uma que lista o contatos cadastrados e outra tela de edição dos dados dos contatos. As figuras abaixo mostram um esboço de como será as telas:

Tela de listagem dos contatos


Tela de edição dos dados dos contatos


Antes de criar a tabela de contatos preciso.

Agora que o nosso objetivo foi estabelecido iremos criar a tabela onde será gravada a agenda de contatos. Mas antes é necessário introduzir ao desenvolvedor o conceito de Classe e Hierarquia de Classes.
Aqui o termo Classe não está sendo usado para se referir a classes de objeto, lembre-se que JavaScript não tem classes de objeto, JavaScript tem construtores e protótipos.  O termo Classe está sendo usado aqui como Classe de Dados. É importante que o leitor perceba esta diferença.

Classes e Hierarquia de Classes

Afinal de contas, o que é mesmo Classe de Dados?
O conceito de Classe de Dados e Hierarquia de Classe será melhor compreendido através de um exemplo. Vamos lá...

Pense em Classe de Dados como o campo TIPO de uma tabela chamada ENTIDADE, onde o tipo pode ser: PESSOA_FISICA, PESSOA_JURIDICA, CLIENTE, FORNECEDOR, VENDEDOR, FUNCIONARIO etc. Graficamente seria algo assim:

Note que entre os possíveis valores para o campo TIPO há intuitivamente uma hierarquia. Veja:

O conceito de Classe de dados do Bematech ERP é bem semelhante ao exemplo apresentado acima, só que o campo TIPO da tabela ENTIDADE no Bematech ERP se chama CLASSE. Veja abaixo a imagem de uma hierarquia de classe de dados doBematech ERP capturada da IDE:

Há, portanto, no Bematech ERP uma tabela chamada CLASSE que armazena todas as Classe de Dados. A tabela CLASSE é referenciada em todos as outras tabelas cadastrais por um campo chamado CLASSE ou ICLASS. Nesta tabela existe um campo chamado MAE que é usado para definir o relacionamento de hierarquia entre as Classes de Dados. Veja o MER simplificado:

Criando a tabela da agenda de contatos

É comum desenvolvedores acharem estranho a seguinte afirmação: "Os registros no Bematech ERP são gravados em uma Classe de Dados". Geralmente eles ficam se perguntando: "Mas os registros não são gravados em tabelas?".
Bem, o fato é que as duas afirmações estão corretas. LOGICAMENTE o Bematech ERP grava registros em classes, mas FISICAMENTE, estes registros são gravados em tabelas.
Na verdade o nome deste sub-tópico estaria mais adequado se ele se chamasse "Criando a CLASSE da agenda de contatos", mas para fins didáticos foi usando o termo tabela.

Criaremos uma classe chamada Contatos que na hierarquia de classes será filha de Raiz\Dados\Cadastrais\Entidades\Pessoas. Para isso clique com o botão direito na classe Pessoa e depois em Insert. Veja figura abaixo:

Após clicar Insert, aparecerá uma nova classe chamada Nova Classe <Chave da Classe> a qual o desenvolvedor poderá renomear para Contatos.
Como a classe contatos é filha da classe Pessoas ela herda os campos definidos em Pessoas e inclusive o desenvolvedor já poderá ver se há registros na classe Contatos. Veja como:

Na guia IDCSql da IDE execute o comando:

//Nova API
classes.getCachedDataSet( <Chave da classe Contatos> );

Vale lembrar aqui que a IDE possui o recurso de auto-complete que auxilia o desenvolvedor a pegar a chave da classe Contatos. Veja como usar:
Note que basta digitar o nome da classe mãe, no caso é Pessoas e tecla .(ponto). Após selecionar Contatos na lista e teclar ENTER, o resultado será algo semelhante a:

classes.getCachedDataSet( 43614400 /* Contatos */ );

Ao executar o comando acima a IDE apresenta o seguinte resultado:

Como é de ser esperar o resultado não trouxe registros afinal não foi inserido nenhum registro na classe Contatos. O mesmo resultado pode ser obtido executando um SELECT no banco de dados. Veja como:

//Nova API
database.query( "Select * From ENTIDADE Where CLASSE = 43614400 /* Contatos */" );

Veja que a tabela onde ficam os registros da Classe de Dados Contatos é a tabela ENTIDADE.

Neste ponto você pode estar se perguntando: Como é possível saber qual o nome da tabela que guarda os registro de uma classe que criamos?
Bem, para responder este questionamento é necessário introduzir o conceito de x-class e x-model. Vamos lá então!

X-Class

Atenção: Os arquivos do tipo x-class são deprecated e não devem ser mais utilizados, prefira utilizar x-model e x-view.

Os X-Classes no Bematech ERP são arquivos do tipo application/x-class que possuem a extensão ".ic". São usados basicamente para configurar o software Bematech ERP e definir telas de processo do Framework HTML. No X-Class são definidos, por exemplo:
  • O nome da tabela que irá armazenar os registros de uma classe;
  • Quais serão os campo da classe;
  • etc.
Como podemos usar o X-Class para configurar a tabela que irá guardar os registros de uma classe?
Bem, basta criar um arquivo X-Class dentro da classe em questão e colocar no nome da tabela na propriedade this.tableName.
Veja como exemplo um trecho do X-Class: /Dados/Cadastrais/Entidades/0100 INTEQerp infrastructure.ic que define as configuração para a classe /Dados/Cadastrais/Entidades:

includeOnce -1897051447 /* /products/INTEQerp infrastructure/library/auxiliaresDeCadastros.js */
includeOnce -1897053112 /* /inteq/erp/library ERP/Funcoes relacionadas a entidade.ij */
includeOnce -1899925557 /* /inteq/library/validators.js */
includeOnce -1897036310 /* /products/INTEQerp infrastructure/.../endereco/EnderecosEntidade.ijs */


//Definição do nome da tabela que irá guardar os registros da classe Entidades e das suas filhas.
this.tableName = "ENTIDADE";
this.upgradeChangesTableStructure = true;
this.cachedData = true;
this.justToGroup = true;


...



//Definição do campo CODIGO
var fld = this.field( 'CODIGO', 'string', 25 );
fld.tableViewWidth = 20;
fld.label = 'Código';
fld.order = 10;
fld.help = 'Informe aqui o código a ser utilizado para este registro que está sendo cadastrado.\r\nProcure utilizar códigos alfanuméricos.\r\nEx. "Fulano SP", "Fulano RJ", "Paraf Sex 1/2", etc\r\n\r\nOs código são muito úteis para o preenchimento de informações, através \r\ndo \'preenchimento rápido\', em todas as operações e cadastros.';
fld.required = true;
fld.onBeforeChange.set( function ( field, value ) {
      if ( value )  // só valida campos <> de null
         verificaDuplicacaoDeCodigo( -2007900000 /* Entidades */, value, field.parent.ds.chave);
});


//Definição do campo NOME
var fld = this.field( 'NOME', 'string', 150);
fld.tableViewWidth = 35;
fld.width = 80;
fld.column = 10;
fld.label = 'Nome';
fld.order = 15;
fld.help = 'Informe aqui o nome completo deste registro.\r\nO objetivo do nome é identificar claramente este registro.';
fld.required = true;
fld.caseType = 'mixed';

//Definição do campo ESTABELECI
var fld = this.field( 'ESTABELECI', 'integer');
fld.column = 10;
fld.label = 'Estabelecimento';
fld.order = 30;
fld.classKey = -1899933495;  /* Estabelecimentos */
fld.help = 'Informe aqui o estabelecimento ao qual esta entidade está associada.';


//Definição do campo STATUSINTERNO
var fld = this.field( 'STATUSINTERNO', 'string', 10);
fld.tableViewable = false;
fld.column = 20;
fld.label = 'Status';
fld.order = 31;
fld.help = 'Status atual desta entidade.';
fld.onCalculate.set( function ( field) {
      //inherited( field)
      if ( field.parent.ds.inativo ) {
         return 'Inativo';
      }
      if ( field.parent.ds.findField( 'CONFIRMACAO') >= 0 && field.parent.ds.confirmacao ) {
         return 'Confirmado';
      }
      return 'Cadastrado';
   } );


//Definição do campo CCUSTRES
var fld = this.field( 'CCUSTRES', 'integer');
fld.tableViewable = false; // deixe esta propriedade aqui, pois em redeclaracoes deste campo, o visible pode se tornar true, mas o table viewable deve continuar false
fld.visible = false;
fld.label = 'C Custos Resultados';
fld.order = 40;
fld.classKey = -2007877000;
fld.help = 'Informe aqui o nome do centro de custos ou resultados que será \r\nutilizado, como sugestão, quando o sistema fizer \r\noperações envolvendo esta entidade.';
fld.userCanChangeNegativeKey = true;

O this no contexto do X-Class acima representa a própria classe Entidades.
Uma configuração definida em um arquivo X-Class pode ser sobrescrita por outro X-Class, desde que este outro X-Class esteja na hierarquia inferior, ou se estiver na mesma hierarquia, está posicionado em ordem alfabética após o primeiro. Vamos ao exemplo para melhor entender:

Na classe /Dados/Cadastrais/Entidades existem 3 arquivos X-class dispostos na seguinte ordem:

0100 INTEQerp infrastructure.ic
0150 INTEQaccounting.ic
0200 INTEQstore.ic

Isso sigifica que as configuração definidas em 0200 INTEQstore.ic podem sobrescrever as configuração e definidas em 0150 INTEQaccounting.ic, que por sua vez podem sobrescrever as configurações definidas em 0100 INTEQerp infrastructure.ic.
As configurações definidas nestes 3 X-Classes poderam ser sobrescritas por qualquer X-Class criado nas classes filhas de /Dados/Cadastrais/Entidades.

Agora que vimos o que é um X-Class, para que que ele serve e como criar, é preciso aprender como obter os valores configurados em um X-Class de uma classe. Vamos lá então ver os exemplos...

Obtem o nome da propriedade tableName da classe /Dados/Cadastrais/Entidades
__includeOnce(-1898147512); /* /products/INTEQengine/library/extensions/Connection.ijs */

var classDef = connection.instanceClassDefinition( -2007900000 /* Entidades */ );

classDef.tableName;

Obtem a lista de campos definidos na classe /Dados/Cadastrais/Entidades
includeOnce -1898147512 /* /products/INTEQengine/library/extensions/Connection.ijs */

var classDef = connection.instanceClassDefinition( -2007900000 /* Entidades */ );

var fieldsList = [];

for(var i = 0; i < classDef.fields.count; i++) {
   fieldsList.push(classDef.fields.field(i).name);
}

fieldsList;

Note que o método que processa os arquivos X-Class's e gera o objeto com as configuração é o connection.instanceClassDefinition(). Este método não é nativo do objeto connection, ele é adicionado ao protótipo do connection pelo script /* /products/INTEQengine/library/extensions/Connection.ijs */ por isso este script foi adicionado no inicio do código.
Se você estiver desenvolvento um processo do Bematech ERP (arquivos com a extesão *.ip) não será necessário fazer o includeOnce do Connection.ijs isso porque o próprio frameworkHTML já o incluiu.

Existe uma outra forma mais rápida e portanto mais indicada de criar o objeto classDef com as definições dos X-Classes das classes, esta outra forma é usando o objeto global classDefManager. O ganho em desempenho ao usar o objeto classDefManager ocorre devido o fato deste objeto gerar um cache da configurações com isso evitando o reprocessamentos de todos os X-Class's de uma classe. Veja como usar o classDefManager:

Obtendo o nome da tabela onde são armazenados os registros da classe /Dados/Cadastrais/Entidades.
//Neste script está a definição do método executeStartupScripts(), por isso ele foi incluido.
__includeOnce(-1898146446); /* /products/INTEQengine/library/extensions/Session.ijs */

//Além de outras atividades, cria a instância global do objeto classDefManager
session.executeStartupScripts();

//Criando o objeto com as configurações da classe.
var classDef = classDefManager.getClassDef( -2007900000 /* Entidades */ );

classDef.tableName;


Se estivermos programando dento de um processo do Bematech ERP (arquivos com a extensão *.ip) poderemos omitir as linhas includeOnce -1898146446 /* /products/INTEQengine/library/extensions/Session.ijs */ e session.executeStartupScripts()

ficando apenas:
var classDef = classDefManager.getClassDef( -2007900000 /* Entidades */ );
classDef.tableName;

Esta omissão das linhas includeOnce -1898146446 /* /products/INTEQengine/library/extensions/Session.ijs */ e
session.executeStartupScripts()pode ser feita dentro dos processos do Bematech ERP porque o próprio frameworkHTML já faz para você.

Bom para finalizar este tópico sobre X-Class iremos salientar que o this que é especificado no código do X-Class será o objeto classDef após o processamento do X-Class. Veja:

No X-Class temos:
this.tableName = "ENTIDADE";

e para ler este valor eu faremos:
var classDef = classDefManager.getClassDef( -2007900000 /* Entidades */ );
classDef.tableName;

Qualquer propriedade definida no X-Class poderá ser lida através do objeto classDef.

X-Model

Arquivos x-model são do tipo de arquivo application/x-model e possuem a extensão ".model". Contém as definições do esquema de uma tabela representada por uma classe, seguindo o conceito de ORM(Object Relational Mapping). Essas definições são responsáveis pela estrutura de tabelas, configurações de campos e regras de negócio que independem da interface.

Existe a divisão de definições e portanto tudo que diz respeito a esquema de tabelas do banco de dados deve estar somente no arquivo x-model.

Como poderemos usar os arquivos de definição para configurar uma tabela que irá guardar os registros de uma classe?
Para isso é necessário criarmos um arquivo x-model dentro da classe em questão e colocar no nome da tabela na propriedade this.tableName

Deve ser definido também a propriedade this.lookupDisplayFieldName. Apesar do nome dessa propriedade remeter uma definição visual, ela trata o valor que será derivado do registro de uma tabela, ou seja, qualquer consulta utilizando as definições de classe sobre o dado de uma classe A que possui lookup para outra classe B irá retornar o valor do campo de mesmo nome que estiver definido em this.lookupDisplayField para o mesmo registro da tabela da classe B.
.
Abaixo temos um trecho do arquivo /Dados/Sistema/Engines/0100 Engine.model:

**/Dados/Sistema/Engines/0100 Engine.model**

__includeOnce(-1898144572) /* /products/INTEQengine/library/NetworkUtilities.ijs */

//Definição do nome da tabela que irá guardar os registros da classe Engines e das suas filhas.
this.tableName = 'iHost';
this.upgradeChangesTableStructure = true;
this.cachedData = true;
this.lookupDisplayFieldName = 'iName';

this.on('lookupDisplay', function (evt){
    evt.displayValue = evt.key.iname;
})

//Definição do campo IKEY
var fld = this.field('iKey', 'integer');
fld.label = 'Chave';
fld.required = true;
fld.readOnly = true;
fld.order = -1000;
fld.help = $R(-1898140920);

//Definição do campo ICLASS
fld = this.field('iClass', 'integer');
fld.label = 'Classe';
fld.required = true;
fld.order = 20;
fld.classKey = this.key;
fld.lookupType = LookupType.CLASS;
fld.help = $R(-1898140918);
fld.dbConstraints = 'NOT NULL';

var dhcpWord = 'dhcp';

//Definição do campo IPADDRESSES
//Eventos responsáveis pela manipulação e validação dos dados pertencem ao model.
var fld = this.field('iIPAddresses', 'string', 100);
fld.label = 'Endereços IP';
fld.required = true;
fld.order = 50;
fld.on('beforeChange', function (evt){
    var value = evt.newValue;
    if (value){
        var ar = value.split(",");
        for (var i = 0; i < ar.length; ++i){
            if (ar[i] == dhcpWord || NetworkUtilities.isIPv4Address(ar[i])){
                ar[i] = ar[i].trim();
            } else {
                throw new Error("O valor \"" + ar[i] + "\" não é um endereço IP válido.");
            }
        }
        evt.newValue = ar.join(',');
    }
});
fld.help = $R(-1898140912, [dhcpWord]);

//Definição do campo ISERVICES
//O tipo masterdetail é utilizado para abstrair relações existentes entre tabelas,
//1 para N ou N para N, é uma forma de validar que o registro de uma tabela possui
//(conceito contrário ao de uma chave estrangeira) outros registros associados a ele.
//As seguintes propriedades são necessárias para esse tipo de relação.

//classKey: Indica qual classe de dados será usada para fazer a relação entre os
//registros. Esta classe será definida como a classe detalhe da relação.

//materFieldNames: Indica os campos da classe mestre que serão usados como filtro de
//dados da classe definida em classKey.

//detailFieldNames: Indica quais campos serão usados para filtrar os dados. Os campos
//serão comparados com os definidos em masterFieldNames.

//Vale salientar que este campo não é criado no schema do banco de dados.

fld = this.field('iServices', 'masterdetail');
fld.order = 500;
fld.label = 'Serviços';
fld.classKey = -1898146242; /* Services */
fld.masterFieldNames = 'iKey';
fld.detailFieldNames = 'iServer';
fld.help = $R(-1898140898); 
 
Obs: Embora a propriedade order do field tenha relação com a forma de exibição, há o desejo de que ele seja utilizado para determinar a precedência das validações a serem realizadas durante a persistência de um registro, onde o valor de um campo depende do valor de outro. 

O this no contexto do x-model acima representa a própria classe Engines. Uma lista com as propriedades que podem ser definidas no x-model pode ser consultada em http://desenvolve.bematech.com:8001/help/symbols/ngin.classdef.ModelDef.html.

Uma prática que deve ser adotada é o uso de Resource Strings na propriedade help, tendo como principal objetivo melhorar o uso da memória, uma vez que a informação só é carregada quando for necessária. Mais informações sobre sua utilização você pode encontrar neste manual.

Uma configuração definida em um arquivo x-model pode ser sobrescrita por outro x-model, desde que o mencionado anteriormente esteja com ordem lexicográfica.

Para exemplificar este sistema de prioridades dos arquivos, suponhamos que existam os seguintes arquivos numa classe do sistema:

 0100 Engine.model
 0150 Engine.model
 0200 Engine.model

O caso acima mostra que as configurações que forem definidas no arquivo 0200 Engine.model irão sobrescrever as configurações definidas no arquivo 0150 Engine.model, que por sua vez, também sobrescreverão o arquivo 0100 Engine.model. Isso também acontece na hierarquia de classes, ou seja, as definições nas classes filhas sobrescrevem as configurações da classe mãe.

Abaixo está definido o padrão numérico utilizado para os intervalos por tipo de arquivo:

x-config - 0000 até 0099.
x-model - 0100 até 4999.
x-view  - 5000 até 8999.
definições de configuração para produto custom  - 9000 até 9099.
definições de modelo para produto custom  - 9100 até 9499.
definições de visão para produto custom  - 9500 até 9999.

Obs: Entende-se por produto custom chaves positivas também.

A seguir mostramos como podemos acessar os dados de configurações de uma classe.

A melhor forma de acessar as definições de modelo de uma classe é através do método ngin.classdef.Manager.getInstance().getModelDef(classKey). Ao utilizar o ngin.classdef.Manager.getInstance().getClassDef(classKey), será obtida a definição dos arquivos x-model em composição à x-view.

Veja como usar o ngin.classdef.Manager.getInstance().getModelDef(classKey):

Obtendo o nome da tabela onde são armazenados os registros da classe /Dados/Sistema/Engine.
// Neste script está a definição do método ngin.classdef.Manager.prototype.runStartupScripts(),
// por isso ele foi incluido.
__includeOnce('ufs:/engine/startup/session.js');
__includeOnce('ufs:/ngin/keys/classes.js');

// Criando o objeto com as configurações da classe.
var classDef = ngin.classdef.Manager.getModelDef(ngin.keys.Classes.ENGINES);
classDef.tableName;

Se você estivermos programando dentro de um processo do Bematech ERP (arquivos com a extensão *.ip) poderemos omitir a linha 
__includeOnce('ufs:/engine/startup/session.js');

ficando apenas:
var classDef = ngin.classdef.Manager.getModelDef(ngin.keys.Classes.ENGINES);
classDef.tableName;

Esta omissão da linha __includeOnce('ufs:/engine/startup/session.js'); pode ser feita dentro dos processos do Bematech ERP porque o próprio frameworkHTML já faz para você.

O escopo do this dentro de um arquivo x-model é a instância obtida a partir de getModelDef, ou seja, é o que será guardado na variável classDef, conforme podemos ver a seguir:

No x-model tem-se:
this.tableName = "ENGINES";

Abaixo o código para obter esse valor:
var classDef = classDefManager.getClassDef(-1898145089 /* Engines */);
classDef.tableName;

Qualquer propriedade definida no X-Model poderá ser lida através do objeto classDef.

X-View

Arquivos x-view são do tipo de arquivo application/x-view, possuem a extensão ".view" e são usados para definir a interface com o usuário. Nele temos:

1- As configurações dos campos que irão exibir os dados no WebFramework.

2- As validações de exibição dos dados devem ser feitas no próprio x-view. Como por exemplo a regras de visão que são definidas no evento onCalculate.

Obs: Os arquivos x-view definem uma interface para visualização dos dados. É importante ressaltar que NÃO devem ser inseridas regras de negócio no x-view.

Para exemplificar o uso do arquivo x-view, segue abaixo o arquivo /Dados/Sistema/Engines/5000 Engine.view:

**/Dados/Sistema/Engines/5000 Engine.view**

this.lookupTableViewWidth = 20;

var fld = this.field('iKey');
fld.visible = false

fld = this.field('iClass');
fld.tableViewWidth = 10;

fld = this.field('iIPAddresses');
fld.tableViewWidth = 20;

fld = this.field('iServices');
fld.detailIndexFieldNames = 'iName';
fld.on('defineGrid', function (evt){
    var grid = evt.field.grid;
    grid.on('defineFields', function (evt){
        var fld = evt.grid.field("iHost");
        fld.visible = false;
    });

    grid.on('afterInsert', function (evt){
        var ds = evt.grid.ds;
        var masterGrid = evt.grid.parent.parent;
        ds.iserver = masterGrid.ds.ikey;
    })
})

Como pode-se observar no último exemplo, as definições de evento devem utilizar o método on() da API de eventos, mais detalhes na documentação. Embora ainda suportada, a forma antiga tornou-se deprecated e NÃO deve ser utilizada.

Uma lista com as propriedades que podem ser definidas no x-view pode ser consultada em http://desenvolve.bematech.com:8001/help/symbols/uwi.classdef.ViewDef.html.

Cache local

Em cada instância do iEngine.exe, há um banco de dados desenvolvido pela Bematech que possui um réplica das tabelas cadastrais do sistema. A este banco de dados que cada iEngine.exe possui, dá-se o nome de cache local.
O cache local é automaticamento atualizado pelo próprio iEngine através de uma rotina de sincronismo de dados que é executada a cada 30 segundos. O campo VERSAO ou iVersion de cada tabela é usado para realizar o sincronismo de forma íntegra.

//Colocar apresentação com animação do processo de atualização.

O DataSet

Após termos aprendido o conceito de classe e como configura-las usando X-Class teremos que aprender como usar o objeto DataSet.
O DataSet é responsável por lista dados, inserir, alterar e remover registros de uma tabela, seja do cache local, do banco de dados ou do banco temporário.


//Esboço: Explicar melhor

//Cria um DataSet com dados da tabela iVFS do cache local
var ds = dbCache.getTable("iVFS")

//Alternativa deprecated
var ds = connection.cloneLocalCache("iVFS")



//Cria um DataSet com dados da tabela iVFS do banco de dados
//Uma vez montado o DataSet os dados baixados do banco de dados ficaram armezados em um banco temporário.
var ds = database.query("Select * From iVFS Where ROWNUM <= 10")

//Alternativa deprecated
var ds = connection.getDataSet("Select * From iVFS Where ROWNUM <= 10")



//Cria um DataSet avulso
var ds = new DataSet()
ds.createField("codigo", "integer")
ds.createField("nome", "string", 20)
ds.create()
ds


Conteúdo Complementar