Categoria: Tecnologias Web (Página 1 de 2)

Automatizando o acesso a uma página web a.k.a. como eu “burlei” um sistema de agendamentos online

Neste artigo mostrarei, de maneira resumida, como automatizar a navegação em uma página web para fins diversos.

Mais especificamente: como eu fiz para conseguir um horário melhor num sistema de agendamento online através de um pequeno código que verifica novos horários vagos automaticamente. 😀

Leia mais

Melhorando o tempo de carregamento em grandes aplicações web na nuvem [vídeo]

Meu trabalho na Atlassian envolve primariamente o desempenho do JIRA na nuvem. Descrevi parte do meu trabalho no artigo Hello, JIRA, mas existem muitos detalhes complexos que acabam ficando de fora.

Neste artigo vou descrever brevemente um importante aspecto sobre desempenho de grandes aplicações web e indicar um vídeo que explica o assunto do ponto de vista do Facebook em maiores detalhes.

Leia mais

Curso de Bootstrap, jQuery e Web Standards na Fatec Sorocaba

Nos últimos dois sábados (11 e 18 de Abril de 2015) ministrei o curso Bootstrap, jQuery e Web Standards, uma breve introdução às tecnologias e boas práticas envolvidas no desenvolvimento do front end de páginas web, na Faculdade de Tecnologia de Sorocaba.

curso-bootstrap

Para este curso não preparei uma apostila ou apresentação teórica, mas uma série de exercícios práticos onde cada participante pôde construir sua própria página e seus próprios scripts, que vão desde eventos simples até manipulação do HTML e animações.

Ainda estou aprendendo a ministrar aulas e cursos. Há quem diga que para ensinar basta ter disposição. Concordo, mas estou aprendendo que existe uma grande diferença entre simplesmente transmitir conhecimento e ser um mestre, no sentido de realmente transmitir os princípios do ofício. Como sempre, apliquei uma pesquisa rápida e anônima para obter feedback. Consegui melhorar um pouco em relação a pesquisas anteriores. 😀

O guia de atividades junto com a resolução de todos os exercícios está disponível no meu Google Drive.

Fique à vontade para usar o material como desejar sob a mesma licença Creative Commons deste blog e de todos os meus materiais. 🙂

Jericho Selector, ou jQuery para Java

CSS3

É com grande satisfação que venho anunciar a publicação da primeira versão estável (stable release) da biblioteca Jericho Selector.

O Jericho Selector é uma extensão para a conhecida biblioteca de leitura e manipulação de HTML Jericho HTML Parser que permite selecionar elementos de um documento HTML ao estilo do jQuery, ou seja, utilizando seletores CSS.

Por quê Jericho? Diferente do jsoup, por exemplo, ele permite alterar documents HTML mantendo a formatação original. Outras bibliotecas reescrevem o documento inteiro. De qualquer forma, eu gosto de ter opções! 😉

Jericho Selector é totalmente livre, sob a licença MIT.

Como usar

O Jericho Selector está disponível no Repositório Central do Maven, então basta configurar o seu projeto com a seguinte dependência:

<dependency>
    <groupId>br.com.starcode.jerichoselector</groupId>
    <artifactId>jericho-selector</artifactId>
    <version>1.0.1-RELEASE</version>
</dependency>

Depois, faça a importação do método estático $ que pode ser usado como ponto de entrada para uso do Jericho Selector:

import static br.com.starcode.jerichoselector.jerQuery.$;

Então você poderá realizar consultas da seguinte forma:

$(html, "p.my-text")

O que foi feito

Antes de iniciar a implementação do Jericho Selector, tive que implementar um interpretador (parser) completo de seletores CSS. Para isso, implementei outra biblioteca que chamei de parCCSer. Ela foi baseada na especificação oficial do W3C para CSS3 e cobre praticamente todos os aspectos da especificação, exceto alguns detalhes que se aplicariam apenas no contexto de um navegador e também algo relacionado a suporte de caracteres UTF-8. Ela também está sob a licença MIT.

O Jericho Selector então utiliza a árvore de objetos gerada pelo parCCser, assim como a API do Jericho HTML Parser para consultar os elementos de um documento HTML com base em um dado seletor.

Todas as implementações possuem cobertura em testes unitários de mais de 90% do código, sem contar os casos excepcionais que o plugin de cobertura não consegue analisar.

O que precisa ser feito

Nas próximas semanas pretendo adicionar ao Jericho Selector funcionalidades de uma API fluente, análogas ao jQuery, para que seja possível realizar operações com os elementos recuperados usando lambdas e outros recursos. Métodos como closest, parentsUntil, find, each são meus primeiros alvos.

Outro ponto a melhorar é o desempenho da biblioteca. Alguns seletores específicos podem ter sua execução otimizada utilizando cache ou métodos específicos do Jericho HTML Parser como getAllElementsByClass.

O que você pode fazer

Reporte qualquer problema e sugira novas funcionalidades!

Código-fonte

