Integrare AJAX con JQuery, PHP e JSON

Ajax è una tecnologia JavaScript che abbiamo già capito essere essenziale per una web-app moderna e che risponda alle esigenze di UI/UX simili alle applicazioni tradizionali da desktop ma con i dovuti vantaggi. JQuery è una libreria Javascript che abbiamo già visto su queste pagine semplifica enormemente alcune funzionalità e Ajax è una di queste! Vediamo come.

Abbiamo già proposto un semplice utilizzo di Ajax/JS in questo articolo. Complichiamoci un po’ la vita e proponiamo un esempio più concreto. Quante volte vi siete imbattuti in una form dove dovevate compilare una serie di tendine regione + provincia di residenza? O meglio, una tendina dove selezionate la regione desiderata e in corrispondenza la tendina successiva visualizza solo le province della regione scelta, semplificando di fatto la selezione senza scorrere centinaia di voci? L’idea è di realizzarlo con ajax, in modo che non ci sia passaggio tra script differenti ma l’utente abbia l’impressione di rimanere sempre sulla stessa pagina, pur richiamando script differenti per la gestione dei dati.

Creiamoci prima un paio di tabelle che ci servono per questa nostra applicazione di esempio, una per le regioni ed una per le province. Riporto il codice esemplificativo con create table e dati che può tornare utile per tanti altri esercizi.

Tabella Regioni

