Category Archives: web2.0

Transparência inédita na saúde pública

É com satisfação que vejo o trabalho da Milfont Consulting participando diretamente na transparência da saúde pública no estado do Ceará.

O governo do estado inaugurou essa semanaA Conta do Paciente“, um projeto inédito no Brasil que vai informar ao paciente quanto foi sua despesa desde a entrada no hospital até sua alta. Esse tipo de atuação aproxima o governo da agilidade que a sociedade cobra em relação à transparência nas contas públicas, antes era quase impossível saber o custo real por paciente. Fora que a secretaria vai saber precisamente e em tempo real os custos por unidade, além de facilitar a tomada de decisões que podem salvar vidas.

Esse formulário detalhado com a conta do paciente é possível graças ao ERP especialista em gestão hospitalar pública da empresa Insystem, nosso cliente e parceiro. A Insytem acreditou em nosso trabalho e é um dos maiores Cases, senão o melhor.

O ERP foi construído 100% com base em TDD em Java usando DWR, Hibernate e  Spring basicamente. Alguns requisitos necessários de usabilidade utilizam Reverse Ajax com DWR. O sistema é totalmente ajax e utiliza o Extjs seguindo a filosofia model 3. Fizemos algumas customizações no Extjs para se integrar ao DWR de forma transparente.

Fomos ágeis desde o primeiro momento, mas nunca nos preocupamos em implantação de processo, metodologia ou qualquer coisa que o foco não fosse software saudável. XP foi algo natural, valores e princípios foram assimilados desde o primeiro dia, mas foi e é o software funcionando e livre de erros [o mais livre possível] que nos moveu.

Destaque para o Felipe Andrade, funcionário da Insystem que se tornou especialista em Extjs com DWR e hoje domina e é talvez o maior conhecedor da união desses Frameworks no estado.

Agradecimentos especiais aos diretores Evando Chaves e Marcelo Meirelles que investiram nessa solução e tiveram a sagacidade de sair na frente da concorrência entendendo que software funcionando é mais importante do que processos bonitos e pomposos, afinal o barco não chega na frente por causa do tambor e sim dos remadores. A Insystem está de parabéns por ter enfrentado todas as correntes contrárias e ter chegado a essa vitória investindo e apostando no fator humano como responsável para a vitória.

Esse é um Case que entrou para a história, estamos procurando outra solução semelhante na saúde pública do Brasil e até agora não encontramos nada.

Orgulhoso por participar dessa conquista.

Extjs e DWR

Nesse artigo eu pretendo trabalhar dois conceitos principais de uso do Extjs, extensão/customização de componentes e acesso a dados server-side com base em experiência recente em um projeto que desenvolvemos. Esse projeto em questão é um ERP que tinha a necessidade de manter a usabilidade similar a sua versão antiga, feita em Delphi, para o desktop.

Para suprir essa necessidade de usabilidade tivemos que adotar alguns conceitos, como ser totalmente stateless e modificar a arquitetura MVC2 para o MVC3. No server-side trabalhamos com um domain model baseado em Hibernate, Spring e Facades e Services com DWR. Nada de frameworks MVC2, não nos preocupamos com renderização e sim com a API. No lado cliente usamos Extjs com algumas modificações que fiz para integrar de forma suave com o DWR.

Primeiro precisamos entender como o Extjs trabalha com herança. Basicamente há um método no objeto Ext que faz esse trabalho de extensão dos componentes, funciona da seguinte maneira:

//Formato:
var NovoComponente = Ext.extend(velhoComponente, { 
          /* metodos e propriedades que serão reescritas */ 
});

//Exemplo:
var MilfontGridPanel = Ext.extend(Ext.grid.GridPanel, {
        //novo construtor
        constructor: function(config) {
            // Seu preprocessamento vai aqui
        	MilfontGridPanel.superclass.constructor.apply(this, arguments);
            // Seu pos-processamento vai aqui
        },

        NovoMethod: function() {
            // algum novo método que você queira criar para o novo componente
        }
    });

Para esse projeto, criei um Ext.data.DataProxy (como visto no artigo passado) especialista para o DWR, criativamente denominado DWRProxy. A idéia é modificar o comportamento de buscar os dados para usar um Creator do DWR.

Definimos primeiro o objeto e suas propriedades necessárias:

