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 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