WirePhrame

From COREPHP

Jump to: navigation, search

Contents

Download

Faça aqui o download da WirePhrame!

Introdução

ATENÇÃO Estão pendentes no documento, os seguintes itens:

   * 22 Campos editáveis em listview com callback para onchange
   * 23 Trabalhando com Treeviews
   * 25 Enviando Arquivos ao Servidor

A fim de obter uma estrutura organizacional mais coesa e focada no desenvolvimento com o paradigma SOA - Services Oriented Applications - com o uso de RBAC - Role-Based Access Control - iniciamos o desenvolvimento de uma estrutura que casasse todas as tecnologias utilizadas por nós.

Para atingir estes objetivos, uma estrutura foi criada para sistemas permitindo a reutilização de código no controle de fluxo da aplicação através do consumo de WebServices XML com a utilização do padrão COSA, o compartilhamento das interfaces entre aplicações e prototipação acelerada com a ferramenta JsDesigner, e aceleração e redução de erros na interação com o banco de dados através da biblioteca de persistência POP.

Esta estrutura implementa o Padrão de Projetos MVC de maneira bastante modularizada, permitindo separar a lógica da aplicação (Model), da interface do Usuário (View) e do fluxo da aplicação (Controller) de maneira bastante simples e produtiva. Sistemas simples podem ser desenvolvidos em prazos recordes de 3 a 7 dias, enquanto sistemas mais complexos não demorariam mais que 3 meses até terem sua primeira versão final no ar.

O principal objetivo do framework é retirar das mãos do programador a preocupação de lidar com elementos repetitivos e simples de programação como criação de SQLs, validação de dados em relação ao banco de dados (tipo, comprimento, etc), filtragem de ataques de SQL Injection, recepção e envio de dados entre Interfaces e Controles, construção de chamadas XML para os WebServices, etc.

Componentes do WirePhrame

Para atingir seus propósitos de ser um framework MVC de fácil utilização, são utilizadas as seguintes bibliotecas:

1 - POP - Camada de Modelo e Persistência

2 - COSA - Camada de Comunicação entre Controle e Interface

3 - JsWebGets - Camada de Interface

4 - RePHPort - Engine de Templates para Relatórios

Qualquer uma destas libs pode ser substituída por outra que melhor convier ao programador sem prejuízo organizacional do código, porém, no âmbito do COREPHP, estas são as oficialmente suportadas.

Requisitos de Sistema

1 - Cliente: navegador Firefox 3.5 ou superior, ou IE 7 ou superior

2 - Servidor Web com PHP 5.3, com a extensão SOAP e PDO para algum dos bancos de dados a seguir

3 - Servidor de Banco de Dados PostgreSQL, Oracle, MySQL ou MSSQL (variará apenas a performance).

Estrutura Física da Aplicação com WirePhrame

Originalmente, uma aplicação feita com o WirePhrame apresentará a seguinte estrutura

   Sistema
       + visual (elementos visuais da aplicação)
       |    + css
       |    + images
       + includes (todos os elementos necessários para a aplicação)
       |    + js (arquivos JS de configuração, includes, etc)
       |    |    + libs (bibliotecas do framework)
       |    + php (arquivos PHP de configuração, includes, conexão de banco, etc)
       |         + libs (bibliotecas do framework)
       + system (biblioteca do sistema)
            + app (Controle e Interface da aplicação)
            |    + base (estrutura básica para desenvolvimento de sistemas - tela de login, etc)
            |    |    + control (Controle)
            |    |    + view (Interfaces)
            |    + outros módulos (diversas pastas com o nome do módulo com a estrutura abaixo)
            |         + control (Controle)
            |         + view (Interfaces)
            + config (arquivos de configuração)
            + model (Objetos)
            + report (Relatórios)
                 + output (Saída dos relatórios)

A visão acima não foca em quais tecnologias serão utilizadas, permitindo o uso dessa estrutura para qualquer sistema, e a utilização de quaisquer outras bibliotecas (Smarty, Lumine, etc), sem prejuízo na organização. WirePhrame visa antes de ser um framework, ser uma proposta de wireframe de aplicação.

Se focarmos a organização baseado nas libs que são utilizadas oficialmente, a distribuição será como abaixo:

   Sistema
       + index.php (inclui sysincludes.php e InterfaceBase.php)
       + install.php (script para instalação da aplicação desenvolvida - removido após instalação)
       + create_db.php (script para criação e população do banco de dados - removido após instalação)
       + webservice.php (inclui sysincludes.php e cria serviço baseado na COSA)
       + visual
       |    + css
       |    |    + JsThemes (Temas da JsWebGets)
       |    + images (imagens da aplicação)
       + includes (todos os elementos necessários para a aplicação)
       |    + js
       |    |    + sysgeneralfunctions.js (funções genéricas em Javascript)
       |    |    + syslistviewEditFunctions.js (funções que servem para acelerar o uso do JsListView)
       |    |    + sysincludes.js.php
       |    |    + libs (bibliotecas externas)
       |    |         + JsInterface (objeto base das Interfaces)
       |    |         + JsMainWindow (objeto base da Interface)
       |    |         + JsWebGets (biblioteca de Interface JsWebGets)
       |    + php
       |        + syscosa.php (implantação da COSA de acordo com as necessidades da aplicação)
       |        + sysgeneralfunctions.php (funções genéricas em PHP)
       |        + sysincludes.php (inclui tudo que é necessário para funcionamento da aplicação)
       |        + libs (bibliotecas externas)
       |              + COSA
       |              + POP
       |              + RePHPort
       + system (biblioteca do sistema)
            + app (Controle e Interface da aplicação)
            |    + base (estrutura básica para desenvolvimento de sistemas - tela de login, etc)
            |    |    + control (Controle)
            |    |    |    + ControlInterface.php
            |    |    |    + ControlLogin.php
            |    |    |    + ControlReport.php
            |    |    + view (Interfaces)
            |    |         + InterfaceBase.php
            |    |         + InterfaceLogin.php
            |    + outros módulos (diversas pastas com o nome do módulo com a estrutura abaixo)
            |         + control (Controle)
            |         + view (Interfaces)
            + config (arquivos de configuração sysconf.php e sysdb.php)
            |        + sysconf.php (traz toda a configuração da aplicação)
            |        + sysdb.php (conexão com o banco de dados)
            + model (Objetos)
            + report (Relatórios)
                 + output (Saída dos relatórios)

Este modelo é muito útil para aplicações que compartilharão informações através de seus webservices com outras aplicações. Visto que as regras de negócio estão tratadas dentro do webservices a integração se mostra muito mais simples e com um reaproveitamente enorme do código já gerado anteriormente.

Roteiro para criação de novos Sistemas

Antes de iniciar qualquer sistema, certifique-se que as Etapas de um projeto já estão todas atendidas, a fim de evitar problemas futuros em relação ao planejamento e execução do projeto.

Para criar um novo sistema utilizando a estrutura supra-citada, é preciso

1 - Baixe aqui a estrutura básica da WirePhrame

2 - Edite o arquivo includes/php/base/sysconf.php, alterando os parâmetros que julgar necessário, mas em especial, defina o W_AMBIENT como 'D' (desenvolvimento)

3 - Execute o script para criação da base do sistema no banco de dados.

4 - Todo o trabalho agora, será realizado dentro da pasta includes/php/system/app. Você deverá criar pastas para os módulos de sua aplicação (ex: uma pasta para o módulo de cadastro de clientes, outra para o de fornecedores, etc). Você pode, claro, só jogar os arquivos de interface e controle todos misturados dentro da pasta app, e o sistema ainda assim funcionará, mas por uma questão de organização, aconselhamos que NÃO O FAÇA.

5 - Para ter o trabalho mais organizado ainda, para cada pasta de módulo crie subpastas chamadas control e view, e coloque lá dentro seus arquivos de cada uma das duas camadas. MANTENHA SEU TRABALHO ORGANIZADO.

