Development

Documentation/it_IT/book/1.1/11-Ajax-Integration

You must first sign up to be able to contribute.

Capitolo 11 - Integrazione di Ajax

Le interazioni lato client, complicati effetti visuali e comunicazioni asincrone sono comuni in applicazioni web 2.0. Ognuna di tali interazioni necessita JavaScript, ma scrivere le funzioni a mano risulta spesso noioso e lungo da debuggare. Fortunatamente, symfony automatizza tramite un set di helper diverse fra le piu' comuni funzioni utilizzate nei template. Alcuni comportamenti lato client possono addirittura essere realizzati senza alcuna linea di codice JavaScript. Lo sviluppatore si deve solo preoccupare dell'effetto che vuole realizzare, e symfony gestirà le problematiche di sintassi e di compatibilità.

Questo capitolo descrive gli strumenti inclusi in symfony per facilitare lo scripting lato client:

  • Gli helper JavaScript di base stampano in output tag <script> rispettosi degli standard, per aggiornare il DOM (Document Object Model) o per chiamare degli script tramite link.
  • Prototype è una libreria JavaScript integrata in symfony, che velocizza lo sviluppo lato client aggiungendo nuovi metodi e funzioni al core JavaScript.
  • Gli helper Ajax permettono all'utente di aggiornare parti di una pagina cliccando un link, inviando una form o modificandone un elemento.
  • Le diverse opzioni di questi helper forniscono anche grande flessibilità e potenza, grazie all'utilizzo di funzioni di callback.
  • Script.aculo.us è un'altra libreria JavaScript, anch'essa integrata in symfony, che aggiunge effetti visuali dinamici e migliora l'interfaccia e la fruibilità.
  • JavaScript Object Notation (JSON) è uno standard usato per la comunicazione tra uno script server ed uno client.
  • Complicate interazioni lato client sono possibili in applicazioni symfony combinando le tecnologie appena menzionate. Auto-completamento, drag-and-drop, liste ordinabili e testo modificabile sono implementabili con una singola linea di PHP, ovvero una chiamata ad un helper di symfony.

Helper JavaScript di base

Per un lungo tempo JavaScript ha avuto poca considerazione in applicazioni web professionali, a causa della mancanza di compatibilità cross-browser. Ad oggi, i problemi di compatibilità sono per la maggior parte risolti, ed alcune robuste librerie permettono di scrivere interazioni JavaScript complesse senza perdere ore in debug e centinaia di linee di codice. La tecnica piu' avanzata si chiama Ajax, che sarà discussa nella sezione "Helper Ajax" piu' avanti in questo capitolo.

Paradossalmente, vedrai poche linee di codice JavaScript in questo capitolo. Questo perché symfony ha un approccio molto originale allo scripting lato client: esso pacchettizza ed astrae comportamenti JavaScript in helper, per cui alla fine le tue template non conterranno codice JavaScript. Per uno sviluppatore aggiungere un behavior ad un elemento della pagina significa scrivere una linea di codice PHP; ma tale chiamata naturalmente genera codice JavaScript in output, la cui complessità risulterà chiara nei sorgenti della pagina generata. Gli helper gestiscono le problematiche di consistenza del browser, complessi casi limite e così via, per cui l'ammontare di codice JavaScript che contengono può diventare assai importante. Ad ogni modo, il presente capitolo ti insegnerà come non utilizzare codice JavaScript per raggiungere effetti che di solito necessitano la scrittura di JavaScript.

Tutti gli helper descritti nel seguito sono disponibili nei template, a patto di dichiarare l'utilizzo del gruppo di helper Javascript:

[php]
<?php use_helper('Javascript') ?>

Come imparerai a breve, alcuni di questi helper stampano in output codice HTML, altri codice JavaScript.

JavaScript nei template

In XHTML, i blocchi di codice JavaScript devono essere racchiusi in dichiarazioni CDATA. Ma le pagine che richiedono molti blocchi di codice JavaScript possono diventare noiose da scrivere. Ecco perché symfony mette a disposizione l'helper javascript_tag(), che trasforma una stringa in un tag <script> rispondente ai requisiti XHTML. Il Listato 11-1 ne mostra un esempio.

Listato 11-1 - Includere JavaScript tramite l'helper javascript_tag()

