Componenti
Partendo dal modello di funzionamento della pagina web (evento, azione, reazione) e la programmazione ad oggetti in Javascript (oggetti e classi) possiamo progettare applicazioni web che si basano sul concetto di componente.
Un componente è una porzione della pagina web che contiene in modo completo sia una parte della pagina HTML che interagisce con l’utente, sia tutto il codice Javascript necessario a farlo funzionare.

In pratica il componente è un elemento autoconclusivo della applicazione Web, collegato ad un elemento della pagina, che gestisce autonomamente, tramite un oggetto, l’interazione con l’utente e la gestione delle logiche applicative ad esso collegate. Ad esempio un componente potrebbe essere una tabella, una form, un elemento contenente informazioni dinamiche, un elenco. Inoltre i componenti possono essere anche contenitori di altri componenti, come ad esempio tabelle complesse, dove ogni riga è essa stessa un componente.
Le architetture a componenti sono alla base delle applicazioni Javascript moderne, e sono utilizzati da tutti i framework Javascript più diffusi, come React, Vue ed Angular.
In questa lezione vedremo come è possibile utilizzare i componenti anche con Javascript nativo.
Closure
Una closure, in programmazione, è una funzione che restituisce un oggetto che oltre ad avere metodi e proprietà propri, è in grado di utilizzare variabili e funzioni create nella funzione stessa, che continuano ad esistere anche dopo che la funzione ha terminato la sua esecuzione. Questo può apparire controintuitivo, perché una funzione crea le sue variabili locali nello stack e quindi al termine della sua esecuzione normalmente lo stack viene liberato e quindi le variabili sono cancellate.
Ad esempio in questa funzione:
const myFunction = (a,b) => {
let c = a*b;
console.log(c);
}
myFunction(3,5)
dopo averla eseguita la variabile c viene cancellata dalla memoria. Tuttavia Javascript (ed altri linguaggi) consente di restituire oggetti che continuano ad esistere dopo il termine della funzione e che inoltre possono ancora utilizzare variabili create dentro la funzione, come in questo esempio:
const createAdder = () => {
let sum = 0;
return {
sum: (value) => {
sum += value;
return sum;
}
}
}
const adder = createAdder();
console.log(adder(3)); // stampa 3
console.log(adder(3)); // stampa 6
Come si può vedere stavolta la variabile sum rimane in memoria anche dopo che la funzione ha terminato la sua esecuzione. Questo perché viene ancora referenziata dalla funzione sum. Questo sistema rende possibile quindi creare oggetti che hanno una o più variabili (o metodi) privati, inaccessibili all’esterno, e garantisce una delle caratteristiche fondamentali della programmazione ad oggetti, ovvero l’incapsulamento.
Un componente tabella
Un componente deve quindi gestire l’intero ciclo di vita applicativo, quindi sia la fase di render iniziale, sia la gestione di evento, azione e reazione (cioè la nuova render). In questo esempio scriviamo una semplice tabella sotto forma di componente. Questa tabella avrà le seguenti proprietà private: parentElement (che contiene il riferimento all’elemento del DOM in cui essere inserita) e data (che contiene i dati della tabella, espressi come array bidimensionale, dove la prima riga contiene le intestazioni).
const createTable = (parentElement) => {
let data;
return {
build: (dataInput) => {
data = dataInput;
},
render: () => {
let htmlTable = "<table>";
htmlTable += data.map((row) =>
"<tr>" + row.map((col) =>
"<td>" + col + "</td>"
).join("")
).join("") + "</tr>";
htmlTable += "</table";
parentElement.innerHTML = htmlTable;
}
}
}
const table1 = createTable(document.querySelector("#table1"));
table1.build([["Cognome", "Voto"], ["Pogba", "6"], ["Vlahovic", 8], ["Thuram", 6.5]]);
table1.render();
const table2 = createTable(document.querySelector("#table2"));
table2.build([["Squadra", "punti"], ["Torino", 11], ["Napoli", 10], ["Udines", 10]]);
table2.render();
Come si può osservare abbiamo creato due oggetti tabella, associati a due contenitori distinti del DOM. E’ anche possibile rapidamente cambiare i dati usando il metodo build() ed eseguendo una nuova render().
Un componente form
In questo caso creiamo una semplice form che espone 3 metodi pubblici (setLabels, onsubmit e render) che gestiscono le label della form, la render e la onsubmit che associa la callback da eseguire una volta che l’utente ha cliccato. Qui il codice:
const createForm = (parentElement) => {
let data;
let callback = null;
return {
setLabels: (labels) => { data = labels; },
onsubmit: (callbackInput) => { callback = callbackInput},
render: () => {
parentElement.innerHTML =
data.map((name, index) => {
return `<div>${name}\n<input id="${name}" type="text" /></div>`;
}).join('\n')
+ "<button type='button' id='submit'>Submit</button>";
document.querySelector("#submit").onclick = () => {
// modo 1: lista di valori
const result = data.map((name) => {
return document.querySelector("#" + name).value;
});
// modo 2 alternativo dizionario
const result = {};
data.forEach((name) => {
result[name] = document.querySelector("#" + name).value;
});
callback(result);
}
},
};
};
const form = createForm(document.querySelector('#app'));
form.setLabels(["Nome", "Cognome", "Età"]);
form.onsubmit(console.log);
form.render();
Ricapitolando:
- il metodo setLabels definisce le label associate agli input della form;
- il metodo onSubmit serve per indicare all’oggetto form quale sarà la funzione callback da richiamare una volta che è stata effettuata la submit (cioè quando l’utente preme il pulsante submit)
- il metodo render fa due cose:
- crea la form dinamicamente in base alle labels definite sopra e la inserisce nel contenitore parentElement;
- dopo il rendering, viene gestito il click al pulsante, che attiva una funzione che prima raccoglie i dati inseriti dall’utente nelle input e li mette in una lista, e poi richiama la funzione di callback memorizzata dal metodo onSubmit