quarta-feira, abril 19, 2006

Captura de teclas em Javascript - Parte 1

A sigla Web 2.0 tem sido muito utilizada recentemente. Talvez seja o nome mais marketeiro depois de AJAX. Aliás Web 2.0 depende muito de AJAX. O fato é que softwares Web-based com comportamentos muito semelhantes à aplicações desktop tem pipocado pela internet, e com certeza os exemplos mais impressionantes e eficientes são os produtos lançados pela Google, como o Google Maps, Google Calendar, Google Reader, entre outros.  E de fato, o grande barato da Web 2.0 é tentar mudar o paradigma de uso da internet, tornando a experiência do usuário mais rica e interativa (detesto essa expressão "experiência rica", mas no momento não me veio nada mais apropriado), fazendo com que a web deixe de ser um conjunto de hipertextos, e passe a ser um conjunto de serviços.

Neste esforço de deixar as interfaces web mais interativas, dinâmicas e eficientes, a captura e tratamento de eventos de teclado pode ser uma característica desejável e desejada. A boa notícia é que fazer isso é relativamente simples, usando Javascript. Existem basicamente 3 tipos de eventos de tecla que se pode capturar: keyDown, keyPress e keyUp. Os eventos keyDown e keyUp são disparados respectivamente no momento de descida e subida da tecla, e o evento keyPress é disparado quando a tecla está pressionada. Portanto quando uma tecla é pressionada, os eventos são disparados na ordem keyDown, keyPress e keyUp.

É interessante notar que a forma como os eventos é disparada varia ligeiramente dependendo do S.O: no Linux, os eventos keyDown e keyUp são disparados apenas uma vez por evento de tecla, e keyPress é disparado várias vezes, enquanto a tecla estiver pressionada (não sei bem qual a taxa de disparo, mas é bastante rápida). Já no Windows (tanto no IE quanto no Firefox), apenas o evento onKeyUp é disparado apenas uma vez por evento. Os eventos keyDown e keyPress são disparados sequencialmente enquanto a tecla estiver pressionada (ou seja, caso você mantenha a tecla apertada, o sistema irá lançar a seqüência keyDown-keyPress-keyDown-keyPress....). Não tive a oportunidade de testar no Mac OS, o que é uma folha imperdoável já que eu só uso Mac em casa, mas testarei assim que possível. Esta pequena diferença faz com que a estratégia de captura seja diferente  dependo do evento desejado. As seguinte situações me vêem à cabeça:

  1. O sistema deseja capturar apenas um evento por tecla pressionada. No Linux pode se usar onKeyDown ou onKeyUp. No Windows, apenas onKeyUp. Portanto, para que o script seja funcional em todos os sistemas, a melhor escolha é onKeyUp.

  2. O sistema deseja capturar repetidamente uma tecla pressionada, para simular por exemplo o funcionamento de uma tecla pressionada numa caixa de texto (imprime várias vezes o caractere pressionado). Neste caso, a melhor escolha é onKeyPress, que funciona nos dois SOs. Mas é preciso tomar cuidado no caso do Windows:  caso o handler de onKeyDown estiver habilitado também, o sistema irá capturar duas vezes o evento, o que pode ser problemático.

  3. O sistema deseja capturar uma vez a descida da tecla, e uma vez a subida. Isso pode ser útil para medir o tempo de pressão da tecla. No caso  do Linux, é muito simples: um handler para keyDown e um  handler para keyUp, e ambos eventos serão disparados apenas uma vez. No caso do Windows, a estratégia é mais complicada, uma vez que onKeyDown será lançado repetidas vezes. Eu não tenho uma solução em mãos, mas deve ser necessário fazer um controle de qual tecla foi apertada, e não executar caso o evento ja tenha sido processado.

  4. O sistema deseja capturar a descida da tecla uma vez, processar a tecla pressionada várias vezes, e capturar a subida da tecla uma vez.  Tanto no caso do Windows quanto do Linux, me parece que a melhor solução é criar um handler para cada evento, mesclando as situações 2 e 3. Em ambos os casos, a ordem de execução será keyDown, keyPress e keyUp. No caso do windows, será necessário utilizar a mesma solução da situação 3 para evitar que keyDown seja processado várias vezes.
Como eu não sei qual o tamanho máximo de um artigo neste blog, vou deixar para o próximo post a descrição de como escrever o código para capturar estes eventos.

terça-feira, abril 18, 2006

Boot Triplo

