Les feuilles de calcul sont devenues une partie intégrante de l’informatique moderne. Elles permettent aux utilisateurs d’organiser, de manipuler et d’analyser des données sous forme tabulaire. Des applications comme Google Sheets ont établi la norme pour des feuilles de calcul puissantes et interactives.
Dans cet article de blog, nous vous guiderons à travers le processus de création d’une application de feuille de calcul en utilisant JavaScript. Nous nous concentrerons sur les concepts de programmation clés, explorerons les fonctionnalités de JavaScript et inclurons des extraits de code détaillés avec des explications.
Le code source complet est disponible ici dans mon Codepen.
Qu’est-ce que Google Spreadsheet ?
Google Spreadsheet est une application web qui permet aux utilisateurs de créer, de modifier et de collaborer sur des feuilles de calcul en ligne. Il offre des fonctionnalités telles que des formules, une validation des données, des graphiques et une mise en forme conditionnelle.
Notre projet émule certaines fonctionnalités essentielles de Google Spreadsheet, en mettant l’accent sur :
- Cellules éditables.
- Interprétation et évaluation des formules.
- Mises à jour en direct via un modèle Pub/Sub.
- Navigation au clavier et sélection de cellules.
- Évaluation des dépendances dynamiques entre les cellules.
Fonctionnalités de ce projet
- Cellules éditables: Permet aux utilisateurs d’entrer du texte ou des équations dans les cellules.
- Prise en charge des formules : Traite les formules commençant par
=
et évalue les expressions. - Mises à jour en direct: Les changements dans les cellules dépendantes déclenchent des mises à jour en utilisant un modèle de publication/abonnement.
- Navigation au clavier: Permet le déplacement entre les cellules en utilisant les touches fléchées.
- Évaluation dynamique: Assure des mises à jour en temps réel pour les formules dépendantes d’autres cellules.
- Gestion des erreurs: Fournit des messages d’erreur significatifs pour les entrées invalides ou les dépendances circulaires.
- Conception évolutive: Permet une extension facile pour ajouter plus de lignes, de colonnes ou de fonctionnalités.
Composants clés de l’application
1. Gestion des modes
const Mode = {
EDIT: 'edit',
DEFAULT: 'default'
};
Cet énumération définit deux modes :
- ÉDITION: Permet de modifier une cellule sélectionnée.
- PAR DÉFAUT: Autorise la navigation et l’interaction sans modification.
Pourquoi utiliser des modes?
Les modes simplifient la gestion de l’état de l’interface utilisateur. Par exemple, en mode PAR DÉFAUT, les entrées clavier se déplacent entre les cellules, tandis qu’en mode ÉDITION, les entrées modifient le contenu de la cellule.
2. Classe Pub/Sub
Le modèle Pub/Sub gère les abonnements et les mises à jour en direct. Les cellules peuvent s’abonner à d’autres cellules et se mettre à jour dynamiquement lorsque les dépendances changent.
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);
});
}
}
Fonctionnalités clés:
- Gestion des dépendances dynamiques: Suit les dépendances entre les cellules.
- Propagation des mises à jour: Met à jour les cellules dépendantes lorsque les cellules sources changent.
- Recherche en largeur d’abord: Évite les boucles infinies en suivant tous les nœuds dépendants.
Exemple d’utilisation:
let ps = new PubSub();
ps.subscribeAll(['A1'], 'B1');
ps.subscribeAll(['B1'], 'C1');
console.log(ps.get('A1')); // Output: ['B1', 'C1']
3. Création de lignes et de cellules
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);
}
}
}
Principales caractéristiques :
- Génération de table dynamique : Permet d’ajouter des lignes et des colonnes de manière programmable.
- Identification des cellules : Génère des identifiants en fonction de la position (par exemple, A1, B2).
- Cellules éditables : Les cellules sont éditables uniquement si elles sont valides (lignes/colonnes non-têtes).
Pourquoi utiliser des lignes et des cellules dynamiques ?
Cette approche permet à la taille de la table d’être évolutive et flexible, prenant en charge des fonctionnalités telles que l’ajout de lignes ou de colonnes sans modifier la structure.
4. Gestion des événements pour l’interaction
addEventListeners() {
this.table.addEventListener('click', this.onCellClick.bind(this));
this.table.addEventListener('dblclick', this.onCellDoubleClick.bind(this));
window.addEventListener('keydown', this.onKeyDown.bind(this));
}
Principales caractéristiques :
- Événement de clic : Sélectionne ou édite des cellules.
- Événement de double-clic : Permet l’édition des formules.
- Événement de touche enfoncée : Prend en charge la navigation avec les touches fléchées.
5. Analyse et évaluation de formules
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);
}
Principales caractéristiques :
- Calcul dynamique : Calcule les formules faisant référence à d’autres cellules.
- Évaluation récursive : Résout les dépendances imbriquées.
- Gestion des erreurs : Identifie les références invalides et les dépendances circulaires.
6. Gestion des erreurs pour l’entrée utilisateur
function isValidCell(str) {
let regex = /^[A-Z]{1}[0-9]+$/;
return regex.test(str);
}
Principales caractéristiques :
- Validation : Garantit que les entrées font référence à des identifiants de cellules valides.
- Évolutivité : Prend en charge l’expansion dynamique de la table sans compromettre la validation.
Sujets JavaScript couverts
1. Gestion des événements
Gère les interactions comme les clics et les pressions de touches.
window.addEventListener('keydown', this.onKeyDown.bind(this));
2. Manipulation du DOM
Crée et modifie dynamiquement des éléments du DOM.
let cell = document.createElement('td'); cell.appendChild(document.createTextNode('A1'));
3. Récursivité
Traite dynamiquement les dépendances.
function calcCell(str) { if (isNaN(str)) { return calcCell(getCellValue(str)); } }
4. Gestion des erreurs
Détecte les cellules invalides et les dépendances circulaires.
if (!isValidCell(p)) throw new Error(`invalid cell ${p}`);
Conclusion
Ce projet démontre une puissante feuille de calcul utilisant JavaScript. Il exploite la gestion des événements, la récursivité et les modèles Pub/Sub, posant les bases pour des applications web complexes. Étendez-le en ajoutant des fonctionnalités telles que l’exportation de données, des graphiques ou des règles de mise en forme.
Références
Source:
https://dzone.com/articles/spreadsheet-application-javascript-guide