diff --git a/front/public/src/quiz.js b/front/public/src/quiz.js
index 71c0d11..7277170 100644
--- a/front/public/src/quiz.js
+++ b/front/public/src/quiz.js
@@ -2,27 +2,28 @@
/// Il n'est pas nécessaire d'avoir une base de données locale active pour répondre à un quiz.
/// Mais si ce n'est pas déjà le cas et que cela semble techniquement possible, il est proposé au répondant de sauvegarder durablement son résultat.
-/// Dans ce but son résultat est d'abord stocké temporairement.
+/// Dans ce but, son résultat est d'abord stocké temporairement.
/// Si la base de donnée locale existe déjà, l'enregistrement de son résultat se fait automatiquement dans IndexedDB et ses éventuels précédents résultats sont affichés.
// Configurations générales provenant du backend :
import { availableLangs } from "../../../config/instance.js";
const lang=availableLangs[0];
+// Textes :
+const { wantToSaveResponses, wantToSeaPreviousResults }=require("../../../lang/"+lang+"/answer");
+const { serverError }=require("../../../lang/"+lang+"/general");
+
// Fonctions :
-import { checkAllPreviousResults, checkUserAnswers, getResultOutput, resultsOpenDb, saveNewQuiz, saveResult, saveResultTemp, showPreviousResults } from "./tools/answers.js";
-import { saveIsReady } from "./tools/clientstorage.js";
import { addElement } from "./tools/dom.js";
import { helloDev } from "./tools/everywhere.js";
import { isEmpty } from "../../../tools/main";
import { loadMatomo } from "./tools/matomo.js";
-// Textes :
-const { wantToSaveResponses, wantToSeaPreviousResults }=require("../../../lang/"+lang+"/answer");
-const { localDBConnexionFail, serverError }=require("../../../lang/"+lang+"/general");
+// Classe s'occupant du stockage des résultats aux quizs :
+import { userQuizsResults} from "./tools/userQuizsResults";
-// Informations du quiz, utile pour enregistrement dans base de donnée locale :
-const myForm=document.getElementById("quiz");// quiz
+// Informations du quiz en cours, utiles pour les enregistrements dans la base de donnée locale :
+const myForm=document.getElementById("quiz");
const quizInfos=
{
url: window.location.pathname,
@@ -31,41 +32,29 @@ const quizInfos=
title: myForm.dataset.title
};
-// Autres éléments du DOM manipulés :
-const propose2Save=document.getElementById("propose2Save");
+// Éléments du DOM manipulés :
const btnSave=document.getElementById("want2Save");
const btnSubmit=document.getElementById("checkResponses");
-const divResponse=document.getElementById("response");
+const propose2Save=document.getElementById("propose2Save");
+const responseTxt=document.getElementById("response");
-// L'url permet de savoir si nous sommes sur un quiz unique ou groupé :
-let btnShow;
-if(quizInfos.url.indexOf("/gp/") == -1)
- btnShow=document.getElementById("showQuestionnaire"); // le quiz est affiché directement pour les groupes, ce qui déclenche le compteur
-
-let userDB, allPreviousAnswers=[];
+let userDB, allPreviousAnswers=[], btnShow, myResults;
const initialise = async () =>
{
try
{
- if(btnShow)
- btnShow.style.display="inline"; // Le bouton est caché si le JS inactif, car le JS est nécessaire pour la suite...
- // On vérifie si la navigateur accepte l'utilisation d'IndexedDB :
- const saveIsPossible=saveIsReady();
- if(saveIsPossible)
+ // Pour les quizs uniques (non groupés), un bouton permet à l'utilisateur d'afficher le quiz après avoir lu le contenu Wikipédia proposé, ce qui déclenche le chrono :
+ if(quizInfos.url.indexOf("/gp/") == -1)
{
- // On essaye ensuite de se connecter à la base de données (ce qui va la créer si inexistante) :
- userDB=await resultsOpenDb("myAnswers", 1);
- if(userDB === undefined)
- console.error(localDBConnexionFail);
- else
- {
- // Vérifie si l'utilisateur a déjà sauvegardé au moins un résultat (et donc est ok pour les sauvegardes) :
- allPreviousAnswers=await checkAllPreviousResults(userDB);
- // Si oui, on affiche ceux enregistrés pour ce quiz :
- if(allPreviousAnswers.length !== 0)
- await showPreviousResults(userDB, quizInfos.QuestionnaireId, quizInfos.GroupId);
- }
+ btnShow=document.getElementById("showQuestionnaire");
+ btnShow.style.display="inline"; // Le bouton est caché si le JS est inactif, car le JS est nécessaire pour la suite...
}
+ // Instanciation de la classe s'occupant du stockage des résultats aux quizs :
+ myResults=await userQuizsResults.initialise("myResults", 1);
+ // Si la base de données est fonctionnel et que des résultats sont déjà enregistrés, on affiche ceux pour le quiz en cours :
+ if(myResults.allResults.length !== 0)
+ await myResults.showPreviousResultsForId(quizInfos.QuestionnaireId, quizInfos.GroupId);
+ // Statistiques :
loadMatomo();
}
catch(e)
@@ -76,8 +65,7 @@ const initialise = async () =>
initialise();
helloDev();
-// Affichage du quiz, quand il est caché par défaut (= l'internaute doit d'abord lire un article Wikipédia).
-// Déclenche en même temps le chronomètre mesurant la durée de réponse aux questions.
+// Fonction affichant le quiz, quand il est caché par défaut+ déclenchant le chronomètre mesurant la durée de réponse aux questions.
const showQuestionnaire = () =>
{
chronoBegin=Date.now();
@@ -93,10 +81,9 @@ const showQuestionnaire = () =>
window.location.assign(here+"#questionnaire");
}
-let chronoBegin=0;
+let chronoBegin;
if(btnShow)
{
- // L'utilisateur demande à voir le quiz :
btnShow.addEventListener("click", function(e)
{
try
@@ -106,7 +93,7 @@ if(btnShow)
}
catch(e)
{
- addElement(divResponse, "p", serverError, "", ["error"]);
+ addElement(responseTxt, "p", serverError, "", ["error"]);
console.error(e);
}
});
@@ -130,34 +117,34 @@ myForm.addEventListener("submit", async function(e)
{
e.preventDefault();
btnSubmit.style.display="none"; // seulement une réponse à la fois, SVP :)
- divResponse.innerHTML=""; // supprime les éventuels messages déjà affichés
- answer=checkUserAnswers(myForm);
+ responseTxt.innerHTML=""; // supprime les éventuels messages déjà affichés
+ answer=userQuizsResults.checkUserAnswers(myForm);
answer.duration=Math.round((Date.now()-chronoBegin)/1000);
answer.QuestionnaireId=quizInfos.QuestionnaireId;
answer.GroupId=quizInfos.GroupId;
- // Affichage du résultat, suivant les cas :
- let getOuput=getResultOutput(answer);
+ // Enregistrement et affichage du résultat, suivant les cas :
+ let getOuput=userQuizsResults.getResultOutput(answer);
// S'il y a déjà une réponse dans la bd, c'est que l'utilisateur est ok pour les enregister.
- if(allPreviousAnswers.length !== 0)
+ if(myResults.allResults.length !== 0)
{
- const saveResponses=await saveResult(userDB, answer);
+ const saveResponses=await myResults.addResult(answer);
if(saveResponses)
- await showPreviousResults(userDB, quizInfos.QuestionnaireId, quizInfos.GroupId);
+ await myResults.showPreviousResultsForId(quizInfos.QuestionnaireId, quizInfos.GroupId);
getOuput+="
"+wantToSeaPreviousResults.replace("URL","#explanations");
// Nouveau quiz pour cette personne ?
- await saveNewQuiz(userDB, quizInfos);
+ await myResults.saveNewQuiz(quizInfos);
}
else
{
// S'il n'a pas encore de données, on stocke temporairement le résultat et propose de l'enregistrer :
- if(saveResultTemp(answer) && !isEmpty(userDB))
+ if(myResults.saveResultTemp(answer) && myResults.dbIsReady)
{
getOuput+="
"+wantToSaveResponses;
propose2Save.style.display="block";
}
}
- addElement(divResponse, "p", getOuput, "", ["info"]);
+ addElement(responseTxt, "p", getOuput, "", ["info"]);
// On redirige vers le résultat :
const here=window.location;
if(window.location.hash !== "")
@@ -167,6 +154,7 @@ myForm.addEventListener("submit", async function(e)
}
else
window.location.assign(here+"#response");
+
// + Affichage des textes d'explication pour chaque question
const explanations=document.querySelectorAll(".help");
for(let i in explanations)
@@ -177,10 +165,10 @@ myForm.addEventListener("submit", async function(e)
}
catch(e)
{
- addElement(divResponse, "p", serverError, "", ["error"]);
+ addElement(responseTxt, "p", serverError, "", ["error"]);
console.error(e);
}
-})
+});
// L'utilisateur demande à sauvegarder son résultat :
btnSave.addEventListener("click", async function(e)
@@ -188,15 +176,15 @@ btnSave.addEventListener("click", async function(e)
try
{
e.preventDefault();
- if(!isEmpty(userDB) && !isEmpty(answer)) // On ne devrait pas me proposer d'enregistrer dans ce cas, mais...
+ if(!isEmpty(myResults.dbIsReady) && !isEmpty(answer)) // On ne devrait pas m'avoir proposé d'enregistrer dans ce cas, mais...
{
- const saveResponses=await saveResult(userDB, answer);
+ const saveResponses=await myResults.addResult(answer);
if(saveResponses)
{
// Nouvel enregistrement = actualisation nécessaire de la liste des résultats pour ce quiz :
- await showPreviousResults(userDB, quizInfos.QuestionnaireId, quizInfos.GroupId);
- // Nouveau quiz ?
- await saveNewQuiz(userDB, quizInfos);
+ await myResults.showPreviousResultsForId(quizInfos.QuestionnaireId, quizInfos.GroupId);
+ // Nouveau quiz (ce qui doit être le cas, mais...) :
+ await myResults.saveNewQuiz(quizInfos);
// Redirection vers la liste des résultats :
const here=window.location; // window.location à ajouter pour ne pas quitter la page en mode "preview".
if(window.location.hash !== "")
@@ -212,7 +200,7 @@ btnSave.addEventListener("click", async function(e)
}
catch(e)
{
- addElement(divResponse, "p", serverError, "", ["error"]);
+ addElement(responseTxt, "p", serverError, "", ["error"]);
console.error(e);
}
});
\ No newline at end of file
diff --git a/front/public/src/tools/answers.js b/front/public/src/tools/answers.js
deleted file mode 100644
index a112245..0000000
--- a/front/public/src/tools/answers.js
+++ /dev/null
@@ -1,350 +0,0 @@
-// Fonctions externes :
-import { addElement } from "./dom.js";
-import { dateFormat, isEmpty, replaceAll } from "../../../../tools/main";
-import { saveLocaly, getStore } from "./clientstorage.js";
-import { getDatasFromInputs } from "./forms.js";
-
-// Textes :
-import { availableLangs } from "../../../../config/instance.js";
-const lang=availableLangs[0];
-const { localDBNeedDatas, localDBNeedQuizId, noPreviousResults, previousResultsLine, previousResultsTitle, previousResultsStats, userAnswersFail, userAnswersMedium, userAnswersSuccess }=require("../../../../lang/"+lang+"/answer");
-
-// Vérification des réponses de l'utilisateur au quiz
-export const checkUserAnswers = (myForm) =>
-{
- // Les réponses sont regroupées par question, donc quand idQuestion change, on connaît le résultat pour la question précédente.
- // Pour qu'une réponse soit bonne, il faut avoir coché toutes les bonnes réponses (si QCM) à la question ET n'avoir coché aucune des mauvaises.
- const userResponses=getDatasFromInputs(myForm);
- let idChoice, idQuestion="", goodResponse=false, answer={ nbCorrectAnswers:0, nbQuestions:0 };
- for(let item in userResponses)
- {
- if(item.startsWith("isCorrect_response_")) // = Nouvelle réponse possible.
- {
- idChoice=item.substring(item.lastIndexOf("_")+1);
- // Si on change de question :
- if(userResponses["question_id_response_"+idChoice] != idQuestion) // on commence à traiter une nouvelle question
- {
- idQuestion=userResponses["question_id_response_"+idChoice];
- answer.nbQuestions++;
- if(goodResponse) // Résultat de la question précédente
- answer.nbCorrectAnswers++;
- goodResponse=true;// Réponse bonne jusqu'à la première erreur...
- }
- if(userResponses[item] == "true")
- {
- document.getElementById("response_"+idChoice).parentNode.classList.add("isCorrect");
- if(userResponses["response_"+idChoice] === undefined) // Une des bonnes réponses n'a pas été sélectionnée :(
- goodResponse=false;
- }
- else
- {
- if(userResponses["response_"+idChoice] === "on") // Réponse cochée ne faisant pas partie des bonnes :(
- {
- goodResponse=false;
- document.getElementById("response_"+idChoice).parentNode.classList.add("isNotCorrect");
- }
- }
- }
- }
- // Si j'ai bien répondu à la dernière question, il faut le compter ici, car je suis sorti de la boucle :
- if(goodResponse)
- answer.nbCorrectAnswers++;
- return answer;
-}
-
-// Retourne un texte suivant le nombre de bonnes réponses
-export const getResultOutput = (result) =>
-{
- if(!isEmpty(result.duration) && !isEmpty(result.nbCorrectAnswers) && !isEmpty(result.nbQuestions))
- {
- const ratio=result.nbCorrectAnswers/result.nbQuestions;
- const mapObj=
- {
- DURATION: result.duration,
- NBCORRECTANSWERS: result.nbCorrectAnswers,
- NBQUESTIONS: result.nbQuestions
- }
- let output="";
- if(ratio < 0.4)
- output=replaceAll(userAnswersFail, mapObj);
- else if(ratio < 0.8)
- output=replaceAll(userAnswersMedium, mapObj);
- else
- output=replaceAll(userAnswersSuccess, mapObj);
- return output;
- }
- else
- return "";
-}
-
-const checkIfResultIsComplete = (result) =>
-{
- if(!isEmpty(result.duration) && !isEmpty(result.nbCorrectAnswers) && !isEmpty(result.nbQuestions) && (!isEmpty(result.QuestionnaireId) || !isEmpty(result.GroupId)))
- return true;
- else
- return false;
-}
-
-// Enregistrement temporaire du dernier résultat à un quiz en attendant de savoir si l'utilisateur souhaite une sauvegarde durable :
-export const saveResultTemp = (result) =>
-{
- if(checkIfResultIsComplete(result))
- {
- saveLocaly("lastResult", result); // écrasera l'éventuel résultat précédent.
- return true;
- }
- else
- {
- console.error(localDBNeedDatas);
- console.log(result);
- return false;
- }
-}
-
-// Se connecte à la base de données de sauvegarde des résultats
-// Et la créé si c'est la première connexion (ou mise à jour de version)
-export const resultsOpenDb = (dbName, dbVersion) =>
-{
- return new Promise( (resolve, reject) =>
- {
- let req=indexedDB.open(dbName, dbVersion);
- req.onupgradeneeded= (e) =>
- {
- // Création du stockage des quizs auxquels l'utilisateur a déjà répondu :
- let store=e.currentTarget.result.createObjectStore("userQuizs", { keyPath: "id", autoIncrement: true });
- store.createIndex("url", "url", { unique: true });
- store.createIndex("QuestionnaireId", "id", { unique: true }); // = simple quiz
- store.createIndex("GroupId", "id", { unique: true }); // = quiz après lecture d'un ou +sieurs articles.
- store.createIndex("title", "title", { unique: false });
- // Création du stockage des résultats :
- store=e.currentTarget.result.createObjectStore("userResults", { keyPath: "id", autoIncrement: true });
- store.createIndex("QuestionnaireId", "QuestionnaireId", { unique: false });
- store.createIndex("GroupId", "GroupId", { unique: false });
- store.createIndex("duration", "duration", { unique: true });
- store.createIndex("nbCorrectAnswers", "nbCorrectAnswers", { unique: false });
- store.createIndex("nbQuestions", "nbQuestions", { unique: false });
- store.createIndex("date", "date", { unique: false }); // bien que doublons peu probables...
- /// Revoir comment gérér les mises à jour de version de la BD sans tout casser...
- };
- req.onsuccess= (e) =>
- {
- resolve(req.result); // On retourne la base de données
- };
- req.onerror= (e) =>
- {
- console.error(e); // dans cette fonction, peut simplement planter parce que navigation privée...
- resolve(undefined);
- };
- })
-}
-
-// Fonction cherchant les éventuels résultats déjà enregistrés pour un quiz :
-export const checkPreviousResultsForId= (db, QuestionnaireId=0, GroupId=0) =>
-{
- return new Promise( (resolve, reject) =>
- {
- const resultsStore=getStore(db, "userResults", "readonly");
- let myIndex, getResults;
- if(QuestionnaireId != 0)
- {
- myIndex=resultsStore.index("QuestionnaireId");
- getResults=myIndex.openCursor(QuestionnaireId);
- }
- else if(GroupId != 0)
- {
- myIndex=resultsStore.index("GroupId");
- getResults=myIndex.openCursor(GroupId);
- }
- else
- reject(localDBNeedQuizId);
-
- const answers=[];
- getResults.onsuccess = (e) =>
- {
- const cursor=e.target.result;
- if (cursor)
- {
- answers.push(cursor.value);
- cursor.continue();
- }
- else // = on a parcouru toutes les données
- resolve(answers);
- };
- getResults.onerror= (e) =>
- {
- console.error(e);
- reject(e);
- };
- })
-}
-
-// Fonction affichant les précédents résultats connus pour le quiz encours :
-export const showPreviousResults = async (userDB, QuestionnaireId, GroupId) =>
-{
- if(isEmpty(userDB) || (isEmpty(QuestionnaireId) && isEmpty(GroupId)))
- return false;
-
- // Recherche dans la base de données :
- const previousResults=await checkPreviousResultsForId(userDB, QuestionnaireId, GroupId);
- if(previousResults === undefined) // Peut être un tableau vide si ancun résultat enregistré, mais pas undefined.
- console.error(localDBGetPreviousResultsFail);
- else
- {
- const explanationsContent=document.getElementById("explanationsContent");
- const explanationsTitle=document.getElementById("explanationsTitle");
- // Les précédents résultats sont classés par ordre d'enregistrement et sont donc à inverser :
- previousResults.reverse();
- const nbPrevious=previousResults.length;
- let previousResultsContent="";
- addElement(explanationsTitle, "span", previousResultsTitle);
- if(nbPrevious !== 0)
- {
- let totNbQuestions=0, totNbCorrectAnswers=0, totDuration=0, mapLineContent;
- for(const i in previousResults)
- {
- totNbQuestions+=previousResults[i].nbQuestions; // ! on ne peut se baser sur la version actuelle du quiz, car le nombre de questions peut évolué.
- totNbCorrectAnswers+=previousResults[i].nbCorrectAnswers;
- totDuration+=previousResults[i].duration;
- mapLineContent=
- {
- DATEANSWER: dateFormat(previousResults[i].date, lang),
- NBCORRECTANSWERS: previousResults[i].nbCorrectAnswers,
- NBQUESTIONS: previousResults[i].nbQuestions,
- AVGDURATION: previousResults[i].duration
- };
- previousResultsContent+="