Ext.ux.data.DWRProxy = function(dwr_facade, dwr_filter, dwr_errorHandler){

    Ext.ux.data.DWRProxy.superclass.constructor.call(this);

	/* Propriedade que receberá a classe Java configurada como Creator */
    this.data = dwr_facade;
    /*
	 * Propriedade que receberá uma classe java configurada como converter
	 * que servirá como filtro de busca
	 */

	this.dwr_filter = dwr_filter;

	/**
	 *
	 * Propriedade para fazer paginação, indica que deve cachear a consulta de
	 * total de elementos o controlador [fachada] deve implementar a logica de
	 * negocios adequada, quando for false consulta o total, quando for true
	 * consulta apenas a listagem e repete o total
	 */

	this.dwr_total_cache = false;

	this.dwr_errorHandler = dwr_errorHandler;

};

Após isso extendemos do Ext.data.DataProxy :

Ext.extend(Ext.ux.data.DWRProxy, Ext.data.DataProxy, {

    /**
     * Método Load do Ext.data.DataProxy overrided
     */

    load : function(params, reader, callback, scope, arg) {

	/**
	 * Escopo "this" mapeado para a variável "s" porque dentro do callback do
	 * DWR o escopo "this" não pertence ao objeto Ext.ux.data.DWRProxy.
	 */

	var s = this;

        params = params || {};

        if(params.cache != undefined) {
		this.dwr_total_cache = params.cache;
	}

	if(params.filter != undefined) {
		this.dwr_filter = params.filter;
	}

        var result;

        try {
		this.data(this.dwr_filter, params.start, params.limit, this.dwr_total_cache, {
			callback:function(response) {
                                //aqui passamos o retorno do DWR 
                               // que chamei de response,  para o extjs
				result = reader.readRecords(response);
				callback.call(scope, result, arg, true);
			},
			errorHandler:function(a, e) {
				scope.fireEvent("loadexception", s, arg, null, e);
				s.dwr_errorHandler(a);
			},
			timeout:100000
		});

		this.dwr_total_cache = true;

        } catch(e) {
            this.fireEvent("loadexception", this, arg, null, e);
            callback.call(scope, null, arg, false);
            return;
        }

    }

});

A fachada DWR é uma classe comum, segue um exemplo de uso com Hibernate:

//classe para satisfazer o transporte para o Extjs
public final class DataTransferObject {
    private int total;
    private List results;
    //sets e gets
}

public class AjaxFacade {
    //injeta um repositorio, whatever
    private Repository repository = null;

