domingo, 21 de julho de 2013

Como programar interfaces web reutilizaveis - Programacao orientada a widgets

O que eh uma Interface web?
Interface web eh um conjunto de elementos graficos renderizados pelo browser. O usuario interage com estes elementos para se comunicar com o servidor. O servidor armazena os dados relevantes para a aplicacao inteira, enquanto que uma interface em particular mostra para o usuario apenas os dados que sao relevantes para uma determinada tarefa. O ideal eh que a interface web seja bem intuitiva, facil e rapida de se usar, de maneira que o usuario consiga se comunicar com o servidor sem se preocupar com os detalhes tecnicos que o servidor emprega para o armazenamento dos dados.

Ate onde vao as interfaces web?

Interface web pode ser qualquer pagina ou site que permita ao usuario interagir com o servidor para atingir um objetivo. E de fato a internet esta transbordando de interfaces web: cada dia surge uma pagina nova ou um site novo com bastante ou pouca  interatividade. Vale lembrar que as interfaces web ocorrem nao so na internet mas tambem em redes privadas, denominadas "intranet". Aqui podem ser utilizadas as mesmas tecnologias da web para criacao de interfaces que permitem aos funcionarios interagir com o sistema de uma empresa, por exemplo.

O que eh uma Interface web reutilizavel ou widget?
Ao acessar uma url, seja da internet ou de uma intranet, voce visualiza uma pagina cheia de elementos, efeitos visuais e  interatividade. Mas sera que o pessoal que produziu esta pagina seria capaz de reaproveitar seus elementos em outras paginas? Ou sera que toda vez que ha necessidade de replicar aquele formulario ou calendario de eventos ou qualquer coisa do genero eles acabam copiando e colando o codigo? Ou talvez refazendo tudo do zero e portanto perdendo tempo reinventando a roda?

Pois eh, o programador ou programadora que se preocupa nao apenas com o resultado final mas igualmente com a qualidade do software tenta fazer algo diferente. Ele ou ela procura estruturar seus codigos de maneira que os mesmos possam ser reaproveitados. Porem o reaproveitamento de interfaces, e especialmente interfaces interativas que requerem uma mistura de linguagens e tecnologias (http, html, javascript, css, etc...) nao eh um objetivo tao facilmente atingido. Criar interfaces web passiveis de reutilizacao em diferentes contextos de um mesmo site ou sistema eh algo realmente dificil e requer disciplina.

Ao perceber que uma interface moderna depende da fusao de diferentes elementos de programacao, como voce faz para instanciar uma interface a qualquer momento, inseri-la no local desejado sem causar conflito em outras coisas existentes na mesma pagina? Por exemplo: voce precisa ser capaz de chamar o mesmo formulario em diferentes paginas quando o usuario o solicita atraves de um link ou botao. Entao voce resolve desenvolver a interface do formulario separadamente e percebe que esta interface vai precisar das bibliotecas javascript x, y e z. Porem, ao incluir esta interface numa pagina, como saber que estas bibliotecas ja nao foram carregadas por outra interface? Talvez fosse melhor sempre carregar estas bibliotecas, por default, em todas as paginas, para evitar este tipo de conflito? Mas dai voce acaba correndo o risco de carregar estas bibliotecas mesmo quando elas nao sao necessarias. Entao o que fazer?

Eu nao sei o que fazer no seu caso, mas eu saberia o que fazer no meu caso, quando eu tenho que criar um sistema com uma serie de interfaces que provavelmente serao reutilizadas em diferentes partes do sistema e que requerem bibliotecas iguais ou distintas. A verdade eh que nao existe uma solucao universal, pois cada caso eh um caso a parte, entende? Ninguem vai ensinar voce como reaproveitar codigo e muito menos como reaproveitar interfaces ja que isso eh algo que cabe a voce descobrir baseado nas necessidades e complexidades do sistema que voce esta prestes a criar. A unica coisa que eu posso oferecer sao alguns conceitos basicos que eu saquei ao longo do tempo, apos passar anos desenvolvendo interfaces para web e tentando atingir um nivel de reaproveitamento sem tornar o codigo fonte uma gambiarra incompreensivel (o que acontece muitas vezes ao tentar separar as coisas em modulos e invoca-los depois).

Mas antes de comecar com os meus humildes conceitos sobre modularizacao de interfaces eu gostaria de dar uma palavrinha sobre o pessoal que anda, de maneira frenetica, escrevendo tutoriais sobre programacao na internet.