CREATE TABLE `regioni` (
  `id` int(11) NOT NULL,
  `nome` varchar(128) NOT NULL,
  `capoluogo` varchar(128) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

INSERT INTO `regioni` (`id`, `nome`, `capoluogo`) VALUES
(1, 'Lombardia', 'Milano'),(2, 'Lazio', 'Roma'),(3, 'Campania', 'Napoli'),(4, 'Sicilia', 'Palermo'),(5, 'Veneto', 'Venezia'),(6, 'Emilia-Romagna', 'Bologna'),(7, 'Piemonte', 'Torino'),(8, 'Puglia', 'Bari'),(9, 'Toscana', 'Firenze'),(10, 'Calabria', 'Catanzaro'),
(11, 'Sardegna', 'Cagliari'),(12, 'Liguria', 'Genova'),(13, 'Marche', 'Ancona'),(14, 'Abruzzo', 'L\'Aquila'),(15, 'Friuli-Venezia Giulia', 'Trieste'),(16, 'Trentino-Alto Adige', 'Trento'),(17, 'Umbria', 'Perugia'),(18, 'Molise', 'Campobasso'),(19, 'Basilicata', 'Potenza'),(20, 'Valle d\'Aosta', 'Aosta');

ALTER TABLE `regioni`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `nome` (`nome`);

Tabella Province

CREATE TABLE `province` (
  `id` int(16) NOT NULL,
  `nome` varchar(128) NOT NULL,
  `sigla` varchar(5) NOT NULL,
  `regione` varchar(128) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


INSERT INTO `province` (`id`, `nome`, `sigla`, `regione`) VALUES
(1, 'Agrigento', 'AG', 'Sicilia'),(2, 'Alessandria', 'AL', 'Piemonte'),(3, 'Ancona', 'AN', 'Marche'),(4, 'Arezzo', 'AR', 'Toscana'),(5, 'Ascoli Piceno', 'AP', 'Marche'),
(6, 'Asti', 'AT', 'Piemonte'),(7, 'Avellino', 'AV', 'Campania'),(8, 'Bari', 'BA', 'Puglia'),(9, 'Barletta-Andria-Trani', 'BT', 'Puglia'),(10, 'Belluno', 'BL', 'Veneto'),(11, 'Benevento', 'BN', 'Campania'),(12, 'Bergamo', 'BG', 'Lombardia'),(13, 'Biella', 'BI', 'Piemonte'),(14, 'Bologna', 'BO', 'Emilia-Romagna'),(15, 'Bolzano', 'BZ', 'Trentino-Alto Adige'),(16, 'Brescia', 'BS', 'Lombardia'),(17, 'Brindisi', 'BR', 'Puglia'),(18, 'Cagliari', 'CA', 'Sardegna'),(19, 'Caltanissetta', 'CL', 'Sicilia'),
(20, 'Campobasso', 'CB', 'Molise'),(21, 'Carbonia-Iglesias', 'CI', 'Sardegna'),(22, 'Caserta', 'CE', 'Campania'),(23, 'Catania', 'CT', 'Sicilia'),(24, 'Catanzaro', 'CZ', 'Calabria'),(25, 'Chieti', 'CH', 'Abruzzo'),(26, 'Como', 'CO', 'Lombardia'),(27, 'Cosenza', 'CS', 'Calabria'),(28, 'Cremona', 'CR', 'Lombardia'),(29, 'Crotone', 'KR', 'Calabria'),(30, 'Cuneo', 'CN', 'Piemonte'),(31, 'Enna', 'EN', 'Sicilia'),(32, 'Fermo', 'FM', 'Marche'),(33, 'Ferrara', 'FE', 'Emilia-Romagna'),(34, 'Firenze', 'FI', 'Toscana'),(35, 'Foggia', 'FG', 'Puglia'),(36, 'Forlì-Cesena', 'FC', 'Emilia-Romagna'),(37, 'Frosinone', 'FR', 'Lazio'),(38, 'Genova', 'GE', 'Liguria'),(39, 'Gorizia', 'GO', 'Friuli-Venezia Giulia'),(40, 'Grosseto', 'GR', 'Toscana'),(41, 'Imperia', 'IM', 'Liguria'),(42, 'Isernia', 'IS', 'Molise'),(43, 'L\'Aquila', 'AQ', 'Abruzzo'),(44, 'La Spezia', 'SP', 'Liguria'),(45, 'Latina', 'LT', 'Lazio'),(46, 'Lecce', 'LE', 'Puglia'),(47, 'Lecco', 'LC', 'Lombardia'),(48, 'Livorno', 'LI', 'Toscana'),(49, 'Lodi', 'LO', 'Lombardia'),(50, 'Lucca', 'LU', 'Toscana'),(51, 'Macerata', 'MC', 'Marche'),(52, 'Mantova', 'MN', 'Lombardia'),(53, 'Massa e Carrara', 'MS', 'Toscana'),(54, 'Matera', 'MT', 'Basilicata'),(55, 'Medio Campidano', 'VS', 'Sardegna'),(56, 'Messina', 'ME', 'Sicilia'),(57, 'Milano', 'MI', 'Lombardia'),
(58, 'Modena', 'MO', 'Emilia-Romagna'),(59, 'Monza e Brianza', 'MB', 'Lombardia'),
(60, 'Napoli', 'NA', 'Campania'),(61, 'Novara', 'NO', 'Piemonte'),(62, 'Nuoro', 'NU', 'Sardegna'),(63, 'Ogliastra', 'OG', 'Sardegna'),(64, 'Olbia-Tempio', 'OT', 'Sardegna'),(65, 'Oristano', 'OR', 'Sardegna'),(66, 'Padova', 'PD', 'Veneto'),(67, 'Palermo', 'PA', 'Sicilia'),(68, 'Parma', 'PR', 'Emilia-Romagna'),(69, 'Pavia', 'PV', 'Lombardia'),(70, 'Perugia', 'PG', 'Umbria'),(71, 'Pesaro e Urbino', 'PU', 'Marche'),
(72, 'Pescara', 'PE', 'Abruzzo'),(73, 'Piacenza', 'PC', 'Emilia-Romagna'),(74, 'Pisa', 'PI', 'Toscana'),(75, 'Pistoia', 'PT', 'Toscana'),(76, 'Pordenone', 'PN', 'Friuli-Venezia Giulia'),(77, 'Potenza', 'PZ', 'Basilicata'),(78, 'Prato', 'PO', 'Toscana'),(79, 'Ragusa', 'RG', 'Sicilia'),(80, 'Ravenna', 'RA', 'Emilia-Romagna'),(81, 'Reggio Calabria(metropolitana)', 'RC', 'Calabria'),(82, 'Reggio Emilia', 'RE', 'Emilia-Romagna'),(83, 'Rieti', 'RI', 'Lazio'),(84, 'Rimini', 'RN', 'Emilia-Romagna'),
(85, 'Roma', 'RM', 'Lazio'),(86, 'Rovigo', 'RO', 'Veneto'),(87, 'Salerno', 'SA', 'Campania'),(88, 'Sassari', 'SS', 'Sardegna'),(89, 'Savona', 'SV', 'Liguria'),(90, 'Siena', 'SI', 'Toscana'),(91, 'Siracusa', 'SR', 'Sicilia'),(92, 'Sondrio', 'SO', 'Lombardia'),(93, 'Taranto', 'TA', 'Puglia'),(94, 'Teramo', 'TE', 'Abruzzo'),
(95, 'Terni', 'TR', 'Umbria'),(96, 'Torino', 'TO', 'Piemonte'),(97, 'Trapani', 'TP', 'Sicilia'),(98, 'Trento', 'TN', 'Trentino-Alto Adige'),(99, 'Treviso', 'TV', 'Veneto'),(100, 'Trieste', 'TS', 'Friuli-Venezia Giulia'),(101, 'Udine', 'UD', 'Friuli-Venezia Giulia'),(102, 'Aosta', 'AO', 'Valle d\'Aosta'),(103, 'Varese', 'VA', 'Lombardia'),(104, 'Venezia', 'VE', 'Veneto'),(105, 'Verbano-Cusio-Ossola', 'VB', 'Piemonte'),(106, 'Vercelli', 'VC', 'Piemonte'),(107, 'Verona', 'VR', 'Veneto'),(108, 'Vibo Valentia', 'VV', 'Calabria'),(109, 'Vicenza', 'VI', 'Veneto'),(110, 'Viterbo', 'VT', 'Lazio');

ALTER TABLE `province`
  ADD PRIMARY KEY (`id`),
  ADD KEY `regione` (`regione`);

ALTER TABLE `province`
  MODIFY `id` int(16) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=111;
ALTER TABLE `province`
  ADD CONSTRAINT `province_ibfk_1` FOREIGN KEY (`regione`) REFERENCES `regioni` (`nome`) ON DELETE RESTRICT ON UPDATE RESTRICT;
COMMIT;

Ci sono anche le INSERT con i dati per popolare le tabelle. Adattate il codice usando eventualmente il vostro phpMyAdmin o altro sistema per importare il codice sql.

Procediamo ora con gli script. Il primo è una semplice form html dove andremo a richiamare il codice php per caricare le regioni e uno script JQuery che si collegherà ad un secondo script che effettuerà la query delle province con un parametro “regione” passato e restituirà il risultato sotto forma di oggetto JSON. Vi rimando a questo articolo della nostra pagina per approfondire il tema JSON. 

scegliprovincia.php 

Prima di tutto il nostro script deve necessariamente chiamare i dati corretti delle regione. Questo lo possiamo fare in modo tradizionale con il nostro script PHP. Per comodità, ho separato il codice che pesca le regioni su db in uno script a parte rispetto alla pagina principale. Per le province, invece, impostiamo solo il codice della tendina ma ancora non sappiamo quale sarà la selezione della regione che verrà fatta in corsa con la pagina già caricata, quindi tocca attendere per poter passare la scelta allo script che vedremo tra poco. 

<!DOCTYPE html>
<html lang="it">
	<head>
		<title>Esempio AJAX/JQuery</title>
		<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
	</head>
<body>
<h1>Registrati</h1>
<form method="post" action="action.php">
<fieldset>
	<legend>Luogo di Nascita</legend>
	<label for="regione">Regione:</label> <span id="errregione"></span>

	<select name="regione" id="regione">
		<option value="">-- scegli una regione</option>    
		<?php include("getregioni.php"); ?>         
	</select>

	<br>
	
	<label for="provincia">Provincia:</label> <span id="errprovincia"></span>
	<select name="provincia" id="provincia">  
		<option value="">-- scegli una provincia</option>    
	</select>
</fieldset>	
</form>
</body>	
</html>

Lo script che carica le nostre regioni è relativamente semplice e visto in molte esercitazioni analoghe. Abbiamo diviso la parte che gestisce i dati dalla parte di logica/grafica, pratica che dovrebbe essere attuata spesso nel nostro codice, soprattutto se cresce di complessità.

<?php
require_once("config.php");

$connessione = mysqli_connect($mysql_host,$mysql_user,$mysql_pass,$mysql_db);
if (mysqli_connect_errno())
  die("Connessione non riuscita: " . mysqli_error($connessione));

$query = "SELECT nome FROM regioni WHERE 1";
$res = mysqli_query($connessione,$query) or die("Errore: ".mysqli_error($connessione));
mysqli_close($connessione);

while ($row = mysqli_fetch_assoc($res))
{
	$nome= $row['nome'];
	echo "<option value='$nome'>$nome</option>";
} 
?>

Come noterà il lettore, getregioni.php non torna una pagina HTML ma un componente HTML impostato con i valori giusti via back-end.

Benissimo, testiamo il nostro script: se tutto è andato per il verso giusto abbiamo qualcosa di simile a questo sotto con la tendina Regione valorizzata, mentre Provincia è decisamente vuota.

Passiamo quindi a valorizzare la tendina Provincia con le voci corrispondenti alla Regione scelta dall’utente. Quale miglior occasione per prelevare valori lato client se non con JQuery, fratello maggiore di JavaScript. L’idea è che bisogna prelevare la regione scelta solo a fronte del click sulla tendina delle province contraddistinta dall’id #province, quindi da li scatenare la richiesta delle province corrette.

Ora abbiamo due possibilità:

1. Approccio JSON puro -> creiamo uno script PHP che ritorna un oggetto JSON

2. Approccio testuale con JSON -> creiamo uno script PHP che stampa una riga testuale che descrive il contenuto in JSON

I due metodi sono pressoché equivalenti, il primo decisamente più versatile, il secondo forse più intuitivo. Entrambi gli script in questione faranno comunque una query con un vettore associativo di ritorno.

getprovince.php – Approccio 1. JSON puro

<?php
header("Content-type: application/json");
require_once("config.php");

$connessione = mysqli_connect($mysql_host,$mysql_user,$mysql_pass,$mysql_db);
if (mysqli_connect_errno())
  die("Connessione non riuscita: " . mysqli_error($connessione));

if (isset($_GET['regione']))
{
	$regione = mysqli_real_escape_string($connessione, stripcslashes(trim($_GET['regione'])));
	$query = "SELECT nome,sigla FROM province WHERE regione = '$regione'";
        $res = mysqli_query($connessione,$query) or die("Errore: ".mysqli_error($connessione));
	mysqli_close($connessione);

	$jsonarray= array();
	while ($row = mysqli_fetch_assoc($res))  
	{
		$jsonarray[] = $row;
	}

	echo json_encode($jsonarray); 
	exit;
}
?>

Notiamo subito che la pagina PHP ha intestazione Content-type: application/json che è magari poco intuitivo per il lettore meno esperto, ma PHP non è costretto a tornare una documento di testo o html, ma un qualsiasi tipo di documento a patto di “avvisare” il browser che gli stanno mando un PDF o un XML o un JSON. Terminata la query, procediamo a spacchettare il risultato della query con la classica funzione fetch_assoc ma il risultato invece di stamparlo a video lo impachettiamo riga per riga dentro un vettore che conterrà quindi tutte le righe di vettori associativi del risultato. Procediamo quindi con l’encoding attraverso la funzione standard. Se proviamo a lanciare lo script con apache, da solo, col semplice parametro necessario, avremo un risultato simile a quello sotto dove non c’è una pagina HTML, ma una insolita formattazione con tanto di dicitura JSON.

Abbiamo esposto i dati, ovvero sono reperibili a patto che qualcosa richiami lo script PHP e riconverta il risultato. Ma questo è perfettamente fattibile con Ajax, ovvero uno script lato client che ne invoca uno lato server, prelevandone i dati risultati senza obbligare l’utente ad abbandonare la pagina o effettuare un refresh della stessa. Ajax in JQuery poi è particolarmente semplice ed intuitivo. Il seguente script lo abbiamo creato nella cartella js, col nome di loadprovince.js e può essere richiamato in qualsiasi punto del nostro script principale sceglieprovincia.php, ad esempio nello <head> col classico

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="js/loadprovince.js"></script> 

loadprovince.js

$(document).ready(function() {
    $("#regione").change(
        function(){
        var scelta = $("#regione").val(); 
        $('#provincia').empty();   
        $.ajax({
        url: "getprovince.php", 
        dataType: "json",
        data: {"regione": scelta},  
        success:function(json)
                {
                    $.each(json, function(i, riga) {
                        $('#provincia').append("<option value='"+riga.sigla+"'>"+riga.nome+"</option>");
                    });
                },
        error:  function(richiesta,stato)
                {
                    errore= "<b>AJAX fallita:</b><br>"+richiesta.status+" "+stato;
                    $("#errprovincia").html(errore);
                }

        });//chiudi $.ajax
    });//chiudi change
}); //chiudi $(document).ready

Lo script reagisce quando viene cliccata e selezionata una voce nella tendina della select delle regioni; viene pescata il valore scelto nella relativa tendina, si ripulisce il contenuto della tendina province e si procede ad invocare la funzione $.ajax({ }) che avrà il compito di prelevare i dati e aggiungerli alla tendina con l’opportuna forma html. Notiamo come lo script ossa essere scatenato in modo analogo al click della tendina provincia, ma un fastidioso comportamento impedirebbe di selezionare una voce nella fresca tendina dinamica che riscatenerebbe l’evento click.

La funzione ajax ha una serie di parametri nella forma $.ajax({ param0: valore, param1: valore, param2: valore ecc }) che per leggere meglio abbiamo indentato a capo uno sotto l’altro. Molti di questi parametri sono poco utilizzati se non applicazioni specifiche. A noi ne bastano una manciata ma vi rimando a questo link per leggerli tutti. Sono opzionali e si possono digitare nell’ordine più comodo che si vuole. A noi interessa:

url -> è l’indirizzo dello script o risorsa da chiamare che espone i dati del database. Può essere o un path relativo (es. ../script.php o ./lib/pippo.php) o assoluto (es. http://www.pippo.it/script.php), oppure direttamente un file xml o json

dataType ->  può essere di tre tipi: json, jsonp, text. Il primo è relativo ai file json scaricati all’interno dello stesso dominio, il secondo quelli che vengono chiamati su host differenti da quelli che ospitano i nostri file, il terzo quando a tornare è una stringa di testo generica o una stringa di testo contenete un json che vediamo dopo.

data -> sono i parametri che passiamo allo script come se fosse il ben noto script.php?parametro=’valore’. Qui vediamo che il parametro è regione con valore passato dalla variabile scelta. Si usa la & per aggiungere più parametri

method-> di default è GET ma si può usare il più sicuro POST

success-> una funzione che gestisce la chiamata ajax e i dati di ritorno passati su una variabile che qui chiamiamo json

error-> una funzione che gestisce eventuali errori passando alle variabibili che qui chiamiamo richiesta e stato le informazioni utili

$(document).ready(function() {
    $("#provincia").click(
        function(){
        var scelta = $("#regione").val(); 
        $('#provincia').empty();   
        $.ajax({
        url: "getprovince.php", 
        dataType: "json",
        data: {"regione": scelta},  
        success:function(json)
                {
                    $.each(json, function(i, riga) {
                        $('#provincia').append("<option value='"+riga.sigla+"'>"+riga.nome+"</option>");
                    });
                },
        error:  function(richiesta,stato)
                {
                    errore= "<b>AJAX fallita:</b><br>"+richiesta.status+" "+stato;
                    $("#errprovincia").html(errore);
                }

        });//chiudi $.ajax
    });//chiudi change
}); //chiudi $(document).ready

Nella funzione success, andiamo ad aggiungere delle option alla tendina prelevando i dati con una sorta di ciclo while detto $.each che scorre tutti i risultati della variabile json e li spacchetta riga per riga come se fosse una sorta di vettore associativo ad oggetti.

getprovince.php – Approccio 2. JSON testuale

<?php
require_once("config.php");

$connessione = mysqli_connect($mysql_host,$mysql_user,$mysql_pass,$mysql_db);
if (mysqli_connect_errno())
  die("Connessione non riuscita: " . mysqli_error($connessione));

if (isset($_GET['regione']))
{
	$regione = mysqli_real_escape_string($connessione, stripcslashes(trim($_GET['regione'])));
	$query = "SELECT nome,sigla FROM province WHERE regione = '$regione'";
	$res = mysqli_query($connessione,$query) or die("Errore nella login: " . mysqli_error($connessione));
	mysqli_close($connessione);

	$jsonarray= array();
	while ($row = mysqli_fetch_assoc($res))  
	{
		$jsonarray[] = $row;
	}

	echo json_encode($jsonarray); 
	exit;
}
?>

che è pressoché identico tranne per l’intestazione header, che se non specificata, viene intesa text/html ed infatti viene stampata una riga di testo come la seguente:

Correggiamo anche lo script jquery, dove compare una funzione JSON.parse che converte la riga testuale dello script php in un oggetto JSON vero e proprio da utilizzare:

$(document).ready(function() {
    $("#provincia").click(
        function(){
        var scelta = $("#regione").val(); 
        $('#provincia').empty();   
        $.ajax({
        url: "getprovince.php", 
        dataType: "text",
        data: {"regione": scelta},  
        success:function(json)
                {
                    dati = JSON.parse(json)
                    $.each(dati, function(i, riga) {
                        $('#provincia').append("<option value='"+riga.sigla+"'>"+riga.nome+"</option>");
                    });
                },
        error:  function(richiesta,stato)
                {
                    errore= "<b>AJAX fallita:</b><br>"+richiesta.status+" "+stato;
                    $("#errprovincia").html(errore);
                }

        });//chiudi $.ajax
    });//chiudi change
}); //chiudi $(document).ready

Se tutto è scritto a dovere, abbiamo realizzato una funzionalità non banale del nostro sito web. Immaginate quanto possa tornare utile con form di iscrizione complesse, carrelli o iti di e-commerce in generale una tecnologia simile.

Ultima modifica 8 Luglio 2022