< Home
Stampa

Evento, azione, reazione

Sommario

Best practices e worst practices

Javascript è un linguaggio, che nei suoi 30 anni di vita, è cambiato molto.

Nella prima parte della sua vita, è stato utilizzato come semplice linguaggio di scripting per dare qualche animazione alle pagine web, mentre il grosso dell’elaborazione era svolta lato server. Non solo ma il linguaggio aveva numerose limitazioni che ne impedivano la realizzazione di applicazioni vere e proprie.

Per questa ragione Javascript è stato considerato un linguaggio di serie B, usato da non programmatori. Il risultato è che buona parte dei siti Internet ha codice Javascript di pessima qualità, e soprattutto che si sono diffuse pratiche di sviluppo poco efficaci, che per la forza dei numeri del Web sono diventate diffusissime.

Solo dal 2010 si è evoluto in un linguaggio moderno, e con esso si è creata una comunità di sviluppatori ed esperti in grado di proporre un linguaggio di programmazione e delle tecniche di programmazione efficienti e valide.

Per questa ragione quando uno studente o un programmatore cerca aiuto su web, molto spesso trova esempi di codice obsoleti o deprecati, e non è in grado di capire se oggi sono ancora validi. Lo stesso vale per le IA, che per pura forza statistica, propongono soluzioni spesso antiquate o errate.

In questa lezione vogliamo dare delle linee guida che aiutino a capire come fare una pagina Web con Javascript, ed un metodo per costruire una applicazione ben strutturata.

Separation of concerns

Ci deve essere è una netta separazione tra HTML, Javascript e CSS:

  • nell’HTML, sebbene sia tecnicamente possibile, non c’è codice JS; per usare quindi un pulsante o una form è necessario crearne un riferimento nel DOM dello script e usarlo in questo modo.
  • nel JS non vi è manipolazione diretta del DOM, ma solo generazione di testo HTML, tramite templates.
  • non si applicano stili in linea in HTML ma si creano classi CSS. Il codice deve essere sempre riusabile.

Questo significa che molte worst practices sono da evitare:

  1. <button onclick="funzione()" ../> è una pratica scorretta, perché inseriamo codice JS nell’HTML
  2. div.appendChild(element) è un modo per inserire elementi DOM direttamente in pagina. E’ tecnicamente corretto ma è prestazionalmente inefficace. Il problema però è che la manipolazione diretta del DOM da parte di JS (ovvero in modo programmatico) è rischiosa perché non permtte un debugging efficace.
  3. button.addEventListener("click", function()) è un modo molto rischioso per aggiungere gestori evento, perché se è eseguita più volte, continua ad aggiungere gestori evento allo stesso elemento. Molto meglio usare button.onclick = function() che sostituisce ogni funzione precedente.

Evento, azione, reazione

Una pagina web segue un modello di comportamento interattivo. Essa mostra dei contenuti in pagina, ma il suo comportamento dipende da molteplici eventi:

  • l’interazione dell’utente: inserimento form, click su pulsanti e link, mouse scroll, ecc.
  • eventi di sistema: load della pagina, eventi legati al caricamenti di dati, ecc.
  • eventi generati dal programma stesso: setTimeout, setInterval, ecc.

Allo scopo di semplificare e razionalizzare il comportamento dell’applicazione, è opportuno suddividere la gestione degli eventi in tre blocchi concettuali:

  • Event bind: andiamo ad associare ogni evento ad uno specifico gestore evento (event handler) che lo gestirà;
  • azione: l’azione consiste nel contenuto dell’event handler, che analizza l’evento, il contenuto, i dati passati e svolge una qualche azione sui dati e le variabili che gestisce l’applicazione. L’azione comporta una modifica dello stato interno dell’applicazione.
  • reazione: al termine dell’azione viene svolta una operazione sulla destinazione, ad esempio viene aggiornata la pagina HTML ovvero l’interfaccia utente con una operazione chiamata render.

Facciamo un esempio. Ipotizziamo di voler scrivere una pagina web che contiene una form (input text e button) ed un ul. Ad ogni pressione del pulsante, JS legge il contenuto della form e lo aggiunge ad una lista di valori che stampa. Vediamo il codice:

<div >
    <h2>La mia Lista</h2>
    
    <form>
        <input type="text" id="item" placeholder="Scrivi qualcosa...">
        <button id="button" type="button">Aggiungi</button>
    </form>

    <ul id="itemList"></ul>
