quarta-feira, 19 de março de 2008

GreaseMonkey para feed do InfoBlogs no GoogleReader

Quem assina o feed do InfoBlogs deve ter o mesmo problema que eu. O link deste feed não é para o post em si, ele é para uma cópia do post na página do próprio InfoBlogs. Particularmente acho isso irritante, pois só costumo entrar no blog do autor quando o post é interessante e desejo comentá-lo. Nesse caso, preciso aguardar o carregamento (que é bem lento) da página do InfoBlogs para só depois poder clicar e ver o post original, onde eu posso comentar.

Para resolver esse problema implementei um script no GreaseMonkey para remontar a página do GoogleReader com os links corretos. Para quem não sabe o GreaseMonkey é uma extensão do Firefox, o melhor navegador existente na atualidade, que permite ao usuário configurar scripts para serem rodados ao término do carregamento de uma determinada página. Eu falei brevemente dele no post em que falo sobre as extensões do Firefox essenciais para um desenvolvedor web, e agora com este exemplo ficará mais fácil entender como ele funciona.

O código do script encontra-se abaixo. O que ele faz é verificar a cada 3 segundos se houve alguma alteração de estado no GoogleReader. Para então mudar os links caso haja necessidade. Só há uma ressalva, ele não funciona para o modo de visualização de lista, só a visão expandida. O motivo é que não gosto da visualização em lista e portanto não me interessei em testar nela :P
// ==UserScript==
// @name GoogleReaderGM
// @namespace http://www.google.com/reader
// @include http://www.google.com/reader/*
// ==/UserScript==

function GoogleReaderGM() {}
GoogleReaderGM.prototype = {
isChanged: function() {
var e = document.getElementById("entries");
var ds = e.getElementsByTagName("div");
if( this._oldUrl != window.location.href ) {
this._oldUrl = window.location.href;
this._oldQtds = ds.length;
return true;
}
if( this._oldQtds != ds.length ) {
this._oldQtds = ds.length;
return true;
}
return false;
},
mudaLinksInfoBlogs: function() {
if( this.isChanged() ) {
var e = document.getElementById("entries");
var as = e.getElementsByTagName("a");
for( i=0; i<as.length; i++ ) {
var a = as[i];
if( a.className=="entry-title-link" && a.href.indexOf("www.infoblogs.com.br/view.action")!=-1 ) {
a.href = a.href.replace("view","redir/go");
}
}
}
}
}

var gr = new GoogleReaderGM();

setInterval(function() { gr.mudaLinksInfoBlogs(); }, 3000);
Como pode ser visto eu defino como um comentário JavaScript em quais páginas esse script será executado. No caso, todas aquelas abaixo de http://www.google.com/reader.

O interessante é que se você quiser pode utilizar esse código como template para executar outras funcionalidades no GoogleReader. Basta adicionar mais alguma função, e utilizando isChanged você fica sabendo se alguma entrada nova apareceu na área que exibe as entradas dos feeds. Isso é claro se você utilizar a visão expandida (Expanded view), pois o método isChanged só foi feito pra ela.

quinta-feira, 13 de março de 2008

Escapando textos em funções de javascript executadas em links

Para determinar a execução de uma função javascript no clique de um link temos duas possibilidades. Ou utilizamos o próprio atributo href da tag a com o protocolo javascript: ou adicionamos o atributo onclick fazendo a chamada à função.

Contudo o comportamento de ambos não é totalmente igual. Principalmente quando chamamos funções que passam como argumento algum texto escapado com a função escape. Normalmente usa-se esse escape para evitar error de javascript devido ao uso de aspas simples e duplas nos textos.

Veja abaixo dois exemplos de cógigo com o erro por não usar escape. O primeiro com aspas simples, que faz com que o javascript ache que o texto terminou antes do que deveria e o segundo com aspas duplas fazendo o navegador acreditar que o atributo terminou:
var textoComAspaSimples = "Quero um copo d'água";
var s = '<a href="javascript:alert(\'' + textoComAspaSimples+ '\')">Clique aqui</a>';
document.writeln(s);

var textoComAspaDupla = 'Você está com "sede"?';
var s = '<a href="javascript:void(0)" onclick="alert(\'' + textoComAspaDupla+ '\')">Clique aqui</a>';
document.writeln(s);
Para evitar esses erros uma boa idéia é utilizar a função escape. No entanto isso não funciona direito por href. Neste atributo o navegador interpreta o código escapado causando o mesmo erro. Isso pode ser observado no exemplo a seguir, no primeiro link gerado o erro acontece, no segundo não:
var textoComAspaSimples = "Quero um copo d'água";
var s = '<a href="javascript:alert(\'' + escape(textoComAspaSimples) + '\')">Clique aqui href</a><br/>';
s += '<a href="javascript:void(0)" onclick="alert(\'' + escape(textoComAspaSimples) + '\')">Clique aqui onclick</a>';
document.writeln(s);
O problema é que com essa solução a função que recebe este parâmetro precisa executar uma transformação reversa utilizando a função unescape. Caso contrário veremos algo parecido com o que o segundo link do exemplo acima mostra: Quero%20um%20copo%20d%27%C3%A1gua.

