29/12
2009

Todos estes anos trabalhando com desenvolvimento web e eu nunca havia implementado um autocomplete. Após uma rápida busca o candidato óbvio foi o jQuery Autocomplete plugin. A implementação foi tranquila. O autocomplete em questão puxa uma lista de aeroportos de um arquivo JSON.

O problema da acentuação

Infelizmente o plugin é rígido ao tratar caracteres. O comportamente desejado era que os mesmos resultados fossem devolvidos digitando-se, por exemplo, “são” ou “sao” (São Paulo e outros resultados apareceriam neste caso). Isso não acontecia. Ao digitar sao, “São Paulo” não era um dos resultados. Entretanto, percebi que o plugin não diferenciava caixa alta e baixa: SÃO e são retornam o mesmo resultado.

A gambiarra

Modificar plugins é uma péssima prática por vários motivos:

  • um bug pode ser introduzido (e só percebido depois)
  • se uma nova versão do plugin for lançada, a modificação tem que ser refeita
  • pior: a modificação pode não funcionar numa nova versão

Plugins bem feitos geralmente fornecem formas de personalização do comportamento através de objetos de configuração. Em algumas situações, como no nosso caso, isso não é possível. Vasculhei o código do plugin pelo método toLowerCase (conversão de caixa alta para caixa baixa):

177
178
179
180
if( data[i].result.toLowerCase() == q.toLowerCase() ) {
    result = data[i];
    break;
}
255
256
if (!options.matchCase)
    currentValue = currentValue.toLowerCase();

Continuei buscando e encontrei ocorrências da conversão em mais cinco ou seis lugares. Então criei um método prototype para o objeto String chamado filterData:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
String.prototype.filterData = function() { 
 
    var
    str = this.toLowerCase(),
    specialChars = [
        {val:'a', let:'áàãâä'},
	{val:'e', let:'éèêë'},
	{val:'i', let:'íìîï'},
	{val:'o', let:'óòõôö'},
	{val:'u', let:'úùûü'},
	{val:'c', let:'ç'},
    ],
    regex;
 
    for (var i in  specialChars) {
	regex = new RegExp('[' + specialChars[i].let + ']', 'g');
	str = str.replace(regex, specialChars[i].val);
	regex = null;
    }
 
    return str;
};
 
// Exemplo de uso
alert('Isto é um teste. NÃO podemos ter acentos nem CAIXA ALTA!'.filterData());

Finalmente, para encerrar a gambiarra, substitui no código do plugin todas as ocorrências de toLowerCase para filterData:

177
178
179
180
if( data[i].result.filterData() == q.filterData() ) {
    result = data[i];
    break;
}
255
256
257
258
if (!options.matchCase)
    currentValue = currentValue.filterData();
 
// e em todo o resto do plugin

E funciona! Vou tentar entrar em contato com o autor do plugin sugerindo esta funcionalidade para a próxima versão.

Observação: utilize UTF-8

Para esta modificação funcionar a página HTML deve ter o seu charset declarado como UTF-8. O charset ISO-8859-1 não funciona. Sei disso porque a página original teve que ser alterada para UTF-8. Com ISO-8859-1 buscas com acentos não retornavam resultado algum. Mistério, não tenho idéia porque isso ocorre.

4 comentários até agora

Comente!
  1. Dá pra trocar o seu for por:

    $(specialChars).each(function(i){
    regex = new RegExp(‘[' + this.let + ']‘, ‘g’);
    str = str.replace(regex, this.val);
    regex = null;
    });

    ;)

  2. Oi, tem alguma coisa de errado na String.prototype.filterData , quando digito lim
    ele me retorna lundefinedm, testei no chrome.

    antes:lim
    depois:lundefinedm
    fiz um console.log antes do for e depois do for.

  3. Editar o arquivo bsn.AutoSuggest_2.1.3.js procurar por encodeURIComponent() e remover apenas isto e irá funcionar a acentuação.
    ficando assim:
    var url = this.oP.script((this.sInp));
    antes era:
    //var url = this.oP.script(encodeURIComponent(this.sInp));

  4. Olá.

    Eu estava com problemas com acentuação em um sort de tabela. Com o filterData a coisa funcionou.

    Valeu!!!