Contrôler les données restaurées
Objectif : Valider la forme des données chargées avant de les utiliser et écarter les entrées incorrectes.
Une donnée enregistrée dans le navigateur n’est pas nécessairement encore exploitable : elle peut être tronquée, modifiée ou provenir d’une ancienne version de votre application. Si vous l’utilisez sans contrôle, un titre absent ou un statut au mauvais type peut perturber le rendu et les actions du gestionnaire.
Vous allez donc vérifier chaque tâche avant de la placer dans l’état applicatif. Vous allez aussi enregistrer les données dans une enveloppe versionnée, afin de distinguer le format actuel d’un ancien tableau encore présent dans le stockage.
Valider chaque tâche restaurée
Le gestionnaire utilise des tâches ayant trois propriétés : id, titre et terminee. Avant d’afficher une tâche restaurée, vérifiez que l’objet existe, que son identifiant et son titre sont des chaînes non vides et que son statut est un booléen. Une entrée qui échoue à l’un de ces contrôles est écartée plutôt que transmise au rendu.
Voici le fichier complet js/main.js à cette étape. Il conserve le fonctionnement du gestionnaire, mais n’utilise les données restaurées qu’après validation. Le bloc HTML de référence reste celui du contrat : ses sélecteurs data-* sont utilisés ici sans modifier les classes ni la structure.
const cleStockage = 'atelier-taches';
const versionStockage = 1;
const formulaire = document.querySelector('[data-formulaire]');
const champTitre = document.querySelector('#nouvelle-tache');
const liste = document.querySelector('[data-liste]');
const modeleTache = document.querySelector('[data-modele-tache]');
if (!formulaire || !champTitre || !liste || !modeleTache) {
throw new Error('Un élément nécessaire au gestionnaire est absent.');
}
let taches = restaurerTaches();
let prochainId = 1;
function estTacheValide(valeur) {
return valeur !== null
&& typeof valeur === 'object'
&& !Array.isArray(valeur)
&& typeof valeur.id === 'string'
&& valeur.id.trim() !== ''
&& typeof valeur.titre === 'string'
&& valeur.titre.trim() !== ''
&& typeof valeur.terminee === 'boolean';
}
function validerTaches(valeur) {
if (!Array.isArray(valeur)) {
return [];
}
return valeur.filter(estTacheValide);
}
function restaurerTaches() {
let contenu;
try {
contenu = localStorage.getItem(cleStockage);
if (contenu === null) {
return [];
}
const donnees = JSON.parse(contenu);
// Accepte le tableau sauvegardé avant l’ajout du versionnement.
if (Array.isArray(donnees)) {
return validerTaches(donnees);
}
if (donnees === null || typeof donnees !== 'object' || Array.isArray(donnees)) {
return [];
}
if (donnees.version !== versionStockage) {
return [];
}
return validerTaches(donnees.taches);
} catch (erreur) {
console.error('Impossible de restaurer les tâches :', erreur);
return [];
}
}
function enregistrerTaches() {
const donnees = {
version: versionStockage,
taches
};
try {
localStorage.setItem(cleStockage, JSON.stringify(donnees));
} catch (erreur) {
console.error('Impossible d’enregistrer les tâches :', erreur);
}
}
function afficherTaches() {
liste.replaceChildren();
for (const tache of taches) {
const fragment = modeleTache.content.cloneNode(true);
const ligne = fragment.querySelector('.atelier-tache');
const caseTache = fragment.querySelector('[data-action="basculer"]');
const titre = fragment.querySelector('[data-titre]');
if (!ligne || !caseTache || !titre) {
continue;
}
ligne.dataset.id = tache.id;
caseTache.checked = tache.terminee;
titre.textContent = tache.titre;
liste.append(fragment);
}
}
function traiterSoumission(evenement) {
evenement.preventDefault();
const donneesFormulaire = new FormData(formulaire);
const titre = String(donneesFormulaire.get('titre') ?? '').trim();
if (titre === '') {
return;
}
taches = [...taches, {
id: `tache-${Date.now()}-${prochainId}`,
titre,
terminee: false
}];
prochainId += 1;
enregistrerTaches();
afficherTaches();
formulaire.reset();
champTitre.focus();
}
function traiterAction(evenement) {
const commande = evenement.target.closest('[data-action]');
const ligne = evenement.target.closest('.atelier-tache');
if (!commande || !ligne || !liste.contains(commande)) {
return;
}
const id = ligne.dataset.id;
if (commande.dataset.action === 'basculer') {
taches = taches.map((tache) => tache.id === id
? { ...tache, terminee: commande.checked }
: tache);
} else if (commande.dataset.action === 'supprimer') {
taches = taches.filter((tache) => tache.id !== id);
} else {
return;
}
enregistrerTaches();
afficherTaches();
}
formulaire.addEventListener('submit', traiterSoumission);
liste.addEventListener('change', traiterAction);
liste.addEventListener('click', traiterAction);
afficherTaches();Protéger la restauration et versionner le format
JSON.parse échoue si le contenu n’est pas un JSON valide ; la lecture du stockage peut aussi échouer dans certains contextes du navigateur. Le bloc try/catch empêche ces erreurs d’interrompre l’initialisation et revient à une liste vide. Le contrôle du type de donnees est également nécessaire avant de lire ses propriétés.
Le format actuel est un objet contenant version et taches. Si la version enregistrée n’est pas celle attendue, l’application ignore ces données au lieu de les interpréter avec une structure inconnue. Le code accepte aussi un ancien tableau brut, le valide, puis le convertit au nouveau format dès la prochaine modification enregistrée.
À retenir
- Vérifiez le type et les propriétés de chaque objet avant de l’ajouter à l’état.
- Traitez séparément un contenu absent, un JSON invalide et une structure inattendue.
- Enregistrez un numéro de version avec les données pour détecter les formats incompatibles.
- Si vous changez le format, prévoyez explicitement le sort des données déjà enregistrées.
La prochaine leçon introduira les requêtes fetch pour charger des tâches depuis une source distante.