Laboratorio 2: Utilizzo di un JSON per creare un DOM

Struttura del JSON

Ricordiamo che un JSON è una rappresentazione strutturata di dati, in questo laboratorio utilizzeremo un file JSON sintetico un cui estratto è riportato di seguito (l’intero file si può trovare nel repository online)

{
    "products": [
        {
            "name": "Smartphone Samsung Galaxy S21",
            "description": "Smartphone Samsung Galaxy S21 5G 128GB Phantom Gray",
            "price": 999.99,
            "stock": 10,
            "pictures": [
                "./images/samsung_galaxy_s21_1.jpg",
                "./images/samsung_galaxy_s21_2.jpg",
            ]
        },
        {
            "name": "Laptop Dell XPS 13",
            "description": "Laptop Dell XPS 13 9310 13.4-inch FHD+ Touch Laptop",
            "price": 1399.99,
            "stock": 5,
            "pictures": [
                "./images/dell_xps_13_1.jpg",
                "./images/dell_xps_13_2.jpg",
                "./images/dell_xps_13_3.jpg",
                "./images/dell_xps_13_4.jpg"
            ]
        },
    ]
}

Recuperare file con fetch

Vediamo ora come recuperare un file JSON mediante Javascript, tale file può trovarsi ad un URL qualsiasi, tuttavia è necessario che il server che ospita tale file abiliti il meccanismo di Cross-Origin Resource Sharing (CORS) altrimenti la richiesta verrà bloccata.

jsonUrl = 'https://...'; 
fetch(jsonUrl)
    .then(res => res.json())
    .then(json => console.log(json))
    .catch(err => console.log(err));

Vale la pena notare che:

  • il metodo then accetta come parametro una funzione, nell’esempio si sono usate arrow function, ma questo non è il solo modo;
  • è stata utilizzata una sequenza di chiamate .then .catch per gestire l’intera catena di operazione: scarica JSON, elabora JSON cattura eventuali errori.

Promise

Il codice sopra mostra l’utilizzo della funzione asincrona fetch che permette di fare una richiesta HTTP senza bloccare il normale flusso (per eseguire la richiesta viene creato un thread apposito).

La funzione fetch restituisce un oggetto di tipo Promise che rappresenta una “promessa” di terminazione di una sequenza di istruzioni che produce un risultato o un errore. Quando la promise termina, la funzione che gli viene agganciata mediante il metodo then viene eseguita. Il parametro passato a questa funzione (chiamato res nell’esempio sopra) rappresenta l’esito della richiesta.

Nell’esempio sopra è stato utilizzato il metodo json() della classe Response (che è la classe della risposta ad un fetch). La caratteristica interessante di tale metodo è che anche esso restituisce un Promise ed è quindi possibile creare una catena di promises utilizzando una seconda volta then, in questo secondo utilizzo il parametro è l’oggetto JSON decodificato che è ora disponibile per essere processato (nell’esempio se esegue una semplice stampa sulla console).

Gestione degli errori con Promise

È sempre buona norma gestire eventuali situazioni di errori, normalmente si notifica l’utente che qualcosa è andato storno e si mostra una pagine contenente tale comunicazione (con eventuali azioni che l’utente può intraprendere).

Quando si utilizzano i Promise, gli errori possono verificarsi in maniera asincrona, ad esempio la richiesta per il JSON fa in timeout (magari il link inserito non è corretto). Per gestire situazioni di errori, si usa l metodo catch alla fine della catena di promise. Questo metodo richiede una funzione come parametro alla quale verrà passato l’errore (nell’esempio sopra l’errore viene stampato sulla console).

Esercizio

Creare un progetto con un file HTML, un file CSS ad esso collegato ed un file contenente il codice Javascript. Dopo aver collegato il file Javascript al file HTML indicare nel body la funzione che rappresenterà il main dello script

<body onload="main()">
<!-- Qui l'HTML statico -->
</body>