    public DataTransferObject find(Object filter, int start, int limit, boolean cache, HttpSession session) {
        DataTransferObject dto = new DataTransferObject();
        //verifica se o Proxy está passando true 
        // indicando que está paginando
        if (cache) {
            Integer total = (Integer) session.getAttribute("totalObject");
            dto.setTotal(total);
       } else {
            session.removeAttribute("totalObject");
            Integer total = repository.count(filter);
            dto.setTotal(total);
            session.setAttribute("totalObject", total);
        }
        List retorno = (List) repository.find(filter, start, limit);
        dto.setResults(retorno);
        return dto;
    }


Para o Grid visto no artigo passado, basta instanciar assim no javscript:

var store = new Ext.data.Store({
    proxy: new Ext.ux.data.DWRProxy(
        AjaxFacade.find, 
        {$dwrClassName:"Project"}, 
        errorHandler
    ),
    reader: new Ext.data.JsonReader({
            root: 'results',totalProperty: 'total',id: 'id'
        }, 
        ['id', 'name', 'manager.name', 'manager.address.country']
    )
});

Para entender o {$dwrClassName:”Project”} visite esse post.

Dessa forma o DWR se torna um proxy para todos os componentes do Extjs.

Código fonte da modificação do javascript eu coloquei aqui no github e uma aplicação demo aqui. No próximo vou integrar o DWR com o Rails, aguardem que sai logo… ou não.

Introdução ao Ext

Eu trabalho com Extjs desde que ele era uma extensão para o YUI, ainda hoje há aplicação no ar usando essa antiga tecnologia [por problema causado por algum idiota, você provavelmente será redirecionado para outro site do governo, dá uma olhada no canto esquerdo superior e clique em “IR PARA A SEPLAG“]. Para ver o Extjs no tempo que ele se chamava ext-yui, vá no link de pesquisa avançada, preencha o input descrição em “dados da matéria” com “secretaria de cultura” por exemplo e clique no botão pesquisar.

Esse tutorial tem o objetivo de preparar o conhecimento para outros posts que estou escrevendo e achei necessário uma introdução apenas nos conceitos do Extjs para não confundir com as tecnologias que uso em conjunto como DWR ou no modelo REST com o RubyOnRails.

O Extjs é um framework javascript de propósito geral, ou seja, tem um conjunto de funcionalidades que tratam Ajax, um conjunto de Widgets bem elaborados [componentes visuais como Grid e TabPanel], manipulação de DOM [Document Object Model] e BOM [Browser Object Model], tratamento de eventos, animações como Fade In e Fade Out, parser de JSON, entre outras coisas. Seus componentes são construídos com técnicas modernas de orientação a objetos no javascript e manipulação de Scripttag para recursos remotos que não suportam Ajax.

Preparação

Após baixar e descompactar o framework [estou trabalhando na versão 2.x que é estável nessa data], recomendo que deixa a disposição das pastas conforme se encontra e coloque no seu projeto de forma que seja acessível via web, já vem com documentação e exemplos que você deve e vai usar durante o desenvolvimento. Temos a opção de montar o Extjs [marcando a opção “Make build available via CacheFly” ] no CacheFly como um servidor CDN para otimizar o tráfego de sua aplicação principalmente se ela será disponibilizada na internet e não apenas na intranet.

É necessário importar o CSS global, o adapter e o Javascript global conforme mostrado abaixo:




Pode usar outros temas para o Extjs, estarão na pasta “resources/css“, assim como podemos internacionalizar os componentes com arquivos que se encontram em “build/locale“. Exemplo com o tema “Gray” e i18n em português do Brasil:






Internacionalização é algo pensado no Extjs de forma a facilitar a criação de arquivos de linguagem aproveitando a estrutura da linguagem [dinâmica e fracamente tipada], todas as propriedades de mensagens e textos são públicas para facilitar a reescrita como mostrado abaixo na i18n do componente de DataPicker:

if(Ext.DatePicker){
   Ext.apply(Ext.DatePicker.prototype, {
      todayText         : "Hoje",
      minText           : "Esta data é anterior a menor data",
      maxText           : "Esta data é posterior a maior data",
      disabledDaysText  : "",
      disabledDatesText : "",
      monthNames        : Date.monthNames,
      dayNames          : Date.dayNames,
      nextText          : 'Próximo Mês (Control+Direita)',
      prevText          : 'Mês Anterior (Control+Esquerda)',
      monthYearText     : 'Escolha um Mês (Control+Cima/Baixo para mover entre os anos)',
      todayTip          : "{0} (Espaço)",
      format            : "d/m/Y",
      okText            : " OK ",
      cancelText        : "Cancelar",
      startDay          : 0
   });
}

Se você usar o CacheFly gerado pela página do Extjs, ele incluir o adapter e o global em um mesmo arquivo, vai ser algo como:




Adapter

Quando o Extjs passou a ser um framework independente do YUI, passou a adotar outros frameworks como base para funções básicas de manipulação de DOM e Ajax, hoje suporta trabalhar em conjunto com YUI, JQuery, Prototype e totalmente independente. Se o projeto já tem Jquery ou outro framework que trabalha com o Extjs, a utilização dos dois é muito fácil e indicada, principalmente para usar os widgets que são provavelmente os mais poderosos hoje em dia em um framework opensource.

//Exemplos de adapters permitidos


Widgets

O principal apelo do Extjs que conquista os desenvolvedores é o layout bem trabalhado dos componentes visuais que são de fácil parametrização. Basicamente todos os componentes funcionam da mesma forma, você o instancia passando um objeto literal de configuração com mostrado abaixo:

//Exemplo do grid
var grid  = new Ext.grid.GridPanel({
autoShow:true, width:750,height:250 //mais parametros 
});
//Exemplo de uma Window
var window = new Ext.Window({
autoShow:true, width:750,height:250 //mais parametros 
});
//Exemplo de um Painel
var panel = new Ext.Panel({
autoShow:true, width:750,height:250 //mais parametros 
});

Vamos usar o GRID para exemplificar como trabalhamos com o Extjs, o mesmo comportamente se repete em todos os componentes.
A documentação do Extjs é muito bem feita e praticamente vai ser a única coisa que você vai precisar depois de entender como os componentes são formados, afinal não vale a pena decorar todas as propriedades de todos os componentes, concentre-se apenas em entender os conceitos.

A GRID é o Widget mais famoso desse framework e é formado basicamente por um objeto “Ext.data.Store” [que é a fonte de dados da GRID] e um objeto “Ext.grid.ColumnModel” [que é a definição das colunas], como mostrado abaixo:

var grid  = new Ext.grid.GridPanel({
    autoShow:true, width:750,height:250, //parametros de configuração de layout
    cm: new Ext.grid.ColumnModel({/*configuração*/}),
    store: new Ext.data.Store({}), //Ou especialização de um Store
    sm: new Ext.grid.RowSelectionModel({singleSelect:true}), //ou outra especialização de um AbstractSelectionModel
});

O objeto ColumnModel é a definição de colunas do Grid, possui propriedades para definição de layout como largura e altura, título da coluna como vai ser exibida e formatação do texto:

var colModel = new Ext.grid.ColumnModel([
    { header: "Ticker", width: 60, sortable: true},
    { header: "Company Name", width: 150, sortable: true},
    { header: "Market Cap.", width: 100, sortable: true},
    { header: "$ Sales", width: 100, sortable: true, renderer: money},
    { header: "Employees", width: 100, sortable: true, resizable: false}
 ]);

A propriedade cm [ColumnModel] pode ser também substituída pela propriedade “columns” que funciona como um “alias”, dessa forma o Grid cria automaticamente um objeto ColumnModel:

var grid = new Ext.grid.GridPanel({
    columns: [
        {id:'id', header: "id", width: 200, sortable: true, dataIndex: 'id'},
        {header: "Nome", width: 120, sortable: true, dataIndex: 'name'}
    ]
});

Caso haja necessidade de formatar o conteúdo da célula, você pode usar uma função como “renderer” para tratar esse conteúdo:

var grid = new Ext.grid.GridPanel({
    columns: [
        {id:'id', header: "id", width: 200, sortable: true, dataIndex: 'id'},
        {header: "Nome", width: 120, sortable: true, dataIndex: 'name'},
        {header: "Criado em", width: 135, sortable: true, renderer: function(value) {
                    return Date.parseDate(value, 'Y-m-d\\TH:i:s\\Z').format('d/m/Y H:i:s');
                    //2009-06-14T12:51:07Z
        }, dataIndex: 'created_at'}
    ]
});

A propriedade “store” da GRID é uma especialização do componente “Ext.data.Store” que é formado basicamente por um “proxy” e um “reader“:

var store = new Ext.data.Store({
    proxy: new Ext.data.DataProxy(), //ou uma especialização
    reader: new Ext.data.DataReader() //ou uma especialização
});

O “proxy” é o componente que obterá os dados e o “reader” o componente que fará a leitura desses dados para um formato comum a todos os componentes do Extjs na forma de um objeto denominado “Ext.data.Record“. O objeto “Record” representa um registro de dados e é usado seja para GRID, para um Form ou qualquer componente que trabalhe com dados editáveis.

Dessa forma podemos usar uma combinação de Proxy e Reader como HttpProxy e JsonReader:

var store = new Ext.data.Store({
    proxy: new Ext.data.HttpProxy({
        url: 'projects.json'
    }),
    reader: new Ext.data.JsonReader({
        totalProperty:'total',
        root:'results',id:'id'
    }, Ext.data.Record.create([
        {name:'id', mapping:'id'},
        {name:'name', mapping:'name'},
        {name:'created_at', mapping:'created_at'},
        {name:'updated_at', mapping:'updated_at'}
    ]))
});

Devido o costume do uso do HttpProxy, o componente Store possui uma propriedade chamada “url” que estando presente cria um HttpProxy automaticamente como mostrado abaixo:

var store = new Ext.data.Store({
    url: 'projects.json',
    reader: new Ext.data.JsonReader({
        totalProperty:'total',
        root:'results',id:'id'
    }, Ext.data.Record.create([
        {name:'id', mapping:'id'},
        {name:'name', mapping:'name'},
        {name:'created_at', mapping:'created_at'},
        {name:'updated_at', mapping:'updated_at'}
    ]))
});

O objeto “Reader” é o único que foge um pouco a regra de instanciação por receber dois parâmetros, um similar aos outros com um objeto literal de configuração e outro com o mapeamento dos dados. O objeto de configuração tem duas propriedades de que representam o total e a lista de dados. O objeto de mapeamento usa um método “estático” do objeto Record para criar um link entre a propriedade do json [com a propriedade “mapping”] e o índice interno do Record [pela propriedade “name”].