Num post recente, falei sobre o BootCamp da Apple, que permite instalar um sistema Dual Boot (Mac OS e Windows XP) nos novos Macs com processadores Intel. E deixei no ar a seguinte pergunta: será que alguém já descobriu um esquema para Triple Boot ? ! O Wiki do OnMac tem uma receita para instalar Gentoo no MacBookPro. Não foi testado com outras distros nem outros modelos (iMac e Mini), mas isso é uma questão de tempo.


segunda-feira, abril 17, 2006

Ambiente de desenvolvimento

Há um tempo atrás eu pensei em escrever um post descrevendo o ambiente de desenvolvimento que eu uso para escrever aplicações Web em Java. Basicamente Eclipse+Tomcat+Ant. A idéia era fazer um passo a passo, explicando os motivos das esolhas, características e vantagens do setup utilizado. Confesso que nunca escrevi por pura preguiça. Mas hoje resolvi escrever algo sobre ambientes de desenvolvimento de forma mais genérica. Dedico ao meu colega Raphael, que vive dizendo que ama emacs, mas vira e mexe me pentelha porque não consegue trabalhar direito com Eclipse e Tomcat.

Primeira má notícia: configurar ambiente de trabalho pode ser longo e chato. Sobretudo se  estiver trabalhando com  Linux e ferramentas livres. IDEs pagas em geral são mais bem documentadas e portanto mais fáceis, o que aliás justifica o  preço pago. E mesmo assim, tem que se perder um tempinho pra deixar o pacote do jeito desejado. Segunda má notícia: ter um bom ambiente de desenvolvimento é condição sine qua non para ter uma produtividade adequada.

Não vou entrar no mérito de quais ferramentas são as melhores. Isso é muito pessoal, e vai do gosto de cada um...tentar discutir isso é entrar numa guerra de crenças quase religiosas. Até hoje não se chegou a um consendo sobre qual editor é melhor: VI ou Emacs. Eu prefiro Emacs, porque até hoje sou incapaz de abrir um arquivo e editar o seu conteúdo em VI.  O fato é que existe uma grande quantidade de setups adequados para se desenvolver uma aplicação desde que bem configurados. Do meu ponto de vista, uma configuração básica deve ter um esquema de controle de versão, um bom editor de código (se possível com syntax highlight, auto-complete, sistema de refactoring,  busca eficiente), e um bom sistema para compilação, testes, integração e distribuição do aplicativo. No melhor dos mundos, cada uma dessas ações deve ser executada com apenas um comando (one step build, uma das características definidas por Joel Spolsky para medir a qualidade de uma empresa de desenvolvimento). O compilador pode ser  javac, gcj, jikes, gcc. O builder pode ser o ANT, Make, Shell Script. Sistema de versão pode ser CVS, subversion, ou qualquer outro que exista ou que venha a existir e que eu não conheço. Editor tem aos montes: eclipse, emacs, jEdit, VI, entre outros. Não importa muito qual o conjunto de ferramentas utilizado, desde que seja eficiente, compartilhado por toda a sua equipe de desenvolvimento,  e possa ser usado a qualquer momento sem nenhuma grande complicação: ter que modificar comandos manualmente durante o processo ou tentar montar isso emergencialmente pode ser uma fonte de erros e dores de cabeça.

Portanto, conselho de amigo: antes de mais nada, defina quais ferramentas você prefere, assuma essa escolha, e passe um tempinho deixando tudo funcionando perfeitamente. Isso com certeza irá poupar muito tempo mais tarde.

quarta-feira, abril 12, 2006

Divs e combos no IE

Apo sto que muitos desenvolvedores Web já tiveram o seguinte problema com Internet Explorer: criam um DIV (por exemplo, em um menu contextual, ou um tooltip), que aparece bonitinho na tela, mas que para seu desespero fica embaixo de uma caixa de seleção (o famoso combo box). O pior é quando se descobre isso um mês depois, quando um cliente resolve redimensionar a janela, e o combo box em questão vai parar no meio da área visível do DIV. Quem já teve este problema, sabe que mudar o z-index não adianta nada, porque no IE o combobox fica acima de tudo, não levando em conta o parâmetro de profundidade. Graças a algumas pesquisas do meu colega Christian, encontramos uma solução para isso: basta criar um IFRAME e colocar exatamente embaixo do DIV, com as mesmas coordenadas e tamanho e com zindex do IFRAME inferior ao do DIV. Por algum motivo que me escapa, o IFRAME encobre o COMBOBOX, e mesmo assim aceita o parâmetro de profundidade, exibindo o DIV de forma correta.

quinta-feira, abril 06, 2006

StringBuffer em Javascript