</div>

Come si vede, l’HTML non contiene JS.

Vediamo ora il JS:

    const elements = [];

    // binding del DOM
    const input = document.getElementById('item');
    const button = document.getElementById('button');
    const ul = document.getElementById('itemList');

    // EVENT bind
    button.onclick = eventHandler

    // AZIONE
    function eventHandler {
        const value = input.value.trim();
        if (value !== '') {
            elements.push(value);
            render(); // azione -> reazione
        }
    });

    // REAZIONE
    function render() {
        input.value = '';
        const template = '<li>%VALUE</li>';
        const rows = elements.map((element) => {
           return template.replace("%VALUE", element);
        }
        ul.innerHTML = rows.join("\n");
    }

Come si vede:

  • ogni evento è gestito separatamente dal suo gestore eventi;
  • il gestore eventi è l’azione, che svolge una operazione sui soli dati, e NON modifica l’HTML;
  • l’azione chiama la reazione, ad esempio la funzione render, che modifica l’HTML (e non fa niente altro)

E’ importante capire che l’evento può avere diverse origini, e che origine e destinazione (di norma l’HTML) non coincidono. Inoltre gestore eventi e reazione sono due step distinti, ed anzi con molti eventi si può avere una situazione con azioni differenti, ma che convergono tutti sulla stessa reazione.

Il modello è semplice, lineare ed evita il 99% degli errori di programmazione. E’ alla base di tutti i moderni framework Javascript, come Angular e soprattutto React (che prende proprio il nome da questo modello).

Render

Generazione di HTML da Javascript

Un sistema veloce, semplice e soprattutto efficiente per creare html consiste nel creare una stringa di testo e “iniettarla” nella pagina, usando la proprietà element.innerHTML. In pratica il codice HTML viene inserito come se fosse testo, ed automaticamente il browser esegue il “rendering” in html e quindi modifica la pagina web.

Ad esempio se abbiamo questo elemento:

<div id="container">

</div>

Ed eseguiamo questo codice[2]:

const container = document.getElementById("container");
container.innerHTML = `
 <div>Ciao</div>
`;

Otterremo questo html:

<div id="container">
    <div>
      Ciao
    </div>
  </div>

L’html una volta generato si comporta esattamente come l’html scritto in fase di coding. Quindi è a sua volta possibile eseguire il binding in oggetti DOM e manipolarlo. 

Quando dobbiamo scrivere html in modo ripetitivo, ovvero ad esempio righe di una tabella dove la struttura del codice è sempre la stessa, ma cambiano solo i dati, conviene usare delle stringhe con dei segnaposto da modificare, ovvero dei template.

Trasformazioni dai dati all’HTML

E’ possibile usare map() e join() per trasformare velocemente una tabella con dei dati in html. Per generare un elenco di colonne di tabella HTML si può fare così:

const list = [1,2,3];
const template = "<td>%VALUE</td>";
const htmlElements = list.map(element => template.replace("%VALUE", element));
// Otterremo: ["<td>1</td>", "<td>2</td>", "<td>3</td>"]

Da qui è poi possibile usare join():

const htmlRow = htmlElements.join(""); 
// "<td>1</td><td>2</td><td>3</td>"
container.innerHTML = html;

La stessa tecnica si può replicare anche per gli UL

const list = [1,2,3];
const template = "<li>%VALUE</li>";
const htmlElements = list.map(element => template.replace("%VALUE", element));
const html = htmlElements.join("");
container.innerHTML = html;

Un esempio di applicazione

Facciamo un esempio concreto. Vogliamo creare una web app che contiene una form con 3 campi (nome, cognome, età), un pulsante di conferma e una tabella (inizialmente vuota) con 3 colonne (nome, cognome, ed età). La nostra web app, ogni volta che l’utente inserisce i valori nella form e preme su continua, aggiornerà la lista dei record inseriti ed aggiornerà la tabella con tutti i dati.

Vediamo il codice.

Qui l’html:

<div class="container">
      <div class="row">
        <div class="col-3">
          <label for="inputValue1" class="form-label">Nome</label>
          <input id="inputValue1" type="text" class="form-control" />
          <label for="inputValue2" class="form-label">Cognome</label>
          <input id="inputValue2" type="text" class="form-control" />
          <label for="inputValue3" class="form-label">Età</label>
          <input id="inputValue3" type="text" class="form-control" />
        </div>
      </div>
      <div class="row mt-2">
        <div class="col-3">
          <button type="button" id="button" class="btn btn-primary">
            Invio
          </button>
        </div>
      </div>
      <table id="table" class="table mt-4">
      </table>
    </div>

Come si vede dall’esempio la tabella è inizialmente vuota. 

Passiamo al Javascript. Prima di tutto eseguiamo il binding degli oggetti del DOM:

const button = document.getElementById("button");
const table = document.getElementById("table");
const inputValue1 = document.getElementById("inputValue1");
const inputValue2 = document.getElementById("inputValue2");
const inputValue3 = document.getElementById("inputValue3");

Fatta l’inizializzazione veniamo alle funzionalità.

Alla pressione del pulsante (cioè l’evento), quello che ci aspettiamo che faccia l’applicazione è:

1) aggiungere i nuovi valori inseriti in una variabile lista (che chiamiamo “data”), ovvero l’azione;

