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:
Clique com o botão direito no ícone do Engine na área de notificação e selecione a opção Manage.
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.
Clique em Configuration e em seguida em General.
Marque a opção JavaScript Debugger Enabled
Clique em Save.
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 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:
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:
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