Implementando a transformação reversa na função que recebe teríamos algo como o mostrado abaixo. Uma nova função que recebe o parâmetro escapado e o transforma para utilizá-lo:
function escapedAlert(s) {
alert( unescape(s) );
}
var textoTodoErrado = "Sentes \"sede\"?\nQuero um copo d'água";
var s = '<a href="javascript:void(0)" onclick="escapedAlert(\'' + escape(textoTodoErrado) + '\')">Clique aqui onclick</a>';
document.writeln(s);
Mas isso ainda não é o ideal pois continuaríamos sem poder utilizar a função dentro do atributo href, e além disso precisaríamos sempre preparar as funções para receber parâmetros escapados. Uma outra alternativa é criar nossa própria função de escape. Como é mostrado no exemplo abaixo:
function escapeForLink(s) {
if(s) {
var r = s.split("\n").join("\\n");
r = r.split("\r").join("\\r");
r = r.split("'").join("\\u0027");
r = r.split('"').join("\\u0022");
return r;
}
return s;
}
var textoTodoErrado = "Sentes \"sede\"?\nQuero um copo d'água";
var s = '<a href="javascript:alert(\'' + escapeForLink(textoTodoErrado) + '\')">Clique aqui href</a><br/>';
s += '<a href="javascript:void(0)" onclick="alert(\'' + escapeForLink(textoTodoErrado) + '\')">Clique aqui onclick</a>';
document.writeln(s);
Com essa função os problemas de escape para passagem de parâmetro de texto em funções executadas apartir de links fica resolvido.

quarta-feira, 27 de fevereiro de 2008

Component do VRaptor só pode ter um construtor público

Nem tudo são flores com o framework MVC VRaptor. Uma classe anotada com Component só pode ter um construtor público. Senão o seguinte erro é exibido na inicialização do container web:
org.vraptor.config.ConfigException: cannot load classes
...
Caused by: org.vraptor.component.InvalidComponentException:
com.globo.ekzameno.vraptor.QuestionarioLogic component has 2 accessible constructors.
This is not desirable as it may reflect optional arguments and create complex attributes.
Ok, ok, isso não é o fim do mundo para o VRaptor, mas me atrapalhou um pouco com os testes unitários, pois eu pretendia fazer injeção de dependência por construtor. Como esse framework não me permite isso, terei que fazer por método set. Ou seja, ao invés de fazer assim:
public TiagoLogic() {
this(new RepositorioImpl());
}
public TiagoLogic(Repositorio repositorio) {
this.repositorio = repositorio;
}
Terei que fazer assim:
public TiagoLogic() {
setRepositorio(new RepositorioImpl());
}
public void setRepositorio(Repositorio repositorio) {
this.repositorio = repositorio;
}
Não é um grande problema, só questão de preferência mesmo. Prefiro por construtor.

NoClassDefFoundError de Expectations no VRaptor 2


Atenção, não consegui mais replicar o problema descrito neste post. Provavelmente a necessidade da classe não era do VRaptor.


Comecei a utilizar o VRaptor em um projeto pessoal e na primeira tentativa obtive um erro que foi simples corrigir, mas que achei pertinente postar aqui porque não encontrei no google ninguém com o mesmo problema e porque a documentação deste framework não informava nada sobre.

O erro que obtive foi o seguinte:
javax.servlet.ServletException: Servlet.init() for servlet vraptor2 threw exception
Cuja causa foi:
java.lang.NoClassDefFoundError: org/jmock/Expectations
Para resolver isso bastou incluir no classpath da aplicação o jar do JMock. Esse jar não é incluso no blank-project oferecido no site do VRaptor, por isso acho que outras pessoas poderão passar pelo mesmo problema.

terça-feira, 26 de fevereiro de 2008

Problema do gets com uso de parametros na linha de comando em ruby