2) aggiornare la tabella sulla base dei dati inseriti, ovvero la reazione.

Qui il codice dell’azione che aggiunge il nuovo elemento alla pressione del pulsante (evento ed azione sono quindi collegati):

const data = [];
button.onclick = () => {
  const nome = inputValue1.value;
  const cognome = inputValue2.value;
  const età = inputValue3.value;
  data.push({
    nome: nome,
    cognome: cognome,
    età: età
  });
  render();
};

A un certo punto viene richiamata una funzione render non ancora definita. Questa funzione esegue la reazione: ovvero aggiorna la pagina web creando una tabella html a partire dalla variabile data.

Per fare questo abbiamo però bisogno di creare un template: uno per l’header e uno per la singola riga, dove mettiamo dei segnaposto al posto dei dati, che useremo per tutte le righe inserite.

const tableHeader = `
  <tr>
    <th>Nome</th>
    <th>Cognome</th>
   <th>Età</th>
  </tr>
`;

const template = `
  <tr>
    <td>%NOME</td>
    <td>%COGNOME</td>
    <td>%ETA</td>
 </tr>
`;

A questo punto possiamo scrivere la funzione render, che riscrive tutta la tabella e sostituisce l’html ogni volta che viene eseguita.

Tutto il lavoro consiste in manipolazione di stringhe:

– prima si inserisce il table header sopra definito;
– poi per ogni riga, si trasforma il template in una stringa contenente effettivo codice html coi dati;
– infine si inserisce la stringa creata al posto dell’html esistente.

const render = () => {
  let html = tableHeader;
  data.forEach((element) => {
    let rowHtml = template.replace("%NOME", element.nome);
    rowHtml = rowHtml.replace("%COGNOME", element.cognome);
    rowHtml = rowHtml.replace("%ETA", element.età);
    console.log(rowHtml);
    html += rowHtml;
  });
  table.innerHTML = html;
};

Ricapitolando vediamo il meccanismo di funzionamento:

1) L’utente compila i campi e fa click su “aggiungi” (evento).

2) La funzione associata a "button.onclick" fa partire l’aggiunta del nuovo elemento alla lista (azione).

3) La funzione render() legge la lista e ricrea tutta la tabella da zero (reazione).

Con questo sistema  non si manipola quindi l’html precedente, ma lo si cancella e lo si ricrea. Questo sistema è più efficiente di altri sistemi, come per esempio quello di aggiungere solo l’html di una riga alla volta: in questo caso dovremmo analizzare l’html già generato analizzando quindi il DOM ed eseguendo il binding. Questa tecnica è ormai oggi considerata deprecata perché molto inefficiente nei browser moderni. E’ sempre più efficiente invece cancellare e rigenerare l’html, operazione che viene agevolata dai motori di visualizzazione dei moderni browsers.

Scrivere il codice in questo modo aiuta a tenere separati i due ambiti principali dell’applicazione:

  • i dati sono memorizzati in modo efficiente usando array, dizionari, ecc. e sono usati dal Javascript, in una parola, il modello dati;
  • l’interfaccia utente è invece in HTML e JS crea solo delle stringhe di testo HTML tramite una funzione render: JS non manipola MAI l’HTML, ma esegue il binding solo delle parti della pagina da leggere (es. form) o da cui intercettare eventi (es. pulsanti);