6 - Só há uma única regra que deve ser seguida à risca agora: para classe de controle deve ser declarada e salvo em um arquivo .php com o mesmo nome da classe. Não crie pastas ou arquivos com espaços em branco no nome, pois isto é necessário para que o autoload das classes funcione.

6.a - Ok, eu menti, há ainda mais uma regrinha... Não coloque o mesmo nome no arquivo de Controle e no de Interface. Embora em pastas separadas, o autoload carregará o primeiro arquivo que encontrar, ou seja, o mesmo nome acarretará que um dos dois nunca seja encontrado. ;-)

7 - Comece a trabalhar como um doido, que o prazo já está apertando... Você deve ter gasto mais de 15 minutos até o momento (se muito). Crie seu modelo com a lib POP, crie o banco a patir do modelo, altere o ControlLogin.php padrão para atender suas necessidades (ou use-o tal qual está! funciona muito bem),

Para a criação de Interfaces, Controles e Objetos, veja respectivamente a documentação das bibliotecas JsWebGets, COSA e POP.

O framework WirePhrame utiliza-se destas bibliotecas para construção da interface, controle do fluxo da aplicação, comunicação entre interface e webservices, e comunicação entre aplicação e banco de dados. Abaixo, estão apresentados os itens de maneira resumida. Para compreensão completa do framework, veja a documentação isolada de cada biblioteca.

Você poderá substituí-las por qualquer outra que lhe convier, como Doctrine no lugar da POP, ou jQuery no lugar da JsWebGets, mas isto pode ter um custo alto, pois tal alteração não poderá ser mantida oficialmente por nós. Ou seja, terá que ser com todo seu conhecimento.

Aconselhamos que no caso de trocar a lib de interface, mantenha ao mesmo os seguintes objetos da JsWebGets, para que este documeto siga tendo validade no que tange ao uso da COSA, e que os mesmo sejam carregados na seguinte ordem:

  • JsTranslation
  • JsGeneral
  • JsXML
  • JsHTTPRequest
  • JsWebServiceConnector

Não aconselhamos a reedição de qualquer arquivo que esteja fora das pastas includes/js/system e includes/php/system, pois caso deseje atualizar o WirePhrame de sua aplicação, todos as alterações serão perdidas

Como se dá a comunicação e construção do sistema

O ponto de entrada em qualquer sistema é o arquivo index.php, que já possui os includes necessários para o correto funcionamento do PHP do lado do servidor. Se houver algum erro de configuração, o mesmo já será visível nesta páginas.

Este arquivo tem um require recursive para o InterfaceBase.php correspondente da aplicação. O arquivo InterfaceBase.php inclue as bibliotecas javascript necessárias e cria uma instância de JsMainWindow nomeada app, além de definir a variável interface_principal_sistema (dica: você pode mudar o ControlLogin para fazer o carregamento diferenciado de uma tela inicial de acordo com o perfil do usuário!), que é a interface que será carregada logo após o usuário executar o login com sucesso. Também serão definidos o tema da JsWebGets, Título da Aplicação, Título da Janela da Aplicação e logotipo a ser apresentado no canto direito superior da tela.

Ao final, será utilizado o objeto JsWebServiceConnector instanciado dentro de JsMainWindow com a referência app.ws para então realizar o bind, ou ligação, ao webservice.php da aplicação. Será feita uma varredura e todas as operações definidas no cosa.wsdl do webservice estarão disponíveis para uso pela interface, sendo logo em seguida disparado o callback initInterface definido em sysgeneralfunctions.js, que solicitará o diálogo de login. Sistemas que não vão ter este diálogo podem sobrescrever este callback redefinindo-o dentro de seu próprio InterfaceBase.php. Não aconselhamos a reedição de qualquer arquivo que esteja dentro da pasta /includes, pois caso deseje atualizar o WirePhrame de sua aplicação, todos as alterações serão perdidas! Tente limitar o trabalho à pasta /system da aplicação.. A solicitação do diálogo de login é feito agora através do método criado dinamicamente a partir do cosa.wsdl chamado loadInterface. Todas as operações do cosa.wsdl estão amarradas dentro do app.ws podendo ser chamadas a qualquer momento como mostrado abaixo:

   app.ws.getData(nomedocontrole, metodo, parametros, callback, fallback);

Note que todas as operações do webservice da aplicação serão chamadas tal qual definidas no WSDL, porém acrescidas de dois novos parâmetros, que é um callback que pode ou não ser definido para processar automaticamente a resposta do WebService, e um fallback no caso da aplicação retornar algum erro.

Depuração - Checando envio e recepção de mensagens entre cliente e servidor

A WirePhrame inclui a biblioteca e extensão para o Firefox FirePHP para facilitar a depuração de códigos. Seu uso deve ser feito através das chamadas:

  • WPDebug::log
  • WPDebug::info
  • WPDebug::error
  • WPDebug::warn
  • WPDebug::exception

O objetivo da FirePHP é facilitar a depuração quando utilizando Webservices. O único problema é que ela funciona apenas com o Firefox, por isso outra forma de depuração também foi inclusa.

Com o uso da classe JsWebServiceConnector e JsDataConnector, há uma nova forma de depurar o que é enviado e recebido do servidor. Uma variável global na JsWebGets chamada jsDebug é a responsável por isso, e definindo-a como true ou false a depuração é ativada.

Para uma maior conveniência essa variável pode ser ativada e desativada "on-the-fly" através do uso das teclas CTRL+ALT+D. Isso definirá a variável como true ou false e apresentará imediatamente a janela de depuração do WebService ou DataConnector que está sendo utilizado. Isto servirá tanto em ambiente de Desenvolvimento quanto Homologação ou Produção (uma mão na roda quando o PHP não emite erros...)

Caso as teclas não funcionem, e o ambiente estiver como Desenvolvimento, um botão chamado "Comm Window" aparecerá no canto superior direito da tela, com a mesma funcionalidade, mas apenas em Desenvolvimento.

Nota importante: O arquivo sysconf.php contém a declaração de uma variável chamada W_AMBIENT, que quando definida para "D" força o recarregamento automático das interfaces ao serem solicitadas, evitando a necessidade de recarregar todo o sistema. Adapte o arquivo para que a variável seja carregada com valores diferentes em ambientes diferentes.

Programação Client-Side

Abaixo, seguem exemplos de como trabalhar a comunicação de dados do lado do Cliente do WirePhrame.

Criando Interfaces

Para a construção de Interfaces, utiliza-se a ferramenta JsDesigner.

Edite o arquivo control.php passando para o método setClassesPath o valor do caminho do diretório das interfaces do sistema sendo editado. Altere também a extensão para .php com o método setExtension.

Isto provavelmente será modificado em versões futuras, permitindo a navegação no filesystem.

Carregando Interfaces

Com a estrutura básica criada, e as interfaces prototipadas com a ferramenta JsDesigner, fica fácil agora ir carregando interfaces sob demanda. Para tal, basta utilizar os métodos da JsMainWindow, que são:

1 - loadInterface - uma interface carregada com este método ocupará a área central da janela, e automaticamente utilizará como callback o método renderInterface. É possível definir outro callback, se necessário. A chamada para carregamento da interface deve ser feita da seguinte forma:

app.loadInterface("sistema/Interface");

Definindo outro callback, o uso seria:

app.loadInterface("sistema/Interface", outrocallback);

2 - loadDialog - Como o próprio nome já diz, serve para carregar diálogos ao invés de Interfaces. O diálogo é sempre carregado como modal. Da mesma forma que os métodos anteriores, permite 2 parâmetros, a Interface e um callback. Se nenhum callback for definido, será utilizado o callback automático chamado renderDialog.

Ex:

app.loadDialog("sistema/InterfaceDialogSearch");

A utilização destes métodos otimizará o uso das interfaces, evitando o tráfego excessivo entre cliente e servidor, uma vez que após instanciar a interface a mesma é guardada durante a execução para reutilização posterior quando reconvocada a mesma interface.

Carregando Sub-Interfaces

