Matteo Vignoli
Sviluppatore Web Full-Stack, autodidatta, curioso per natura,
attualmente impiegato a Milano in ContactLab
Una feature molto utile quando stiamo presentando una serie di dati all'utente è quella di poterli scaricare direttamente sul proprio PC, magari in un formato pratico e leggero come il CSV, per poterli utilizzare offline e con l'ausilio di altri programmi dedicati, ad esempio l'onnipresente Excel.
Normalmente abbiamo quindi un bel tasto di download che effettua una chiamata al server, che con gli header appropriati ci restituisce lo stream di dati che il browser ci permette di salvare come file in locale; questa è sicuramente la via migliore, più semplice da controllare e gestire (sia in caso di successo che di errore) nonchè universalmente supportato.
Con l'evolversi nel tempo dell'EcmaScript è diventato possibile, tuttavia, avere una funzionalità simile senza dover ricorrere ad un server esterno, lasciando tutto l'onere di creare lo stream di dati a JavaScript ed il merito di tutto ciò va all'oggetto Blob, di cui l'oggetto File è un sottoinsieme.
Immaginiamo di avere, ad esempio, questo semplice array di oggetti:
[
{'col1' : 'val1', 'col2' : 'val2', 'col3' : 'val3', 'col4': 'val4'},
{'col1' : 'val11', 'col2' : 'val22', 'col3' : 'val33', 'col4': 'val44'},
{'col1' : 'val111', 'col2' : null, 'col3' : 'val333', 'col4': 'val444'},
{'col1' : 'val_1', 'col2' : 'val_2', 'col3' : 'val_3', 'col4': 'val_4'},
{'col1' : 'val_11', 'col2' : 'val_22', 'col3' : 'val_33', 'col4': 'val_44'}
]
che magari è rappresentato sulla pagina da una tabella:
Col1 | Col2 | Col3 | Col4 |
---|---|---|---|
val1 | val2 | val3 | val4 |
val11 | val22 | val3 | val44 |
val111 | val333 | val444 | |
val_1 | val_2 | val_3 | val_4 |
val_11 | val_22 | val_33 | val_44 |
Usando del semplice javascript possiamo creare una funzione come questa richiamabile al click su un <button>
function download() {
let a = document.createElement('a');
let blob = new Blob([my_csv_data], {type: 'text/csv' }),
url = window.URL.createObjectURL(blob);
a.href = url;
a.download = "reports.csv";
a.click();
window.URL.revokeObjectURL(url);
a.remove();
}
che non fa altro che creare "al volo" un elemento <a>
che ha come attributo href
uno stream di dati che vive per la sola durata della richiesta, generato dal metodo window.URL.createObjectURL()
a partire dai dati binari contenuti, appunto, nell'oggetto Blob()
che abbiamo creato con i valori che vogliamo mostrare; la proprietà download
contiene il nome che daremo file, mentre il resto del codice non è altro che un trigger per l'evento "click", la distruzione dell'oggetto url e la rimozione dell'elemento dal DOM.
Insomma, Blob()
prende un array (di stringhe, un ArrayBuffer, ecc.) e lo converte in stream di dati, usando il MIME specificato come secondo argomento (poteva anche essere "plain/text", o "application/json", ecc.).
Ma come facciamo a convertire il JSON originario in CSV?
function jsonToCsv(jsonData) {
const header = Object.keys(data[0]);
let csv = jsonData.map(row => header.map(fieldName => JSON.stringify(row[fieldName], replacer)).join(',')); // separatore per i valori
csv.unshift(header.join(',')); // separatore per l'header
return csv.join('\r\n'); // terminatore di riga
}
Questa funziona itera sugli elementi dell'array di oggetti, creando l'intestazione del csv con le chiavi del primo record ed unendo i valori di quelli successivi con il separatore di campo ed il terminatore di riga specificati (nell'esempio sopra si potrebbero parametrizzare questi valori, come possibile miglioria).
replacer
invece è un callback usato per effettuare trasformazioni sul valore passato, come trasformare un null
in stringa vuota o qualunque altra cosa vorremmo fare.
Per semplificarci la vita, e per ridurre problematiche di compatibilità (che è comunque buona, https://developer.mozilla.org/en-US/docs/Web/API/Blob), possiamo affidarci alla libreria file-saver:
npm install file-saver --save
Ricordiamoci poi che se stiamo usando TypeScript dobbiamo installare anche la definizione del @type corrispondente:
npm install @types/file-saver --save-dev
Questa libreria mette a disposizione un'interfaccia saveAs
per cui basta passargli l'oggetto Blob
ed il nome del file.
Et voilà:
Matteo Vignoli
Sviluppatore Web Full-Stack, autodidatta, curioso per natura,
attualmente impiegato a Milano in ContactLab