 new Ext.data.JsonReader({
        totalProperty:'total',
        root:'results',id:'id'
    }, Ext.data.Record.create([
        {name:'id', mapping:'id'},
        {name:'name', mapping:'name'},
        {name:'created_at', mapping:'created_at'},
        {name:'updated_at', mapping:'updated_at'}
    ]))

Dessa forma você tem um link entre o ColumnName pela propriedade dataIndex e o Store por meio do Reader, como abaixo:

Ext.data.Record.create([
         {name:'nome_linkado', mapping:'name'}
    ])
//
 columns: [
        {header: "Nome", width: 120, sortable: true, dataIndex: 'nome_linkado'}
    ]

Para melhorar a navegação da GRID, você pode também acrescentar um componente de Toolbar no header ou no footer:

var grid  = new Ext.grid.GridPanel({
    autoShow:true, width:750,height:250, //parametros de configuração de layout
    cm: new Ext.grid.ColumnModel({/*configuração*/}),
    store: new Ext.data.Store({}), //Ou especialização de um Store
    sm: new Ext.grid.RowSelectionModel({singleSelect:true}),
    bbar: new Ext.Toolbar(), //Bottom Toolbar
    tbar: new Ext.Toolbar() //Top Toolbar
});

A Toolbar mais usada é sua especialização com paginação, a Ext.PagingToolbar que necessita ser linkada com o Store:

new Ext.PagingToolbar({
    pageSize:10, //propriedade opcional, default é 20
    store: store
})

A PagingToolbar é I18n, mas se as mensagens não agradarem você pode mudá-las [eu sempre faço]:

new Ext.PagingToolbar({
    pageSize:10,store: store,
    displayInfo: true,	
    displayMsg: 'Exibindo o resultado: {0} a {1} de {2} registros',
    emptyMsg: "Sem resultados a exibir"
})

Uma coisa bacana nesse componente é que você pode agrupar botões [já que é uma Toolbar] e até padronizar o layout:

new Ext.PagingToolbar({
    pageSize:10,store: store,
    displayInfo: true,	
    displayMsg: 'Exibindo o resultado: {0} a {1} de {2} registros',
    emptyMsg: "Sem resultados a exibir",
    items: ['-', {
        pressed: true,enableToggle: true,text: 'Alterar',
        toggleHandler: function(){}
    }, {
        pressed: true,enableToggle: true,text: 'Excluir',
        toggleHandler: function(){}
	}]
})

Código da GRID inteira:

var store = new Ext.data.Store({
    proxy: new Ext.data.HttpProxy({
        url: 'projects.json'
    }),
    reader: new Ext.data.JsonReader({
        totalProperty:'total',
        root:'results',id:'id'
    }, Ext.data.Record.create([
        {name:'id', mapping:'id'},
        {name:'name', mapping:'name'},
        {name:'created_at', mapping:'created_at'},
        {name:'updated_at', mapping:'updated_at'}
    ]))
});

var colModel = new Ext.grid.ColumnModel([
    {id:'id', header: "id", width: 200, sortable: true, dataIndex: 'id'},
    {header: "Nome", width: 120, sortable: true, dataIndex: 'name'},
    {header: "Criado em", width: 135, sortable: true, 
        renderer: function(value) {
          return Date.parseDate(value, 'Y-m-d\\TH:i:s\\Z').format('d/m/Y H:i:s');

    }, dataIndex: 'created_at'}
 ]);

var pagingToolbar = new Ext.PagingToolbar({
    pageSize:10,store: store,
    displayInfo: true,	
    displayMsg: 'Exibindo o resultado: {0} a {1} de {2} registros',
    emptyMsg: "Sem resultados a exibir",
    items: ['-', {
        pressed: true,enableToggle: true,text: 'Alterar',
        toggleHandler: function(){}
    }, {
        pressed: true,enableToggle: true,text: 'Excluir',
        toggleHandler: function(){}
	}]
});

var grid  = new Ext.grid.GridPanel({
    autoShow:true, width:750,height:250,
    cm: colModel,
    store: store,
    sm: new Ext.grid.RowSelectionModel({singleSelect:true}),
    bbar: pagingToolbar
});

Como eu falei, todos os componentes possuem o mesmo comportamento, notaram que dá para aproveitar esse código semelhante e reaproveitar em todos os CRUDs?
Vou falando de um por um de acordo com os posts que forem saindo, aguardem que o próximo sai logo… ou não.