lunedì 24 dicembre 2007

AJAX: Liste di Selezione Dinamiche

Visto il periodo festivo non è il caso di trattare argomenti troppo lunghi o complicati. Quindi mi limito ad un semplice esempio di codice che dimostra come risolvere un problema piuttosto comune. Utilizzare AJAX per generare dinamicamente il contenuto di una lista di selezione in base al valore selezionato dall'utente in un'altra lista.

Creeremo una pagina html con una lista di selezione (tag html select) contenente un elenco di regioni. Quando si seleziona una regione nella lista, un elenco di capoluoghi di provincia appartenenti a tale regione sarà aggiunto dinamicamente ad una seconda lista di selezione.

Il tutto ovviamente si appoggia a due tabelle (regioni e province) in un database MySQL.

Il listato dimostrativo completo è scaricabile dal sito (Liste AJAX). L'archivio zip può essere decompresso in una directory del vostro sito web o testato sul vostro PC se avete installato localmente Apache, MySQL e PHP (tramite ad esempio Wamp, Xampp o simili).

Il contenuto del file liste_ajax.zip è il seguente

  • index.html
  • lists.js
  • request.php
  • dbconfig.php
  • db.sql

Limiterò la spiegazione alle parti essenziali senza riportare qui tutto il codice, potrete trovare maggiori dettagli nei commenti all'interno dei sorgenti.

1) index.html

Il documento html contenente le due liste di selezione regioni e province. Da notare che i tag <select> non contengono nessun tag <option> al loro interno in quanto questi saranno generati ed aggiunti dinamicamente con AJAX.

2) lists.js

Contiene tutte le funzioni Javascript necessarie.

getXmlHttpObject crea un oggetto XMLHttpRequest che verrà utilizzato per inviare le richieste al server. Si tratta di codice direttamente tratto (per non saper nè leggere nè scrivere) dagli esempi presenti sul sito www.w3schools.com. Conviene utilizzare sempre questa funzione quando si lavora con AJAX perché è fatta in modo da garantire la compatibilità degli script con diversi browser.

loadList invia una richiesta al server passando a request.php il nome della tabella (regioni o province) da cui estrarre i valori da inserire nella lista di selezione e il valore del campo ID da usare nella clausola WHERE della query. Per la tabella province l'ID è quello della regione selezionata nella prima lista, per la tabella regioni il valore di ID è sempre zero.

La richiesta viene inviata al server in modalità asincrona quindi si specifica una funzione (stateChanged) che processerà il risultato della richiesta.

xmlHttp.onreadystatechange = stateChanged;

stateChanged riceve la risposta dal server e, se tutto è andato bene (xmlHttp.status == 200), svolge il lavoro di riempimento della lista di selezione generando dinamicamente gli elementi <option>. Il codice della funzione dovrebbe essere chiaro una volta esaminato il formato della risposta inviata dal server (vedi request.php).

addOption è una funzione di servizio che serve ad aggiungere dinamicamente un elemento <option> ad una lista di selezione <select>.

getSelected restituisce il valore dell'elemento correntemente selezionato in una lista. Viene utilizzata nell'evento onchange della lista regioni (vedi index.html).

3) request.php

Lo script che riceve la richiesta inviata dall'oggetto XMLHttpRequest, interroga il database e restituisce i dati per la generazione dinamica del contenuto della lista di selezione.

Quando la richiesta riguarda la tabella regioni vengono sempre restituiti tutti i record. Per la tabella province vengono restituiti i record il cui campo id_regione è uguale all'id passato nella richiesta ($_REQUEST['id']).

Il formato della risposta è

nome_tabella;id1|nome1;id2|nome2 ...

Le coppie id e nome sono ripetute tante volte quanti sono i record estratti.

4) dbconfig.php

Contiene le costanti per la connessione al database. Devono essere modificate con i valori necessari per connettersi al vostro server MySQL e database.

5) db.sql

Lo script sql necessario alla creazione delle tabelle regioni e province usate dal codice di esempio. Sono pochi campi, ma lanciando lo script vi risparmiate la fatica di creare le tabelle a mano e vi resta più tempo per incartare gli ultimi regali!

Per facilitare la comprensione del meccanismo conviene fare un esempio di cosa accade quando si visita la pagina web e si seleziona un elemento di una lista. Conviene seguire il percorso avendo i sorgenti a portata di mano.

1) index.html viene caricata nel browser. Le due liste di selezione sono vuote.

2) Un'istanza dall'oggetto XMLHttpRequest viene creata dall'istruzione

var xmlHttp = getXmlHttpObject();

nel file lists.js (caricato nell'header di index.html)

3) Questo script (prima di </body> in index.html)

<script type="text/javascript">
//<![CDATA[
loadList('regioni', 0);
//]]>
</script>

viene eseguito.

4) La funzione loadList invia questa richiesta al server

request.php?table=regioni&id=0

5) In request.php la richiesta si traduce in questa query

SELECT id, nome FROM `regioni` ORDER BY nome

Selezionati i record, il server invia questa risposta

regioni;4|Campania;2|Liguria;1|Lombardia;3|Toscana

6) La funzione Javascript stateChanged in lists.js elabora la risposta: la stringa è trasformata in array con la funzione Javascript split

var values = resp.split(';');

Il primo elemento dell'array viene estratto e utilizzato per identificare l'id della lista di selezione

var listId = values.shift();
var select = document.getElementById(listId);

Gli altri elementi dell'array sono processati uno ad uno. I valori del campo ID e Nome (separati da |) sono utilizzati rispettivamente come attributo value e testo visibile dei tag <option> che sono creati ed aggiunti alla lista di selezione con la funzione addOption.

