Avertit avant la perte des réponses sur les trois TD

Les réponses saisies dans les exercices ne sont pas enregistrées : un
rechargement ou une navigation les efface sans prévenir.

Ajoute sur BioCID, FDS et TP Nuisibles un indicateur de saisie et un
gestionnaire beforeunload : dès qu'une réponse a été renseignée, le
navigateur demande confirmation avant de recharger ou de quitter la page.
Aucune alerte tant que rien n'a été saisi.

Sur FDS, les parties glisser-déposer, liaisons FT et zones cliquables de
l'étiquette ne produisent pas d'événement input : elles signalent la
saisie explicitement. Même chose pour le choix du cas pratique du TP.

Note : le texte affiché dans la boîte de dialogue est imposé par le
navigateur et n'est pas personnalisable.
This commit is contained in:
2026-07-20 20:36:19 +02:00
parent 421ae4c2bf
commit a42957ad41
3 changed files with 51 additions and 1 deletions

View File

@@ -859,6 +859,21 @@ function exporterPDF() {
setTimeout(() => w.print(), 300); setTimeout(() => w.print(), 300);
} }
/* ======================== PROTECTION CONTRE LA PERTE DES RÉPONSES ======================== */
/* Les réponses ne sont pas enregistrées : on avertit avant tout rechargement ou départ.
Le texte de la boîte est imposé par le navigateur, il n'est pas personnalisable. */
let reponsesModifiees = false;
function marquerModifie() { reponsesModifiees = true; }
document.addEventListener('input', marquerModifie, true);
document.addEventListener('change', marquerModifie, true);
window.addEventListener('beforeunload', e => {
if (!reponsesModifiees) return;
e.preventDefault();
e.returnValue = '';
});
updateUI(); updateUI();
</script> </script>
</body> </body>

View File

@@ -1113,6 +1113,7 @@ function onEtiClick(e) {
// Exercice terminé : plus d'interaction (les corrections sont dans la correction générale) // Exercice terminé : plus d'interaction (les corrections sont dans la correction générale)
if (etiPos >= etiOrder.length) return; if (etiPos >= etiOrder.length) return;
marquerModifie();
const target = etiZones[etiOrder[etiPos]]; const target = etiZones[etiOrder[etiPos]];
if (clicked === target.zone) { if (clicked === target.zone) {
hs.classList.remove('hs-wrong'); hs.classList.remove('hs-wrong');
@@ -1356,7 +1357,7 @@ document.addEventListener('pointerup', e => {
if (!ftDrag) return; if (!ftDrag) return;
const target = document.elementFromPoint(e.clientX, e.clientY); const target = document.elementFromPoint(e.clientX, e.clientY);
const answerDot = target && target.closest('.match-dot[data-side="right"]'); const answerDot = target && target.closest('.match-dot[data-side="right"]');
if (answerDot) ftConnections[ftDrag.qid] = answerDot.dataset.answer; if (answerDot) { marquerModifie(); ftConnections[ftDrag.qid] = answerDot.dataset.answer; }
document.querySelectorAll('.match-dot.active').forEach(d => d.classList.remove('active')); document.querySelectorAll('.match-dot.active').forEach(d => d.classList.remove('active'));
ftDrag = null; ftDrag = null;
refreshFtState(); refreshFtState();
@@ -1407,6 +1408,7 @@ function initFdsDnd() {
r.className = 'result-message'; r.innerHTML = ''; r.className = 'result-message'; r.innerHTML = '';
}; };
const fill = (slot, v, l) => { const fill = (slot, v, l) => {
marquerModifie();
slot.dataset.value = v; slot.textContent = l; slot.classList.add('filled'); slot.dataset.value = v; slot.textContent = l; slot.classList.add('filled');
resetResults(); updateFdsGate(); resetResults(); updateFdsGate();
}; };
@@ -1820,6 +1822,23 @@ function launchConfetti() {
rafId = requestAnimationFrame(draw); rafId = requestAnimationFrame(draw);
} }
/* ======================== PROTECTION CONTRE LA PERTE DES RÉPONSES ======================== */
/* Les réponses ne sont pas enregistrées : on avertit avant tout rechargement ou départ.
Le texte de la boîte est imposé par le navigateur, il n'est pas personnalisable. */
let reponsesModifiees = false;
function marquerModifie() { reponsesModifiees = true; }
// Les champs texte déclenchent input/change ; les parties FDS (glisser-déposer),
// FT (liaisons) et Étiquette (zones cliquables) appellent marquerModifie() directement.
document.addEventListener('input', marquerModifie, true);
document.addEventListener('change', marquerModifie, true);
window.addEventListener('beforeunload', e => {
if (!reponsesModifiees) return;
e.preventDefault();
e.returnValue = '';
});
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
generateQuizzes(); generateQuizzes();
updateUI(); updateUI();

View File

@@ -1317,6 +1317,7 @@ function updateUI() {
} }
function selectCase(key, icon, title, desc) { function selectCase(key, icon, title, desc) {
marquerModifie();
selectedCase.key = key; selectedCase.key = key;
selectedCase.icon = icon; selectedCase.icon = icon;
selectedCase.title = title; selectedCase.title = title;
@@ -1620,6 +1621,21 @@ function escapeHtml(str) {
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
} }
/* ======================== PROTECTION CONTRE LA PERTE DES RÉPONSES ======================== */
/* Les réponses ne sont pas enregistrées : on avertit avant tout rechargement ou départ.
Le texte de la boîte est imposé par le navigateur, il n'est pas personnalisable. */
let reponsesModifiees = false;
function marquerModifie() { reponsesModifiees = true; }
document.addEventListener('input', marquerModifie, true);
document.addEventListener('change', marquerModifie, true);
window.addEventListener('beforeunload', e => {
if (!reponsesModifiees) return;
e.preventDefault();
e.returnValue = '';
});
updateUI(); updateUI();
</script> </script>
</body> </body>