Um monte de gente tem falado sobre Unobtrusive Javascript (ou UJS, para simplificar), mas há poucos que mencionam COMO fazer. Da mesma forma, o próprio Rails parece não ser muito decidido às melhores práticas (o que, sinceramente, é uma pena dada a idéia original do Rails de facilitar o desenvolvimento web). Aliás, talvez algumas mudanças do Rails valeriam para outro post, mas por hora, vamos para UJS.
A idéia do UJS, basicamente, é não misturar Javascript com HTML nas views. O que, sinceramente, é uma ótima idéia ao meu ver. O problema é como fazer isso de forma não-traumática. Para este exemplo, vamos ver inicialmente qual era a abordagem que eu usei, no passado, sobre UJS:
Antes, eu usava algum helper do Rails e criava as tags tipo <a href="algumaUrl" data-remote="true>, e no controller eu pedia para renderizar um template .js.erb. Isso trazia diversos problemas, por exemplo:
$('div').html("<%= @user.name %>");
Mas se o usuário tiver espaços no nome, precisaremos de "escape_javascript"… e aí começam alguns antipatterns (imagine HTML+ERB, mas dessa vez com todos os problemas de Javascript incorporados).
O ideal, e algo que tanto o Twitter como outros serviços web estão fazendo, é manter uma API web que renderiza os dados, e seu ambiente web nada mais é do que uma interface que consome esses dados (e renderiza algo na tela). Para esse exemplo, só vou usar jQuery e nada de Rails/Ruby/Whatever…
Bom, primeira coisa que faremos será montar a interface que fará as chamadas ajax. Com o jQuery, colocamos num arquivo ujs.js o seguinte:
jQuery(function() { $(document).on("click", "a[data-remote=true]", function() { var element=$(this); var url = element.attr('href'); var trigger = element.attr('data-event'); var fn; if(trigger) { fn = function(data) { element.trigger(trigger, data); }; } $.get(url, fn); return false; }); });
Ok, o que exatamente isso faz? Bom, primeiro lugar, faz um "listen" em todos os objetos "a" (ou seja, links) que possuem o atributo "data-remote" como "true". A função "on", do jQuery, basicamente diz para o documento escutar os eventos "click" para todos os objetos achados que atendem ao CSS Selector (no nosso caso, "a[data-remote=true]") que existem e que eventualmente vão aparecer na página. Isso é muito útil para nosso caso.
Segundo, a variável "trigger", que recebe a propriedade "data-event". Basicamente, cada elemento pode trazer um "trigger", isto é, registrar um callback que será chamado quando a chamada Ajax terminar de rodar. Para tal, usaremos OUTRO javascript, este específico para cada página HTML, aonde customizaremos e determinaremos como cada chamada Ajax se comporta.
(bonus points no código acima, cada elemento pode ou não trazer uma trigger que será chamada quando ele for clicado. Se não for passada a trigger, o conteúdo do Ajax é interpretado, como se fosse um Javascript)
Já no arquivo "ujs.html":
&lt;!DOCTYPE HTML&gt; &lt;html&gt; &lt;head&gt; &lt;script type=&quot;text/javascript&quot; src=&quot;http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js&quot;&gt;&lt;/script&gt; &lt;script type=&quot;text/javascript&quot; src=&quot;ujs.js&quot;&gt;&lt;/script&gt; &lt;script type=&quot;text/javascript&quot; src=&quot;custom_script.js&quot;&gt;&lt;/script&gt; &lt;/head&gt; <pre><code>&amp;lt;body&amp;gt; &amp;lt;a href=&amp;quot;first.json&amp;quot; data-remote=&amp;quot;true&amp;quot; data-event=&amp;quot;updateDiv&amp;quot;&amp;gt;Click Here&amp;lt;/a&amp;gt; &amp;lt;div id=&amp;quot;foo&amp;quot;&amp;gt; &amp;lt;/div&amp;gt; &amp;lt;/body&amp;gt; </code></pre> &lt;/html&gt;
Nenhuma novidade aqui, apenas montamos uma página que possui um link que rodaremos via Ajax. Como teste, vamos criar o arquivo "first.json", com o conteúdo {"name": "Foo Bar", "src": "http://google.com"}.
Para este exemplo, achei interessante fazer a atualização da tag "div", com um link com os dados do JSON. Logo, criamos um arquivo "custom_script.js", com:
jQuery(function() { $(document).on('updateDiv', 'a', function(event, data) { var div = $('div#foo'); div.html("<a href='"+data.src+"'>"+data.name+"</a>"); }); });
Ao rodar a página "ujs.html", vemos nosso produto em ação. Para os que usam Chrome/Chromium, é provável que a página não renderize o Ajax, então seria ideal rodá-lo sobre um webserver (um simples, é apenas digitar "python -m SimpleHTTPServer" na raíz do projeto).
Ok, explicando então: o que isso faz?
Primeiramente, a página ujs.html inicia os dois javascripts. No ujs.js e no custom_script.js, quando a página termina de carregar, o jQuery faz o "bind" dos eventos. Terminado isso, quando alguém clica no botão, primeiramente a trigger é do evento "click" – que, basicamente, chama o Ajax e dispara a trigger correta. Depois, o que vai ser chamado é a trigger definida pela tag "a" – no nosso caso, updateDiv, que cria uma tag "a".
A graça da coisa é que, como os eventos escutados estão no objeto "document", novas tags que forem criadas depois que a página for carregada automaticamente funcionam também.