Confira o código e maiores detalhes na minha página do GitHub:

https://github.com/utluiz/jericho-selector/

Depuração Avançada de JavaScript

js-logo-badge-512

Ainda me lembro do tempo em usava alerts para encontrar erros em JavaScript. :/

Hoje todo navegador tem sua ferramenta de ajuda ao desenvolvedor. Basta pressionar F12 (pelo menos no Chrome, Firefox e Internet Explorer para Windows) em qualquer página e você já pode “hackear” o HTML, CSS e JavaScript, monitorar o tempo de carregamento total ou de cada artefato pertencente ao site, depurar JavaScript linha a linha e muito mais.

Infelizmente, muitos desenvolvedores ainda estão limitados às técnicas arcaicas de depuração JavaScript que consistem praticamente em tentativa e erro, por exemplo inspecionando valores com a função alert. Alguns avançaram um pouco e usam o console.log.

Veremos a seguir algumas técnicas um tanto menos triviais para obter uma experiências mais agrádavel ao desenvolver e testar páginas web.

Depurando Javascript

No Chrome, navegador desenvolvido pelo Google e meu predileto, você pode facilmente depurar a execução de código JavaScript.

Para isso, basta acessar a aba Source da Ferramenta do Desenvolvedor. Ali é possível abrir qualquer asset como CSS e JavaScript, editá-los, salvá-los e ver o resultado da edição sem recarregar a página.

Nos fontes JavaScript você pode definir breakpoints como em qualquer IDE de desenvolvimento. Quando aquele trecho for executado, a execução será interrompida. Então você pode inspecionar variáveis e avançar a execução linha por linha.

01-breakpoint

Porém, embora a depuração com breakpoints seja uma tremenda mão-na-roda, ela nem sempre é suficiente.

Depuração Avançada

O Chrome disponibiliza através do console uma API de linha de comando que possibilita meios mais avançados para depuração. Vejamos algumas funcionalidades interessantes nos tópicos a seguir.

Monitorando eventos

Breakpoints funcionam bem, pelo menos até precisarmos depurar um caso mais complexo envolvendo eventos de teclado e mouse.

Imagine o cenário onde você está trabalhando em uma tela que outros desenvolvedores enfeitaram com vários scripts que usam eventos como mouse move, mouse up, mouse down, key up, key down, etc. Agora sobrou para você corrigir um bug em algum dos vários eventos.

Breakpoints podem não ser adequados para analisar o comportamento dos scriots, pois interromper a sua execução no meio de um evento acaba alterando o resultado final da execução, afinal outros eventos deixarão de ocorrer ou não acontecerão na sua sequência natural. Colocar logs em todos os eventos não é muito produtivo nem viável em alguns casos.

Para facilitar esta tarefa, a API do Chrome tem a função monitorEvents, que permite ao desenvolvedor listar os eventos que ocorrem em um objeto.

Um exemplo para capturar eventos do mouse na página é:

monitorEvents(document.body, 'mouse');

Após a execução deste comando, qualquer ação do mouse na página vai gerar um log no console.

02-console-monitor-events

Para desativar o monitoramento, execute o método unmonitorEvents com os mesmos argumentos de antes. Exemplo:

unmonitorEvents(document.body, 'mouse');

Listando manipuladores de eventos (listeners) associados a um objeto

Depurar uma página complexa é uma tarefa árdua. Em algumas situações, é quase impossível saber quais eventos estão associados a quais objetos. Uma função interessante da API de comandos do Chrome permite descobrir exatamente isso: getEventListeners.

O exemplo a seguir mostra os listeners associados ao documento:

getEventListeners(document);

03-listeners

Monitorando chamadas a funções

Outro caso onde breakpoints não resolvem é quando funções são executadas várias vezes em sequência. Neste caso, podemos monitorar cada chamada realizada a determinadas funções, inclusive quais argumentos foram passados, através da função monitor da API do Chrome. Basta passar o nome de uma função por parâmetro:

monitor(minhaFuncao);

Isso vai gerar um log para cada execução.

04-monitor

Para deixar de monitorar as chamadas, use a função unmonitor:

unmonitor(minhaFuncao);

Analisando o desempenho da aplicação

Você pode testar o desempenho da execução dos scripts através da aba Profile da ferramenta do desenvolvedor.

Entretanto, para testes mais específicos é interessante poder iniciar e parar a análise de consumo de CPU programaticamente. Isso pode ser feito através das funções profile e profileEnd.

Dessa forma, você pode analisar as execuções inclusive de forma intercalada:

profile("A");
profile("B");
profileEnd("B");
profileEnd("A");

05-profiles

Outros navegadores

Vale lembrar também que várias dessas funcionalidades de depuração não estão limitadas ao Google Chrome. Praticamente todos os navegadores modernos possuem uma API e um console onde você pode manipular a página.

O plugin Firebug tem uma API bem completa para depuração. Mesmo sem plugin, o Firefox disponibiliza uma API nativa. O mesmo vale para o Safari da Apple. Até o Internet Explorer tem vários métodos em sua API.

Já a API do Opera não parece contar com muitos comandos avançados, mas possui diversas utilidades.

Conclusões

Quem trabalha com front-end ou qualquer outra camada de um sistema web pode acabar, cedo ou tarde, tendo que consertar algum JavaScript.

Portanto, é sempre bom acompanhar a evolução das ferramentas de depuração de páginas web. Aprender as funcionalidades um pouco mais avançados certamente irá lhe economizar tempo e desgaste desnecessários.

Qual a diferença entre MVC action based e component based?

Frameworks Component Based

Frameworks Component Based mantém sincronia entre os estados dos componentes da view e do seu modelo de dados no lado do servidor.

Quando o usuário interage com a tela, as alterações realizadas são, em um dado momento, refletidas no modelo que fica no servidor.

No JSF, por exemplo, a “tela” é gerada por um facelet, que nada mais é que um XML que define quais componentes serão exibidos para o usuário e associam os valores desses componentes a um objeto (Java Bean) que fica no servidor. Esses componentes são então renderizados em HTML e, quando o usuário executa uma ação, o JSF atualiza os objetos no servidor.

Não encontrei uma representação visual adequada, mas algo aproximado num artigo da Caelum sobre o tema:

Component Based Request

Component Based Request

Em frameworks component based, a view é responsável por mapear valores para os beans e para o modelo. A imagem acima ilustra a ordem de chamadas:

  1. O usuário executa uma ação no sistema
  2. O front controller do framework atualiza os componentes da view com o estado atual
  3. O método do Managed Bean é chamado (usando JSF como exemplo), podendo executar alguma regra de negócio com os novos valores
  4. Finalmente, o modelo do sistema é atualizado

Frameworks Action Based

Já os frameworks Action Based não mantém necessariamente esse vínculo entre os estados do servidor e do cliente.

Isso não quer dizer que o desenvolvedor não possa armazenar estado no servidor, por exemplo, na sessão do usuário, mas que o vínculo entre o modelo e a view não é tão acoplado como no modelo Component Based.

Um framework Action Based geralmente irá receber diretamente requisições HTTP. Isso torna o modelo action based mais flexível, já que o desenvolvedor pode optar por qualquer tipo de view que gere uma requisição HTTP compatível.

Considere a ilustração a seguir (da mesma fonte anterior):

Action Based Request

Action Based Request

O resumo dos passos da execução é:

  1. O usuário executa uma ação no sistema
  2. O front controller do framework direciona a requisição e os parâmetros para um método do controller
  3. O controller lê os parâmetros necessários e executa regras de negócio que atualizam o modelo
  4. O controller “devolve” uma view para o usuário

Conclusão

Podemos dizer que os frameworks component based são mais centrados nas views (com seus componentes que mapeiam o modelo e os dados do usuário), enquanto os action based são mais centrados nos controllers (que recebem parâmetros via request).


Este artigo é baseado na minha resposta do Stack Overflow em Português

HTML e Javascript: trocando dois elementos de posição (swap)

Você tem dois elementos quaisquer na estrutura do documento da sua página (DOM). Como invertê-los, isto é, colocar um no lugar do outro?

Abordagens para solução

O problema não é tão simples quanto pode parecer a princípio, pois ao movermos um dos elementos, perdemos sua posição original e não é tão fácil armazenar a localização de um elemento em Javascript.

Vamos analisar algumas abordagens nos tópicos abaixo.

Variável auxiliar

Podemos usar o princípio básico da troca de valores entre variáveis. Por exemplo, dadas as variáveis A e B, podemos trocar os valores usando uma variável auxiliar AUX:

AUX = A
A = B
B = AUX

Simples, não? Mas estamos aqui falando da posição de elementos na estrutura HTML. O que seria nossa variável auxiliar? A resposta é: um elemento criado dinamicamente.

Colocando isso numa solução reutilizável em forma de um plugin para jQuery:

(function ($) {
    $.fn.swap = function(anotherElement) {
        var a = $(this).get(0);
        var b = $(anotherElement).get(0);
        var swap = document.createElement('span');
        a.parentNode.insertBefore(swap, a);
        b.parentNode.insertBefore(a, b);
        swap.parentNode.insertBefore(b, swap);
        swap.remove();
    }
}(jQuery));

Demo no jsfiddle

A utilização é muito simples:

$(seletorElemento).swap(seletorOutroElemento);

Puff! Os elementos foram trocados.

Armazenando a posição do elemento

Outra abordagem, que não envolve a criação de um elemento adicional, é armazenar a posição de pelo menos um dos elementos. Na verdade, só precisamos armazenar a posição do elemento que será movido primeiro.

O problema está em como representar a posição de um elemento, pois a sua inserção será feita relativamente a algum outro. Se usarmos o anterior como referência, teremos problemas se ele for o primeiro. Se usarmos o posterior, teremos problemas se ele for o último. Pior, os dois elementos a serem trocados podem estar próximos um do outro, em sequência!