Há casos nos quais você pode desejar ter um Toolbar que chame subinterfaces de uma interface maior. Essa prática é boa para permitir uma maior granularização das interfaces, permitindo um maior compartilhamento das mesmas. Neste caso, o botão do toolbar chamará um método que disparará o app.loadSubInterface.

1 - loadSubInterface - similar ao método acima, loadInterface, porém não chamará o método renderInterface como callback automático. Este método deve ser utilizado para carregar subInterfaces da Interface que estiver carregada atualmente, e o callback provavelmente será um método da Interface atual, e não de JsMainWindow. O callback é obrigatório e deve ser implementado pelo programador na Interface que carregará a subinterface. Neste caso, a implementação do callback pode variar bastante de uma interface para outra, de acordo com a área que for destinada a apresentar uma interface contextual. Um exemplo disso seria a interface de Módulos Didáticos ou de Turmas do sistema WebCEF, onde apenas uma pequena região é atualizada com uma nova interface, mantendo constante a apresentação do treeview.

Ex:

app.loadSubInterface("sistema/InterfaceDetalhes", interfaceatual.addInterface);

A implementação do callback addInterface pode ser feita conforme ilustrado abaixo. Note o controle de js_ambient para forçar o recarregamento da interface em ambiente de desenvolvimento.

       self.UIcallbacks.addInterface = function (jsEvent)
       {
           if (!Interfaces[newSubInterfaceName] || js_ambient == "devel")
           {
               code_str = app.getResponse();
               eval("newInterface = " + code_str);
               Interfaces[newSubInterfaceName] = new newInterface;
           }
   
           if (self.childNodes[1])
           {
               if (self.childNodes[1].bkpXMLData)
                   self.childNodes[1].bkpXMLData();
               self.removeChild(self.childNodes[1]);
           }    
   
           self.addItem(Interfaces[newSubInterfaceName]);
   
           if (Interfaces[newSubInterfaceName].type == "JsWindow")
               Interfaces[newSubInterfaceName].showWindow();
   
           if (Interfaces[newSubInterfaceName].type == "JsDialog")
               Interfaces[newSubInterfaceName].showDialog();
       }

Note que há uma linha chamando o método getXMLData descrito em "Obtendo o XML da Interface". Essa linha é importante no caso de subinterfaces, pois garante a integridade e manutenabilidade dos dados editados na interface, enquanto alternando a mesma. Porém, isto exige que o programador coloque uma chamada para o initInterface de forma a verificar de onde os dados devem ser lidos.

O método bkpXMLData copia as alterações feitas dentro da interface, para a referência XML sendo editada. Ao remover uma subinterface, esses dados devem ser mantidos de forma automático, por isso a linha no callback.

Na SubInterface, o tratamento para correta seleção da fonte de dados pode ser feito como apresentado abaixo:

           //verifica se deve usar o XML da subinterface ou da interface
           //quando um objeto é carregado pela interface pai, é colocado no app_xml
           //neste caso, usamos o CPF como chave para verificar se deve ou não
           //usar o da aplicação.
           if (self.xml_data)
           {
               var selfcpf = self.xml_data.getElementsByTagName("cpf")[0].text;
               var appcpf = app.xml_data.getElementsByTagName("cpf")[0].text;
   
               if(selfcpf != appcpf)
                   self.xml_data = app.xml_data;
           }
   
           if (!self.xml_data)
               self.xml_data = app.xml_data;
   
           self.UIcallbacks.loadXMLData(self.xml_data);

Neste caso específico, o campo de valor único era o CPF da pessoa, porém, outros itens podem ser utilizados, desde que sejam únicos.

Inicializando uma Interface

Após carregar uma interface, muitas vezes é necessário inicializá-la. O método que deve ser criado para isso chama-se initInterface. Este é um método que será chamado automaticamente após a renderização da interface na tela.

Crie este método conforme sua necessidade. De maneira geral, um bom initInterface deve fazer o seguinte:

1 - Resetar a interface - se você estava editando algo antes e carrega um novo objeto para edição, é uma boa remover as informações dele antes de iniciar a edição.

2 - Definição dos relacionamentos dos listviews para edição

3 - Carregar dados de Apoio - carregamento dos comboboxes, listas, etc, que são necessários para a edição dos dados.

4 - Carregar dados na Interface para edição.

Veja abaixo como realizar cada um dos passos acima citados.

Carregando Dados do Servidor

Para carregar dados do servidor, deve ser utilizado o método getData do Webservice da aplicação.

Você deve ainda saber:

1 - Qual Controle deve ser invocado.

2 - Qual o método a ser executado.

3 - Quais os parâmetros aceitos pelo método chamado.

Estas informações podem ser obtidas na documentação do respectivo sistema, encontrada na página de Sistemas do Wiki.

Dessa forma, deve ser informada a string com o nome do Controle, e construída uma string XML contendo os 2 itens subsequentes, como ilustrado abaixo. No exemplo, acrescentamos um callback, método ou função que será executado após receber os dados do servidor com sucesso. O callback para o getData é obrigatório, pois os dados não serão tratados automaticamente pela aplicação, cabendo ao programador a implantação do tratamento e apresentação dos dados ao usuário. Há ainda a possibilidade de implantação de um fallback, que é um método ou função chamados quando há falha na resposta do webservice, ou seja, foi retornado um SoapFault. O objeto JsWebServiceConnector já tem um tratamento automático para estes casos, porém, em alguns momentos, o programador pode querer tratar a exceção lançada de forma distinta na interface. Nestes casos, basta implementar um fallback.

   var dados  = "<parametro1>valor1</parametro1>";
   dados     += "<parametro2>valor2</parametro2>";
   dados     += "<parametro3>valor3</parametro3>";
   
   app.ws.getData("ControleExemplo", "metodoExemplo", dados, callback, fallback);

O callback, pode ser construído de formas distintas, dependendo do tipo de resultado que for recebido pelo servidor.

Exemplos de callbacks:

Resposta XML (é retornado um objeto XML):

   function callback()
   {
       var xml = app.getResponseXML();
       alert(xml.xml);
   }

Resposta JSON (a resposta é interpretada como código Javascript - não há retorno):

   function callback()
   {
       app.getResponseJSON();
   }

Resposta Texto (a resposta é texto puro):

   function callback()
   {
       txt = app.getResponse();
       alert(txt);
   }

ATENÇÃO: Os retornos devem ser preferencialmente sempre no formato XML, segundo determinação do e-Ping, que especifica o formato XML como o padrão para interoperabilidade de sistemas dentro do Governo Federal.

JSON e Texto devem ser utilizados apenas para Controles que tenham relação direta com o funcionamento operacional da aplicação, como por exemplo o ControleInterface. Métodos que recebem ou enviam dados e tenham regras de negócio, tais como ControleInscricao, devem ser sempre em XML. Evitar JSON usar pois representa mistura do código da Interface com o código de Controle, o que poderá causar maior dificuldade na manutenção do código.

Sobre como tratar XML com Javascript na JsWebGets, veja a seção JsXML

Sobre uma maneira mais otimizada para carga de dados do Servidor na Interface, veja Carregamento automático de dados na Interface a partir de XML

Carregando Dados de Apoio na Interface

O conceito utilizado para Dados de Apoio refere-se aos dados que estejam em tabelas paralelas ao objeto principal do contexto, que sirvam para enumerar valores específicos. Exemplos de Dados de Apoio seriam as tabelas de Status de AlunoTurma, Tipo de Turma, Tipo de Colaborador, Tipo de Endereço, Tipo de Email, Tipo de Telefone, etc.

Para Maior comodidade do programador, foi implantado um Controle especial chamado ControleApoio. O ControleApoio possui apenas um método chamado carregarApoio, que aceitará n parâmetros que devem ser enviados como um nó xml chamado parametros e cada item como um subnó chamado item. O valor de cada item deve ser o nome da classe de apoio que deve ser chamada para carregamento da lista.