O cancer dos tutoriais sobre programacao na internet
Na internet voce encontra qualquer coisa. Voce deseja criar um shopping cart usando jquery, codeigniter e blablabla? Eh so procurar no google e voce encontra uma porrada de tutoriais "ensinando" voce a juntar um monte de codigo de diferentes linguagens de maneira a fazer uma porcaria de interface funcionar do jeito esperado. O resultado final eh atingido, tudo funciona perfeitamente, porem a unica coisa que voce aprende eh a replicar a gambiarra e nao a programar! Pra comecar nao existe um site ou sistema com apenas um modulo, com apenas uma interface. Entao o ideal nao eh saber como fazer tal coisa, e sim aprender a programar de forma modular, ou seja, separar e reutilizar codigo.

E sim, tem muito framework promovendo a ideia de reaproveitamento e muitos tutoriais e artigos escritos sobre tais frameworks - mas nao se engane, pois o framework vai deixar voce nao mao na hora em que o bixo pegar. Se voce fizer como eu e simplemente ignorar estes frameworks vai perceber que a vida eh boa, muita coisa eh possivel sem ter que ficar perdendo tempo e perguntando coisas em foruns. Se precisa fazer alguma coisa, introduzir uma feature no seu sistema, voce simplesmente programa esta feature, voce modifica o seu sistema para que acomode a nova feature, entede? Simples assim.

OBS: Aqui estou me referindo mais aos frameworks para php, que sao todos cancerigenos - eu ja usei mais de um deles e nao recomendo nenhum. O sistema fica um pouco mais complexo e voce ja nao suporta mais aquela montoeira de lixo que nao serve para nada. Caem bem para criacao de blogs e no maximo portais de noticias, mas para um sistema mais complexo o framework ja cai por terra. Ja as bibliotecas para javascript sao sempre bem-vindas por resolver o problema de incompatilidade entre browsers, e eu nao vejo nada de errado em tutoriais humildemente mostrando como tirar proveito dessas bibliotecas. Porem qualquer coisa como "Construa um sistema de nao sei o que usando biblioteca tal" voce pode descartar porque nao passa de lixo! Isso por que voce nao vai basear um sistema numa bilioteca e sim o contrario: as bibliotecas certas serao escolhidas com base na necessidade e complexidade do seu site ou sistema.

Mas se voce esta comecando a programar, qualquer coisa eh valida, serve para pegar o embalo ;) Aqui eu vou introduzir alguns conceitos e dicas que servem para quem ja esta ha algum tempo batendo na mesma tecla, reinventando a roda o tempo todo.

Introduzindo o conceito de programacao orientada a widgets
Entao, programacao orientada a widgets tem tudo a ver com identificacao dos diferentes elementos que compoem uma interface maior. E quando eu falo em elementos eu nao me refiro a botoezinhos ou campos de texto: estou falando de mini-interfaces, ou widgets. A moral eh criar os widgets separadamente, de maneira que voce seja capaz de acessar e testar a interface de um widget de forma isolada. Ao fazer isso voce so tem a ganhar. Alem de poder reaproveitar um widget em diferentes partes de um sistema (ja que o widget funciona "stand-alone", ou seja, por si proprio) voce ainda eh capaz de dar manutencao nele sozinho, sem precisar carregar uma pagina inteira a fim de testa-lo.

Ao programar orientado a widgets voce deve identificar todos os elementos de programacao que compoem os widgets. Eles sao objetos que possuem codigo javascript associado a um markup ou "template". Voce precisa entao carregar o javascript + template, e, de alguma forma, dizer "este objeto javascript pertence a esta parte do markup". Eu recomendo criar uma convencao legal para automatizar o carregamento e renderizacao dos seus widgets. Uma ideia que eu sempre acabo implementando e nomear o objeto com o mesmo nome dado ao arquivo .js e colocar todo o markup do widget dentro de um arquivo com o mesmo nome e dentro de uma div tambem com o mesmo nome. Exemplo de widget: lista de produtos. Voce vai ter os seguintes elementos:

1) arquivo javascript "lista_de_produtos.js"
2) objeto definido dentro deste arquivo: var lista_de_produtos = new function(){ ... }
3) arquivo de template "lista_de_produtos.html" (ou .php)
4) conteudo dentro do template: <div id="lista_de_produtos">...</div>

Assim, sempre que fosse carregar este widget em alguma pagina .php, por exemplo, voce faria o seguinte:


<div id="main">
    <script src="lista_de_produtos.js"></script>
    <?php require("lista_de_produtos.php"); ?>
    <script>
        $(function(){
            lista_de_produtos.render(); // faz um "bind" do objeto javascript com o markup do widget
            lista_de_produtos.load(); // carrega os dados do widget
        })
    </script>
</div>

Como voce esta seguindo uma convencao que da o mesmo nome para as diferentes partes do widget voce pode simplesmente criar uma funcao que recebe o nome do widget como parametro e ja faz tudo por voce, que tal?

function includeWidget($widget_name){
    echo '<script src="'.$widget_name.'.js"></script>';
    require("$widget_name.php");
    // etc...
}

Dai, quando precisasse reutilizar um widget em alguma pagina, voce so teria o trabalho de fazer:

<div id="main">
    <?php includeWidget($widget_name); ?>
</div>

Eu tambem recomendaria criar uma funcao no javascript para encapsular a definicao de widgets. Metodos como "render" e "load" sao tao padroes que deveriam ser herdados por todos os widgets. Assim voce teria que ter um script "utils.js" (carregado em todas as paginas e antes de tudo) definindo uma funcao tipo esta:

var defineWidget = function(name, extender){
    // objeto base:
    var widget = {};
    widget.render = function(){ /* ... */ };
    widget.load = function(){ /* ... */ };
    widget.element = function(){
        // retorna o elemento do widget
        return $('#'+name);
    }
   
    // objeto extendido
    extender.call(widget);
   
    // exporta
    window[name] = widget;
   
}

Dai, nos seus scripts que definem widgets voce teria algo desse tipo:

"lista_de_produtos.js"
defineWidget('lista_de_produtos',function(){
   
    // ja herda os metodos "render" e "load"
   
    // inclui funcionalidade especifica
    this.itens = [];
    this.valor_total = function(){
        // percorre os itens e soma os precos
        // retorna o total
    }
   
})

O widget nao sabe aonde esta situado

Eu acho um erro comum o programador colocar o conteudo do widget dentro de um container e dar titulo para o mesmo. Isto por que container e cabecalho sao detalhes de layout determinados pela interface maior. A interface maior, que pode ser composta por um conjunto de widgets, pode decidir que todos os widgets sao carregados em forma de janelas ou modais. Entao, o widget nao sabe aonde esta sendo carregado, pode ja estar sendo carregado dentro de um container, entao por que se preocupar em criar um container ou uma capsula para si? Como saber aonde aonde vai o seu "heading" ou "titulo" se ele nao tem visao de onde esta situado? O que da para fazer no maximo eh definir uma variavel no seu widget informando o seu titulo padrao (mas que a interface maior pode sobrescrever), ex:

defineWidget('meu_widget',function(){
    this.title = "Sou um widget muito legal e reutilizavel";
})

Assim, quando na interface "master" voce estiver para carregar uma porrada de widgets em forma de modais, por exemplo, basta fazer algo do tipo:

$.each(lista_de_widgets, function(i, widget){
    widget.element().dialog({
        autoOpen : false,
        title : widget.title,
        modal :true,
        etc...
    });
    // adiciona um metodo on-the-fly ao widget (eu amo javascript ;)
    widget.show = function(){
        widget.element().dialog('open');
    }
})

Comunicacao entre widget e servidor


Alem destes elementos os widgets tambem necessitam de scripts no servidor que permitam consultar dados e/ou executar acoes relevantes ao objetivo da interface/widget. Estes scripts sao o que eu costumo chamar de "apis", por terem como funcao unicamente receber dados como parametros e retornar dados como resultado (geralmente em json).

O tipo de api mais comum de um widget eh o tipo utilizado para inicializar ou carregar os dados mais relevantes para a sua aplicacao. Eu geralmente chamo este de "api.load.php" ou "api.get_data.php". Sua logica eh simples: recebe o id de algum registro, consulta este registro em alguma database, e retorna os dados do registro consultado em formato json para ser apresentado pela interface.

No caso da nossa "lista_de_produtos", se esta fosse uma lista de produtos disponiveis numa vitrine, o nosso widget teria um script api que nao ia receber nada como parametro, apenas ia retornar os dados de todos os produtos disponiveis para venda ou algo do tipo. Porem se fosse uma lista_de_produtos selecionados pelo cliente entao nos teriamos um script "api.get_item.php", por exemplo, que ia receber como parametro o id do produto selecionado, consultar os dados do produto, e retorna-los para a interface para que a mesma mostrasse um item a mais na lista de compras ou algo do genero.

