CRUD con AJAX in Javascript vanilla e PHP

In queste pagine abbiamo visto numerosi esercizi con AJAX per testarne le possibilità (il primo introduttivo della serie qui). Vediamo forse il più lampante degli aspetti che si affronta già abbondantemente nei corsi di informatica degli Istituti tecnici tecnologici e Servizi informativi aziendali, ma utilizzando JS e AJAX per interrogare ed aggiornare il database. L’approccio è volutamente basato su JavaScript puro. In rete potete trovare numerosi esempi che affrontano la tematica in modo similare ma con l’uso di JQuery, tecnologia che negli ultimi tempi sta perdendo di appeal tra i programmatori a vantaggio di strutture framework differenti e che non trattiamo in ambito scolastico per strettezze temporali.

L’esempio è un classico molto semplice: inserimento, cancellazione, modifica e visualizzazione di news costituite dal solo titolo e corpo della notizia. Ovviamente l’esempio può essere replicato con qualsiasi oggetto più complesso. Inserimento, visualizzazione e cancellazione li effettuiamo tutti in un unica pagina. La modifica per ragioni pratiche e di UX siamo costretti o a farli in in una pagina nuova o ad aprire una finestra modale. Come visibile dall’immagine, abbiamo volutamente spinto per creare una grafica che fosse accattivante con un piccolo foglio di stile e Bootstrap. Lo studente che affronta l’esame di stato o che deve affrontare una verifica cartacea può epurare il suo codice dalle classi di Bootstrap ma valutare di lasciare le aggiunte di stile css di sfondi e contenitori che sono in realtà molto semplici.

Esempio di realizzazione

La pagina iniziale

Il codice della pagina iniziale è molto scarno ma ce lo aspettiamo: ci sono due contenitori per l’inserimento e la visualizzazione ma in realtà i l grosso della visualizzazione dei dati è lasciata al javascript che vedremo più avanti. L’unico codice particolare è il modal bootstrap posto in fondo allo script. Questo codice è ovviamente preso dalla guida di Bootstrap e rimane nascosto finché non viene premuto un link di modifica tra i record presenti. La tendina modale che si apre conterrà una semplice form e due pulsanti uno dei quali, il salvataggio, è collegato alla funzione AJAX di modifica. La tendina si apre con la form precompilata con i dati del record da cui si è scatenato il click, dati prelevati ovviamente con AJAX. In fondo ancora al nostro script html, ricordiamoci di includere il file js customizzato con le funzioni js e ajax.

<!DOCTYPE html>
<html lang="it">
    <head>
        <title>News AJAX</title>
        <meta charset="utf-8">
               
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
        <link href="css/stile.css" rel="stylesheet">     
    </head>

    <body>
        <br>

        <div class="container w-50 p-3 box">
            <h1>CRUD con AJAX</h1>
            <br>
            <h2>Inserisci News</h2>
            <div id="messaggio"><br></div>
            <form>
                <div class="mb-3 w-50">
                    <label for="titolo" class="form-label fw-bold">Titolo</label> 
                    <input type="text" class="form-control" id="titolo" placeholder="titolo news"> 
                </div>   
                <div class="mb-3 w-50">
                    <label for="corpo" class="form-label fw-bold">Corpo</label>
                    <textarea class="form-control" id="corpo" placeholder="corpo news"></textarea>
                </div> 
                <button type="reset" class="btn btn-lightblue" id="cancella">Cancella</button>
                &nbsp;&nbsp;
                <button type="button" class="btn btn-blue" id="inserisci">Inserisci</button> 
                
            </form>

        </div>
               
        <br><br>
        <!-- VISUALIZZO IL RISULTATO AJAX -->

        <div class="main">
            <div class="container w-50 p-3 box">
            <h2>Visualizza/Elimina News</h2>
            <div id="msgelimina"><br></div>
                <div class="container">
                    <ul id="lista">
                    </ul>
                </div>                        
            </div>
        </div>
        <br>

        <!-- Modal Bootstrap -->
        <div class="modal fade" id="modificaModal" tabindex="-1" aria-labelledby="labelModifica" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
            <div class="modal-header">
                <h1 class="modal-title fs-5" id="labelModifica">Modifica News</h1>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
            <div class="mb-3">
                    <label for="modificaId" class="form-label">Id</label>
                    <input type="text" class="form-control" id="modificaId" readonly>
                </div>                
                <div class="mb-3">
                    <label for="modificaTitolo" class="form-label">Titolo</label>
                    <input type="text" class="form-control" id="modificaTitolo">
                </div>
                <div class="mb-3">
                    <label for="modificaCorpo" class="form-label">Corpo</label>
                    <textarea class="form-control" id="modificaCorpo" rows="3"></textarea>
                </div>

                <div class="mb-3">
                    <div id="msgaggiorna"><br></div>
                </div>    

            </div>

            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
                <button type="button" class="btn btn-primary" id="salvamodifiche">Salva modifiche</button>
            </div>
            </div>
        </div>
        </div>

    </body>
    
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN" crossorigin="anonymous"></script>
    <script src="js/elenco-ajax.js"></script>        

</html>

La visualizzazione

La porzione del codice che si occupa della visualizzazione è racchiusa in una funzione aggiornaElenco(). Questa funzione viene richiamata a necessità ogni qual volta si debba fare un refresh dell’elenco, a cominciare dal caricamento iniziale a pagina aperta. Il codice non è complesso. E’ uno script AJAX classico che richiama a sua volta uno script news.php con modalità GET, ne preleva il risultato esposto in JSON. Se readystate e status sono ottimali, si resetta lo spazio dedicato al listato dei titoli, si scorre il JSON e si crea una struttura di nodi figlio. Qui le parti più delicate. Mostrare un titolo e due link non presenta troppi problemi ma bisogna tener conto che i link devono portare con loro dei dati significativi sulla chiave del record a cui afferiscono. Ci sono due modi: o usiamo dei parametri automatizzati all’interno dei link dove aggiungiamo direttamente http://link?param=valore oppure lasciamo i link vuoti con # e inseriamo uno o più attributi custom che poi vogliamo intercettare con il javascript. Per motivi tecnologici, consigliamo sempre questa seconda soluzione, anche se forse, la meno calcata sui libri di testo e le esercitazioni scolastiche. Qui vediamo la dicitura setAttribute(“data-id”,dati[chiave].id) che è decisiva per raggiungere il nostro scopo. Per il link elimina, oltre ad esserci lo stesso attributo, ce ne sono due data-bs-* che servono ad aprire la finestra modale di Bootstrap.

//carica l'elenco iniziale già presente nel DB
aggiornaElenco();

/**
 * READ
 */
function aggiornaElenco()
{
    const xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function()
        {
            if (this.readyState == 4 && this.status == 200)        
            {
                //QUI VISUALIZZO IL RISULTATO AJAX
                let lista = document.querySelector("#lista");
                lista.innerHTML = "";  //resetta l'elenco e ricalcola da zero
                let dati = JSON.parse(this.response);
                for(let chiave in dati)
                {
                    let li = document.createElement("li");
                    li.classList.add("list-group-item");
    
                    let testo = dati[chiave].titolo
                    let titolo = document.createTextNode(testo + " ");
                    li.appendChild(titolo);
                    
                    //ci aggiungo un link senza url
                    let aElmina = document.createElement("a");
                    aElmina.setAttribute("href",  "#"); 
                    aElmina.setAttribute("data-id",dati[chiave].id);
                    aElmina.classList.add("elimina");
                    let aText = document.createTextNode("Elimina");
                    aElmina.appendChild(aText);
                    aElmina.addEventListener("click", ajaxElimina, true);
                    li.append(aElmina);
                    li.append(document.createTextNode(" "));

                    let aModifica = document.createElement("a");
                    aModifica.setAttribute("href",  "#"); 
                    aModifica.setAttribute("data-id",dati[chiave].id);
                    aModifica.setAttribute("data-bs-toggle","modal");
                    aModifica.setAttribute("data-bs-target","#modificaModal");
                    aModifica.classList.add("elimina");
                    let aMText = document.createTextNode("Modifica");
                    aModifica.appendChild(aMText);
                    aModifica.addEventListener("click", ajaxModifica, true);
                    li.append(aModifica);

                    lista.appendChild(li);
    
                }
            }
        };
    
    //QUI METTI LO SCRIPT PHP/JSON DA RICHIAMARE
    xmlhttp.open("GET", "news.php", true);
    xmlhttp.send();    
}

Non resta che visionare il codice della pagiana PHP richiamato da AJAX. Molto emplice e lineare

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

require ("db.config.php");

$connessione = mysqli_connect($mysql_host, 
                    $mysql_user,
                    $mysql_pass, 
                    $mysql_db) or die("Errore conenssione");

//FASE 2: interrogo il db e restituisco l'elenco aggiornato
$query = "SELECT * FROM News WHERE 1";
$res   = mysqli_query($connessione, $query) or die("Errore query");
mysqli_close($connessione);

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

echo json_encode($jsonarray);
die();

Inserimento

Operazione abbastanza snella: prelevo i valori compilati della formettina e li mando via POST allo script inserisci.php. La risposta di esito positivo viene mostrata in una box/pannello Bootstrap per 3 secondi e poi viene fatta sparire con una sotto-funzione setTimeout

const ulLista = document.querySelector("#lista");
const btnInserisci = document.querySelector("#inserisci");
btnInserisci.addEventListener("click", ajaxInserici, true);

function ajaxInserici()
{
    const titolo = document.querySelector("#titolo").value;
    const corpo = document.querySelector("#corpo").value;

    const xmlhttp = new XMLHttpRequest();
    const url = "inserisci.php";
    const params = "titolo="+titolo+"&corpo="+corpo;

    
    xmlhttp.onreadystatechange = 
        function()
        {
            if (this.readyState == 4 && this.status == 200)        
            {
                //QUI VISUALIZZO IL RISULTATO AJAX
                //VISUALIZZO O UNO SPAN O UN ALERT O UN MODAL O UN TOAST BOOTSTRAP
                let messaggio = document.querySelector("#messaggio");
                messaggio.className="";
                messaggio.innerHTML = "Inserito con successo!";
                messaggio.classList.add("alert");
                messaggio.classList.add("alert-success");                
                setTimeout(function () 
                    {
                        //ora elimino il messaggio di notifica
                        messaggio = document.querySelector("#messaggio");
                        messaggio.className="";
                        messaggio.innerHTML="<br>"; 
                    }, 3000);
                
                //in questo caso mando un messaggio di successo e carico anche la lista in un altro div
                aggiornaElenco();

            }
        };

    //QUI METTI LO SCRIPT PHP/JSON DA RICHIAMARE
    xmlhttp.open("POST", url, true);
    xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xmlhttp.send(params);        
}

Lo script PHP richiamato da AJAX è piuttosto semplice ma lo riportiamo pr completezza

<?php
require ("db.config.php");

$connessione = mysqli_connect($mysql_host, 
                    $mysql_user,
                    $mysql_pass, 
                    $mysql_db) or die("Errore connessione");


//FASE 1: inserisco
if (isset($_REQUEST["titolo"]) && isset($_REQUEST["corpo"]))
{
    $titolo = $_REQUEST["titolo"];
    $corpo = $_REQUEST["corpo"];
}


$query = "INSERT INTO News (titolo, corpo) VALUES ('$titolo', '$corpo');";
$res   = mysqli_query($connessione, $query) or die("Errore query");

mysqli_close($connessione);

echo "ok";

Cancellazione