A solicitação deve ser sempre feita com o uso de um callback que tratará os dados de apoio recebidos colocando-os nas respectivas combos ou listas.

Por padronização, o método de callback para o ControleApoio deve ser sempre chamado carregarDadosApoio

Ex:

   var dados = "<parametros>";
   dados     += "<item>TipoTelefone</item>";
   dados     += "<item>TipoEmail</item>";
   dados     += "<item>TipoEndereco</item>";
   dados     += "</parametros>";
   
   app.ws.getData("ControleApoio", "carregarApoio", dados, callback);

O Retorno sempre será um XML no seguinte formato:

   <response>
     <TipoTelefone>
       <result>
         <item>
           n campos... (cada classe tem um tipo de retorno distinto)
         </item>
         <item>
           n campos... (cada classe tem um tipo de retorno distinto)
         </item>
       </result>
     </TipoTelefone>
     <TipoEmail>
       <result>
         <item>
           n campos... (cada classe tem um tipo de retorno distinto)
         </item>
         <item>
           n campos... (cada classe tem um tipo de retorno distinto)
         </item>
       </result>
     </TipoEmail>
     <TipoEndereco>
       <result>
         <item>
           n campos... (cada classe tem um tipo de retorno distinto)
         </item>
         <item>
           n campos... (cada classe tem um tipo de retorno distinto)
         </item>
       </result>
     </TipoEndereco>
   </response>

Sobre como tratar XML com Javascript na JsWebGets, veja a seção JsXML para maiores detalhes.