Brincando um pouco com ruby me deparei com um problema que parecia um pouco exotérico. Utilizando a função gets num script que recebe parâmetros por linha de comando, o seguinte erro era exibido:
tiago@tiago-laptop:~/Projetos/testeRuby$ ruby Pessoa.rb Tiago
Pessoa.rb:14:in `gets': No such file or directory - Tiago (Errno::ENOENT)
Esse mesmo erro não ocorria quando não era enviado nenhum argumento na linha de comando. Pesquisando sobre o assunto no Google, encontrei uma discussão que clareou minhas idéias. O Ruby herda o comportamento da função gets do Perl, ou seja, se houver um parâmetro na linha de comando, essa função assume que este parâmetro é o path de um arquivo e portanto tenta abrí-lo.

Para resolver essa questão basta deixar claro que se deseja que a função gets recupere apenas o texto digitado. Na verdade ao invés de usarmos gets puro devemos usar o do STDIN da seguinte maneira STDIN.gets.

quarta-feira, 30 de janeiro de 2008

Tela cheia em flash no Globo Vídeos Player

Subiu agora pouco uma nova versão do Globo Vídeos contendo diversas melhorias e algumas correções pontuais. A maioria dessas alterações poucos irão perceber, pois são problemas que somente o controle de qualidade interno tem a paciência de procurar. Mais uma das melhorias sem duvida será muito bem notada, o botão de tela cheia.

Na versão anterior, que usava Windows Media Player, a funcionalidade de tela cheia existia para os usuários de Internet Explorer. Quando entrou o player em flash ela foi removida. Portanto muitos usuários reclamaram de sua remoção.

Acontece que fazer o full screen no flash não é uma tarefa trivial devido as diversas versões de flash que teríamos que suportar. Assim preferimos implementar uma versão mínima do player antes do Big Brother, para posteriormente implementar essas pequenas melhorias. Se tivessemos optado por lançar já com um player completo, talvez só pudéssemos entregá-lo ao final do Big Brother.

Eu particularmente acredito que tomamos a decisão correta. Apesar de termos perdido por um mês a funcionalidade de tela cheia, milhares de usuários que não conseguiam ver vídeos, por causa da M que é o Windows Media Player, agora conseguem. E melhor ainda, agora também têm o botãozinho de tela cheia.

segunda-feira, 14 de janeiro de 2008

Repassando parâmetros rest para funções que esperam parâmetros rest

No ActionScript 3 existe um parâmetro especial, comumente chamando de parâmetro rest, que permite que uma função receba "infinitos" parâmetros. O Java, apartir da versão 5, também possui essa facilidade, que é chamada de varargs. Veja abaixo o exemplo de uma função e o logo abaixo o uso dela:
//Funcao que mostra todos os parametros
public function mostra(... numeros) {
if( numeros && numeros.length > 0 ) {
for each( var i in numeros ) {
trace(i);
}
}
}
//Uso da funcao:
mostra(1);
mostra(1,2,3);
mostra(1,2,3,5,8);
Apesar de ser um recurso muito bom, a forma como ela foi implementada no ActionScript nos traz um problema no momento em que precisamos repassar esses parâmetros para uma outra função que também receba parâmetros opcionais rest.

Isso acontece porque quando a primeira função é chamada, ela é informada com os parâmetros separados por vírgula, mas ela os recebe como um array. Então se você enviar este array para o segundo método, ele será considerado apenas um parâmetro. No Java há um tratamento mais esperto, de forma que quando enviado um array o compilador não permite que mais nenhum parâmetro seja colocado na chamada à função.

Para ilustrar melhor vamos pensar num cenário que mostrei no post passado sobre como testar funções que executem javascript. No caso era uma função proxy para isolarmos a execução de ExternalInterface.call:
public function executarJavaScript(funcao:String, ... parametros) {
try {
return ExternalInterface.call(funcao,parametros);
} catch(erro:Error) {
return null;
}
}
No caso, a função call de ExternalInterface também espera receber parâmetros opcionais, mas como estamos mandando um array, ele achará que estamos mandando apenas dois parâmetros, um com o nome da função javascript e outro com um array. No Google é fácil encontrar uma solução POG e muito feia para isso. Nada mais é que uma cadeia megazord de ifs:
public function executarJavaScript(funcao:String, ... p) {
try {
if( p.length == 0 ) return ExternalInterface.call(funcao,null);
else if( p.length == 1 ) return ExternalInterface.call(funcao,p[0]);
else if( p.length == 2 ) return ExternalInterface.call(funcao,p[0],p[1]);
else if( p.length == 3 ) return ExternalInterface.call(funcao,p[0],p[1],p[2]);
else if( p.length == 4 ) return ExternalInterface.call(funcao,p[0],p[1],p[2],p[3]);
} catch(erro:Error) {
return null;
}
}
Ô coisa feia!

A solução mais elegante para esse problema é utilizar os recurso de reflexão do ActionScript. Ou seja, vamos utilizar o método apply da classe Function, que aguarda os parâmetros como um array, exatamente como recebemos na função ao utilizar um parâmetro rest. Veja como ficaria:
public function executarJavaScript(funcao:String, ... parametros) {
try {
var f:Function = ExternalInterface.call;
var parametrosEnviar:Array = new Array(funcao);
parametrosEnviar = parametrosEnviar.concat(parametros);
return f.apply(null,parametrosEnviar);
} catch(erro:Error) {
return null;
}
}
No caso, concatenamos num array de parâmetros a serem enviados a função e os parâmetros rest. Ao enviá-los pela função apply eles são enviados como se fossem diversos parâmetros separados por vírgula e o método call os recebe corretamente como parâmetros rest.