terça-feira, junho 26, 2007

"Auto-complete" Avançado em Rails

Atenção! Nerdice avançada abaixo! Este post discute como implementar campos com "auto-complete" avançado em Rails. Se você não tem a mínima idéia do que seja um "auto-complete" básico, recomendo que leia este tutorial.

O Rails vêm com duas bibliotecas de Javascript para facilitar a implementação de interfaces AJAX em suas aplicações: a Prototype (para requisições assíncronas básicas) e a Script.aculo.us para "efeitos especiais".

Um dos efeitos especiais mais apreciados pelo usuário é o tal "auto-complete", onde ele começa a digitar algo em um campo de texto e vê, magicamente, um menu aparecer logo abaixo com várias alternativas que ele pode escolher com o mouse ou as setas do teclado, fazendo com que o valor do campo seja completado automaticamente com a opção escolhida. De acordo com o Agile Web Development with Rails, a reação inicial do usuário ao usar isso pela primeira vez é algo entre surpresa e admiração. O mesmo livro explica como usar os helpers do Rails para implementar o efeito.

Os helpers ajudam mesmo, mas como tudo no Rails eles cuidam apenas do caso mais comum. Fazer algo mais complexo vai tomar alguns minutos do seu tempo.

Por exemplo, ontem eu precisei de um "auto-complete" que preenchia vários campos da mesma página, ao invés de apenas um. Basicamente, o usuário escolhia o nome de um objeto, e os campos eram preenchidos com informações desse objeto. Eis como isso foi feito:

Um dos elementos de um auto-complete básico, como pode ser visto nos bons tutoriais on-line, é um partial contendo o template para o fragmento de HTML retornado pela chamada ao auto-complete (normalmente a lista de nomes). Ele se parece com algo do tipo:

<ul>
<% @objects.each do |object| %>
<li><%= object.name %></li>
</ul>


A primeira coisa a fazer é alterar o fragmento acima para que retorne os demais atributos dos quais precisamos, mas que ainda assim só mostre o nome para o usuário.

<ul>
<% @objects.each do |object| %>
<li>
<span><%= object.name %></span>
<span class="hidden"><%= object.outro_atributo %></span>
<span class="hidden"><%= object.e_mais_outro %></span>
...
</li>
</ul>




Na sua folha de estilo, você coloca algo do tipo "span.hidden { display: none }" , fazendo com que os attributos marcados como hidden acima não apareçam.

E, para finalizar, inclua o método abaixo entre seus helpers customizados:


def custom_auto_complete( field_id, div_id, url, fields )
assignments = []
fields.each_with_index do |field, i|
assignments << "$('#{field}').value = dn[#{i}].firstChild.nodeValue;"
end
<<-END
<script type="text/javascript">
new Ajax.Autocompleter( '#{field_id}',
'#{div_id}',
'#{url_for( url )}',
{ updateElement: function( selected ) {
Element.cleanWhitespace( selected );
dn = selected.childNodes;
#{assignments.join("\n")}
}
} );
</script>
END
end



O método acima funciona de maneira parecida com o helper padrão, apesar de aceitar uma variedade menor de parâmetros. O que ele faz é gerar um bloco de Javascript que cuida das funções de auto-complete avançadas. Os parâmetros que ele deve receber são:

field_id - o identificador (valor do atributo "id") do campo "primário", ou seja, aquele em que o usuário digita as informações.
div_id - o identificador do "div" que vai mostrar as opções.
url - um hash do tipo que se passa ao método url_for, com a URL da qual serão recuperadas as opções.
fields - um array contendo os mesmos nomes dos campos colocados no partial acima, na mesma ordem.

Basta usar o helper acima na hora de implementar o "auto-complete" avançado:

<%= custom_auto_complete(...) %>

Nenhum comentário: