Mostrando postagens com marcador AJAX. Mostrar todas as postagens
Mostrando postagens com marcador AJAX. Mostrar todas as postagens

segunda-feira, dezembro 17, 2007

Chamadas Periódicas Sem Atropelo

Recentemente, precisei criar uma página que fazia chamadas periódicas ao servidor, para saber se um determinado processamento longo havia acabado, e carregar os resultados no caso positivo. No Rails, a melhor maneira de se fazer isso é com a função periodically_call_remote, que dispara uma chamada AJAX em intervalos regulares. Mas, aí, como é de praxe, encontrei um pequeno problema quando o código foi para o ambiente de integração. As chamadas são assíncronas (o primeiro A do AJAX), acontecendo a cada X segundos, mas a chamada que finalmente carregava os resultados demora mais de X segundos para ser completa. Então, no meio do carregamento, uma outra chamada era iniciada e terminava justo a tempo de substituir a maravilhosa tela de resultados pela tela de "por favor espere".

Tornar as chamadas síncronas resolvia este problema, mas fazia o browser congelar completamente nos n*X segundos que a resposta demorava para carregar. Então, a saída que eu acabei utilizando foi criar uma variável de "estado", manipulada pelos callbacks do Prototype, para garantir que apenas uma chamada assíncrona executasse por vez.

Este é o resultado:



<script>
var stop_polling = false;
var polling = false;
</script>
<%= periodically_call_remote(
:url => ping_search_search_results_url(@search),
:method => :get,
:frequency => 3,
:condition => "stop_polling == false && polling == false",
:after => "polling = true",
:complete => "polling = false"
) %>


A primeira variável ("stop_polling") tem seu valor mudado pelo servidor quando os resultados ficam prontos. A segunda é alterara pelo próprio script para impedir que uma nova chamada seja executada enquanto a anterior ainda está esperando sua resposta. Com isso, o browser do usuário não congela.

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(...) %>