#205 Unobtrusive Javascript
- Download:
- source codeProject Files in Zip (163 KB)
- mp4Full Size H.264 Video (18.8 MB)
- m4vSmaller H.264 Video (13.4 MB)
- webmFull Size VP8 Video (35 MB)
- ogvFull Size Theora Video (25.6 MB)
In questo episodio della nostra serie sulle nuove funzionalità di Rails 3, daremo uno sguardo all’utilizzo del cosiddetto "unobtrusive JavaScript". Unobtrusive JavaScript è una tecnica volta a separare il comportamento di una applicazione web dal suo contenuto in un modo simile a quello che permettono di fare i CSS fra contenuto e presentazione. Prima di partire, ad usare l’unobtrusive JavaScript in Rails, vediamo un piccolo esempio di cosa si intende, con un semplice documento HTML.
Lo screenshot sotto mostra una pagina web che ha un link. Al click sul link, viene mostrato un alert JavaScript che mostra la stringa “Hello world!”:
La pagina HTML appare così:
<!DOCTYPE html> <html> <head> <title>UJS Example</title> </head> <body> <h1><a href="#" onclick="alert('Hello world!'); return false;">Click Here</a></h1> </body> </html>
Nella pagina c’è un link con l’attributo onclick che contiene un po’ di JavaScript. Dal momento che tutto la script è contenuto inline assieme all’ HTML, non è unobtrusive ed è una cosa brutta, perchè significa che stiamo mischiando contenuto e comportamento. Negli anni ’90, le pagine web erano spesso progettate usando l’elemento <font> per impostare i caratteri, la dimensioni e i colori dei testi, perchè allora non c’era CSS. Questo voleva dire (allora), che se occorreva modificare la dimensione di tutti i paragrafi di testo del sito, potenzialmente si dovevano fare centinaia di modifiche. Quando i browser hanno cominciato a supportare CSS, si è potuto portare questo genere di informazione relativa agli stili al di fuori della pagina, nei fogli di stile, rendendo così i siti molto più semplici da mantenere.
La stessa cosa si può dire per il JavaScript. Inserire pezzetti di JavaScript fra gli attributi degli elementi HTML di una pagina mischia di concetti e rende i siti web difficili da aggiornare. Spostare il JavaScript al di fuori, in un file separato, riduce le duplicazioni, rende possibili i refactoring e soprattutto rende molto più semplice scrivere e debuggare applicazioni web complesse.
Come rendere unobstrusive il JavaScript del nostro semplice esempio? Il passo fondamentale è quello di spostare il JavaScript contenuto al momento come valore dell’attributo onclick in un file separato e usare un framework JavaScript, in questo caso jQuery, per collegare gli script agli eventi sugli elementi. Vi mostreremo ora la pagina modificata per poi spiegare le modifiche fatte:
<!DOCTYPE html> <html> <head> <title>UJS Example</title> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript" charset="UTF-8"></script> <script type="text/javascript"charset="UTF-8"> $(function () { $('#alert').click(function () { alert('Hello, world!'); return false; }) }); </script> </head> <body> <h1><a href="#" id="alert">Click Here</a></h1> </body> </html>
La prima cosa da notare è che a dispetto di quanto detto poc’anzi, non abbiamo spostato il JavaScript al di fuori in un file separato, ma ciò è stato fatto solamente per scopi didattici, per fare in modo che fosse più semplice confrontare le modifiche.
Nell’elemento link nel body HTML abbiamo rimosso l’attributo onclick
e lo abbiamo sostituito con un id
, in modo tale da essere in grado di riferire in modo univoco quell’elemento all’interno dell’intero documento da codice jQuery. Poi, nella sezione head dell’HTML. abbiamo aggiunto un riferimento alla libreria jQuery e, sotto questo riferimento, lo script che dovrebbe essere in un file esterno. Lo script parte chiamando la funzione jQuery $
con una funzione come argomento. La funzione in argomento sarà chiamata quando il DOM avrà terminato di caricarsi, e il codice in essa contiene una linea di codice jQuery che trova l’elemento link per id e gli associa una funzione al suo evento di click. Tale funzione contiene l’alert che era prima inline con l’HTML e poi restituisce false in modo tale che il link non venga seguito.
Se ricarichiamo la pagina ora, si comporterà esattamente come prima, con l’alert mostrato al click sul link.
Potrebbe sembrare che abbiamo fatto in questo caso un sacco di lavoro per poca cosa, ma il semplice esempio qui riportato non rende giusto merito ai vantaggi dell’unobtrusive JavaScript, dal momento che siamo passati, in effetti, da una linea di Javascript a sei. L’esempio in realtà non aveva come obiettivo quello di evidenziare i vantaggi di questa tecnica, quanto piuttosto far vedere come viene realizzata. I benefici dell’unobtrusive JavaScript non saranno in realtà evidenti fintanto che la nostra pagina non conterrà un po’ più di JavaScript, poichè solo allora l’avere tutti gli script in un file separato e la possibilità di rimuovere le duplicazioni saranno palesi.
Un problema di questo approccio è il fatto che il JavaScript solitamente risiede in un file JavaScript statico. Come possiamo inserire del contenuto dinamico lato server nel JavaScript ora che non possiamo più farlo inline?
In HTML 5 si possoni usare attributi dato personalizzati per memorizzare le informazioni relative ad un elemento della pagina. Questi particolari attributi si comportano esattamente come ogni altro attributo, con la sola particolarità di avere un nome che comincia per data-
. Per salvare un messaggio che va mostrato quando un determinato link viene cliccato, in un attributo data possiamo riscrivere il link in questo modo:
<a href="#" id="alert" data-message="Hello from UJS">Click Here</a>
Nel JavaScript possiamo modificare l’alert per mostrare il testo proveniente dal nostro nuovo attributo:
$(function () { $('#alert').click(function () { alert(this.getAttribute('data-message')); return false; }) });
Se ricarichiamo la pagina ora, vedremo il messaggio proveniente dal data attribute.
Come Rails 3 usa i data attribute
Rails 3 usa questi data attribute personalizzabili nel suo approccio all’unobtrusive JavaScript come un modo per passare dati al JavaScript. Daremo ora uno sguardo a come tutto ciò si applichi ad un’applicazione Rails 3. La nostra applicazione di esempio è una semplice applicazione di e-commerce che ha una lista di prodotti che possono essere cercati. Ci sono anche i link per modificare e cancellare i prodotti ed è quando si prova a cancellare un prodotto che scopriamo un problema, in quanto il link sembra non funzionare:
Questo genere di problema è comune nelle applicazioni Rails 3. Se vi state spostando da una versione precedente di Rails, avrete potuto notare come alcune parti dell’applicazione che usano JavaScript non funzionino più.
Il codice nella vista che genera il link “Destroy” è un metodo link_to standard con una opzione :confirm inserita per mostrare un alert JavaScript di conferma confirm
e con una opzione :method
che è impostata a :delete
in modo tale che la richiesta sia inoltrata sottoforma di DELETE anzichè come GET.
<%= link_to "Destroy", @product, :confirm => "Are you sure?", :method => :delete %>
Ciò che qui è interessante è l’HTML generato:
<a href="/products/8" data-confirm="Are you sure?" data-method="delete" rel="nofollow">Destroy</a>
In Rails 2 l’utilizzo del link_to
per creare un collecamento di cancellazione link genera un sacco di JavaScript inline per creare la popup di conferma ed una form che possa simulare una richiesta DELETE piuttosto che PUT. Confrontando questo codice con l’equivalente Rails 3, si nota come quest’ultimo sia molto più pulito e faccia uso dei data attribute HTML 5 visti prima, creandone uno chiamato data-confirm
che contiene il messaggio di conferma ed un altro chiamato data-method
che contiene il nome del metodo.
Il motivo per cui il link non funziona è che mancano i riferimenti agli opportuni file JavaScript nella sezione head; per come è ora, il link si comporta esattamente come un link standard ed esegue una richiesta GET, dal momento che non c’è nulla che indichi di fare altrimenti.
Per risolvere questo problema, dunque, è sufficiente aggiungere le seguenti due linee nella sezione head del file di layout della nostra applicazione:
<%= javascript_include_tag :defaults %> <%= csrf_meta_tag %>
La prima linea di sopra dovrebbe esservi familiare. Serve ad includere i file JavaScript standard per un’applicazione Rails. La seconda linea crea due meta tag che contengono il token di autenticazione necessario per fare una richiesta di DELETE. Se ricarichiamo la pagina e riguardiamo il codice sorgente, vediamo l’HTML che queste due linee hanno generato:
<script src="/javascripts/prototype.js?1268677667" type="text/javascript"></script> <script src="/javascripts/effects.js?1268677667" type="text/javascript"></script> <script src="/javascripts/dragdrop.js?1268677667" type="text/javascript"></script> <script src="/javascripts/controls.js?1268677667" type="text/javascript"></script> <script src="/javascripts/rails.js?1268677667" type="text/javascript"></script> <script src="/javascripts/application.js?1268677667" type="text/javascript"></script> <meta name="csrf-param" content="authenticity_token"/> <meta name="csrf-token" content="9ImdFvbeW7ih9oKqBDQ3O889q/hJ1q5uajpT4DFDAoA="/>
Nella pagina ora abbiamo tutti i file JavaScript di cui ha bisogno la nostra applicazione e due meta tag necessari per evitare attacchi di tipo cross-site request forgeries1. Questi ultimi garantiscono che le richieste di tipo PUT e DELETE provengano sempre dall’utente corretto e autorizzato, e non da un altro sito o da un hacker.
Con questi due elementi a posto, il nostro link di cancellazione ora funzionerà come ci aspettiamo.
Aggiungere AJAX alla form di ricerca
Di seguito modificheremo la form di ricerca presente nella pagina index per farle usare AJAX al submit, piuttosto che una richiesta GET. Il codice della vista index
che contiene la form è mostrato qui sotto:
<% title "Products" %> <% form_tag products_path, :method => :get do %> <p> <%= text_field_tag :search, params[:search] %> <%= submit_tag "Search", :name => nil %> </p> <% end %> <div id="products"> <%= render @products %> </div> <p><%= link_to "New Product", new_product_path %></p>
La form usata qui per ricercare, usa la tecnica usata nell’episodio 37. Nelle prime versioni di Rails, per far funzionare le form con AJAX, avremmo dovuto sostituire il form_tag
con un form_remote_tag
. Questo metodo genera un mucchio di JavaScript inline, però, che è esattamente ciò che stiamo cercando di evitare.
Molti metodi helper della serie remote non sono più disponibili in Rails 3. Per riaverne le funzionalità. possiamo installare il plugin Prototype Legacy Helper, ma piuttosto che tornare al passato, proviamo ora ad adottare il nuovo approccio promosso da Rails 3.
Per farlo, invece di usare il form_remote_tag
, usiamo il form_tag
, ma con l’aggiunta di un parametro chiamato :remote
:
<% form_tag products_path, :method => :get, :remote => true do %> <p> <%= text_field_tag :search, params[:search] %> <%= submit_tag "Search", :name => nil %> </p> <% end %>
Questo parametro :remote
può anche essere usato con altri metodi helper che un tempo avevano il corrispettivo "remote", tipo il link_to
, il button_to
ed il form_for
. Se ricarichiamo la pagina e diamo un’occhiata al sorgente HTML, possiamo vedere come funzioni il nuovo codice della form:
<form action="/products" data-remote="true" method="get"> <p> <input id="search" name="search" type="text" /> <input type="submit" value="Search" /> </p> </form>
L’elemento form è esattamente lo stesso di prima dell’aggiunta del parametro remote, ma in più ha un nuovo attributo denominato data-remote
. Non c’è JavaScript inline, il nuovo attributo è sufficiente per dire al JavaScript presente in rails.js
che la form deve essere inoltrata al sever mediante AJAX.
Ora dobbiamo scrivere il codice per gestire la risposta a partire dalla chiamata AJAX. La lista di prodotti è contenuta in un div
con un id
"products", per cui possiamo aggiornare i contenuti di questo div
in modo tale da mostrare i prodotti di rilievo. La form viene inviata alla action index
del ProductController
: tutto ciò che occorre fare è aggiungere un nuovo template di vista per gestire la richiesta JavaScript chiamata index.js.erb
.
Possiamo scrivere il JavaScript che vogliamo in questo template, che verrà eseguito quando viene rimandato al browser in risposta alla richiesta AJAX. Il codice nel nuovo template aggiornarà i contenuti del div products con una lista di prodotti.
$("products").update("<%= escape_javascript(render(@products))%>");
Quando ricarichiamo la pagina e facciamo il submit della form, verrà fatta la ricerca con una chiamata AJAX e potremo vedere ciò dal fatto che l’URL della pagina non cambierà all’atto della ricerca:
In definitiva, in Rails 3 è facile inserire in modo unobtrusive del codice AJAX, usando il parametro :remote ed eseguendo il JavaScript restituito dal server.
Cambiando Framework
Concludiamo l’episodio mostrandovi come cambiare il framework JavaScript usato dalla nostra applicazione. Attualmente la nostra applicazione usa Prototype, che è la scelta di default di Rails, ma come dovremmo fare per usare jQuery, se volessimo?
Per prima cosa, dovremmo sostituire questa linea presente nel layout di applicazione:
<%= javascript_include_tag :defaults %>
con:
<%= javascript_include_tag "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js", "jquery.rails.js" %>
Il primo file in lista è l’ultima versione di jQuery da Google. Di per se non è, sufficiente dal momento che ci occorre un equivalente jQuery del file rails.js
usato prima per gestire l’unobtrusive JavaScript, ma che è specifico di Rails. Possiamo trovare la versione ufficiale di quest’ultimo sul sito del progetto jquery-ujs su Github. Questo progetto contiene un file chiamato rails.js
che si può scaricare e usare nei propri progetti. Lo abbiamo scaricato nella cartella /public/javascripts
della nostra applicazione e lo abbiamo rinominato come jquery.rails.js
: questo file gestirà tutto l’unobtrusive JavaScript usato da Rails.
Ora dobbiamo solo modificare ogni JavaScript che abbiamo scritto nella nostra applicazione e che fa uso di Prototype per fare in modo che usi codice compatibile con jQuery. Dobbiamo fare due piccole modifiche al codice nel file index.js.erb
creato poco fa, usando #products
al posto di products
come selettore per il div prodotti e sostituendo il metodo update
di Prototype con quello equivalente di jQuery html
:
$("#products").html("<%= escape_javascript(render(@products))%>");
La nostra applicazione ora funziona esattamente come prima, ma usando jQuery al posto di Prototype.
Degradare con grazia
Se un utente guarda la nostra applicazione usando un browser che non ha abilitato il JavaScript, la form degraderà con grazie, e userà una normale richiesta GET al submit. La cancellazione dei prodotti, tuttavia, non funzionerà. Questo è un problema comune, causato dal fatto che i link HTML possono fare solamente richieste di tipo GET, per cui Rails adotta un po’ di JavaScript per simulare una DELETE. Una soluzione è quella di sostituire il link con un pulsante, usando il metodo helper button_to
, ma ciò potrebbe apparire bruttino, per cui vorremmo mantenere il link. Una tecnica preferibile è quella mostrata nell’episodio 77, che mostra una pagina di conferma separata prima di cancellare un elemento, quando l’applicazione viene usata con un browser che non ha abilitato il JavaScript.
- http://en.wikipedia.org/wiki/Cross-site_request_forgery. <<<<<<< HEAD =======