Copier un résumé avec Clipboard
Objectif : Copier un résumé des tâches terminées en traitant le refus de l’API Clipboard.
Quand vous voulez partager l’avancement d’une liste, recopier les tâches une à une est lent et source d’erreurs. Vous allez ajouter une commande qui construit un résumé des tâches terminées et tente de le placer dans le presse-papiers. Si le navigateur refuse cette opération, l’application affichera un message au lieu de laisser la promesse échouer sans explication.
Le bouton « Copier le résumé » et le point d’ancrage `data-copier` sont déjà présents dans le HTML de référence. La leçon sur les interactions avec le DOM a établi comment centraliser les événements et les convertir en actions ; ici, vous ajoutez l’écoute du bouton dans cet adaptateur, puis confiez l’opération de presse-papiers à `clipboard.js`.
Comprendre les conditions de copie
L’API `navigator.clipboard.writeText` reçoit une chaîne et retourne une promesse. La copie est généralement disponible dans un contexte sécurisé, par exemple une page servie en HTTPS ou depuis `localhost`, et doit être déclenchée par une action explicite de la personne. L’accès peut tout de même être refusé : vous devez donc intercepter le rejet de la promesse et fournir un retour compréhensible.
Créer le service de copie
Dans `js/clipboard.js`, exportez une fonction nommée qui reçoit l’état courant et l’élément où afficher le message. Le résumé ne contient que les tâches terminées ; si aucune tâche n’est terminée, il l’indique explicitement. La fonction retourne `true` en cas de succès et `false` si l’API est indisponible ou refuse la copie, ce qui permet à l’application de distinguer les deux résultats.
export async function copierResume(etat, elementMessage) {
const terminees = etat.taches.filter(function (tache) {
return tache.terminee;
});
const lignes = terminees.map(function (tache) {
return `- ${tache.libelle}`;
});
const resume = lignes.length > 0
? `Tâches terminées (${lignes.length}) :\n${lignes.join("\n")}`
: "Aucune tâche terminée.";
if (!navigator.clipboard || typeof navigator.clipboard.writeText !== "function") {
elementMessage.textContent = "La copie n’est pas disponible dans ce navigateur ou ce contexte.";
return false;
}
try {
await navigator.clipboard.writeText(resume);
elementMessage.textContent = "Résumé copié dans le presse-papiers.";
return true;
} catch (erreur) {
elementMessage.textContent = "La copie a été refusée. Vérifiez les permissions du navigateur.";
return false;
}
}Le champ `libelle` correspond à la forme des tâches utilisée par le réducteur du projet ; `terminee` indique leur état d’achèvement. Le texte est construit sans toucher au DOM de la liste. Le message est écrit dans l’élément `data-message` déjà prévu pour le retour utilisateur, et aucun style inline ni classe supplémentaire n’est nécessaire.
Relier le bouton à l’état courant
Complétez `js/dom.js` pour écouter le clic sur le bouton et transmettre l’intention à `main.js`. Le gestionnaire reçoit une fonction `surCopier` fournie par l’application : l’adaptateur DOM ne construit donc pas le résumé et ne dépend pas de l’API Clipboard. Conservez dans ce fichier vos autres écouteurs déjà établis pour le formulaire et la liste.
export function connecterEvenements(elementRacine, gestionnaires) {
const formulaire = elementRacine.querySelector("[data-formulaire]");
const liste = elementRacine.querySelector("[data-liste]");
const boutonCopier = elementRacine.querySelector("[data-copier]");
formulaire.addEventListener("submit", function (evenement) {
evenement.preventDefault();
const champ = formulaire.elements.tache;
const libelle = champ.value.trim();
if (libelle.length === 0) return;
gestionnaires.surAjouter(libelle);
formulaire.reset();
});
liste.addEventListener("click", function (evenement) {
const bouton = evenement.target.closest("button[data-action]");
if (!bouton || !liste.contains(bouton)) return;
const elementTache = bouton.closest("[data-id]");
if (!elementTache) return;
gestionnaires.surActionListe(bouton.dataset.action, elementTache.dataset.id);
});
boutonCopier.addEventListener("click", function () {
gestionnaires.surCopier();
});
}Dans `js/main.js`, importez `copierResume` et fournissez `surCopier` lors de l’appel à `connecterEvenements`. Ce gestionnaire utilise la variable `etat` restaurée au démarrage et mise à jour après chaque action ; il transmet aussi le nœud du message à afficher. Gardez les imports, la restauration, la sauvegarde et les autres gestionnaires déjà en place dans votre fichier : voici le raccordement à intégrer à cette composition existante.
import { copierResume } from "./clipboard.js";
const elementRacine = document.querySelector(".taches");
const elementMessage = elementRacine.querySelector("[data-message]");
connecterEvenements(elementRacine, {
surAjouter: function (libelle) {
appliquerAction({ type: "AJOUTER_TACHE", libelle: libelle });
},
surActionListe: function (type, id) {
appliquerAction({ type: type, id: id });
},
surCopier: async function () {
await copierResume(etat, elementMessage);
}
});Vérifier le résultat et les erreurs
Ajoutez une tâche, marquez-la comme terminée, puis cliquez sur « Copier le résumé ». Le presse-papiers doit contenir le titre et le libellé de cette tâche ; l’élément de statut annonce la réussite. Avec plusieurs tâches terminées, chacune apparaît sur sa propre ligne, tandis que les tâches encore ouvertes sont exclues.
Pour vérifier le chemin d’erreur, refusez l’autorisation de presse-papiers dans les paramètres du navigateur si celui-ci le permet, ou testez dans un contexte où l’API n’est pas disponible. Le gestionnaire affiche alors un message d’échec et retourne `false` ; les tâches restent intactes, car cette opération ne modifie que le presse-papiers et le message. L’erreur est traitée au plus près de l’appel asynchrone, sans interrompre les autres interactions.
À retenir
- `navigator.clipboard.writeText` copie une chaîne de façon asynchrone et requiert généralement un contexte sécurisé.
- Construisez le résumé à partir des tâches terminées, sans modifier l’état applicatif.
- Vérifiez la disponibilité de l’API et interceptez le refus de la promesse.
- Affichez un message de succès ou d’échec dans le point d’ancrage `data-message`.
- Le bouton du DOM transmet une intention ; le service Clipboard réalise la copie.
Dans la prochaine leçon, vous regrouperez les modifications de la liste pour limiter les opérations de rendu lorsque plusieurs tâches sont affichées.