La porzione di codice per eliminare un record è semplice e lineare poiché non prevede una visualizzazione complessa dei dati risultanti. Abbmo deciso di realizzarla col POST. Occorre definirsi la varibile url e params e al send specificando la voce xmlhttp.setRequestHeader(‘Content-type’, ‘application/x-www-form-urlencoded’); Lo script elimina.php invocato in modalità asincrona restituisce un banale messaggio di testo. Qui la scelta dello studente: o lo visualizziamo in qualche punto dello schermo vicino all’elenco, o usiamo un altro stratagemma. Qui, per fare un lavoro dettagliato e realistico, con Bootstrap mostriamo un piccolo pannello colorato con le relative classList. Per farlo sparire in modo automatico dopo 3 secondi, abbiamo aggiunto un setTimeout(function () che dopo i secondi specificati, sbianca il selettore del messaggio.

Lo script di eliminazione basa il suo lavoro sul reperire l’attributo data-id posto all’interno del link Elimina. In più, per evitare il fastidioso effetto di reload, abbiamo aggiunto event.preventDefault()

/**
 * DELETE
 * 
 * Forse la più semplice delle CRUD. Deve essere però presente in fase di inserimento un attributo
 * custom, qui data-id, che permetta di identificare il record che sia di una tabella o elenco
 * Qui usiamo due finezza: al click sul link disabilitiamo il follow e facciamo comparire un messaggio di 
 * avvenuta cancellazione
 */
function ajaxElimina(event)
{
    const xmlhttp = new XMLHttpRequest();
    const url = "elimina.php";
    const params = "id="+ this.getAttribute("data-id");

    //console.log("Cancello",params);
    event.preventDefault();  //evita al link di comportarsi come link, ricaricando l'anchor

    xmlhttp.onreadystatechange = 
        function()
        {
            if (this.readyState == 4 && this.status == 200)        
            {
                //QUI VISUALIZZO IL RISULTATO AJAX
                //VISUALIZZO O UNO SPAN O UN ALERT O UN MODAL BOOTSTRAP o UN TABELLA O LISTA
                //USO UNA FINEZZA: mostro il messaggio per 3 secondi poi scompare, potete evitare e stampare direttamente    
                let messaggio = document.querySelector("#msgelimina");
                messaggio.className="";
                messaggio.innerHTML = "Cancellato con successo!";
                messaggio.classList.add("alert");
                messaggio.classList.add("alert-warning");                
                setTimeout(function () 
                    {
                        //ora elimino il messaggio di notifica
                        messaggio = document.querySelector("#msgelimina");
                        messaggio.className="";
                        messaggio.innerHTML="<br>"; 
                    }, 3000);
                
                //in questo caso mando un messaggio di successo e carico anche la lista in un altro div
                aggiornaElenco();

            }
        };

    //QUI METTI LO SCRIPT PHP/JSON DA RICHIAMARE
    xmlhttp.open("POST", url, true);
    xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xmlhttp.send(params);        
}

Anche qui riportiamo il coidice dello script PHP invocato

<?php
require ("db.config.php");

$connessione = mysqli_connect($mysql_host, 
                    $mysql_user,
                    $mysql_pass, 
                    $mysql_db) or die("Errore conenssione");

if (isset($_REQUEST["id"]))
    $id = $_REQUEST["id"];

//FASE 2: interrogo il db e restituisco l'elenco aggiornato
$query = "DELETE FROM News WHERE id=$id";
$res   = mysqli_query($connessione, $query) or die("Errore query");
mysqli_close($connessione);

echo "ok";

Modifica

Consta di due momenti. Al click del link modifica, devo caricare dal database i dati del record cliccato di cui conosciamo il data-id e inserirli nella form così precompilata. Decisivo è il il lancio dello script xmlhttp.open(“GET”, “newsbyid.php?id=”+this.getAttribute(“data-id”), true); che passa al php l’attributo per la query. Il risultato atteso è un solo record che viene comunque esposto come JSON

/**
 * UPDATE PARTE 1
 * reperisco i dati del record che si vuole modificare
 * mostro un elemento grafico ad esempio un modal precaricato
 * per lo studente che affronta il codice carta/penna potrebbe bastare 
 * una form ex novo più in basso con altri id e bottoni
 */
function ajaxModifica()
{
    const modificaId = document.querySelector("#modificaId");    
    const modificaTitolo = document.querySelector("#modificaTitolo");
    const modificaCorpo = document.querySelector("#modificaCorpo");

    const xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function()
        {
            if (this.readyState == 4 && this.status == 200)        
            {
                //QUI VISUALIZZO IL RISULTATO AJAX
                let dati = JSON.parse(this.response);
                    //ho un solo record JSON: aggiorno i valori della form
                    modificaId.value     = dati[0].id;
                    modificaTitolo.value = dati[0].titolo;
                    modificaCorpo.value  = dati[0].corpo;
            }
        };
    
    //QUI METTI LO SCRIPT PHP/JSON DA RICHIAMARE
    xmlhttp.open("GET", "newsbyid.php?id="+this.getAttribute("data-id"), true);
    xmlhttp.send();        
}

La seconda parte è analogamente snella. Al click del bottone salva della finestra modale, prelevo i valori della form e li fornisco in modalità POST allo script aggiorna.php. La chiamata asincrona, ancora una volta non restituisce dati, ma solo una stringa di testo da mostrare con il pannello Bootstrap a tempo. Al termine richiamo un refresh della lista dei dati.

/**
 * UPDATE PARTE 2
 */
const salvaModifiche = document.querySelector("#salvamodifiche");
salvaModifiche.addEventListener("click",ajaxSalvaModifica, true);

function ajaxSalvaModifica()
{
    const modificaId = document.querySelector("#modificaId").value;
    const modificaTitolo = document.querySelector("#modificaTitolo").value;
    const modificaCorpo = document.querySelector("#modificaCorpo").value;

    console.log("Sto cercando di aggiornare...");

    const xmlhttp = new XMLHttpRequest();
    const url = "aggiorna.php";
    const params = "id="+modificaId+"&titolo="+modificaTitolo+"&corpo="+modificaCorpo;

    
    xmlhttp.onreadystatechange = 
        function()
        {
            if (this.readyState == 4 && this.status == 200)        
            {
                //QUI VISUALIZZO IL RISULTATO AJAX
                //VISUALIZZO O UNO SPAN O UN ALERT O UN MODAL O UN TOAST BOOTSTRAP
                let messaggio = document.querySelector("#msgaggiorna");
                messaggio.className="";
                messaggio.innerHTML = "Modificato con successo!";
                messaggio.classList.add("alert");  
                messaggio.classList.add("alert-success");                
                setTimeout(function () 
                    {
                        //ora elimino il messaggio di notifica
                        messaggio = document.querySelector("#msgaggiorna");
                        messaggio.className="";
                        messaggio.innerHTML="<br>"; 
                    }, 3000);
                
                //in questo caso mando un messaggio di successo e carico anche la lista in un altro div
                aggiornaElenco();

            }
        };

    //QUI METTI LO SCRIPT PHP/JSON DA RICHIAMARE
    xmlhttp.open("POST", url, true);
    xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xmlhttp.send(params);           
}
<?php

require ("db.config.php");

$connessione = mysqli_connect($mysql_host, 
                    $mysql_user,
                    $mysql_pass, 
                    $mysql_db) or die("Errore connessione");


//FASE 1: aggiorno
if (isset($_REQUEST["titolo"]) && 
    isset($_REQUEST["corpo"]) && 
    isset($_REQUEST["id"]))
{
    $id = $_REQUEST["id"];
    $titolo = $_REQUEST["titolo"];
    $corpo = $_REQUEST["corpo"];
}

$query = "UPDATE News SET titolo = '$titolo' , corpo = '$corpo' WHERE id=$id";
$res   = mysqli_query($connessione, $query) or die("Errore query");

mysqli_close($connessione);

echo "ok";

Il file CSS

Il codice CSS sfrutta alcuni elementi preconfezionati di Bootstrap aggiungendo degli spunti grafici che ricalcano lo stile di Windows 11, quanto meno nei colori della paletta riportati.

/**
 Paletta Windows 11
 #bdd5ea
 #4dbeff
 #0184fd
 #006cdf
 #025ac1
 #003d9c
 #011d6f
*/


body
{
    background-color: #bdd5ea;
}

.box
{
    background-color: #fdfdfd;
    border:1px solid #0184fd;
    border-radius: 5px;
}

h2
{
    width: 100%;
    background-color: #bdd5ea;
    padding: 5px;
    color: #011d6f;
    border-radius: 5px;
}

.btn-blue
{
    background-color:#003d9c;    
    color: #fdfdfd;
}

.btn-lightblue
{
    background-color:#4dbeff;    
    color: #fdfdfd;
}

Il database e la connessione

Per la connessione utilizziamo il solito file di configurazione da include nel codice e che è possibile modificare all’occorrenza che sia in locale o remoto. Qui il nostro db è di prova in locale con le classiche credenziali di default.

<?php

$mysql_host = "localhost";
$mysql_user = "root"; 
$mysql_pass = "root"; 
$mysql_db = "test";

Per completezza riportiamo, il codice per la creazione della tabella News ma può essere replicata ed ampliata in modo molto semplice con un PHPMyAdmin.

CREATE TABLE `News` (
  `id` int NOT NULL,
  `titolo` varchar(60) NOT NULL,
  `corpo` varchar(200) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

--
-- Dump dei dati per la tabella `News`
--

INSERT INTO `News` (`id`, `titolo`, `corpo`) VALUES
(1, 'notizia 1', 'corpo notizia 1'),
(2, 'notizia 2', 'corpo notizia 2'),
(3, 'notizia 3', 'corpo notizia 3'),
(4, 'notizia 4', 'corpo notizia 4'),
(5, 'notizia 5', 'corpo notizia 5'),
(6, 'notizia 6', 'corpo notizia 6'),
(7, 'notizia 7', 'corpo notizia 7');

Ultima modifica 15 Giugno 2023