JavaScript Discreto (UJS) e AJAX no Ruby on Rails

E ai pessoal, hoje estou aqui para ensinar como utilizar JavaScript discreto (unobtrusive JavaScript - UJS) no Ruby on Rails para implementação AJAX.

Vamos supor que temos um scaffold simples gerado pelo Rails e desejamos implantar requisições assíncronas via JavaScript.

scaffold-simples-rails

Poderiamos criar um arquivo JavaScript e com a ajuda de algum framework como jQuery fazer uso das funções apropriadas para isso..

Mas nosso objetivo aqui é utilizar o JavaScript discreto que vem por padrão com o Rails desde a versão 3.

JavaScript Discreto

As versões antigas do Rails, utilizavam-se de um JavaScript inline (junto com HTML) e dependente de recursos do framework Prototype para implementação de funcionalidades como submissão de formulários de maneira assíncrona. Isso deixava o código mais sujo e não muito manutenível.

Para resolver esse problema passou-se a utilizar uma biblioteca chamada Unobtrusive JavaScript que permite a geração de código mais limpo e bem organizado utilizando recursos de HTML 5. Existem versões para vários frameworks JavaScripts (como jQuery, Prototype e MooTools), assim o desenvolvedor não precisa ficar preso há um framework como era antes.

Utilizando UJS para AJAX

Agora vamos ver como utilizar a biblioteca que citamos acima para AJAX.

Primeiro de tudo, precisamos garantir duas coisas:

  • O layout da aplicação está em HTML 5: seu HTML deve começar com <!DOCTYPE html> .
  • Temos incluso o jQuery e o UJS para jQuery no layout: se você tiver <%= javascript_include_tag "application" %> no layout já estará sendo incluso pelo Rails.

Já temos o que precisamos para começar, que tal agora passarmos o método de exclusão do scaffold para AJAX?

Na listagem de registros temos o seguinte código gerado pelo Rails:

<% @posts.each do |post| %>
  <tr>
    <td><%= post.name %></td>
    <td><%= post.content %></td>
    <td><%= post.date %></td>
    <td><%= link_to 'Show', post %></td>
    <td><%= link_to 'Edit', edit_post_path(post) %></td>
    <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>

Irei dizer ao Rails para que o método de exclusão seja chamado de forma assíncrona, apenas adicionando o trecho remote: true, veja:

1
2
3
4
5
6
7
8
9
10
<% @posts.each do |post| %>
  <tr class="<%= post.id %>">
    <td><%= post.name %></td>
    <td><%= post.content %></td>
    <td><%= post.date %></td>
    <td><%= link_to 'Show', post %></td>
    <td><%= link_to 'Edit', edit_post_path(post) %></td>
    <td><%= link_to 'Destroy', post, method: :delete, remote: true, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>

Observe na linha 2 que adiciono uma classe para a tr de cada registro com o id, isso será usado para remover a linha com JavaScript depois que o registro for excluído.

Olhando o código fonte, veremos que o Rails adiciona um atributo data-remote com o valor true em nosso link, isso é o que a biblioteca UJS precisa saber para fazer a requisição via AJAX.

usando-ujs-rails

Se você for na listagem de registros e clicar no link Destroy verá que a requisição já está sendo de forma assíncrona, só que como o Rails ainda não sabe como manipular ela, obtemos um erro de rota.

erro-de-rota-rails

Para resolver isso, basta dizermos ao Rails como responder as chamadas feitas via JavaScript adicionando format.js no respond_to da action que está sendo requisitada. Com isso nossa action destroy ficará assim:

1
2
3
4
5
6
7
8
9
10
def destroy
  @post = Post.find(params[:id])
  @post.destroy

  respond_to do |format|
    format.html { redirect_to posts_url }
    format.json { head :no_content }
    format.js
  end
end

Esse trecho adicionado fará com que o Rails procure por um arquivo com o nome da action e com a extensão .js.erb na pasta de views, nesse caso, ele procurará por destroy.js.erb. Dentro desse arquivo podemos colocar código JavaScript e Ruby que serão executados depois da chamada assíncrona ter sido executada.

Em meu arquivo destroy.js.erb eu adicionei o código abaixo responsável por excluir a linha do registro excluído:

$('tr.<%= @post.id %>').remove();

Callbacks AJAX via Eventos Customizados

Imagina que desejamos aplicar exclusão via AJAX para todos CRUDs de nossa aplicação, existe uma maneira mais fácil de executarmos algo no callback da requisição assíncrona invés de ter que criar um arquivo .js.erb para cada action.

As chamadas AJAX do UJS nos fornece os seguintes callbacks para as requisições assíncronas:

  • ajax:before: antes da chamada AJAX
  • ajax:loading: antes da chamada AJAX, mas depois da criação do objeto XmlHttpRequest
  • ajax:success: chamada AJAX executada com sucesso
  • ajax:failure: chamada AJAX que falhou
  • ajax:complete: chamada AJAX terminada (executada depois do ajax:success e ajax:failure)
  • ajax:after: depois da chamada AJAX ter sido feita (antes do retorno)

Na action podemos definir que não queremos nenhuma renderização quando a requisição vier por JavaScript:

1
2
3
4
5
6
7
8
9
10
def destroy
  @post = Post.find(params[:id])
  @post.destroy

  respond_to do |format|
    format.html { redirect_to posts_url }
    format.json { head :no_content }
    format.js { render nothing: true}
  end
end

Então criamos um bloco JavaScript no application.js responsável por tratar todos callbacks de chamadas AJAX com sucesso por exemplo:

$(function(){
  $('.delete-link').on('ajax:success', function() {
      $(this).parents('tr:first').remove();
  });
});

Nesse exemplo, para cada link de exclusão que for via AJAX será necessário adicionar a classe delete-link para passar pelo callback que removerá a linha do registro removido.

Essa foi a dica de hoje. O que vocês acharam?

Até mais.

Written on February 23, 2013

Share: