homeASCIIcasts

43: AJAX con RJS 

(view original Railscast)

Other translations: En

Other formats:

Written by Andrea Salicetti

In questo episodio useremo RJS per aggiungere un po’ di funzionalità AJAX al sito. RJS è il modo più semplice per usare AJAX con Rails, specialmente se occorre aggiornare più di un elemento nella pagina.

Nella pagina dei prodotti del nostro sito, gli utenti possono aggiungere opinioni su un prodotto mediante una form. Quando viene fatto il submit della form, viene eseguita una richiesta HTTP POST al server remoto e la pagina viene ricaricata.

La nostra pagina dei prodotti che mostra le opinioni.

La nostra pagina dei prodotti che mostra le opinioni.

Dopo che la form è stata inviata cambiano diversi elementi nella pagina originaria: il testo che mostra il numero di opinioni è cambiato, l’opinione è stata aggiunta alla lista, la form è stata resettata e c’è un messaggio in cima alla pagina che ringrazia l’utente per avere aggiunto l’opinione. Tutto ciò dovrà essere aggiornato dal nostro template RJS dopo che avremo AJAXificato la form.

Modifiche alla vista

Prima di cominciare ad aggiornare il codice della vista, dobbiamo sincerarci che stiamo includendo prototype e gli altri file standard JavaScript. Per farlo, aggiungiamo questa linea alla sezione <head> del file di layout:

<%= javascript_include_tag :defaults %>

Cominciamo ora a modificare il codice della vista. Ecco come appare ora, secondo una forma piuttosto standard per una vista Rails:

<% form_for [@product, @review] do |form| %>
<ol class="formList">
  <li><%= form.label :name, "Name:" %> <%= form.text_field :name %></li>
  <li><%= form.label :content, "Review:" %> <%= form.text_area :content, :rows => 5 %></li>
  <li><%= submit_tag "Add comment" %></li>
</ol>
<% end %>

Per fare in modo che la form invii una richiesta AJAX è sufficiente sostituire form_for nella prima riga con form_remote_for. Rails fornisce un numero di metodi che rendono i ‘normali’ elementi di una form in elementi AJAX-enabled; per maggiori informazioni è disponibile una lista di tali metodi sul sito delle API di Rails1.

Dopo avere eseguito la modifica ed aggiornata la pagina, il tag di apertura della form avrà un attributo onsubmit che consentirà il submit asincrono della form stessa. Se il sito fosse utilizzato da qualcuno che non avesse il JavaScript abilitato sul browser, la form continuerebbe a funzionare, ma alla stessa maniera in cui funzionava prima (cioè con una normale POST HTTP).

<form action="/products/1/reviews" class="new_review" id="new_review" method="post" 
onsubmit="new Ajax.Request('/products/1/reviews', 
{asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); 
return false;”>

Modifiche al controller

La form dell’opinione viene inviata ad un action chiamata create nel controller delle opinioni. Questa action ha il compito di creare una nuova opinione, impostare una notifica flash e ridirigere indietro alla pagina dei prodotti:

def create
  @review = Review.new(params[:review])
  @review.product_id = params[:product_id]
  @review.save
  flash[:notice] = "Thanks for your review!"
  redirect_to product_path(params[:product_id])
end

Il redirect non funzionerebbe con una richiesta AJAX, per cui modifichiamo il metodo per fare in modo che il controller risponda in maniera differente a seconda di quale effettiva richiesta gli arriva (HTTP oppure JavaScript). Il metodo respond_to ci permette di intervenire su questa cosa. Prende un blocco in cui mettiamo il codice per ogni formato possibile. Sostituiamo il redirect_to nella action precedente con questo:

respond_to do |format|
  format.html { redirect_to product_path(params[:product_id]) }
  format.js
end

Ora, con questo codice, la redirect agirà ancora correttamente per le richieste che giungono alla action mediante HTTP, ma non ci sarà per quelle inoltrate mediante AJAX. Poichè non c’è codice associato al blocco js, si ricadrà direttamente ad un template RJS.

Creazione di un template RJS

Il file di template va messo nella cartella /app/views/reviews e, poichè è eseguito dalla action create, dovrà chiamarsi create.rjs. Il file RJS produrrà il JavaScript e lo restituirà al client in risposta alla richiesta AJAX.

La prima cosa che dobbiamo fare è aggiungere una nuova opinione alla lista. Le opinioni sono renderizzate sottoforma di lista ordinata per id di review. Ogni opinione è creata in un partial chiamato _review.html.erb. Il codice RJS per aggiungere la nuova opinione in fondo alla lista è il seguente:

page.insert_html :bottom, :reviews, :partial => 'review', :object => @review

Il codice mostrato usa insert_html per aggiungere HTML alla pagina. Gli argomenti che prende indicano:

La nuova opinione sarà aggiunta alla lista al submit della form, ma il testo che mostra il numero di opinioni non sarà aggiornato. Aggiungiamo dell’altro codice RJS per farlo. Questa volta sostituiremo dell’HTML già esistente piuttosto che aggiungerne di nuovo, per cui useremo la replace_html:

page.replace_html :reviews_count, pluralize(@review.product.reviews.size, 'Review')

Alla replace_html occorre fornire l’id dell’elemento da aggiornare e il nuovo contenuto. Passiamo un testo usando lo stesso metodo pluralize usato nel codice della vista. Non avendo visibilità della variabile @product che abbiamo usato nel caso della vista, dobbiamo prenderci il prodotto associato all’opinione e il numero di opinioni che ha.

Vogliamo anche resettare la form una volta che una nuova opinione è stata aggiunta. La form ha per id "new_review" e possiamo usare l’oggetto page per passare un metodo JavaScript ad essa nel seguente modo:

page[:new_review].reset

Infine, dobbiamo mostrare un messaggio flash. Come abbiamo fatto per l’elemento che mostra in numero di opinioni, possiamo di nuovo usare la replace_html per mostrare il messaggio:

page.replace_html :notice, flash[:notice]

Ultimo problema

La nostra form ora funziona come volevamo e le opinioni possono essere aggiunte senza la necessità di fare una POST completa e ricaricare l’intera pagina. C’è ancora un piccolo problema da risolvere, comunque. Se si esegue un refresh della pagina a seguito di un inserimento, il messaggio flash rimarrà visibile, benchè scompaia ad un ulteriore refresh. Questo accade a causa del meccanismo in cui funziona la messaggistica flash: il messaggio si mantiene per una richiesta.

Il modo per aggirare questo problemino consiste semplicemente nello scartare il flash subito dopo che lo si è passato al JavaScript per il rendering. E’ dunque sufficiente aggiungere un’altra linea al nostro file RJS:

flash.discard

Ora il messaggio flash non apparirà nuovamente a seguito di un refresh della pagina o di una navigazione in un’altra, conseguente all’aggiunta di un’opinione.

La nostra pagina si comporta ora esattamente come prima, ma senza la post back. Il file RJS finale appare così:

page.insert_html :bottom, :reviews, :partial => 'review', :object => @review
page.replace_html :reviews_count, pluralize(@review.product.reviews.size, 'Review')
page[:new_review].reset
page.replace_html :notice, flash[:notice]
flash.discard
  1. http://api.rubyonrails.org/classes/ActionView/Helpers/PrototypeHelper.html
  2. In Rails 2.3 tutto ciò può essere abbreviato con :partial => @review.