Outro tipo de script "api" comum eh aquele que salva o trabalho feito pelo usuario para que os dados persistam em alguma database. Ja que estamos na parte de salvar eu gostaria de aproveitar para compartilhar outra dica que pode ajudar no quisito de reaproveitamento e baixo acomplamento entre interfaces/widgets.

O widget nao sabe o que fazer depois de concluir o seu trabalho
Esta dica segue o mesmo raciocinio da dica anterior, de que "O widget nao sabe aonde esta situado". Se o widget nao sabe onde esta situado entao como saber o que fazer depois que o seu trabalho eh concluido? Voce deve pensar: ora, ele deve ser fechado! Isto esta certo, porem aonde voce coloca o codigo que faz isso? Dentro do proprio widget? Mas se ele nao sabe onde esta situado entao como vai saber o que fechar? O unico agente que sabe fazer isso eh aquele que instanciou ou "invocou" o widget dentro de um container, e entao deve fechar este container. Porem aqui tambem temos um ponto de interrogacao: a interface maior sabe como fechar um widget, porem nao sabe quando deve fazer isso. Mas poderia ser notificada, nao?

Eventos: o portal para reaproveitamento de widgets

Para resolver o problema acima a gente pode adotar um esquema da eventos. Pense comigo: a interface maior pode escutar a eventos disparados pelos widgets avisando que alguma coisa aconteceu dentro deles ou que eles adquiriram um certo status. Observe que nos nao estamos acoplando fortemente as coisas. Os eventos podem ser escutados por qualquer interface. Nao eh o mesmo que fazer widget x executar um metodo y na interfaze z a fim de comunicar um evento (isso sim eh acoplamento pois torna o widget x compativel apenas com interface z - ou qualquer outra interface que tenha um metodo y). A metodologia que eu estou sugerindo aqui eh um pouco mais discreta: o widget simplesmente dispara o evento para qualquer um que esteja escutando aquele evento. Entendeu? Algo deste tipo:

defineWidget('widget_responsivo',function(){
   
    var self = this; // alias of "this"
   
   
    // metodo executado quando o trabalho deste widget foi finalizado
    this.done = function(result){
        // requer implementacao pela interface que instancia este widget
    }
   
    this.save_work = function(){
        var dados = {};
        $.getJSON('api.save.php',dados).done(function(result){
            self.done.call(this,result); // dispara o evento
        })
       
    }
})

Agora, a interface maior, que instancia este widget, poderia ficar a par de quando o mesmo tem o seu trabalhado concluido para enfim poder fechar a modal que hospeda a interface do mesmo:

widget_responsivo.done = function(){
    this.element().dialog('close'); // fecha o widget
}

Voce pode argumentar ainda que houve acomplamento entre a interface maior e widget_responsivo, nessa ordem. Neste caso nao ha problema uma vez que o objeto que desejamos reaproveitar em outros contextos/interfaces/paginas nao tem conhecimento do ambiente em que esta inserido e portanto pode ser inserido em qualquer ambiente. Mas sem duvida seria uma boa padronizar eventos padroes como o deste exemplo (done) para que voce pudesse, por exemplo, criar funcoes que reajam a um mesmo tipo de evento disparado por qualquer tipo de widget. (ex: voce poderia querer cronometrar o tempo que uma pessoa leva desde o momento em que ela comeca a trabalhar com um widget ate o momento em que ela termina o seu trabalho - neste caso todos os widgets, sem excessao, deveriam disparar os eventos "inicio" e "fim" ou "init" e "done", como preferisse.)

E ja que qualquer agente pode escutar a eventos de um widget surge uma questao importante no esquema de eventos: por que nao permitir que um mesmo evento seja escutado por mais de um agente? Da forma como foi exemplificado aqui isso nao seria possivel pois o widget so permite sobrescrever o metodo "done" uma vez, ou seja, apenas um agente consegue manipular tal evento. Como voce faria para resolver este problema? Eu sugiro a voce criar no definidor de widgets (defineWidget) uma funcionalidade que sera herdada por todos os widgets e que permite o registro de n callbacks a qualquer tipo de evento (funcao utilizada pelo agente), assim como o disparo de todos os callbacks registrados a um tipo de evento (funcao utilizada internamente pelos widgets). Mas se eu colocar este codigo aqui nao vai ter graca ne, vou deixar esta parte como tema de casa para voces!