Infine realizzare la funzione main nel file di script

function main() {
    // codice
}

Osserva

L’utilizzo della proprietà onload di <body> così come suggerito nell’esercizio precedente permette di eseguire del codice Javascript avendo garanzia che l’intero DOM sia stato costruito. In questo modo è possibile accedere agli elementi del DOM senza incorrere nella possibilità che questi non sia ancora del tutto inizializzati.

Interfaccia Javascript per il DOM

Il DOM (Document Object Model) rappresenta il documento HTML nella memoria del browser e viene usato da questo per il rendering della pagina e per applicare gli stili ai vari elementi.

Quando un documento HTML viene caricato dal browser (ad esempio accedendo ad un sito web), esso genera il DOM a partire dal documento. Ovviamente, è possibile per il browser aggiungere, modificare ed eliminare elementi dal DOM presente in memoria. Queste operazioni possono essere invocate dal programmatore utilizzando i metodi Javascript appropriati che sono accessibili tramite l’oggetto document.

Accesso al DOM

Cominciamo con il vedere in che modo è possibile recuperare un elemento presente nel DOM. Il primo insieme di metodi che vediamo permette di accedere agli elementi già presenti nel DOM. Per identificare un elemento, si usano i selettori CSS, esattamente nello stesso modo in cui li si usano per applicare gli stili. Ad esempio l’elemento con id content sarà accessibile con il selettore #content, gli elementi con classe warning saranno accessibili mediante il selettore .warning.

Per la discussione che segue, faremo riferimento al seguente documento HTML

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="style.css">
    <script src="script.js"></script>
    <title>JSON e DOM</title>
</head>
<body onload="main()">
    <div id="header">
        <h1>Laboratorio 2: JSON e DOM</h1>
        <h2 class="subtitle'">Utilizzo di un JSON per creare un DOM</h2>
    </div>
    <div id="content">
        <!-- Content here -->
    </div>
    <div id="footer">
        <p>This is the footer</p>
    </div>
</body>
</html>

I metodi querySelector e querySelectorAll

Il modo più diretto per accedere ad un elemento del DOM è utilizzare uno dei due metodi querySelector o querySelectorAll. Il primo restituisce sempre un solo elemento, il secondo restituisce sempre un Array (che può contenere uno o più elementi, ma può anche essere vuoto). Sia querySelector che querySelectorAll richiedono un parametro che rappresenta il selettore CSS da utilizzare nella selezione, vediamo qualche esempio.

// element (not necessarily a div) with id content
const contentElement = document.querySelector('#content');
console.log(contentElement); 
// >> <div id="content">

// get the first h2 having the class subtitle
const subtitleH2 = document.querySelector('h2.subtitle');
console.log(subtitleH2);
// >> <h2 class="subtitle">

// get all elements of type div
const allDiv = document.querySelectorAll('div');
console.log(allDiv);
// >> NodeList(3) [ div#header, ... ]

Altri metodi di accesso

I metodi querySelector e querySelectorAll sono stati introdotti relativamente di recente, per questo spesso si trovano (e si usano) metodi diversi.

Manipolazione del DOM

Oltre ad “interrogare” il DOM via Javascript, possiamo anche modificare il DOM inserendo e/o cancellando elementi. Ricordiamo che il DOM è una struttura ad albero per questo motivi, i metodi utilizzati utilizzano la terminologia tipica degli alberi (child, sibling, parent, …).

Creazione di elementi

Gli elementi all’interno del DOM sono rappresentati come oggetti Javascript, questi oggetti vanno creati prima di poterli inserire nel DOM stesso. Per questo scopo, l’interfaccia Javascript al DOM fornisce il metodo createElement. Il metodo createElement richiede che il nome del tag da creare venga indicato, nell’esempio sotto, un elemento di tipo div viene create ed assegnato alla constante newDiv

const newDiv = document.createElement('div');
Attenzione