Questo è il codice html generato dinamicamente ed aggiunto alla lista di selezione regioni

<option value="4">Campania</option>
<option value="2">Liguria</option>
<option value="1">Lomabardia</option>
<option value="3">Toscana</option>

A questo punto supponiamo di selezionare Toscana nella lista. Questo è quello che accade.

1) Viene eseguito il codice nell'evento onchange della lista regioni

onchange="loadList('province', getSelected(this))"

La funzione getSelected ritorna l'attributo value dell'elemento <option> selezionato (Toscana -> 3)

2) La funzione loadList invia questa richiesta al server

request.php?table=province&id=3

3) In request.php viene quindi eseguita la query

SELECT id, nome FROM `province` WHERE id_regione=3 ORDER BY nome

e la risposta è

province;5|Firenze;6|Lucca

Da qui in poi è tutto come sopra. La funzione stateChanged elabora la richiesta e genera il contenuto della lista province.

Possiamo concludere. Dovrebbe essere abbastanza facile estendere il codice per includere ulteriori livelli. Per esempio tre liste di selezione: regioni, province e comuni.

Anche se l'esempio non è originalissimo, la spiegazione, forse, è un po' più dettagliata di quelle che si trovano in giro. Spero vi sia utile.

Buone Feste a tutti.

14 commenti:

Shark69 ha detto...

Semplice ma esauriente proprio quello che cercavo.. grazie e continua così.

Anonimo ha detto...

Ottimo

Angelo ha detto...

Direi che è davvero perfetto come tutorial! Grazie mille per il supporto!

Massimo ha detto...

Ti ringrazio :)

Anonimo ha detto...

ciao!
mi piacciono davvero i tuoi articoli.
Pero' ho un problema.
Io vorrei mettere delle Select dinamiche (provincia/regione) nel backend di joomla.
Ho provato medificando quersti file, ma non mi riesca :(

Massimo ha detto...

Queso è un esempio in AJAX, per integrarlo in Joomla devi imparare come si sviluppa un componente Joomla. Ci sono diversi post sull'argomento anche in questo blog.

Anonimo ha detto...

Congratulazioni!!! blog davvero utile.

Anonimo ha detto...

ho sacicato tutti e funziona molto bene, ho solo problemi a far funzionare correttamente le tre select, regioni, probince e comuni, serebbe opportuno fare apparire anche nella seconda (dopo aver selezionato regione) e terza (dopo aver selezionato provincia) prima di continuare la frase selezionare perchè se uno sbaglia la regione non si riesce a modificare correttamente

Massimo ha detto...

Non ho mica capito se stai parlando dell'esempio in questo post. Perché qui le select sono due: regione / provincia. Se hai aggiunto un altro livello qualche modifica anche al resto del codice dovrai farla.

Anonimo ha detto...

Scusatemi ma stò impazzendo nel mettere nello stesso form 2 volte la stessa istruzione.
Ovvero ho modificato il tuo script per far si che veda 3 select (categorie professionali e ruoli professionali - funziona perfettamente) e una con le regioni e province.
Ho modificato tutto: lists è diventato lists2 - request.php è diventato request2.php.
Inutile dirvi che ho modificato sia in lists che in request le voci corrispondenti!!
Forse non può essere caricato 2 volte sullo stesso form???

Help me vi prego

Massimo ha detto...

Credo il problema sia che se usi due serie di liste, quando vai a creare i valori iniziali delle due liste di primo livello con la funzione loadList() vengono inviate due richieste consecutive e la prima non fa in tempo a rispondere prima che venga inviata la seconda. Prova questa versione: liste_ajax2.zip. Se guardi il codice vedrai che loadList() non invia la richiesta, ma la accoda in un'array. C'è poi una funzione checkQueue() che viene eseguita ad intervalli regolari (setInterval() in index.html) e che controlla la coda ed esegue le richieste una alla volta, utilizzando una variabile globale (busy) come 'semaforo', per evitare di iniziare una richiesta quando la precedente è ancora in esecuzione. La pagina di esempio (index.html) contiene oltre alle due liste regioni/provincie altre tre liste tipo/marca/modello. In questo modo dovrebbe funzionare con più serie di liste sulla stessa pagina e senza bisogno di duplicare gli script. Almeno a me funziona su tutti i browser che ho potuto provare. Il pacchetto come l'originale contiene lo script SQL da eseguire per creare tabelle e dati di esempio (db.sql) mentre dbconfig.php va modificato con i dati per l'accesso al db MySql che ti sarai creato.

Anonimo ha detto...

Fenomenale!! Grazie mille

Anonimo ha detto...

Ciao Massimo, scusami se disturbo ancora, ma ho un piccolo (spero) problema e mi auguro tanto che tu mi possa aiutare.
Le liste le ho inserite in un form di registrazione in asp.
Quando registro mi vengono segnati i valori, o meglio l'ID del valore di ciascuna lista e non ad esempio il nome corrispondente.
Giusto per essere più chiaro:
Esempio:
Campania - Napoli - Gragnano mi scrive sulla tabella: 4 - 5 - 400.

Credo, ma temo di dire cavolate, che il problema (se cosi possiamo chiamarlo) è in request.php.
Mi dai una mano per piacere?
Grazie

Massimo ha detto...

Avrai invertito l'ordine dei campi quando fai la SELECT: prima va l'ID e poi la descrizione. Se no quando viene creato il tag OPTION delle liste l'ID che deve andare nell'attributo value va al posto del testo.