Quem programa em Java sabe que operações de concatenações de String são muito mais eficientes utilizando StringBuffer e função append do que usando o comando String A = String B + String C. Isso porque a cada operação de soma dessas, uma nova String é alocada para que seja feita a cópia dos dados, e alocação de memória em Java é uma operação cara.

Em Javascript, esse problema também ocorre. Pesquisando na Internet, descobri um código que simula o comportamento de um StringBuffer em nessa linguagem. Fizemos alguns testes no IE e no firefox, com um loop que concatena Strings. No Firefox, a diferença entre usar o operador + e a função append é pequena. A grande vantagen é que a segunda é mais estável em termos de performance que a primeira.

Em compensação, no IE o uso de append é absurdamente mais eficiente do que no firefox. Testes mostram que para concatenar 1000 Strings de tamanho 100, o append era 60 vezes mais rápido. Portanto, seu uso é altamente recomendável em scripts que façam muitas concatenações e que precisam ser otimizados.

Segue o código:


function StringBuffer() {
this.buffer = [];
};

StringBuffer.prototype.append = function append(string) {
this.buffer.push(string);
return this;
};

StringBuffer.prototype.toString = function toString() {
return this.buffer.join("");
};
Para usar, basta criar uma variável do tipo StringBuffer:

var buffer = new StringBuffer();

Dual boot em macs

"Os Macs usam uma tecnologia de indústria ultra-moderna chamada EFI para cuidar do booting da máquina. Infelizmente, o Windows XP, e mesmo o não lançado Vista, estão presos nos anos 80 com a antiga BIOS. Mas com o Boot Camp, o Mac podem operar suavemente em ambos os séculos" Esta frase, citada no artigo da IDG Now, é da própria Apple. Sim, a empresa do tio Jobs desenvolveu uma solução oficial (BootCamp) para executar Windows XP em Macs Intel Based, liberada algumas semanas depois da solução desenvolvida por um hacker. Ou seja: é oficial, poderei rodar Flight Simulator no meu Mac. Cool!!!!! Agora so falta termos uma versão de Linux pros novos Macs (já temos ? não sei), e pronto....3 em 1.

terça-feira, abril 04, 2006

Envio de grandes conjuntos de dados com AJAX

Hoje, totalmente por acaso, descobri alguns macetes para envio e recepção de grandes quantidades de dados com AJAX, como por exemplo textos. Estou trabalhando em um sistema que permite que o usuário cadastre pareceres sobre informações do sistema, e estes pareceres não tem limitação de tamanho. O sistema foi implementado com AJAX, para permitir que os pareceres sejam carregados dinâmicamente, e gravados assincronamente. O primeiro problema que tive foi que os textos enviados eram sempre truncados em 4096 bytes. A primeira reação foi verificar se estava enviando os dados via POST ou GET: o correto é enviar por POST, uma vez que GET tem tamanho limitado. Os dados realmente estavam sendo enviados por POST, que a priori não tem limitação alguma. Pesquisando um pouco, descobri que precisava definir um header Content-length com o tamanho do conteúdo do texto, antes de enviar a requisição. O comando correto é
requestObject.open('POST', url, true);
requestObject.setRequestHeader('Content-Type',
                             'application/x-www-form-urlencoded');
if (parameters != null)
    requestObject.setRequestHeader("Content-length",
                                   parameters.length);
requestObject.send(parameters);
Bom, eu não sou um expert do protocolo HTTP, mas a minha explicação seria que de fato o método POST aceita uma quantidade ilimitada de dados, mas por default ele define o tamanho máximo como 4096, para evitar erros. Caso o usuário deseje enviar mais, deve informar o tamanho total. Resolvido este problema, tive um problema semelhante ao ler os dados para exibir na tela. Meu sistema abre uma requisição AJAX, que envia um XML contendo o texto do parecer inserido pelo usuário. Para recuperar o texto, executava o seguinte comando:
var value = decodeURIComponent(
                 root.getElementsByTagName('value')[0].
                                       firstChild.data);
Este código retornava o texto corretamente no IE, mas cortava em 4096 bytes (novamente !) no Firefox. O mais estranho é que tanto no envio quanto na recepção, o XML gerado estava correto. Portanto o problema estava na leitura do CDATA do XML. Fuçando um pouco (desta vez não encontrei nada na internet), descobri que em caso de CDATA com mais de 4096 bytes, o DOM do Firefox considera que a tag value possui mais de um nó filho, cada um com no máximo 4K. Assim, o código correto para obter o texto em ambos os navegadores é
for(var i=0; i i++)
     value += decodeURIComponent(
                     root.getElementsByTagName('value')[0]
                           .childNodes[i].data);