La creazione di un elemento con createElement non inserisce l’elemento appena creato nel DOM.

Aggiungere elementi al DOM

Una volta creato un elemento, il suo inserimento all’interno del DOM richiede che si identifichi di preciso in che punto dell’albero inserire l’elemento. Ricordiamo che in un albero ogni elemento (nodo) è figlio di un altro nodo, a meno che non si tratti della radice. In un documento HTML la radice del DOM è l’elemento relativo al tag <html> e di norma viene gestito dal browser. Di conseguenza, per inserire un nuovo elemento dobbiamo identificare il genitore (parent) di cui il nuovo elemento sarà un figlio (child). Per lo scopo si possono usare i metodi di accesso al DOM.

Una volta ottenuto l’elemento genitore, possiamo usare il metodo append per aggiungere il nuovo elemento come figlio. Il codice di esempio sotto inserisce un nuovo <div> come figlio dell’elemento di id content.

document.querySelector('#content').append(createItemDiv());

Il metodo append può essere usato anche per aggiungere del testo semplice (senza HTML). Ad esempio il codice seguente crea un tag <h1> contenente il testo Titolo in h1.

const titleElement = document.createElement('h1').append('Titolo in h1');
Esercizio

Utilizzando l’array di elementi contenuto nel JSON scaricato, creare per ognuno di questi elementi un <div> contenente un elemento <h4> con il contenuto dell’elemento name nel JSON. Il risultato deve essere equivalente al seguente HTML

<div id="content">
    <div>
        <h4>Smartphone Samsung Galaxy S21</h4>
    </div>
    <div>
        <h4>Laptop Dell XPS 13</h4>
    </div>
</div>

Gestione dello stile via Javascript

Per aggiungere uno stile utilizzando Javascript si può utilizzare la proprietà classList che offre i metodi add e remove per aggiungere o rimuovere classi. Ad esempio, il codice sotto aggiunge la classe with-border ad un div appena creato.

const newDiv = document.createElement('div');
newDiv.classList.add('with-border');

Interazione

All’interno di una pagina Web spesso è possibile interagire con gli elementi per modificare il contenuto visualizzato o per altri scopi. Ad esempio in un sito di commercio elettronico, è auspicabile la possibilità di filtrare i contenuti o ri-ordinare secondo qualche criterio di interesse. Un altro esempio in ambito e-commerce è la gestione del carrello, un sito deve permettere l’aggiunta e la rimozione di oggetti (item) dal carrello in modo semplice ed intuitivo.

Gestione eventi

Dal punto di vista dello sviluppatore, ogni interazione tra utente e documento genera un evento. Un evento viene gestito e segnalato dal browser, è possibile “agganciare” il nostro codice in modo che venga notificato quando un evento si verifica. Una funzione che deve essere eseguita quando una evento accade viene definita event listener.

In Javascript è possibile aggiungere uno o più event listener ad ogni elemento del DOM utilizzando il metodo addEventListener, l’esempio che segue mostra come aggiungere un event listener all’evento click dell’elemento di id clickme

const clickMe = document.querySelector('clickme');
clickMe.addEventListener('click', () => console.log('clicked!!'));

Il metodo addEventListener prende due parametri:

  • type: indica il tipo di evento che si vuole gestire, come click per un click del mouse;
  • listener: indica la funzione che deve essere eseguita quando si verifica l’evento specificato, nell’esempio sopra questa funzione è una arrow function.

Implementazione dell’event listener

L’event listener che viene invocato ogni volta che si verifica un evento deve contenere la logica necessaria a realizzare il comportamento desiderato. Ad esempio, in un sito di e-commerce il click su Add to cart aggiunge l’elemento corrispondente al carrello.

Riassunto metodi manipolazione DOM

Riferimenti

  • Michele Schimd © 2024
  • Ultimo aggiornamento: 17/02/2024
  • Materiale di studio e di esercizio per gli alunni dello Zuccante.

Creative Commons License