I fogli elettronici sono diventati parte integrante del computing moderno. Consentono agli utenti di organizzare, manipolare e analizzare i dati in un formato tabellare. Applicazioni come Google Sheets hanno fissato lo standard per fogli elettronici potenti e interattivi.
In questo post del blog, ti guideremo attraverso il processo di costruzione di un’applicazione per fogli elettronici utilizzando JavaScript. Ci concentreremo sui concetti di programmazione chiave, esploreremo le funzionalità di JavaScript e includeremo frammenti di codice dettagliati con spiegazioni.
L’intero codice sorgente è disponibile qui nel mio Codepen.
Cos’è Google Spreadsheet?
Google Spreadsheet è un’applicazione web che consente agli utenti di creare, modificare e collaborare su fogli elettronici online. Fornisce funzionalità come formule, convalida dei dati, grafici e formattazione condizionale.
Il nostro progetto emula alcune funzionalità centrali di Google Spreadsheet, concentrandosi su:
- Celle modificabili.
- Analisi e valutazione delle formule.
- Aggiornamenti in tempo reale tramite un modello Pub/Sub.
- Navigazione da tastiera e selezione delle celle.
- Valutazione dinamica delle dipendenze tra le celle.
Caratteristiche di Questo Progetto
- Celle modificabili: Consente agli utenti di inserire testo o equazioni nelle celle.
- Supporto alle formule: Elabora formule che iniziano con
=
e valuta le espressioni. - Aggiornamenti in tempo reale: Le modifiche nelle celle dipendenti attivano gli aggiornamenti utilizzando un modello Pub/Sub.
- Navigazione da tastiera: Consente lo spostamento tra le celle utilizzando i tasti freccia.
- Valutazione dinamica: Garantisce aggiornamenti in tempo reale per formule dipendenti da altre celle.
- Gestione degli errori: Fornisce messaggi di errore significativi per input non validi o dipendenze circolari.
- Design scalabile: Consente un’estensione facile per aggiungere più righe, colonne o funzionalità.
Componenti chiave dell’applicazione
1. Gestione delle modalità
const Mode = {
EDIT: 'edit',
DEFAULT: 'default'
};
Questa enum definisce due modalità:
- MODIFICA: Consente la modifica di una cella selezionata.
- PREDEFINITO: Consente la navigazione e l’interazione senza modifica.
Perché utilizzare le modalità?
Le modalità semplificano la gestione dello stato dell’interfaccia utente. Ad esempio, in modalità PREDEFINITO, gli input da tastiera si spostano tra le celle, mentre in modalità MODIFICA, gli input modificano il contenuto della cella.
2. Classe Pub/Sub
Il modello Pub/Sub gestisce le sottoscrizioni e gli aggiornamenti in tempo reale. Le celle possono sottoscrivere altre celle e aggiornarsi dinamicamente quando cambiano le dipendenze.
class PubSub {
constructor() {
this.map = {};
}
get(source) {
let result = [];
let queue = [ (this.map[source] || [])];
while (queue.length) {
let next = queue.shift();
result.push(next.toUpperCase());
if (this.map[next]) queue.unshift(this.map[next]);
}
return result;
}
subscribeAll(sources, destination) {
sources.forEach((source) => {
this.map[source] = this.map[source] || [];
this.map[source].push(destination);
});
}
}
Caratteristiche principali:
- Gestione dinamica delle dipendenze: Traccia le dipendenze tra le celle.
- Propagazione degli aggiornamenti: Aggiorna le celle dipendenti quando cambiano le celle di origine.
- Ricerca in ampiezza: Evita i cicli infiniti tracciando tutti i nodi dipendenti.
Esempio di utilizzo:
let ps = new PubSub();
ps.subscribeAll(['A1'], 'B1');
ps.subscribeAll(['B1'], 'C1');
console.log(ps.get('A1')); // Output: ['B1', 'C1']
3. Creazione di righe e celle
class Cell {
constructor(cell, row, col) {
cell.id = `${String.fromCharCode(col + 65)}${row}`;
cell.setAttribute('data-eq', '');
cell.setAttribute('data-value', '');
if (row > 0 && col > -1) cell.classList.add('editable');
cell.textContent = col === -1 ? row : '';
}
}
class Row {
constructor(row, r) {
for (let c = -1; c < 13; c++) {
new Cell(row.insertCell(), r, c);
}
}
}
Caratteristiche principali:
- Generazione dinamica della tabella: Consente di aggiungere righe e colonne tramite programmazione.
- Identificazione delle celle: Genera ID in base alla posizione (ad esempio, A1, B2).
- Celle modificabili: Le celle sono modificabili solo se sono valide (righe/colonne non di intestazione).
Perché utilizzare righe e celle dinamiche?
Questo approccio consente alla dimensione della tabella di essere scalabile e flessibile, supportando funzionalità come l’aggiunta di righe o colonne senza modificare la struttura.
4. Gestione degli eventi per l’interazione
addEventListeners() {
this.table.addEventListener('click', this.onCellClick.bind(this));
this.table.addEventListener('dblclick', this.onCellDoubleClick.bind(this));
window.addEventListener('keydown', this.onKeyDown.bind(this));
}
Caratteristiche principali:
- Evento di clic: Seleziona o modifica le celle.
- Evento di doppio clic: Abilita la modifica delle formule.
- Evento di pressione del tasto: Supporta la navigazione con i tasti freccia.
5. Analisi e valutazione delle formule
function calcCell(expression) {
if (!expression) return 0;
return expression.split('+').reduce((sum, term) => {
let value = isNaN(term) ? getCellValue(term) : Number(term);
if (value === null) throw new Error(`Invalid cell: ${term}`);
return sum + Number(value);
}, 0);
}
Caratteristiche principali:
- Calcolo dinamico: Calcola formule che fanno riferimento ad altre celle.
- Valutazione ricorsiva: Risolve le dipendenze nidificate.
- Gestione degli errori: Identifica riferimenti non validi e dipendenze circolari.
6. Gestione degli errori per l’input dell’utente
function isValidCell(str) {
let regex = /^[A-Z]{1}[0-9]+$/;
return regex.test(str);
}
Caratteristiche principali:
- Validazione: Garantisce che l’input faccia riferimento a ID di celle valide.
- Scalabilità: Supporta l’espansione dinamica della tabella senza compromettere la validazione.
Argomenti JavaScript trattati
1. Gestione degli eventi
Gestisce interazioni come clic e pressioni dei tasti.
window.addEventListener('keydown', this.onKeyDown.bind(this));
2. Manipolazione del DOM
Crea e modifica elementi del DOM in modo dinamico.
let cell = document.createElement('td'); cell.appendChild(document.createTextNode('A1'));
3. Ricorsione
Elabora le dipendenze in modo dinamico.
function calcCell(str) { if (isNaN(str)) { return calcCell(getCellValue(str)); } }
4. Gestione degli errori
Rileva celle non valide e dipendenze circolari.
if (!isValidCell(p)) throw new Error(`invalid cell ${p}`);
Conclusione
Questo progetto dimostra un foglio elettronico potente utilizzando JavaScript. Sfrutta la gestione degli eventi, la ricorsione e i pattern Pub/Sub, gettando le basi per applicazioni web complesse. Espandilo aggiungendo funzionalità come l’esportazione dei dati, grafici o regole di formattazione.
Riferimenti
Source:
https://dzone.com/articles/spreadsheet-application-javascript-guide