Não vou descrever minuciosamente as nuances do algoritmo que desenvolvi porque não creio ser interessante, mas considere a seguinte implementação:

(function ($) {

    $.fn.swap = function(anotherElement) {

        var sameParentStrategy = function(one, another) {
            var oneIndex = one.index();
            var anotherIndex = another.index();
            var swapFunction = function(first, second, firstIndex, secondIndex) {
                if (firstIndex == secondIndex - 1) {
                    first.insertAfter(second);
                } else {
                    var secondPrevious = second.prev();
                    second.insertAfter(first);
                    first.insertAfter(secondPrevious);
                }
            }
            if (oneIndex < anotherIndex) {
                swapFunction(one, another, oneIndex, anotherIndex);
            } else {
                swapFunction(another, one, anotherIndex, oneIndex);
            }
        };

        var differentParentsStrategy = function(one, another) {
            var positionStrategy = function(e) {
                var previous = e.prev();
                var next = e.next();
                var parent = e.parent();
                if (previous.length > 0) {
                    return function(e) {
                        e.insertAfter(previous);
                    };
                } else if (next.length > 0) {
                    return function(e) {
                        e.insertBefore(next);
                    };
                } else {
                    return function(e) {
                        parent.append(e);
                    };
                }
            }
            var oneStrategy = positionStrategy(one);
            var anotherStrategy = positionStrategy(another);
            oneStrategy(another);
            anotherStrategy(one);
            return this;
        };

        //check better strategy
        var one = $(this);
        var another = $(anotherElement);
        if (one.parent().get(0) == another.parent().get(0)) {
            sameParentStrategy(one, another);
        } else {
            differentParentsStrategy(one, another);
        }

    };

}(jQuery));

Note as diferentes estratégias de troca, dependendo dos elementos serem ou não filhos de um mesmo “pai”.

Demo no jsfiddle.

Clonagem dos elementos

Uma terceira alternativa, um tanto simplista na verdade, é clonar os dois elementos e substituir os originais um pelo outro. Vejamos o código:

(function ($) {
    $.fn.swap = function(anotherElement) {
        // cache elements
        var $div1 = $(this),
            $div2 = $(anotherElement);
        // clone elements and their contents
        var $div1Clone = $div1.clone(),
            $div2Clone = $div2.clone();
        // switch places
        $div1.replaceWith($div2Clone);
        $div2.replaceWith($div1Clone);
    }
}(jQuery));

Análise das soluções

Cada abordagem parece ter seu ponto fraco. Criar um elemento auxiliar deixa qualquer um com um pé atrás por causa da criação e inserção de um elemento “inútil” ao DOM. Porém, a complexidade do algoritmo que calcula a posição dos elementos e as várias funções usadas da API do jQuery chega a dar calafrios na espinha. E quanto a clonar os elementos? Isso não pode ser algo do bem!

Note que a primeira abordagem, por ser simples e direta, pode facilmente ser implementada sem usar a API do jQuery. Usei propositalmente a API nativa do Javascript para gerar um ganho de desempenho, pois, como veremos a seguir, isso faz muita diferença. Já as demais abordagens exigiriam alterações nada triviais para depender apenas da API nativa.

Ao invés conjecturar, vamos logo ao teste de desempenho. Preparei oito cenários com os algoritmos de troca descritos acima. Para cada algoritmo, o desempenho para troca de nós que estão no mesmo nível e o desempenho para troca de nós que estão em locais diferentes do HTML foram medidos separadamente. Além disso, o algoritmo que usa a “variável auxiliar” foi desmembrado em dois: o primeiro usando a API do jQuery e o segundo, como descrito no respectivo tópico, usando a API nativa.

Considere o seguinte gráfico:

Desempenho de algoritmos de swap

A legenda para relacionar cor e algoritmo:

  • Azul escuro: nós no mesmo nível armazenando a posição do elemento.
  • Vermelho: nós em níveis diferentes armazenando a posição do elemento.
  • Amarelo: nós no mesmo nível com variável auxiliar e a API do jQuery.
  • Verde escuro: nós em níveis diferentes com variável auxiliar e a API do jQuery.
  • Roxo: nós no mesmo nível com variável auxiliar e API nativa, sem jQuery.
  • Azul claro: nós em níveis diferentes com variável auxiliar e API nativa, sem jQuery.
  • Pink: nós no mesmo nível usando clonagem.
  • Verde claro: nós de níveis diferentes usando clonagem.

Teste no jsperf

Conclusões

Os resultados confirmaram algumas das suspeitas:

  • A clonagem é muito lenta.
  • Usando a API do jQuery, armazenar o local da variável é mais rápido do que criar e inserir um elemento auxiliar.
  • Porém, usar a API nativa do Javascript gerou um ganho em torno de 10x no desempenho. Mesmo com a alteração no DOM gerada pera inserção da variável auxiliar, o desempenho desta solução superou todas as demais.