Um exemplo do callback carregarDadosApoio seria como abaixo. Note que foi inclusa uma verificação para os ver se os dados de apoio j[a não foram carregados. Isto serve para otimizar o carregamento da interface, reduzindo ainda mais a comunicação cliente servidor. Note também as linhas referentes ao comentário "//será utilizado para edição dos itens do listview". Elas são a associação dos dados daquele ponto do XML com aquela coluna específica do JsListView ou do JsDataView. Mais detalhes sobre o porque destas linhas podem ser encontrados na seção Carregamento automático de dados na Interface a partir de XML

       self.UIcallbacks.carregarDadosApoio = function (jsEvent)
       {
           if (!self.xml_support)
           {
               self.xml_support = app.getResponseXML();
   
               //carrega lista estadocicivil
               self.UIcomponents.estado_civil.clearData();
               self.UIcomponents.estado_civil.addItem(, '[--Selecione abaixo--]');
   
               var estciv = self.xml_support.getElementsByTagName("PEstadoCivil");
               estciv = estciv[0].getElementsByTagName("item");
   
               for (i=0; i< estciv.length; i++)
                   self.UIcomponents.estado_civil.addItem(estciv[i].text);
   
               //carrega lista sexo
               self.UIcomponents.sexo.clearData();
               self.UIcomponents.sexo.addItem(, '[--Selecione abaixo--]');
   
               var sexo = self.xml_support.getElementsByTagName("PSexo");
               sexo = sexo[0].getElementsByTagName("item");
   
               for (i=0; i< sexo.length; i++)
                   self.UIcomponents.sexo.addItem(sexo[i].text);
   
               //carrega lista ufs
               self.UIcomponents.uf_origem.clearData();
               self.UIcomponents.endereco_uf.clearData();
   
               self.UIcomponents.uf_origem.addItem(, '[--Selecione abaixo--]');
               self.UIcomponents.endereco_uf.addItem(, '[--Selecione abaixo--]');
   
               var ufs = self.xml_support.getElementsByTagName("PUF");
               ufs = ufs[0].getElementsByTagName("item");
   
               //será utilizado para edição dos itens do listview
               self.UIcomponents.enderecos.lvheaderdiv.childNodes[4].xml_data = ufs;
   
               for (i=0; i< ufs.length; i++)
               {
                   var id = ufs[i].getElementsByTagName("id")[0].text;
                   var nome = ufs[i].getElementsByTagName("nome")[0].text;
   
                   self.UIcomponents.uf_origem.addItem(id, nome);
                   self.UIcomponents.endereco_uf.addItem(id, nome);
               }
   
               //carrega lista países
               self.UIcomponents.pais_origem.clearData();
               self.UIcomponents.endereco_pais.clearData();
   
               self.UIcomponents.pais_origem.addItem(, '[--Selecione abaixo--]');
               self.UIcomponents.endereco_pais.addItem(, '[--Selecione abaixo--]');
   
               var pais = self.xml_support.getElementsByTagName("PPais");
               pais = pais[0].getElementsByTagName("item");
   
               //será utilizado para edição dos itens do listview
               self.UIcomponents.enderecos.lvheaderdiv.childNodes[5].xml_data = pais;
   
               for (i=0; i< pais.length; i++)
               {
                   var id = pais[i].getElementsByTagName("id")[0].text;
                   var nome = pais[i].getElementsByTagName("nome")[0].text;
   
                   self.UIcomponents.pais_origem.addItem(id, nome);
                   self.UIcomponents.endereco_pais.addItem(id, nome);
               }
   
               //carrega lista tipo
               self.UIcomponents.endereco_tipo.clearData();
               self.UIcomponents.telefone_tipo.clearData();
               self.UIcomponents.email_tipo.clearData();
   
               self.UIcomponents.endereco_tipo.addItem(, '[--Selecione abaixo--]');
               self.UIcomponents.telefone_tipo.addItem(, '[--Selecione abaixo--]');
               self.UIcomponents.email_tipo.addItem(, '[--Selecione abaixo--]');
   
               var tipo = self.xml_support.getElementsByTagName("PTipo");
               tipo = tipo[0].getElementsByTagName("item");
   
               //será utilizado para edição dos itens do listview
               self.UIcomponents.enderecos.lvheaderdiv.childNodes[0].xml_data = tipo;
               self.UIcomponents.telefones.lvheaderdiv.childNodes[0].xml_data = tipo;
               self.UIcomponents.emails.lvheaderdiv.childNodes[0].xml_data = tipo;
   
               for (i=0; i< tipo.length; i++)
               {
                   var id = tipo[i].getElementsByTagName("id")[0].text;
                   var nome = tipo[i].getElementsByTagName("nome")[0].text;
   
                   if (tipo[i].getElementsByTagName("categoria")[0].text =="endereco")
                       self.UIcomponents.endereco_tipo.addItem(id, nome);
                   if (tipo[i].getElementsByTagName("categoria")[0].text =="telefone")
                       self.UIcomponents.telefone_tipo.addItem(id, nome);
                   if (tipo[i].getElementsByTagName("categoria")[0].text =="email")
                       self.UIcomponents.email_tipo.addItem(id, nome);
               }
           }
   
           self.UIcallbacks.loadXMLData(app.xml_data);
       }

Identificando itens com o Controlde de Apoio

Há situações na qual não podemos carregar todos os itens de apoio por ser uma lista muito extensa. Nestes casos, podemos carregar apenas uma listagem de alguns itens que queremos identificar (vide Tela de Áreas de Atuação e Interesse do sistema WebSCO).

Neste caso, utilizamos o método identificarItens do ControleApoio.

   var dados = "<parametros>";
   dados     += "<item>";
   dados     += "<tipo>PArea</tipo>";
    
   dados     += "<valor>1000</valor>";
   dados     += "<valor>1001</valor>";
   dados     += "<valor>1002</valor>";
   
   dados     += "</item>";
   dados     += "</parametros>";
   
   app.ws.getData("ControleApoio", "identificarItens", dados, callback);


Isto nos retornará um array similar ao retornado anteriormente pelo método carregarApoio. O tratamento do retorno pode ser feito da mesma forma.

Carregamento automático de dados na Interface a partir de XML

Após a obtenção dos dados do servidor, é preciso apresentá-los ao cliente na Interface. Para facilitar esta tarefa, foi implementado um método chamado loadXMLData que permite que a Interface leia diretamente os dados do XML enviado pelo WebService para carregamento e apresentação dos mesmo na interface.

Para que isto seja possível, algumas regras devem ser observadas.

1 - Os nomes dos atributos e dos referidos campos devem ser idênticos para que a interface possa fazer o carregamento.

2 - Dados múltiplos (PArrayOf) devem ter o mesmo para o atributo e para o listview ou dataview, e o nome deve ser sempre no plural (ex: enderecos, telefones, emails, etc).

2.a - Dados não editáveis devem ser carregados em JsDataViews

2.b - Dados editáveis devem ser carregados em JsListViews. Neste caso, é preciso definir o método onEdit para o JsListView no initInterface. Este método será chamado quando o ListViewItem for clicado, para permitir a edição do item.

3 - No caso de dados múltiplos, a ordem das colunas deve ser idêntica à dos subobjetos sendo carregados.

4 - Não é possível carregar estruturas hierárquicas (treeviews) com este método.

5 - Se algum dos campos for o id de algum item carregado a partir do ControleApoio, você poderá fazer com que a nomenclatura correta seja apresentada, no lugar do id, definindo o xml_data para aquele item na coluna. Para maiores detalhes sobre como fazer isto, veja a definição do callback na seção Carregando Dados de Apoio na Interface

Para ter o método implantado na Interface correspondente, basta incluir o seguinte código na mesma. Este código pode ser adaptado conforme necessidades específicas do objeto ou da interface.

       self.UIcallbacks.loadXMLData = function ()
       {
           for (var i in self.UIcomponents)
           {
               var contents = app.xml_data.getElementsByTagName(i);
   
               if (self.UIcomponents[i].input)
               {
                   if (
                       contents.length &&
                       self.UIcomponents[i].type != "JsCheckBox"
                       )
                       self.UIcomponents[i].setValue(app.xml_data.getElementsByTagName(i)[0].text);
                   else if (
                       self.UIcomponents[i].type != "JsBoxButton" &&
                       self.UIcomponents[i].type != "JsPushButton" &&
                       self.UIcomponents[i].type != "JsIcon" &&
                       self.UIcomponents[i].type != "JsImageButton" &&
                       self.UIcomponents[i].type != "JsMiniToolBarButton" &&
                       self.UIcomponents[i].type != "JsToolBarButton" &&
                       self.UIcomponents[i].type != "JsColoPicker" &&
                       self.UIcomponents[i].type != "JsTableBuilder" &&
                       self.UIcomponents[i].type != "JsURLBuilder" &&
                       self.UIcomponents[i].type != "JsCheckBox"
                       )
                       self.UIcomponents[i].setValue();
               }
               else
               {
                   if (self.UIcomponents[i].type == "JsListView")
                   {
                       self.UIcomponents[i].clearData();
   
                       if (contents.length)
                       {
                           for (var j=0; j < contents.length; j++) //>
                           {
                               for (var k=0; k < contents[j].childNodes.length; k++) //>
                               {
                                   if (contents[j].childNodes[k].nodeName == self.UIcomponents[i].xmlNodeName)
                                   {
                                       var lstitem = new JsListViewItem();
                                       self.UIcomponents[i].addItem(lstitem);
   
                                       if (self.UIcomponents[i].onEdit)
                                           lstitem.setEvent("click",self.UIcomponents[i].onEdit);
   
                                       lstitem.pid = contents[j].childNodes[k].getAttribute("id");
   
                                       for (var x=0; x < contents[j].childNodes[k].childNodes.length; x++)//>
                                           if (x < self.UIcomponents[i].lvheaderdiv.childNodes.length)//>
                                           {
                                               if(self.UIcomponents[i].lvheaderdiv.childNodes[x].xml_data)
                                               {
                                                   var support_xml = self.UIcomponents[i].lvheaderdiv.childNodes[x].xml_data;
                                                   var rid = contents[j].childNodes[k].childNodes[x].text;
                                                   var nome = "";
                                                   for (var y=0; y< support_xml.length; y++)//>
                                                   {
                                                       var id = support_xml[y].getElementsByTagName("id")[0].text;
                                                       if (rid == id)
                                                       {
                                                           nome = support_xml[y].getElementsByTagName("nome")[0].text;
                                                           break;
                                                       }
                                                   }
                                                   lstitem.addItem(nome);
                                                   lstitem.cells[x].js_realvalue = rid
                                               }
                                               else
                                               {
                                                   //relations para JsDateEdit, JsCheckBox e JsRadioButton
                                                   if (self.UIcomponents[i].relations[x][0].type == "JsDateEdit")
                                                   {
                                                       var data = "";
                                                       if (contents[j].childNodes[k].childNodes[x].text)
                                                           var data = formataData(contents[j].childNodes[k].childNodes[x].text);
                                                       lstitem.addItem(data);
                                                       lstitem.cells[x].js_realvalue = contents[j].childNodes[k].childNodes[x].text
                                                   }
                                                   else if (self.UIcomponents[i].relations[x][0].type == "JsCheckBox")
                                                   {
                                                       if (contents[j].childNodes[k].childNodes[x].text == "1")
                                                           var valor = "Sim";
                                                       else
                                                           var valor = "Não";
   
                                                       lstitem.addItem(valor);
                                                       lstitem.cells[x].js_realvalue = contents[j].childNodes[k].childNodes[x].text
                                                   }
                                                   else if (self.UIcomponents[i].relations[x][0].type == "JsRadioButton")
                                                   {
                                                       valor = self.UIcomponents[i].relations[x][0].getLabelForValue(contents[j].childNodes[k].childNodes[x].text);
                                                       lstitem.addItem(valor);
                                                       lstitem.cells[x].js_realvalue = contents[j].childNodes[k].childNodes[x].text
                                                   }
                                                   else
                                                       lstitem.addItem(contents[j].childNodes[k].childNodes[x].text);
                                               }
                                           }
                                   }
                               }
                           }
                       }
                   }
                   else if (self.UIcomponents[i].type == "JsDataView")
                   {
                       self.UIcomponents[i].clearData();
                       if (contents.length)
                       {
                           var newData = new Array();
                           for (var j=0; j < contents.length; j++) //>
                           {
                               for (var k=0; k < contents[j].childNodes.length; k++) //>
                               {
                                   newData[k] = new Array();
   
                                   for (var x=0; x < contents[j].childNodes[k].childNodes.length; x++)//>
                                       if (x < self.UIcomponents[i].dtheaderdiv.childNodes.length)//>
                                       {
                                           if(self.UIcomponents[i].dtheaderdiv.childNodes[x].xml_data)
                                           {
                                               var support_xml = self.UIcomponents[i].dtheaderdiv.childNodes[x].xml_data;
                                               var rid = contents[j].childNodes[k].childNodes[x].text;
                                               var nome = "";
                                               for (var y=0; y< support_xml.length; y++)//>
                                               {
                                                   var id = support_xml[y].getElementsByTagName("id")[0].text;
                                                   if (rid == id)
                                                   {
                                                       nome = support_xml[y].getElementsByTagName("nome")[0].text;
                                                       break;
                                                   }
                                               }
                                               newData[k][x] = nome;
                                           }
                                           else
                                               newData[k][x] = contents[j].childNodes[k].childNodes[x].text;
                                       }
                               }
                           }
                           self.UIcomponents[i].loadData(newData);
                       }
                   }
               }
           }
       }

Enviando Dados ao Servidor

O envio de dados para o Servidor é idêntico à solicitação de dados, porém, por uma questão semântica, deve ser utilizado o método postData do Webservice da aplicação. Todo o aplicado ao getData também pode ser aplicado, sem distinção ao postData. Apenas solicita-se do programador que utilize o getData para obter dados que serão apresentados ao usuário na interface, e o postData para salvar os dados no servidor.

Você deve ainda saber:

1 - Qual Controle deve ser invocado.

2 - Qual o método a ser executado.

3 - Quais os parâmetros aceitos pelo método chamado.

Estas informações podem ser obtidas na documentação do respectivo sistema, encontrada na página de Sistemas do Wiki.

Dessa forma, deve ser informada a string com o nome do Controle, e construída uma string XML contendo os 2 itens subsequentes, como ilustrado abaixo. No exemplo, acrescentamos um callback, método ou função que será executado após receber os dados do servidor com sucesso. O callback para o postData é opcional, porém é recomendado que sempre seja criado um callback para dar ao usuário algum feedback sobre a execução do comando. Há ainda a possibilidade de implantação de um fallback, que é um método ou função chamados quando há falha na resposta do webservice, ou seja, foi retornado um SoapFault. O objeto JsWebServiceConnector já tem um tratamento automático para estes casos, porém, em alguns momentos, o programador pode querer tratar a exceção lançada de forma distinta na interface. Nestes casos, basta implementar um fallback.

   var dados = "<parametro1>valor1</parametro1>";
   dados     += "<parametro2>valor2</parametro2>";
   dados     += "<parametro3>valor3</parametro3>";
   
   app.ws.postData("ControleExemplo", "metodoExemplo", dados, callback);

O callback, pode ser construído de formas distintas, dependendo do tipo de resultado que for recebido pelo servidor.

Exemplos de callbacks:

Resposta XML (é retornado um objeto XML):

   function callback()
   {
       var xml = app.getResponseXML();
       alert(xml.xml);
   }

Resposta JSON (a resposta é interpretada como código Javascript - não há retorno):

   function callback()
   {
       app.getResponseJSON();
   }

Resposta Texto (a resposta é texto puro):

   function callback()
   {
       txt = app.getResponse();
       alert(txt);
   }

ATENÇÃO: Os retornos devem ser preferencialmente sempre no formato XML, segundo determinação do e-Ping, que especifica o formato XML como o padrão para interoperabilidade de sistemas.

JSON e Texto devem ser utilizados apenas para Controles que tenham relação direta com o funcionamento operacional da aplicação, como por exemplo o ControleInterface. Métodos que recebem ou enviam dados e tenham regras de negócio, tais como ControleInscricao, devem ser sempre em XML. Evitar JSON usar pois representa mistura do código da Interface com o código de Controle, o que poderá causar maior dificuldade na manutenção do código.

Sobre como tratar XML com Javascript na JsWebGets, veja a seção JsXML

Obtendo o XML da Interface

Da mesma forma que é possível fazer o carregamento direto de um XML na interface, é possível obter o XML da mesma.

O XML retornado será um pouco diferente do recebido anteriormente, pois devido à edição de sub-objetos (PArrayOf) é necessário informar ao controle os ids dos itens que foram removidos da interface. Estes ids serão informados dentro do nó referente ao Listview, na tag <removed>. Cada item removido estará definido com a tag <item>. Os campos utilizados para editar o Listview provavelmente também estarão no XML, com valor em branco. Durante o processamento no controle, basta ignorar estes campos.

O método chama-se bkpXMLData. Para ter o método implantado na Interface correspondente, basta incluir o seguinte código na mesma:

       self.UIcallbacks.bkpXMLData = function ()
       {
           //verifica se deve usar o XML da subinterface ou da interface
           //quando um objeto é carregado pela interface pai, é colocado no app_xml
           //neste caso, usamos o CPF como chave para verificar se deve ou não
           //usar o da aplicação.
           if (self.xml_data)
           {
               var selfcpf = self.xml_data.getElementsByTagName("cpf")[0].text;
               var appcpf = app.xml_data.getElementsByTagName("cpf")[0].text;
   
               if(selfcpf != appcpf)
                   return;
           }
   
           for (var i in self.UIcomponents)
           {
               if (self.UIcomponents[i].input)
               {
                   if (
                       self.UIcomponents[i].type != "JsBoxButton" &&
                       self.UIcomponents[i].type != "JsPushButton" &&
                       self.UIcomponents[i].type != "JsIcon" &&
                       self.UIcomponents[i].type != "JsImageButton" &&
                       self.UIcomponents[i].type != "JsMiniToolBarButton" &&
                       self.UIcomponents[i].type != "JsToolBarButton" &&
                       self.UIcomponents[i].type != "JsColoPicker" &&
                       self.UIcomponents[i].type != "JsTableBuilder" &&
                       self.UIcomponents[i].type != "JsURLBuilder"
                       )
                   {
                       var xml = app.xml_data.getElementsByTagName(i);
                       if (xml[0])
                           xml[0].text = self.UIcomponents[i].getValue();
                   }
               }
               else
               {
                   if (self.UIcomponents[i].type == "JsListView")
                   {
                       var values = self.UIcomponents[i].childList;
   
                       var newTreeNode = app.xml_data.getElementsByTagName(i)[0];
   
                       var temp_removed = null;
                       var counter = newTreeNode.childNodes.length;
                       for (var j=0; j < counter; j++) //>
                       {
                           var node = newTreeNode.removeChild(newTreeNode.childNodes[0]);
                           if (node.nodeName == "removed")
                               temp_removed = node;
                       }
   
                       for (var j=0; j<values.length;j++) //>
                       {
                           var newNode = app.xml_data.createElement(self.UIcomponents[i].xmlNodeName);
   
                           for (var x=0; x<values[j].cells.length;x++) //>
                           {
                               var newSubNode = app.xml_data.createElement(self.UIcomponents[i].relations[x][2]);
   
                               if (values[j].cells[x].js_realvalue != undefined)
                                   newSubNode.text = values[j].cells[x].js_realvalue;
                               else
                                   newSubNode.text = values[j].cells[x].getValue();
   
                               newNode.appendChild(newSubNode);
                           }
   
                           if (values[j].pid)
                           {
                               newNode.setAttribute("id", values[j].pid);
                               var newSubNode = app.xml_data.createElement("id");
                               newSubNode.text = values[j].pid;
                               newNode.appendChild(newSubNode);
                           }
   
                           newTreeNode.appendChild(newNode);
                       }
   
                       //itens removidos do listview
                       if ((self.UIcomponents[i].removedItens && self.UIcomponents[i].removedItens.length) || temp_removed)
                       {
                           var newNode = app.xml_data.createElement("removed");
   
                           for (var j=0; j<self.UIcomponents[i].removedItens.length;j++) //>
                           {
                               var newSubNode = app.xml_data.createElement("item");
                               newSubNode.text = self.UIcomponents[i].removedItens[j];
                               newNode.appendChild(newSubNode);
                           }
   
                           self.UIcomponents[i].removedItens = new Array();
   
                           if(temp_removed)
                           {
                               counter = temp_removed.childNodes.length
                               for (var j=0; j< counter;j++) //>
                               {
                                   newNode.appendChild(temp_removed.childNodes[0]);
                               }
                           }
   
                           newTreeNode.appendChild(newNode);
                       }
                   }
               }
           }
       }

Note que para o correto funcionamento deste código, é necessário que vc defina o valor do atributo xmlNodeName no listview, com o nome do tipo de objeto que é apresentado nas linhas. Isto pode ser declarado no método initInterface da seguinte forma.

       self.UIcomponents.emails.xmlNodeName = "PEmail";

Note também que há um backup do XML obtido dentro da interface carregada. Isso é feito para os casos de explicitados em "Carregando Sub-Interfaces"

Limpando uma interface

Para limpar uma interface para reutilização, basta criar o método resetInterface e chamá-lo, preferencialmente no método initInterface. A implementação deve ser igual a abaixo:

       self.UIcallbacks.resetInterface = function ()
       {
           for (var i in self.UIcomponents)
           {
               if (self.UIcomponents[i].input)
               {
                   if (
                       self.UIcomponents[i].type != "JsBoxButton" &&
                       self.UIcomponents[i].type != "JsPushButton" &&
                       self.UIcomponents[i].type != "JsIcon" &&
                       self.UIcomponents[i].type != "JsImageButton" &&
                       self.UIcomponents[i].type != "JsMiniToolBarButton" &&
                       self.UIcomponents[i].type != "JsToolBarButton" &&
                       self.UIcomponents[i].type != "JsColoPicker" &&
                       self.UIcomponents[i].type != "JsTableBuilder" &&
                       self.UIcomponents[i].type != "JsURLBuilder" &&
                       self.UIcomponents[i].type != "JsCheckBox"
                   )
                   {
                       if(self.UIcomponents[i].type == "JsLineEditAdv")
                           self.UIcomponents[i].setValue("", "");
                       else
                           self.UIcomponents[i].setValue("");
                   }
                   else if (self.UIcomponents[i].type == "JsCheckBox")
                   {
                       self.UIcomponents[i].setChecked(false);
                   }
               }
               else
               {
                   if (self.UIcomponents[i].type == "JsListView" || self.UIcomponents[i].type == "JsDataView")
                   {
                       self.UIcomponents[i].clearData();
                       if (self.UIcomponents[i].type == "JsListView")
                           self.UIcomponents[i].removedItens = new Array();
                   }
               }
           }
       }

Editando itens de um listview

Para edição de itens em um listview através de outros campos relacionados, são necessários os seguintes passos:

1 - Criação do Listview

2 - Criação de um botão para Adicionar/Salvar e outro botão para Remover itens do Listview

3 - Criação de um campo para cada coluna do Listview

Após isto feito, é preciso associar os seguintes elementos ao listview:

1 - a função externa listviewLoadData ao atributo onEdit (isto será utilizado no carregamento automático a partir de XML apresentado anteriormente).

2 - os relacionamentos, através da criação de um array bidimensional, com 3 itens na segunda dimensão.

2.a - A ordem dos itens associados deve refletir a ordem das colunas no listview.

2.b - O primeiro item de cada registro do array deve ser a referência para o objeto que será utilizado para editar o valor da coluna, e o segundo é um booleano que marca a obrigatoriedade ou não do preenchimento do campo. O terceiro deve ser o nome do atributo do objeto, para que o método getXMLData possa ser processador de forma correta posteriormente.

3 - Associar a função externa listviewSaveData ao botão de Adicionar/Salvar. Pode-se adicionar outra função ou método da interface no lugar desta, porém, para a execução automática conforme aqui descrito, deve-se pelo menos chamar a função listviewSaveData ao final da execução da outra função.

4 - Definir o atributo listview do botão Adicionar/Salvar como uma referência para o listview sobre o qual o mesmo editará os elementos.

5 - Associar a função externa listviewDelData ao botão de Remover. Pode-se adicionar outra função ou método da interface no lugar desta, porém, para a execução automática conforme aqui descrito, deve-se pelo menos chamar a função listviewDelData ao final da execução da outra função.

6 - Definir o atributo listview do botão Remover como uma referência para o listview sobre o qual o mesmo editará os elementos.

7 - Como elemento adicional, é possível incluir um código de validação do que está sendo colocado no Listview antes, definindo o atributo validacao do mesmo com a função que deverá ser executada para validação dos campos.

Segue abaixo exemplo do código necessário, conforme implantado na InterfaceDetalhesPessoaFisica para o listview de emails. Este código pode ser colocado no initInterface ou ao final do método buildInterface da Interface.

       self.UIcomponents.emails.onEdit = listviewLoadData;
       self.UIcomponents.emails.xmlNodeName = "PEmail";
       self.UIcomponents.email_addbt.setEvent("click",listviewSaveData);
       self.UIcomponents.email_addbt.listview = self.UIcomponents.emails;
       self.UIcomponents.email_delbt.setEvent("click",listviewDelData);
       self.UIcomponents.email_delbt.listview = self.UIcomponents.emails;
       
       self.UIcomponents.emails.relations = new Array();
       self.UIcomponents.emails.relations[0] = new Array();
       self.UIcomponents.emails.relations[0][0] = self.UIcomponents.email_tipo;
       self.UIcomponents.emails.relations[0][1] = true;
       self.UIcomponents.emails.relations[0][2] = "tipo";
       self.UIcomponents.emails.relations[1] = new Array();
       self.UIcomponents.emails.relations[1][0] = self.UIcomponents.email_email;
       self.UIcomponents.emails.relations[1][1] = true;
       self.UIcomponents.emails.relations[1][2] = "email";

Campos editáveis em listview com callback para onchange

Trabalhando com Treeviews

JsLineEditAdv - Campo Autocomplete com WebServices

O objeto JsLineEditAdv é um componente que permite a pesquisa de registros em banco de dados enquanto a pessoa digita no mesmo, apresentando uma lista abaixo de opções pré-existentes que podem ser selecionadas com as teclas de seta do teclado.

Este objeto possui em sua estrutura interna tanto um JsDataConnector quanto um JsWebServiceConnector.

Neste exemplo, vamos ilustrar como utilizar este campo com um WebService.

Para que seja possível utilizar o JsLineEditAdv com Webservices, são necessários 3 passos simples para conexão do mesmo. Primeiro, a realização do bind com o webservice, depois a definição do Controle que será chamado, e por fim a definição do método que será executado pelo controle quando chamado para pesquisa. Isto pode ser feito com as instruções abaixo:

   self.UIcomponents.rg.bind("webservice.php");
   self.UIcomponents.rg.setWSControl("ControleAcessoPedestres");
   self.UIcomponents.rg.setWSMethod("pesquisarPedestres");

Um callback deve então ser definido para tratar o retorno dos dados do Controle, e carregamento do mesmo no objeto JsLineEditAdv para exibição da lista de opções.

A definição do callback se dá da seguinte forma:

   self.UIcomponents.rg.setCallback("self.UIcallbacks.listarRGs");

Caso a interface tenha sido desenvolvida no JsDesigner, a definição do callback deve ser colocada no array UIcallbacksHandlers da seguinte forma:

   self.UIcallbacksHandlers[0] = new Array();
   self.UIcallbacksHandlers[0][0] = "callback";
   self.UIcallbacksHandlers[0][1] = self.UIcomponents.rg;
   self.UIcallbacksHandlers[0][2] = self.UIcallbacks.listarRGs;

A definição deste callbacks deve ser feita consultando diretamente o ws dentro do JsLineEditAdv. Cada Controle pode retornar um formato distinto de XML, não havendo uma regra exata para seu formato. O callback deverá então tratar isso e fazer a melhor apresentação possível. No caso citado, os nós chamados "item" apresentam 3 subnós que são concatenados para a parte de texto, mas só um tem valor real, como apresentado abaixo no loop:

   self.UIcallbacks.listarRGs = function()
   {
       var xml = new JsXML(self.UIcomponents.rg.ws.httprequest.responseText);
       var code = xml.getElementsByTagName("cosa_message");
       var code_str = "";
   
       for (var sys_i=0;sys_i < code[0].childNodes.length;sys_i++) //>
           code_str += code[0].childNodes[sys_i].nodeValue;
   
       xml = new JsXML(code_str);
   
       var itens = xml.getElementsByTagName("item");
   
       self.UIcomponents.rg.clearData();
   
       for (var i=0; i< itens.length; i++)//>
       {
           var rg = itens[i].getElementsByTagName("rg");
           var org = itens[i].getElementsByTagName("org");
           var nome = itens[i].getElementsByTagName("nome");
           self.UIcomponents.rg.addItem(rg[0].text, rg[0].text + " | " + org[0].text + " | " + nome[0].text);
       }
   }

Caso a listagem sirva para realizar uma nova pesquisa no banco para resgatar dados mais completos para preenchimento de outros campos, pode-se definir um método para o evento onchange do JsLineEditAdv, como mostrado abaixo:

   self.UIcallbacksHandlers[1] = new Array();
   self.UIcallbacksHandlers[1][0] = "change";
   self.UIcallbacksHandlers[1][1] = self.UIcomponents.rg;
   self.UIcallbacksHandlers[1][2] = self.UIcallbacks.carregarPedestre;

Neste caso, o callback segue as regras normais apresentadas acima.

Veja também Criação do Controle para trabalhar com JsLineEditAdv para detalhes sobre como criar o Controle da forma correta.

Enviando Arquivos ao Servidor

Gerando Relatórios

A geração de relatórios dentro da nova estrutura de sistemas tenta ser a mais simples o possível. Embora sua implementação seja complexa, o desenvolvimento com a mesma encontra-se bastante facilitado.

Para que seu sistema gere relatórios, basta carregar a interface pública InterfaceRelatorios. Esta interface indagará automaticamente a um controle específico do sistema (ControlReport) quais são os relatórios disponíveis para aquele sistema, e o Controle filtrará a lista de acordo com o usuário em questão. Isso significa já ter cadastrado os relatórios no sistema Central, ter associado os mesmos ao Sistema, ter associado aos perfis, ou ter associado ao usuário.

Após a lista ser carregada, o usuário seleciona o relatório que deseja emitir. Ao selecionar o relatório, a InterfaceRelatorios irá novamente solicitar ao Controle a Interface específica de parâmetros daquele relatório. A interface será carregada, e a codificação da mesma é livre para que o programador faça a implementação que achar necessária, lembrando apenas que os componentes da subinterface em questão devem ter o nome dos parâmetros que serão repassados ao relatório para sua geração.

A InterfaceRelatorios já possui um botão chamado "Gerar", que irá então submeter os parâmetros informados para o ControlReport, no método loadReport.

Esse WebService receberá a quantidade de parâmetros que for necessária para a correta consulta dos dados no banco, e o Controle gerará um XML com formato definido o qual será repassado à engine de templates de relatório.

Este template é então repassado junto com o conjunto de dados consultados pelo Controle à engine, informando ainda o formato de saida dos dados como HTML, Excel ou PDF. A engine RePHPort então processará todo o conjunto e gerará o relatório no formato apropriado.

Os dados repassados devem seguir o formato do XML abaixo:

   <report>
       <header>
           ...
       </header>
       <footer>
           ...
       </footer>
       <data>
           ...
       </data>
   </report>

O template é criado utilizando HTML padrão, com folha de estilo definida sempre em Centímetros. O HTML, após carregado com os dados, é então repassado à engine que converterá o documento para o formato solicitado. Se PDF, será convertido de HTML para DOMPDF diretamente. Se Excel, será o HTML enviado com cabeçalho informando que o mesmo é um Excel.

O arquivo é então salvo no sistema de arquivos do servidor, e é retornado um javascript que abrirá um pop-up apontando para o arquivo. Dependendo do formato, ele será apresentado no navegador ou solicitado que se faça o download do mesmo.

Eis um exemplo de como a chamada ao relatório pode ser feita a partir da interface.

   var dados = "<id_prelatorio>iddorelatorio</id_prelatorio>";
   dados += "<formato>pdf</formato>";
   dados += "<parametro1>valor1</parametro1>";
   dados += "<parametro2>valor2</parametro2>";
   dados += "<parametro3>valor3</parametro3>";
   
   app.ws.loadReport(dados, app.getResponseJSON);

Caberá ao programador então:

1 - Criar a SubInterface com o JsDesigner para definição dos parâmetros do relatório, lembrando que os campos deverão ter o mesmo nome do parâmetro.

2 - Criar o Controle e Método que extraírão os dados e retornaram um XML

3 - Criar o Template em HTML para apresentação do Relatório

4 - Cadastrar no WebCentral o Nome do Relatório, Interface, Controle, Método e Template, e associá-lo a um Perfil e a um Sistema

Programação Server-Side

Abaixo, seguem exemplos de como trabalhar a comunicação de dados do lado do Servidor do WirePhrame.

Criação de um Controle em PHP com ¡COSA!

Para exemplo da criação de um Controle em PHP utilizando o padrão ¡COSA!, vamos usar o ControleApoio, por ser um controle simples e com apenas um método, conforme ilustrado abaixo.

   <?php
   
   class ControleApoio
   {
       public function carregarApoio($params)
       {
           $return = "";
   
           if (is_array($params->parametros->item))
           {
               foreach ($params->parametros->item as $value)
               {
                   $obj = new $value();
                   $return .= "<".$value.">".$obj->search(null, null, "XML")."</".$value.">";
               }
           }
           else
           {
               $value = $params->parametros->item;
               $obj = new $value();
               $return .= "<".$value.">".$obj->search(null, null, "XML")."</".$value.">";
           }
   
           return $return;
       }
   }
   
   ?>

Note que o método criado recebe apenas um parâmetro, chamado $params. $params será um objeto stdClass, criado a partir do XML enviado pela interface. Neste caso o XML enviado tem o seguinte formato:

   <params>
       <metodo>carregarApoio</metodo>
       <parametros>
           <item>TipoTelefone</item>
           <item>TipoEmail</item>
           <item>TipoEndereco</item>
       </parametros>
   </params>

O nó chamado "metodo" já foi utilizado pelo syscosa.php para chamar o método correto do Controle. Este nó continua disponível como um atributo do objeto $params, mas pode ser ignorado pois não tem função dentro do próprio método.

Os valores dos nós "item" podem então ser acessados da seguinte forma:

   $params->parametros->item[0];
   $params->parametros->item[1];
   $params->parametros->item[2];

Note que neste caso, foi enviado um grupo de nós "item" dentro de "parametros". O controle, possui uma condicional que verifica se o nó "item" é um array ou não. Isso é necessário em casos de múltiplos nós, pois o PHP não saberá se o que foi enviado é múltiplo ou não. Assim, o seguinte XML traria um objeto stdClass um pouco diferente.

   <params>
       <metodo>carregarApoio</metodo>
       <parametros>
           <item>TipoTelefone</item>
       </parametros>
   </params>

Neste caso o acesso deveria ser feito da seguinte forma:

   $params->parametros->item;

O PHP não tratará o item como um array, mas como um nó único de acesso direto. Essa distinção é extremamente importante, e deve ser sempre analisada com cuidado.

O retorno do Controle deverá ser uma string XML. Não inclua quebras de linhas ou tabulações entre os nós, pois o navegador Mozilla/Firefox criará nós fantasmas.

Criação do Controle para trabalhar com JsLineEditAdv

A criação do método no Controle utilizando COSA para pesquisa no banco de dados em campos JsLineEditAdv deve seguir algumas pequenas regras básicas.

O caso apresentado faz a consulta diretamente no Banco de Dados, embora pudesse ser feito utilizando algum objeto da POP.

O JsLineEditAdv, ao fazer a consulta no WebService, enviará para o mesmo o parâmetro "searchkey". Como estamos falando no padrão ¡COSA!, o mesmo virá contido dentro um objeto stdClass. No casoi abaixo, $params. Com esta informação, bastaria criar a cláusula SQL ou repassar o valor ao objeto POP com o método search para ter então uma resposta válida. Como dito anteriormente, a resposta pode ser qualquer XML em qualquer formato, porém, para manter compatibilidade com o formato de retorno da POP para o método search, mmarcaremos sempre como <item> cada tupla da resposta SQL dada.

   public function pesquisarPedestres($params)
   {
       //verifica se existe algum registro em aberto
       $sql = "select rg, org, nome from acessopedestre " .
           "where " .
           "    rg like '%".$params->searchkey."%' " .
           "group by rg, org, nome, tipo, destino";
   
       $rs = $GLOBALS["pop_db"]->query($sql);
   
       $result = $rs->fetchAll();
   
       $rs->closeCursor();
   
       $dados = "";
   
       foreach($result as $row)
       {
           $dados .= "<item>" .
                   "<rg>".utf8_encode($row["rg"])."</rg>" .
                   "<org>".utf8_encode($row["org"])."</org>" .
                   "<nome>".utf8_encode($row["nome"])."</nome>" .
                   "</item>";
       }
   
       return $dados;
   }

Veja também JsLineEditAdv - Campo Autocomplete com WebServices para detalhes sobre como receber e tratar os dados na Interface da forma correta.

This page was last modified on 14 October 2009, at 21:08. This page has been accessed 298 times.