[php]
<?php echo javascript_tag("
  function foobar()
  {
  ...
  }
") ?>
 => <script type="text/javascript">
    //<![CDATA[
      function foobar()
      {
        ...
      }
    //]]>
    </script>

Ma l'utilizzo piu' comune di JavaScript, più che pezzi di codice, sono gli hyperlink che attivano altri script. L'helper link_to_function() fa esattamente questo, come mostrato nel Listato 11-2.

Listato 11-2 - Attivare un link JavaScript tramite l'helper link_to_function()

[php]
<?php echo link_to_function('Click me!', "alert('foobar')") ?>
 => <a href="#" onClick="alert('foobar'); return none;">Click me!</a>

Come per l'helper link_to(), puoi aggiungere opzioni come terzo argomento al tag <a>.

NOTE Esattamente come l'helper link_to() ha un fratello button_to(), puoi attivare JavaScript tramite un pulsante (input type="button") chiamando l'helper button_to_function(). E se preferisci una immagine cliccabile, chiama link_to_function(image_tag('myimage'), "alert('foobar')").

Aggiornare un elemento DOM

Un task comune nelle interfacce dinamiche è l'aggiornamento di un elemento della pagina. Si tratta di qualcosa che di solito scrivi come nel Listato 11-3.

Listato 11-3 - Aggiornare un elemento in JavaScript

[php]
<div id="indicator">Data processing beginning</div>
<?php echo javascript_tag("
  document.getElementById("indicator").innerHTML =
    "<strong>Data processing complete</strong>";
") ?>

Symfony fornisce un helper che produce codice JavaScript, non HTML, a tale scopo, e si chiama update_element_function(). Il Listato 11-4 ne mostra l'utilizzo.

Listato 11-4 - Aggiornare un elemento in JavaScript tramite l'helper update_element_function()

[php]
<div id="indicator">Data processing beginning</div>
<?php echo javascript_tag(
  update_element_function('indicator', array(
    'content'  => "<strong>Data processing complete</strong>",
  ))
) ?>

Ti potresti chiedere per quale motivo questo helper è utile, in quanto la lunghezza del codice è la stessa che scrivere JavaScript a mano. Si tratta in effetti di una questione di leggibilita'. Ad esempio, potresti voler inserire contenuto prima o dopo un elemento, o rimuoverlo invece di aggiornarlo, o anche non fare niente sotto certe condizioni. In tali casi, il codice JavaScript si complica in qualche modo, ma update_element_function() mantiene il template molto leggibile, come mostrato dal Listato 11-5.

Listato 11-5 - Opzioni dell'helper update_element_function()

[php]
// Inserire contenuto subito dopo l'elemento 'indicator'
update_element_function('indicator', array(
  'position' => 'after',
  'content'  => "<strong>Data processing complete</strong>",
));

// Rimuovere l'elemento prima di 'indicator', e solo se $condition è vera
update_element_function('indicator', array(
  'action'   => $condition ? 'remove' : 'empty',
  'position' => 'before',
))

Gli helper rendono le tue template facili da leggere, e tu hai una sintassi univoca per behavior simili. Questo è anche il motivo per cui i nomi degli helper sono così lunghi: rendono il codice sufficientemente chiaro da non necessitare altri commenti.

Degradazione scalabile

Il tag <noscript> ti permette di specificare codice HTML visibile solo ai browser senza supporto JavaScript. Symfony fornisce un helper che lavora nel modo opposto: specifica codice visibile solo ai browser che supportano JavaScript. Gli helper if_javascript() e end_if_javascript() facilitano la creazione di applicazioni con degradazione scalabile, come dimostrato dal Listato 11-6.

Listato 11-6 - Utilizzare l'helper if_javascript() per permettere degradazione scalabile

[php]
<?php if_javascript(); ?>
  <p>You have JavaScript enabled.</p>
<?php end_if_javascript(); ?>

<noscript>
  <p>You don't have JavaScript enabled.</p>
</noscript>

NOTE Non c'è bisogno di utilizzare echo con gli helper if_javascript() e end_if_javascript().

Prototype

Prototype è una grande libreria JavaScript che estende le possibilità dello scripting lato client, aggiunge quelle funzioni mancanti di cui hai sempre sognato ed offre nuovi meccanismi per la manipolazione del DOM. Il sito del progetto è [http://prototypejs.org/].

Il file di Prototype sono inclusi in symfony ed accessibili da ogni progetto nella cartella web/sf/prototype/. Ciò significa che puoi usare Prototype inserendo il seguente codice alla tua azione:

[php]
$prototypeDir = sfConfig::get('sf_prototype_web_dir');
$this->getResponse()->addJavascript($prototypeDir.'/js/prototype');

o, più semplicemente, includendolo nel file view.yml:

all:
  javascripts: [%SF_PROTOTYPE_WEB_DIR%/js/prototype]

NOTE Dato che gli helper Ajax di symfony, descritti nella prossima sezione, sono basati su Prototype, tale libreria viene inclusa automaticamente qualora tu utilizzi uno di essi. Ciò significa che non hai bisogno di includere manualmente Prototype se la tua template chiama almeno uno degli helper _remote.

Una volta che la libreria Prototype è caricata, puoi avvantaggiarti di tutte le nuove funzioni che essa aggiunge al core di JavaScript. Tali funzioni esulano dallo scopo del presente libro, ma puoi trovare molta documentazione su Internet, inclusi i seguenti siti:

  • Particletree: [[http://particletree.com/features/quick-guide-to-prototype/]]
  • Sergio Pereira: [[http://www.sergiopereira.com/articles/prototype.js.html]]
  • Script.aculo.us: [[http://wiki.script.aculo.us/scriptaculous/show/Prototype]]

Una delle funzioni che Prototype aggiunge a JavaScript è il dollaro $(). Fondamentalmente questa funzione è solo un alias di document.getElementById(), ma un po' più potente. Vedi il Listato 11-7 per un esempio del suo utilizzo.

Listato 11-7 - Uso della funzione $() per recuperare un ID in JavaScript

[php]
node = $('elementID');

//  È lo stesso di
node = document.getElementById('elementID');

// Può recuperarne anche due allo stesso tempo
// E in questo caso il risultato è un array di elementi DOM
nodes = $('firstDiv', 'secondDiv');

Prototype fornisce anche una funzione di cui si sente veramente la mancanza nel core JavaScript, la quale restituisce un array di tutti gli elementi che appartengono alla classe passata come argomento:

[php]
nodes = document.getElementByClassName('myclass');

Comunque la userai di rado, perché Prototype fornisce anche un'altra utilissima funzione chiamata doppio dollaro, $$(). Tale funzione restituisce un array di elementi DOM basati su un selector CSS. Per cui la funzione precedente puo' essere riscritta:

[php]
nodes = $$('.myclass');

Grazie alla potenza del selector CSS, puoi fare il parsing del DOM per classe, ID e relazioni genitore-figlio o precedente-seguente ancora piu' facilmente di come faresti usando un'espressione XPath. Puoi anche accedere ad elementi con selettori complicati:

[php]
nodes = $$('body div#main ul li.last img > span.legend');

Un ultimo esempio dei miglioramenti della sintassi forniti da Prototype è l'iteratore per ogni array. Fornisce la stessa concisione possibile in PHP, aggiunta alla capacita' di definire funzioni anonime e chiusure in JavaScript. Probabilmente la userai molto se scrivi codice JavaScript a mano.

[php]
var vegetables = ['Carrots', 'Lettuce', 'Garlic'];
vegetables.each(function(food) { alert('I love ' + food); });

Dato che programmare in JavaScript con Prototype è molto più divertente che farlo a mano, e dato che essa fa già parte di symfony, dovresti veramente passare un po' di tempo a leggere la documentazione relativa.

Helper Ajax

Che succede se volessi aggiornare un elemento della pagina, non come nell'esempio 11-5 ma tramite uno script PHP eseguito sul server? Questo ti darebbe la possibilità di cambiare parte della pagina dopo una risposta del server. L'helper remote_function() serve esattamente a questo, come dimostrato dal Listato 11-8.

Listato 11-8 - Usare l'helper remote_function()

[php]
<div id="myzone"></div>
<?php echo javascript_tag(
  remote_function(array(
    'update'  => 'myzone',
    'url'     => 'mymodule/myaction',
  ))
) ?>

NOTE Il parametro url puo' contenere sia una URI interna (module/action?key1=value1&...) sia il nome di una regola di routing, come per url_for().

Quando chiamato, questo script aggiornerà l'elemento con id myzone con la risposta o la richiesta dell'azione mymodule/myaction. Questo tipo di interazione è chiamato Ajax, ed è il cuore delle applicazioni web altamente interattive. Ecco come viene descritto da Wikipedia (http://en.wikipedia.org/wiki/AJAX):

Ajax makes web pages feel more responsive by exchanging small amounts of data with the server behind the scenes, so that the entire web page does not have to be reloaded each time the user makes a change. This is meant to increase the web page's interactivity, speed, and usability.

Ajax si basa su XMLHttpRequest,, un oggetto JavaScript che si comporta come un frame nascosto, che puoi aggiornare con la risposta di un server e riutilizzare per manipolare il resto della tua pagina. Si tratta di un oggetto abbastanza a basso livello, e browser diversi lo gestiscono in maniera diversa, per cui gestire richieste Ajax manualmente comporterebbe scrivere montagne di codice. Fortunatamente, Prototype incapsula tutto il codice necessario in un semplice oggetto Ajax, sul quale si basa symfony; ecco perché Prototype viene caricata automaticamente quando si usano helper Ajax nei template.

CAUTION Gli helper Ajax non funzioneranno se la URL dell'azione remota non appartiene allo stesso dominio della pagina che la chiama. Questa restrizione esiste per questioni di sicurezza, e si basa su limitazioni del browser che non possono essere bypassate.

Una interazione Ajax è composta da tre parti: un chiamante (link, pulsante, form, o qualsiasi altro tipo di elemento utilizzabile dall'utente per chiamare un'azione), un'azione del server ed una zona nella pagina in cui visualizzare la risposta di tale azione. Puoi anche costruire interazioni piu' complesse se i dati restituiti dal server devono a loro volta essere manipolati da JavaScript. Symfony dispone di diversi helper per inserire interazioni Ajax nelle tue template, tutti contenenti la parola remote nel proprio nome. Inoltre condividono tutti la stessa sintassi, ovvero un array associativo di parametri. Fai attenzione al fatto che gli helper potrebbe stampare in output codice HTML oppure JavaScript.

SIDEBAR E le azioni Ajax?

Le azioni chiamate come funzioni remote sono azioni normali. Esse seguono il routing, possono determinare quale vista renderizzare tramite la propria return, ed alterare il modello, proprio come le altre azioni.

Comunque, quando chiamate tramite Ajax, le azioni restituiscono true alla seguente chiamata:

[php]
$isAjax = $this->getRequest()->isXmlHttpRequest();

Symfony sa se un'azione si trova in un contesto Ajax, e può conseguentemente adattare la risposta. Perciò, per default, le azioni Ajax non includono la web debug toolbar nell'ambiente di sviluppo. Inoltre evitano il processo di decoration (ovvero le loro template non vengono incluse per default in un layout). Se vuoi avere una vista Ajax decorata, devi specificare has_layout: true nel file view.yml per tale vista.

Dato che il tempo di risposta è cruciale per interazioni Ajax, se la risposta non è troppo complicata, sarebbe meglio evitare di creare una vista e restituire il risultato direttamente dall'azione. Cosi' puoi usare il metodo renderText() nell'azione per evitare il template e velocizzare la richiesta Ajax.

Novità in symfony 1.1: la maggior parte delle azioni Ajax finiscono in una template che non fa altro che includere un partial, in quanto il codice della risposta Ajax è già usato per visualizzare la pagina iniziale. Per evitare di creare una template solo per una linea di codice, l'azione può usare il metodo renderPartial(). Questo metodo si avvantaggia sia della riusabilità dei partial, che delle loro capacità di caching, nonchè della velocità del metodo renderText().

[php]
public function executeMyAction()
{
  // fai le cose
  return $this->renderPartial('mymodule/mypartial');
}

Link Ajax

I link Ajax costituiscono una grande parte delle interazioni in applicazioni web 2.0. L'helper link_to_remote() stampa in output un link che chiama, non sorprendentemente, una funzione remota. La sintassi è molto simile a quella di link_to() (a parte il secondo parametro, che in questo caso è un array associativo di opzioni Ajax), come mostrato nel Listato 11-9.

Listato 11-9 - Link Ajax tramite l'helper link_to_remote()

[php]
<div id="feedback"></div>
<?php echo link_to_remote('Delete this post', array(
    'update' => 'feedback',
    'url'    => 'post/delete?id='.$post->getId(),
)) ?>

In questo esempio, cliccando sul link 'Delete this post', verra' eseguita in background l'azione post/delete. La risposta restituita dal server sara' collocata nell'elemento con id feedback. Questo processo è illustrato in Figura 11-1.

Figura 11-1 - Eseguire un aggiornamento remoto tramite un hyperlink

Eseguire un aggiornamento remoto tramite un hyperlink

Come mostrato dal Listato 11-10, puoi usare un'immagine invece di una stringa per eseguire il link, sostituire la URL con una regola di routing ed aggiungere opzioni al tag <a> come terzo argomento.

Listato 11-10 - Opzioni per l'helper link_to_remote()

[php]
<div id="emails"></div>
<?php echo link_to_remote(image_tag('refresh'), array(
    'update' => 'emails',
    'url'    => '@list_emails',
), array(
    'class'  => 'ajax_link',
)) ?>

Form Ajax-driven

Le form web tipicamente chiamano un'altra azione, ma questo genera il refresh della pagina. La corrispondente di link_to_function() per una form sarebbe l'aggiornamento di un solo elemento della pagina con la risposta del server, ovvero l'helper form_remote_tag(), la cui sintassi è mostrata dal Listato 11-11.

Listato 11-11 - Form Ajax con l'helper form_remote_tag()

[php]
<div id="item_list"></div>
<?php echo form_remote_tag(array(
    'update'   => 'item_list',
    'url'      => 'item/add',
)) ?>
  <label for="item">Item:</label>
  <?php echo input_tag('item') ?>
  <?php echo submit_tag('Add') ?>
</form>

L'helper form_remote_tag() apre un tag <form>, proprio come form_tag(). Facendo la submit di tale form verra' chiamata in POST l'azione item/add in background, con il campo item come parametro di richiesta. La risposta sostituira' il contenuto dell'elemento item_list, come illustrato in Figura 11-2. Chiudi una form Ajax con il semplice tag di chiusura </form>.

Figura 11-2 - Eseguire un aggiornamento remoto tramite form

Eseguire un aggiornamento remoto tramite form

CAUTION Le form Ajax non possono essere multipart, a causa di una limitazione dell'oggetto XMLHttpRequest. Cio' significa che non si possono gestire upload di file tramite form Ajax. Ad ogni modo, c'è un modo per aggirare questa limitazione, ad esempio utilizzando un iframe nascosto invece di XMLHttpRequest (ne puoi trovare un'implementazione su http://www.air4web.com/files/upload/).

Se vuoi permettere ad una form di lavorare in entrambe le modalita', ossia "page mode" ed "Ajax mode", la soluzione migliore è quella di utilizzare una form normale ma con un pulsante (<input type="button" />) che faccia la submit in Ajax. Symfony chiama tale pulsante submit_to_remote(). Questo aiuta a creare form Ajax con degradazione scalabile. Vedi un esempio nel Listato 11-12.

Listato 11-12 - Form normale con submit in Ajax

[php]
<div id="item_list"></div>
<?php echo form_tag('@item_add_regular') ?>
  <label for="item">Item:</label>
  <?php echo input_tag('item') ?>
  <?php if_javascript(); ?>
    <?php echo submit_to_remote('ajax_submit', 'Add in Ajax', array(
        'update'   => 'item_list',
        'url'      => '@item_add',
    )) ?>
  <?php end_if_javascript(); ?>
  <noscript>
    <?php echo submit_tag('Add') ?>
  </noscript>
</form>

Un altro esempio di utilizzo combinato di tag submit normali ed Ajax è una form che modifica un articolo. Puo' offrire un pulsante di anteprima in Ajax ed uno di pubblicazione che esegue una submit normale.

NOTE Quando l'utente preme il tasto Enter, la form viene spedita utilizzando l'azione definita nel tag form principale; in questo esempio, quello normale.

Form moderne possono reagire non solo quando vengono spedite, ma anche quando un utente cambia il valore di qualche campo. In symfony, puoi usare l'helper observe_field() a questo scopo. Il Listato 11-13 mostra un esempio di utilizzo di tale helper per mostrare suggerimenti: ogni carattere inserito nel campo item scatena un chiamata Ajax che aggiorna il valore dell'elemento item_suggestion.

Listato 11-13 - Chiamata a funzione remota quando un valore cambia con observe_field()

[php]
<?php echo form_tag('@item_add_regular') ?>
  <label for="item">Item:</label>
  <?php echo input_tag('item') ?>
  <div id="item_suggestion"></div>
  <?php echo observe_field('item', array(
      'update'   => 'item_suggestion',
      'url'      => '@item_being_typed',
  )) ?>
  <?php echo submit_tag('Add') ?>
</form>

Il modulo/azione della regola @item_being_typed verra' chiamato ogni volta che l'utente cambia il valore del campo in osservazione (item), anche senza la submit della form. L'azione ricevera' il valore corrente di item dal parametro di richiesta value. Se vuoi passare qualcos'altro oltre al valore del campo in osservazione, lo puoi specificare tramite il parametro with. Ad esempio, se vuoi passare all'azione il parametro param, riscrivi l'helper observe_field() nel modo seguente:

Listato 11-14 - Passare parametri all'azione remota tramite l'opzione with

[php]
<?php echo observe_field('item', array(
    'update'   => 'item_suggestion',
    'url'      => '@item_being_typed',
    'with'     => "'param=' + value",
)) ?>

Nota che questo helper non stampa in output un oggetto HTML, bensi' un behavior per l'elemento passato come parametro. Vedrai altri esempi del genere piu' avanti in questo capitolo.

Se vuoi tenere sotto osservazione tutti i campi di una form, puoi utilizzare l'helper observe_form(), che chiama una funzione remota ogni qualvolta uno qualsiasi dei campi della form viene modificato.

Chiamate periodiche a funzioni remote

L'helper periodically_call_remote() è un'interazione Ajax eseguita ogni tot secondi. Non è collegata ad alcun elmento HTML, bensì gira trasparentemente in background. Essa puo' essere di grande utilità per tracciare la posizione del mouse, salvare automaticamente il contenuto di una textarea e così via. Il Listato 11-15 ne mostra un esempio di utilizzo.

Listato 11-15 - Chiamate periodiche a funzioni remote tramite periodically_call_remote()

[php]
<div id="notification"></div>
<?php echo periodically_call_remote(array(
    'frequency' => 60,
    'update'    => 'notification',
    'url'       => '@watch',
    'with'      => "'param=' + $('mycontent').value",
)) ?>

Se non specifichi il numero di secondi (frequency) di intervallo, viene usato il default, ovvero 10 secondi. Nota che il parametro with viene valutato in JavaScript, per cui puoi usare Prototype su di esso, as esempio tramite la funzione dollaro.

Parametri delle chiamate remote

Tutti gli helper Ajax descritti finora possono ricevere altri parametri, in aggiunta a update e url. L'array associativo di parametri Ajax puo' modificare il comportamento della risposta.

Aggiornare elementi diversi a seconda del valore della risposta

Se l'azione remota fallisce, l'helper remoto puo' aggiornare un elemento diverso da quello che aggiornerebbe in caso di successo. A questo scopo, dividi semplicemente il valore del parametro update in un array associativo, ed imposta valori diversi in caso di success o failure. Questo risulta di grande utilita' se, ad esempio, in una pagina ci sono molte interazioni Ajax ed una zona in cui vengono visualizzati gli errori. Il Listato 11-16 mostra un esempio dell'utilizzo condizionale di update.

Listato 11-16 - Gestire aggiornamenti condizionali

[php]
<div id="error"></div>
<div id="feedback"></div>
<p>Hello, World!</p>
<?php echo link_to_remote('Delete this post', array(
    'update'   => array('success' => 'feedback', 'failure' => 'error')
    'url'      => 'post/delete?id='.$post->getId(),
)) ?>

TIP Solo gli errori HTTP (500, 404, e tutti gli altri codici che non fanno parte del range 2xx) scateneranno un fallimento, non un'azione che resituisca sfView::ERROR. Per cui, se vuoi che la tua azione resituisca un errore Ajax, devi chiamare $this->getResponse()->setStatusCode(404) o similari.

Aggiornare un elemento a seconda della sua posizione

Come con l'helper update_element_function(), puoi specificare quale sia l'elemento da aggiornare a seconda della sua posizione tramite il parametro position. Il Listato 11-17 ne mostra un esempio.

Listato 11-17 - Utilizzo del parametro position

[php]
<div id="feedback"></div>
<p>Hello, World!</p>
<?php echo link_to_remote('Delete this post', array(
    'update'   => 'feedback',
    'url'      => 'post/delete?id='.$post->getId(),
    'position' => 'after',
)) ?>

Questo inserira' la risposta del server dopo l'elemento feedback; ovvero, fra i tag <div> e <p>. Con questo metodo, puoi fare diverse chiamate Ajax ed accumulare le risposte dopo l'elemento specificato in update.

Il parametro position puo' assumere i seguenti valori:

  • before: prima dell'elemento
  • after: dopo l'elemento
  • top: all'inizio dell'elemento
  • bottom: alla fine dell'elemento

Aggiornare un elemento secondo una condizione

Una chiamata remota accetta come parametro addizionale una richiesta di conferma, in modo da inviare effettivamente la richiesta XMLHttpRequest come mostrato nel Listato 11-18.

Listato 11-18 - Chiedere conferma prima di inviare una richiesta remota

[php]
<div id="feedback"></div>
<?php echo link_to_remote('Delete this post', array(
    'update'   => 'feedback',
    'url'      => 'post/delete?id='.$post->getId(),
    'confirm'  => 'Are you sure?',
)) ?>

In questa maniera, dopo aver cliccato il link, si aprira' una finestra di dialogo JavaScript e l'azione post/delete verra' chiamata solo se l'utente la confermera' cliccando su OK.

La richiesta remota puo' essere condizionata anche da un test lato client (in JavaScript), se inserisci il parametro condition, come mostrato nel Listato 11-19.

Listato 11-19 - Chiamata remota condizionata da un test lato client

[php]
<div id="feedback"></div>
<?php echo link_to_remote('Delete this post', array(
    'update'    => 'feedback',
    'url'       => 'post/delete?id='.$post->getId(),
    'condition' => "$('elementID') == true",
)) ?>

Determinare il metodo di richiesta Ajax

Per default le richieste Ajax vengono eseguite in POST. Se vuoi fare una chiamata Ajax che non modifica dati, oppure se vuoi visualizzare una form che include la validazione come risultato di una richiesta Ajax, potresti aver bisogno di cambiare il metodo in GET. L'opzione method fa esattamente questo, come mostrato nel Listato 11-20.

Listato 11-20 - Cambiare metodo di richiesta

[php]
<div id="feedback"></div>
<?php echo link_to_remote('Delete this post', array(
    'update'    => 'feedback',
    'url'       => 'post/delete?id='.$post->getId(),
    'method'    => 'get',
)) ?>

Autorizzare l'esecuzione di script

Se il codice contenuto nella risposta del server in seguito ad una chiamata Ajax contiene JavaScript, potresti essere sorpreso nel notare che tale codice per default non viene eseguito. Questo serve a ridurre i rischi di attacchi remoti, facendo in modo che l'esecuzione di script sia permessa solo quando lo sviluppatore è certo che il codice sia contenuto nella risposta.

Questo è il motivo per cui occorre dichiarare esplicitamente la possibilita' di eseguire script nella risposta, tramite l'opzione script. Il Listato 11-21 mostra come fare.

Listato 11-21 - Autorizzare l'esecuzione di script nella risposta Ajax

[php]
<div id="feedback"></div>
// Se la risposta dell'azione get o post contiene JavaScript,
// permette al browser di eseguirlo
<?php echo link_to_remote('Delete this post', array(
    'update' => 'feedback',
    'url'    => 'post/delete?id='.$post->getId(),
    'script' => true,
)) ?>

Se il template remoto contiene helper Ajax (come ad esempio remote_function()), fai attenzione al fatto che tali funzioni PHP generano codice JavaScript, che non verra' eseguito senza impostare 'script' => true.

NOTE Anche se abiliti l'esecuzione di script per la risposta remota, non vedrai gli script nel codice remoto, utilizzando uno strumento per vedere il codice generato. Lo script verrà eseguito ma non comparirà nel codice sorgente. Anche se particolare, questo comportamento è perfettamente normale.

Creare Callback

Uno degli svantaggi principali delle interazioni Ajax è che esse sono invisibili all'utente fino a che la zona di aggiornamento non venga effettivamente modificata. Ciò significa che in caso di lentezza di rete o fallimento del server, l'utente potrebbe aspettare una risposta che non arriverebbe mai. Ecco perchè è importante notificare all'utente che le interazioni sono in esecuzione.

Per default, ogni richiesta remota è un processo asincrono durante il quale diverse callback JavaScript possono essere scatenate (come ad esempio l'indicatore di progesso). Tutte le callback hanno accesso all'oggetto request, che gestisce la XMLHttpRequest sottostante. Le callback corrispondono agli eventi di qualsiasi interazione Ajax:

  • before: Prima che la richiesta sia iniziata
  • after: Immediatamente dopo l'inizio della richiesta e prima del caricamento
  • loading: Quando la risposta remota è in caricamento sul browser
  • loaded: Quando il browser ha completato il caricamento della risposta remota
  • interactive: Quando l'utente può interagire con la richiesta remota, anche se il caricamento non è completato
  • success: Quando XMLHttpRequest è completato, ed il codice di stato HTTP è nel range 2XX
  • failure: Quando XMLHttpRequest è completato, ed il codice di stato HTTP non è nel range 2XX
  • 404: Quando la richiesta restituisce lo stato 404
  • complete: Quando XMLHttpRequest è completa (succede dopo success o failure, se presenti)

Ad esempio, è molto comune mostrare un indicatore di caricamento quando una richiesta viene inoltrata, per nasconderlo quando viene ricevuta la risposta. Per fare ciò, aggiungi semplicemente i parametri loading e complete alla chiamata Ajax, come mostrato nel Listato 11-22.

Listato 11-22 - Utilizzare callback Ajax per mostrare e nascondere un indicatore di attività

[php]
<div id="feedback"></div>
<div id="indicator">Loading...</div>
<?php echo link_to_remote('Delete this post', array(
    'update'   => 'feedback',
    'url'      => 'post/delete?id='.$post->getId(),
    'loading'  => "Element.show('indicator')",
    'complete' => "Element.hide('indicator')",
)) ?>

I metodi show e hide, come anche l'oggetto Element, sono altre utili aggiunte di Prototype.

Creare effetti visuali

Symfony integra gli effetti visuali della libreria script.aculo.us, per permetterti di fare di più che mostrare o nascondere elementi <div> nelle tue pagine. Troverai una buona documentazione sulla sintassi degli effetti nel wiki di [http://script.aculo.us/]. Fondamentalmente, la libreria fornisce oggetti e funzioni JavaScript per la manipolazione del DOM, in modo da creare complicati effetti visuali. Ne trovi un piccolo esempio nel Listato 11-23. Il sito di script.aculo.us contiene una gallery in cui puoi vedere gli effetti dinamici in azione.

Listato 11-23 - Effetti visuali in JavaScript con script.aculo.us

[php]
// Evidenzia l'elemento 'my_field'
Effect.Highlight('my_field', { startcolor:'#ff99ff', endcolor:'#999999' })

// Crea un effetto "veneziana" su un elemento
Effect.BlindDown('id_of_element');

// Sfuma un elemento fino a farlo scomparire
Effect.Fade('id_of_element', { transition: Effect.Transitions.wobble })

Symfony incapsula l'oggetto JavaScript Effect in un helper chiamato visual_effect(), che fa ancora parte del gruppo di helper Javascript. Stampa codice JavaScript che può essere usato in un link, come mostrato nel Listato 11-24.

Listato 11-24 - Effetti visuali nei template tramite l'helper visual_effect()

[php]
<div id="secret_div" style="display:none">I was here all along!</div>
<?php echo link_to_function(
  'Show the secret div',
  visual_effect('appear', 'secret_div')
) ?>
// Eseguirà una chiamata a Effect.Appear('secret_div')

L'helper visual_effects() può anche essere usato in callback Ajax, come mostrato nel Listato 11-25, il quale mostra un indicatore di attività come nel Listato 11-22, ma molto più carino esteticamente. L'elemento indicator appare progessivamente quando la chiamata comincia, e scompare sfumando quando arriva la risposta. Inoltre, l'elemento feedback viene evidenziato dopo essere stato aggiornato, per attrarre l'attenzione dell'utente in quella zona della pagina.

Listato 11-25 - Effetti visuali con callback Ajax

[php]
<div id="feedback"></div>
<div id="indicator" style="display: none">Loading...</div>
<?php echo link_to_remote('Delete this post', array(
    'update'   => 'feedback',
    'url'      => 'post/delete?id='.$post->getId(),
    'loading'  => visual_effect('appear', 'indicator'),
    'complete' => visual_effect('fade', 'indicator').
                  visual_effect('highlight', 'feedback'),
)) ?>

Nota come puoi combinare effetti visuali concatenandoli in una callback.

JSON

JavaScript Object Notation (JSON) è un formato leggero di interscambio dati. Fondamentalmente, non è niente di più di un hash Javascript (v. un esempio nel Listato 11-26) per trasportare informazioni. Ma JSON ha due grandi benefici per le interazioni Ajax: è facile da leggere in JavaScript e può ridurre la dimensione di una risposta web.

Listato 11-26 - Esempio di oggetto JSON in JavaScript

var myJsonData = {"menu": {
  "id": "file",
  "value": "File",
  "popup": {
    "menuitem": [
      {"value": "New", "onclick": "CreateNewDoc()"},
      {"value": "Open", "onclick": "OpenDoc()"},
      {"value": "Close", "onclick": "CloseDoc()"}
    ]
  }
}}

Se un'azione Ajax ha bisogno di restituire dati strutturati al chiamante per ulteriori operazioni JavaScript, JSON è il formato giusto per la risposta. È molto utile, ad esempio, se una chiamata Ajax deve aggiornare diversi elementi nella pagina che la chiama.

Immagina ad esempio che tale pagina chiamante sia fatta come quella del Listato 11-27. Essa possiede due elementi che potresti dover aggiornare. Un helper remoto potrebbe aggiornarne uno solo, non entrambi.

Listato 11-27 - Esempio di template per aggiornamenti Ajax multipli

[php]
<h1 id="title">Basic letter</h1>
<p>Dear <span id="name">name_here</span>,</p>
<p>Your e-mail was received and will be answered shortly.</p>
<p>Sincerely,</p>

Per aggiornare entrambi, immagina che la risposta Ajax sia un array JSON contenente:

 [["title", "My basic letter"], ["name", "Mr Brown"]]

Così la chiamata remota può interpretare facilmente tale risposta ed aggiornare diversi campi in una riga, con un piccolo aiuto di JavaScript. Il codice del Listato 11-28 mostra cosa può essere aggiunto al Listato 11-27 per raggiungere tale scopo.

Listato 11-28 - Aggiornare più di un elemento da una risposta remota

[php]
<?php echo link_to_remote('Refresh the letter', array(
  'url'      => 'publishing/refresh',
  'complete' => 'updateJSON(ajax)'
)) ?>

<?php echo javascript_tag("
function updateJSON(ajax)
{
  json = ajax.responseJSON;
  for (var i = 0; i < json.length; i++)
  {
     Element.update(json[i][0], json[i][1]);
  }
}
") ?>

Il callback complete ha accesso ai dati JSON della risposta e lo può passare ad una terza funzione. La funzione updateJSON() itera i dati ajax e per ogni membro dell'array aggiorna gli elementi che portano il nome contenuto nel primo parametro con il valore del secondo.

Il Listato 11-29 mostra come l'azione publishing/refresh può restituire una risposta JSON.

Listato 11-29 - Esempio di azione che restituisce dati JSON

[php]
class publishingActions extends sfActions
{
  public function executeRefresh()
  {
    $this->getResponse()->setHttpHeader('Content-Type','application/json; charset=utf-8');
    $output = '[["title", "My basic letter"], ["name", "Mr Brown"]]';
     return $this->renderText('('.$output.')');
  }

Usare il content type application/json consente a Prototype di valutare automaticamente il JSON passato al corpo della rispota, che è preferibile a restituirlo tramite header, perché l'header HTTP è limitato nella dimensione ed alcuni browser hanno problemi con le risposte che non hanno un body. Usando ->renderText() il template non viene usato, ottenendo una performance migliore.

JSON è diventato uno standard nelle applicazioni web. I web services spesso offrono le risposte in JSON invece che in XML, per permettere integrazione del servizio nel client (mashup), invece che nel server. Per cui se ti stai chiedendo quale sia il formato migliore per la comunicazione tra il tuo server ed una funzione JavaScript, probabilmente JSON è la risposta che cerchi.

TIP Dalla versione 5.2, PHP offre due funzioni, json_encode() e json_decode(), che ti permettono di convertire un array tra la sintassi PHP e quella JSON (http://www.php.net/manual/en/ref.json.php). Tali funzioni facilitano l'integrazione di array JSON ed Ajax in generale.

$output = array("title" => "My basic letter", "name" => "Mr Brown");

 return $this->renderText(json_encode($output));

Interazioni complesse con Ajax

Fra tutti gli helper Ajax di symfony, ne troverai qualcuno che permette interazioni complesse con una singola chiamata. Essi ti permettono di aumentare la fruibilità delle interazioni rendendole in stile "applicazione desktop" (drag-and-drop, autocompletion, live editing) senza il bisogno di JavaScript complicati. La sezione seguente descrive gli helper per le interazioni complesse e ne mostra alcuni semplici esempi. Parametri addizionali e trucchi sono disponibili nella documentazione di script.aculo.us.

CAUTION Anche se le interazioni complesse sono possibili, esse fabbisognano di tempo extra per renderle "naturali". Usale solo quando sei certo che migliorano l'interattività, ed evitale quando c'è il rischio che disorientino l'utente.

Autocompletion

Un campo di testo che mostri una lista di parole che coincidano con quello che l'utente sta inserendo viene chiamato autocompletion. Con un singolo helper chiamato input_auto_complete_tag(), puoi raggiungere tale effetto, premesso che l'azione remota restituisca una risposta formattata come una lista HTML, come quella del Listato 11-30.

Listato 11-30 - Esempio di risposta compatibile con il tag autocomplete

<ul>
  <li>suggestion1</li>
  <li>suggestion2</li>
  ...
</ul>

Inserisci l'helper nel template come faresti con un campo di testo normale, come mostrato nel Listato 11-31.

Listato 11-31 - Utilizzare il tag autocomplete nei template

[php]
<?php echo form_tag('mymodule/myaction') ?>
  Find an author by name:
  <?php echo input_auto_complete_tag('author', 'default name',
    'author/autocomplete',
    array('autocomplete' => 'off'),
    array('use_style'    => true)
  ) ?>
  <?php echo submit_tag('Find') ?>
</form>

Questo chiamerà l'azione author/autocomplete ogni volta che l'utente inserisce un carattere nel campo author. Sta a te scrivere l'azione in modo che trovi una lista di possibili coincidenze con quello che l'utente sta inserendo e che la restituisca in un formato simile a quello del Listato 11-30. L'helper quindi visualizzerà la lista sotto il campo author, e cliccare su uno dei suggerimenti o selezionarlo con la tastiera completerà il campo, come mostrato nella Figura 11-3.

Figura 11-3 - Esempio di auto completamento

Esempio di auto completamento

Il terzo argomento di input_auto_complete_tag() può assumere i seguenti parametri:

  • use_style: Stili che la risposta utilizza automaticamente.
  • frequency: Frequenza della chiamata periodica (default a 0.4 secondi).
  • tokens: Permette autocompletamento incrementale "tokenizzato" . Ad esempio, se impostassi questo parametro come virgola , e se l'utente inserisse jane, george, the l'azione riceverebbe solo il valore 'george'.

NOTE L'helper input_auto_complete_tag(), come i seguenti, accettano anche le altre opzioni degli helper remoti spiegate precedentemente in questo capitolo. In particolare, è buona abitudine impostare gli effetti visuali loading e complete per migliorare l'interazione.

Drag-and-Drop

La capacità di afferrare un elemento con il mouse, muoverlo e rilasciarlo da qualche altra parte è familiare in applicazioni desktop ma si vede raramente su un browser. Questo perchè la codifica di tali comportamenti in JavaScript è molto complicata. Fortunatamente, essa richiede solo una linea in symfony.

Il framework fornisce due helper, draggable_element() e drop_receiving_element(), che possono essere visti come modificatori di comportamento; essi aggiungono osservatori e capacità agli elementi. Usali per dichiarare un elemento come semovibile, o come ricevitore per uno in movimento. Un elemento semovibile può essere afferrato tramite un click del mouse. Fino a che tieni premuto il tasto del mouse, l'elemento può essere spostato all'interno della finestra. Un elemento ricevente chiama una funzione remota quando un elemento viene rilasciato su di esso. Il Listato 11-32 mostra tale tipo di interazione con un esempio di carrello per la spesa.

Listato 11-32 - Elementi semovibili e riceventi in un carrello per la spesa

[php]
<ul id="items">
  <li id="item_1" class="food">Carrot</li>
  <?php echo draggable_element('item_1', array('revert' => true)) ?>
  <li id="item_2" class="food">Apple</li>
  <?php echo draggable_element('item_2', array('revert' => true)) ?>
  <li id="item_3" class="food">Orange</li>
  <?php echo draggable_element('item_3', array('revert' => true)) ?>
</li>
<div id="cart">
  <p>Your cart is empty</p>
  <p>Drag items here to add them to your cart</p>
</div>
<?php echo drop_receiving_element('cart', array(
  'url'        => 'cart/add',
  'accept'     => 'food',
  'update'     => 'cart',
)) ?>

Ognuno degli elementi di una lista non ordinata può essere afferrato con il mouse e mosso nella finestra. Quando rilasciato, esso ritorna alla propria posizione originale. Se rilasciato sull'elemento cart, scatena una chiamata remota all'azione cart/add. L'azione sarà capace di determinare quale elemento è stato rilasciato nell'elemento cart controllando il parametro di richiesta id. Per cui il Listato 11-32 simula una sessione di acquisto reale: prendi oggetti e li metti nel carrello, e poi vai a pagare.

TIP Nel Listato 11-32, gli helpere sono scritti immediatamente dopo l'elemento che modificano, ma questo non è necessario. Potresti raggruppare tutti gli helper draggable_element() e drop_receiving_element() alla fine del template. La cosa importante è il primo argomento della chiamata all'helper, che specifica l'identificatore del ricevente.

L'helper draggable_element() accetta i seguenti parametri:

  • revert: Se impostato a true, quando rilasciato l'elemento tornerà alla sua posizione originale. Può anche essere una chiamata ad una funzione personalizzata, eseguita alla fine del trascinamento.
  • ghosting: Clona l'elemento e trascina il clone, lasciando l'originale dov'è fino alla fine del trascinamento.
  • snap: Se impostato a false, non verrà impostato alcun snapping. Altrimenti, l'elemento può essere trascinato solo in una griglia di intervallo x e y, ed in questo caso, assume la forma xy o [x,y] o function(x,y){ return [x,y]}.

L'helper drop_receiving_element() accetta i seguenti parametri:

  • accept: Una stringa od un array di stringhe che descrivono classi CSS. L'elemento accetterà solo elementi appartenenti ad una o più di tali classi.
  • hoverclass: classe CSS aggiunta all'elemento quando un elemento trascinato sopra di esso viene accettato.

Liste ordinabili

Un'altra possibilità offerta dagli elementi trascinabili è quella di ordinare una lista muovendo i suoi elementi con il mouse. L'helper sortable_element() aggiunge tale comportamento ad un oggetto, ed il Listato 11-33 è un buon esempio di implementazione di questa funzione.

Listato 11-33 - Esempio di liste ordinabili

[php]
<p>What do you like most?</p>
<ul id="order">
  <li id="item_1" class="sortable">Carrots</li>
  <li id="item_2" class="sortable">Apples</li>
  <li id="item_3" class="sortable">Oranges</li>
  // Nobody likes Brussel sprouts anyway
  <li id="item_4">Brussel sprouts</li>
</ul>
<div id="feedback"></div>
<?php echo sortable_element('order', array(
  'url'    => 'item/sort',
  'update' => 'feedback',
  'only'   => 'sortable'
)); ?>

Grazie alla magia dell'helper sortable_element(), l'elemento <ul> diventa ordinabile, che significa che i suoi figli possono essere riordinati tramite drag-and-drop. Ogni volta che un utente trascina un elemento e lo rilascia per riordinare la lista, viene effettuata una richiesta Ajax con i seguenti parametri:

POST /sf_sandbox/web/frontend_dev.php/item/sort HTTP/1.1
  order[]=1&order[]=3&order[]=2&_=

Tutta la lista viene passata in forma di array (nel formato order[$rank]=$id, $order comincia da 0 e $id dipendentemente da cosa c'è dopo l'underscore (_) nella proprietà id dell'elemento della lista). La proprietà id dell'elemento ordinabile (nell'esempio order) viene utilizzato come nome dell'array.

L'helper sortable_element() accetta i seguenti parametri:

  • only: Una stringa od un array di stringhe che descrivono classi CSS. Solo i figli che possiedono tale classe possono essere spostati.
  • hoverclass: Classe CSS aggiunta all'elemento quando il mouse gli passa sopra.
  • overlap: Impostalo a horizontal se gli oggetto sono visualizzati tutti in una linea, ed a vertical (valore di default) quando c'è un elemento per linea (come nell'esempio).
  • tag: Se la lista da ordinare non è composta da elementi <li>, devi definire quali figli devono diventare ordinabili (ad esempio div o dl).

TIP >Da symfony 1.1 puoi anche usare l'helper sortable_element() senza l'opzione url. Utile se vuoi ritardare la chiamata AJAX su un bottone salva o simili.

Edit in Place

Sempre più applicazioni permettono all'utente di modificare il contenuto della pagina senza il bisogno di rivisualizzare una form. Il principio di questa interazione è semplice. Un blocco di testo viene evidenziato quando l'utente ci passa sopra con il mouse. Se l'utente lo clicca, il testo viene convertito in un'area di testo con il contenuto del blocco di testo ed appare un pulsante per il salvataggio. L'utente può modificare il testo dentro la textarea, e dopo che ha salvato, la textarea sparisce ed il testo viene visualizzato come prima nella pagina. In symfony, puoi aggiungere questo comportamento ad un elemento tramite l'helper input_in_place_editor_tag(). Il Listato 11-34 mostra come usarlo.

Listato 11-34 - Esempio di live editing

[php]
<div id="edit_me">You can edit this text</div>
<?php echo input_in_place_editor_tag('edit_me', 'mymodule/myaction', array(
  'cols'        => 40,
  'rows'        => 10,
)) ?>

Quando l'utente clicca sul testo modificabile, esso viene inserito all'interno di un'area di testo che appare. Quando viene salvato (submit della form generata) viene chiamata in Ajax l'azione mymodule/myaction, passando il testo nel parametro value. Il risultato dell'azione aggiorna l'elemento. È molto veloce da scrivere e potente.

L'helper input_in_place_editor_tag() accetta i seguenti parametri:

  • cols e rows: Dimensione dell'area di testo che appare (diventa una <textarea se rows è maggiore di 1).
  • loadTextURL: URI dell'azione da chiamare per visualizzare il testo da modificare. Questo è utile se il contenuto dell'elemento modificabile usa una formattazione speciale e se vuoi che l'utente modifichi il testo senza tale formattazione.
  • save_text and cancel_text: Il testo sul link di salvataggio (default "ok") e su quello di annullamento (default "cancel").

Sommario

Se sei stanco di scrivere codice JavaScript nei tuoi template per avere interattività lato client, gli helper JavaScript offrono una semplice alternativa. Non solo essi automatizzano il comportamento normale dei link e l'aggiornamento degli elementi, ma forniscono anche un modo per sviluppare interazioni Ajax velocemente. Con l'aiuto della potenza e dei miglioramenti della sintassi forniti da Prototype e degli effetti visuali forniti da script.aculo.us, anche interazioni complesse non richiedono più di qualche linea da scrivere.