Não pretendo aqui tirar nenhum mérito de uma excelente biblioteca como o jQuery, pois em muitos casos ela nos permite obter um desempenho muito superior a uma “solução caseira”. Porém, não devemos tomar qualquer biblioteca por uma solução definitiva para todos os problemas.

Muitos cenários exigirão código específico e usar uma API nativa, de modo a remover camadas de abstração, muitas vezes é o melhor caminho.

Nós, desenvolvedores, devemos ser flexíveis o suficiente para trabalhar em diferentes níveis de abstração. Devemos saber quando “baixar” o nível… 😉

Este artigo foi baseado na minha resposta no StackOverflow em Português e nas respostas de outros excelentes desenvolvedores como Marcelo Gibson e Zuul!

Como desabilitar campos em sistemas web

Quando lidamos com sistemas web, é frequente a necessidade de desabilitarmos algum campo na página, com a finalidade de impedir a alteração por parte do usuário.

O problema é que o navegador não envia campos desabilitados com disabled na submissão do formulário. Esta característica comumente deixa os novatos atordoados.

Solução: readonly

Uma solução é usarmos o atributo readonly, porém existem efeitos colaterais:

  • Se for um campo de texto, o usuário poderá selecionar e copiar o conteúdo, o que pode ser uma vantagem em alguns casos.
  • Além disso, os estilos do campo permanecerão os originais, isto é, o usuário poderá não perceber que se trata de um campo somente-leitura.

Para contornar o segundo ponto e dar feedback visual para o usuário, adicione um estilo para manter a usabilidade, por exemplo, deixando o fundo da caixa um pouco acinzentado.

Exemplo com CSS inline:

<input type="text" value="Valor fixo" 
    style="background: #EEE; cursor: not-allowed; color: #777"
    readonly />

Exemplo com uma classe CSS:

.desabilitado {
    background: #EEE; 
    cursor: not-allowed; 
    color: #777;
}

<input type="text" value="Valor fixo 2" class="desabilitado" readonly />

Veja o exemplo funcional do código acima no jsfiddle.

Considerações de segurança

Alguém pode argumentar que desabilitar campos na tela não é uma boa prática do ponto de vista de Segurança da Informação, pois hoje um usuário pode facilmente burlar essa limitação através da Ferramenta do Desenvolvedor (F12), alterando as propriedades do campo. Isso está correto, em parte.

O importante é entender que a necessidade de desabilitar campos é parte essencial do funcionamento dos sistemas. Não é seu objetivo garantir a segurança da informação daquele campo.

Este artigo tem por objetivo auxiliar o desenvolvedor front-end com um elemento puramente visual e não isenta as validações necessárias no back-end.

Entendido!?

Este artigo foi baseado na minha resposta no StackOverflow em Português!

Executando Python (ou qualquer outra linguagem) no navegador

browsers-war-java-script-engines

Há alguns dias surgiu uma questão muito interessante no StackOverflow em Português sobre a possibilidade de executar uma linguagem arbitrária num browser, assim como é feito hoje com o Javascript.

Como poderíamos executar a nossa própria linguagem numa página web? Vamos encarar o desafio e implementar isso na prática!

Requisitos

Temos dois métodos para incluir código Javascript numa página web.

Dentro da tag <script>:

<script type="text/javascript">
alert('código javascript executando');
</script>

De uma fonte externa:

<script type="text/javascript" src="script.js"></script>

A ideia é permitir a inclusão de nossa linguagem da mesma forma.

Dentro da tag <script>:

<script type="text/nossalinguagem">
...
</script>

De uma fonte externa:

<script type="text/nossalinguagem" src="nosso-script.abc"></script>

Não estou ainda considerando código inline, como às vezes usado em eventos onclick, onchange, etc.

No entanto, existem particularidades na execução de tags <script>:

  • Elas são executadas sequencialmente, na ordem em que aparecem no HTML.
  • Se um script é incluído com o atributo src, o mesmo deve ser baixado e executado antes do navegador continuar a exibir o restante da página ou executar outros scripts. Este é o motivo pelo qual os scripts incluídos em um site devem ficar no final da página (perto do </body>) sempre que possível.

Nossa linguagem deve co-existir com o Javascript numa página HTML, como no trecho abaixo:

<script type="text/javascript">
var a = 6;
</script>
<script type="text/nossalinguagem">
print(a) # deve mostrar 6
</script>

Análise da solução

Como o navegador por padrão ignora linguagens que ele não conhece, não há problemas em declarar tags <script> com um atributo type qualquer. Entretanto, como fazer o navegador executá-lo?

Inicialmente, havia pensado que a única solução seria criar um loader em Javascript capaz de carregar a página e processar o conteúdo, adicionando programaticamente o HTML ao DOM e executando os scripts encontrados. Isso nada mais é que do uma versão do PHP ou algum template processor em Javascript, não é mesmo?

Porém, os navegadores modernos disponibilizam uma API que permite interceptar alterações na página, incluindo a criação de tags durante o carregamento do HTML. Trata-se do MutationObserver.

As possibilidades são extraordinárias!

Usando o MutationObserver

Para usar o MutationObserver, basta instanciá-lo passando em seu construtor uma função listener, a qual receberá como parâmetro as alterações na página, e invocar o método observe informando o elemento que desejamos monitorar, neste caso, todo o documento.

Vejamos um exemplo básico:

// cria instância do observer
var mutationObserver = new MutationObserver(function(mutations) {

    // itera sobre as alterações no documento
    mutations.forEach(function(mutation) {

        // recupera nós adicionados
        var addedNodes = mutation.addedNodes;

    });

});

// inicia monitoração das alterações de nós em qualquer nível do documento
mutationObserver.observe(document, { childList: true, subtree: true })

Só resta verificar se os nós adicionados são da nossa nova linguagem e delegar a execução a um interpretador.

Definindo uma linguagem: Python

Mas qual linguagem utilizar? Deveria ser uma linguagem de alto nível e poderosa, para obter um ganho de produtividade em relação ao Javascript.

Python me parece uma boa escolha!

Construindo (ou emprestando) um interpretador

O primeiro passo seria construir um interpretador em Javascript para nossa linguagem escolhida. Porém, não vamos reinventar a roda! Há diversas implementações desta linguagem em diversas plataformas. Para Javascript temos o Brython, uma implementação do Python 3.

Este projeto já cumpre parcialmente nossos requisitos quanto à interpretação da linguagem em tags <script> e acesso aos valores do Javascript. Porém, os scripts em Python são executados somente após o carregamento de toda a página através do evento onload do body e não conforme a sequência das tags <script> como desejamos.

Veja o exemplo de uso original do Brython:

<body onload="brython({debug:0, cache:'none'})">

Então vamos usarmos o MutationObserver para delegar a execução dos scripts ao Brython assim que o script for encontrado na página, oferecendo uma integração melhor da linguagem e atendendo os requisitos propostos.

Delegando a execução ao Brython

Nossa linguagem depende do interpretador Brython, então precisamos saber como delegar a execução dos scripts para ele. A má notícia é que o Brython não disponibiliza um meio direto para isso. A boa notícia é que lidamos com um projeto open source, então nada melhor que inspecionar o fonte para descobrir como ele faz isso!

Tudo o que precisamos é do trecho:

//define o nome do módulo como 'main' por que está na página principal
**BRYTHON**.$py_module_path['**main**'] = window.location.href;

//executa o analisador/conversor para Javascript 
var $root = **BRYTHON**.py2js($python_source, '**main**');

//recupera o código Javascript 
$javascript = $root.to_js();

Definindo um nome para nossa linguagem de script: Pyscript

Nossa linguagem de script merece um nome próprio. Como luizscript é muito feio, decidi calcular a equação:

Javascript – Java + Python = Pyscript

Então, nossa tag de script será:

<script type="text/pyscript"></script>

Adicionando suporte à linguagem Pyscript

Com toda a bagagem que juntamos até aqui já podemos criar a implementação do MutationObserver para dar suporte ao Pyscript em uma página web.

Vamos criar o arquivo pyscript.js:

//init brython 
brython();

//create observer instance
var mutationObserver = new MutationObserver(function(mutations) {

    //iterate over document changes
    mutations.forEach(function(mutation) {

        //get new node
        var node = mutation.addedNodes[0];

        //is it the type we want?
        if(node && node.tagName === 'SCRIPT' && node.type === 'text/pyscript') {

            //test log
            console.log('Pyscript found!');

            //python source
            var $src;

            //If src attribute is found, do a synchronous ajax to get 
            //the code in order to execute it immediately
            if (node.src!=='') {

                if (window.XMLHttpRequest){
                    // for IE7+, Firefox, Chrome, Opera, Safari
                    var $xmlhttp = new XMLHttpRequest();
                } else {
                    // for IE6, IE5
                    var $xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
                }
                $xmlhttp.open('GET', node.src, false);
                $xmlhttp.send();
                if ($xmlhttp.readyState === 4 && $xmlhttp.status === 200) {
                    $src = $xmlhttp.responseText;
                }
                if ($src === undefined) { // houston, we have a problem!!!
                    console.log('Error loading pyscript: ' + node.src);
                    return;
                }

            } else {

                //without src, source is tag content
                $src = node.textContent || node.innerText;

            }

            //python -> javascript
            __BRYTHON__.$py_module_path['__main__'] = window.location.href;
            var $root = __BRYTHON__.py2js($src, '__main__');
            var $jssrc = $root.to_js();

            //eval in global scope
            if (window.execScript) {
               window.execScript($jssrc);
               return;
            }

            //fix for old browsers
            var fn = function() {
                window.eval.call(window, $jssrc);
            };
            fn();
        } 
    });    

});

//init observer, monitoring changes in all nodes of any level
mutationObserver.observe(document, { childList: true, subtree: true });

O código acima inicia um observador (MutationObserver) para todos os elementos da página. Se uma tag <script> for encontrada cuja linguagem (atributo type) for text/pyscript, então o código será delegado ao Brython. No caso da tag ter o atributo src, nos dizendo que o código Python está em um arquivo externo, então fazemos uma chamada Ajax síncrona para recuperar o código e executá-lo imediatamente.

Aplicando numa página web

Para a utilização da nossa linguagem Pyscript em uma página qualquer, precisamos incluir tanto o Brython quanto nossa implementação do MutationObserver, nesta ordem, logo no início do código HTML.

Vejamos o exemplo:

<html> 
<head> 
<script type="text/javascript" src="brython.js"></script> 
<script type="text/javascript" src="pyscript.js"></script>

E – voilà – temos nossa própria linguagem rodando na página!

Um exemplo prático

<!DOCTYPE html>  
<html> 
<head> 
<meta charset="UTF-8"> 
<title>Pyscript Test Page</title>

<!-- init brython and pyscript --> 
<script type="text/javascript" src="brython.js"></script> 
<script type="text/javascript" src="pyscript.js"></script>

<!-- set javascript variable --> 
<script type="text/javascript"> 
var value = 1; 
</script>

<!-- python: print text and javascript variable --> 
<script type="text/pyscript"> 
print('Print Test!!!') 
print(value) 
</script>

</head>

<body> 
    <div id="content">Conteúdo</div> 
</body>

<!-- python: browser interaction --> 
<script type="text/pyscript"> 
from _browser import doc, alert

alert('Variable value: ' + str(value))

mylist = ['items', 'of', 'mylist'] 
doc['content'].text = 'Content from python: ' + ' '.join(mylist) 
</script>

<!-- python: execute external script --> 
<script type="text/pyscript" src="myscript.py"></script>

</html>

Nesta página, após incluir nossas dependências no início, existem várias tags de script:

  • A primeira executa Javascript que inicializa a variável value.
  • A segunda executa Python e imprime a variável value no console do navegador.
  • A terceira executa Python e interage com o navegador, exibindo uma caixa de alerta e manipulando o conteúdo (texto) do elemento <div id="content">.
  • A quarta e última executa Python a partir de um arquivo externo.

O conteúdo do arquivo myscript.py é bem simples. Vejamos:

d = { '11': 'um', '22': 'dois' }
for i in d: print(i, '=', d[i])

O script acima cria um “dicionário” d (dict) e então exibe chaves e valores de seus elementos num loop for.

Voltando um pouco ao terceiro script, note que importamos a biblioteca _browser, pertencente ao Brython. Ela nos dá uma interface para acessar as funcionalidades do navegador de forma simples e direta.

É importante observar que o Brython não contém todas as bibliotecas em seu script principal. Para cada import é feito o download da respectiva biblioteca, a não ser que a mesma já tenha sido carregada em um import anterior ou incluída numa tag <script>.

Assista ao vivo

Quer ver o exemplo funcionando? Ele está disponível no link http://utluiz.github.io/pyscript/!

Nota: não se esqueça de acessar a ferramenta do desenvolvedor (F12) para verificar o console!

Quer o código-fonte? Ele está disponível na minha conta do Github: https://github.com/utluiz/utluiz.github.io!

Algumas considerações sobre desempenho

A esta altura, um desenvolvedor mais experiente já estaria se coçando todo sobre a questão do desempenho dessa solução.

Existem iniciativas das empresas que desenvolvem navegadores (Google, Mozilla, etc.) para criar soluções que melhoram o desempenho do Javascript. Hoje, executar código no navegador não é mais um problema. Mas será que isso é suficiente para suportar algo tão profundo quanto uma nova linguagem?

Obviamente, a conversão de código Python para Javascript é muito lenta! Por outro lado, o Brython coloca o código-fonte convertido em um cache para não precisar realizar o processo novamente. Ao analisar o código-fonte do Brython me deparei com soluções de armazenamento usando a API local storage do HTML5, então mesmo se a página for recarregada a conversão não será feita novamente para o mesmo bloco de script.

Mas e quanto à execução? Se o código final executado é em Javascript, então o navegador consegue fazer as mesmas otimizações que no código nativo! Bem, mas isso não significa que será tão eficiente quanto código nativo, afinal existe um overhead natural, pois cada instrução Python pode gerar várias instruções em Javscript.

Limitações da solução

Devido a limitações do Brython, o código Javascript não consegue acessar os objetos e variáveis definidos no código Python. De qualquer forma, não consigo ver isso como uma grande necessidade.

Outra limitação é quanto a código inline mencionado no início do artigo. Por enquanto, não podemos fazer algo como no código abaixo:

<button onclick="python: print(1)">Botão</button>

Não que seja impossível. Poderíamos manipular o valor do atributo onclick dentro do MutationObserver, substituindo-o por uma função Javascript que delega a execução do código para o Brython! Bem, já está meio tarde, então deixo isso como lição de casa. 😉

De qualquer forma, essa limitação é facilmente contornada removendo o atributo onclick e adicionando um listener ao respectivo evento através de código Python, como nos é informado na própria documentação do Brython:

btn.bind('click', show)

Compatibilidade com navegadores

Segundo a documentação, o MutationObserver é compatível com o seguintes navegadores:

  • Google Chrome: versão 26 em diante
  • Mozilla Firefox: versão 14 em diante
  • Internet Explorer: versão 11 em diante
  • Opera: versão 15 em diante
  • Safari: versão 6 em diante

Como sempre, o mais “atrasadinho” é o Internet Explorer. Porém, estamos olhando para o futuro. Em alguns anos a maioria dos usuários do IE poderá executar nossa solução sem dificuldades.

A documentação do Brython não informa sobre sua compatibilidade, mas olhando o código-fonte encontrei implementações específicas para IE 6 e 7. Portanto, a limitação deve ficar mesmo com o MutationObserver.

Considerações finais

Na prática, creio ainda não ser viável aplicar esse tipo de solução em aplicações reais. Por outro lado, é um ótimo exercício!

O que podemos esperar do futuro? Muitos desenvolvedores estão ansiosos por rodar suas linguagens prediletas no navegador!

Hoje JVMs como a do Java já executam diversas linguagens como Scala, Ruby, Python, PHP e assim por diante. Quem sabe, em breve, poderemos testemunhar a ascensão da liberdade de linguagem dentro dos navegadores web!

Bootstrap, uma biblioteca CSS

O desafio

Implementar sistemas baseados na web pode se tornar uma tarefa difícil se for necessário sempre começar sem um layout bem definido.

O grande problema em criar um interface “do zero” é que ela provavelmente terá que passar por muitas iterações até ficar estável e o escopo dela será limitado. Sem falar nas diferenças entre navegadores que exigem hacks nos estilos e no javascript para funcionar de forma semelhante em todos eles.

Criar um layout reaproveitável e que se adapte a muitas situações é um grande desafio e muitas tentativas já foram feitas, muitas das quais vinculadas a uma tecnologia específica.

Uma solução

Há algum tempo descobri que os desenvolvedores do Twitter, pensando em todos esses desafios, criaram uma biblioteca usando CSS e um pouco de javascript para facilitar a vida dos desenvolvedores. Ela chama-se bootstrap, ou seja, a ideia é dar um ponta-pé inicial e evitar que se reinvente a roda a todo momento.

Esta biblioteca traz componentes web reaproveitáveis, muito flexíveis, fáceis de usar e desvinculados de qualquer tecnologia específica do lado do servidor.

O layout gerado por essa biblioteca é muito bem pensado e consegue se adaptar a todo tipo de dispositivo, desde monitores grandes até tablets e celulares em retrato ou paisagem. E o melhor de tudo é que ela é compatível com a grande maioria dos navegadores atuais:

  • Internet Explorer 7 ou superior
  • Google Chrome
  • Mozilla Firefox
  • Opera
  • Safari

Exemplificando tudo isso, basta conferir o visual de uma das telas de um sistema que fiz usando o bootstrap. O CSS específico do sistema possui apenas 150 linhas. Todo o restante foi reutilizado do bootstrap: barras fixas de navegação, botões, submenus, hints, cores, tabelas, campos e ícones.

sistema-bootstrap

Tecnologias envolvidas

bootstrap faz uso da tecnologia LESS (dynamic stylesheet language), que é uma linguagem que gera folhas de estilo CSS, mas possui variáveis e funções, permitindo personalizar o CSS final com suas cores prediletas e outras customizações de forma automática.

É impressionante a capacidade atual do CSS 3 em gerar interfaces bonitas e “responsivas”, o que significa que com um único CSS a página web adapta-se a diferentes orientações e tamanhos de telas dinamicamente, sem necessidade de código.

A parte de javascript que complementa a biblioteca baseia-se no jQuery e dá suporte ao dinamismo que foge ao escopo do CSS, por exemplo, ao capturar eventos e posicionar os “Pop Overs” (uma espécie de hint mais elaborado, com título) sobre os elementos.

Quer testar?

Acesse o link http://twitter.github.com/bootstrap/ para ver toda a documentação auto-exemplificada que utiliza a própria bibliteca.

Para usar é preciso apenas saber html e um pouco de CSS.

Conclusão

bootstrap é ótimo para criar sistemas rapidamente e vai evitar que muitos programadores criem sistemas em amarelo com bolinhas marrons ou fiquem “amarrados” com um determinado tipo de layout da tecnologia utilizada.

Entretanto é uma biblioteca generalista e não substitui os designers que  produzem layouts para necessidades específicas.

Na verdade, designers podem conviver muito bem e até utilizar bibliotecas desse tipo a fim de evitar retrabalho, personalizando aquilo que for necessário.

Página 1 de 2

Creative Commons O blog State of the Art de Luiz Ricardo é licenciado sob uma Licença Creative Commons. Copie, compartihe e modifique, apenas cite a fonte.