Évolution du frontend de manière à permettre la sauvegarde des résultats aux quizs dans le navigateur (IndexDB).
This commit is contained in:
parent
896fabb1b7
commit
70182b481f
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,228 +0,0 @@
|
||||
// -- GESTION DU FORMULAIRE PERMETTANT D'AFFICHER ET RÉPONDRE À UN GROUPE DE QUIZS
|
||||
|
||||
/// Il n'est pas nécessaire d'être connecté pour répondre au quiz et voir son résultat.
|
||||
/// Mais si pas connecté, on propose à l'internaute de se connecter ou de créer un compte pour sauvegarder son résultat.
|
||||
/// Dans ce but son résultat est stocké dans son navigateur.
|
||||
/// Si il est connecté, l'enregistrement de son résultat se fait automatiquement côté serveur et ses éventuels précédents résultats sont affichés.
|
||||
|
||||
// Fichier de configuration tirés du backend :
|
||||
import { apiUrl, availableLangs, theme } from "../../config/instance.js";
|
||||
const lang=availableLangs[0];
|
||||
import { getPreviousAnswers, groupRoutes, saveAnswersRoute } from "../../config/questionnaires.js";
|
||||
const configTemplate = require("../../views/"+theme+"/config/"+lang+".js");
|
||||
|
||||
import { checkAnswerOuput, saveAnswer } from "./tools/answers.js";
|
||||
import { addElement } from "./tools/dom.js";
|
||||
import { helloDev, updateAccountLink } from "./tools/everywhere.js";
|
||||
import { getLocaly } from "./tools/clientstorage.js";
|
||||
import { getDatasFromInputs } from "./tools/forms.js";
|
||||
import { dateFormat, replaceAll } from "../../tools/main";
|
||||
import { loadMatomo } from "./tools/matomo.js";
|
||||
import { checkSession, getTimeDifference } from "./tools/users.js";
|
||||
|
||||
// Dictionnaires :
|
||||
const { noPreviousAnswer, previousAnswersLine, previousAnswersStats, previousAnswersTitle, responseSavedError, wantToSaveResponses } = require("../../lang/"+lang+"/answer");
|
||||
const { serverError } = require("../../lang/"+lang+"/general");
|
||||
|
||||
// Principaux éléments du DOM manipulés :
|
||||
const btnSubmit = document.getElementById("checkResponses");
|
||||
const divResponse = document.getElementById("response");
|
||||
const explanationsTitle = document.getElementById("explanationsTitle");
|
||||
const explanationsContent = document.getElementById("explanationsContent");
|
||||
const myForm = document.getElementById("group");
|
||||
|
||||
// Affiche le bouton de soumission + déclenche le chronomètre mesurant la durée de la réponse.
|
||||
let chronoBegin=0;
|
||||
const beginAnswer = () =>
|
||||
{
|
||||
chronoBegin=Date.now();
|
||||
btnSubmit.style.display="block";
|
||||
}
|
||||
|
||||
let isConnected, user;
|
||||
const initialise = async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Si JS activé, on affiche le bouton de soumission du formulaire :
|
||||
beginAnswer();
|
||||
/*
|
||||
isConnected=await checkSession(["user"]);// "user" car seuls les utilisateurs de base peuvent enregistrer leurs réponses aux quizs
|
||||
// Si l'utilisateur est connecté et a déjà répondu à ce quiz, on affiche ses précédentes réponses à la place du texte servant à expliquer le topo aux nouveaux
|
||||
if(isConnected)
|
||||
{
|
||||
user=getLocaly("user", true);
|
||||
updateAccountLink(user.status, configTemplate);// lien vers le compte adapté pour les utilisateurs connectés
|
||||
checkPreviousResponses(user);
|
||||
}
|
||||
else */
|
||||
loadMatomo();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
initialise();
|
||||
helloDev();
|
||||
|
||||
// Traitement de l'envoi de la réponse de l'utilisateur :
|
||||
let answer = {};
|
||||
myForm.addEventListener("submit", function(e)
|
||||
{
|
||||
try
|
||||
{
|
||||
e.preventDefault();
|
||||
btnSubmit.style.display="none";// seulement un envoi à la fois, SVP :)
|
||||
divResponse.innerHTML="";// supprime les éventuels messages déjà affichés.
|
||||
const userResponses=getDatasFromInputs(myForm);
|
||||
answer.duration=Math.round((Date.now()-chronoBegin)/1000);
|
||||
answer.nbQuestions=0;
|
||||
answer.nbCorrectAnswers=0;
|
||||
answer.GroupId=document.getElementById("groupId").value;
|
||||
// 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 cocher toutes les bonnes réponses (si QCM) à la question ET cocher aucune des mauvaises.
|
||||
let idChoice, idQuestion="", goodResponse=false;
|
||||
for(let item in userResponses)
|
||||
{
|
||||
if(item.startsWith("isCorrect_response_"))// = Nouvelle réponse possible.
|
||||
{
|
||||
idChoice = item.substring(item.lastIndexOf("_") + 1);
|
||||
if(userResponses["question_id_response_"+idChoice] != idQuestion) // = on commence à traiter une nouvelle question.
|
||||
{
|
||||
idQuestion=userResponses["question_id_response_"+idChoice];
|
||||
answer.nbQuestions++;
|
||||
if(goodResponse) // = pas d'erreur à la question précédente
|
||||
answer.nbCorrectAnswers++;
|
||||
goodResponse=true;// La réponse est considérée comme bonne, jusqu'à la première erreur...
|
||||
}
|
||||
if(userResponses[item] == "true")
|
||||
{
|
||||
document.getElementById("response_"+idChoice).parentNode.classList.add("isCorrect");
|
||||
if(userResponses["response_"+idChoice] === undefined)// = une bonne réponse n'a pas été sélectionnée
|
||||
goodResponse=false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(userResponses["response_"+idChoice] === "on")
|
||||
{
|
||||
goodResponse=false; // = une mauvaise réponse a été sélectionnée
|
||||
document.getElementById("response_"+idChoice).parentNode.classList.add("isNotCorrect");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Si j'ai bien répondu à la dernière question, il faut le compter ici, car on est sorti de la boucle :
|
||||
if(goodResponse)
|
||||
answer.nbCorrectAnswers++;
|
||||
|
||||
// Affichage du résultat, suivant si l'utilisateur est connecté ou pas et son score :
|
||||
let getOuput=checkAnswerOuput(answer);
|
||||
/*if(isConnected)
|
||||
{
|
||||
// Si l'utilisateur est connecté, on passe son résultat au serveur pour le sauvegarder.
|
||||
const xhrSaveAnswer = new XMLHttpRequest();
|
||||
xhrSaveAnswer.open("POST", apiUrl+groupRoutes+saveAnswersRoute);
|
||||
xhrSaveAnswer.onreadystatechange = function()
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
{
|
||||
let xhrResponse=JSON.parse(this.responseText);
|
||||
if (this.status === 201 && (xhrResponse.message))
|
||||
{
|
||||
getOuput+="<br>"+xhrResponse.message.replace("#URL", configTemplate.userHomePage);
|
||||
checkPreviousResponses(user);
|
||||
}
|
||||
else
|
||||
getOuput+="<br>"+responseSavedError.replace("#URL", configTemplate.userHomePage);
|
||||
// Puis on le redirige vers son résultat :
|
||||
window.location.hash="";
|
||||
const here=window.location;// window.location à ajouter pour ne pas quitter la page en mode "preview"...
|
||||
window.location.hash="explanations";
|
||||
}
|
||||
}
|
||||
xhrSaveAnswer.setRequestHeader("Authorization", "Bearer "+user.token);
|
||||
xhrSaveAnswer.setRequestHeader("Content-Type", "application/json");
|
||||
answer.timeDifference=getTimeDifference();// On en profite pour mettre les pendules à l'heure.
|
||||
xhrSaveAnswer.send(JSON.stringify(answer));
|
||||
}
|
||||
else
|
||||
{ // Si internaute non connecté, on enregistre le résultat côté client pour permettre de le retrouver au moment de la création du compte ou de la connexion.
|
||||
/*if(saveAnswer(answer))
|
||||
{
|
||||
getOuput+="</p><p>"+wantToSaveResponses+"</p>";
|
||||
addElement(divResponse, "p", getOuput, "", ["success"]);
|
||||
document.querySelector(".subscribeBtns").style.display="block";
|
||||
}
|
||||
else // Mais inutile de proposer de créer un compte si le stockage local ne fonctionne pas */
|
||||
addElement(divResponse, "p", getOuput, "", ["success"]);
|
||||
// Puis on le redirige vers son résultat :
|
||||
window.location.hash="";
|
||||
const here=window.location;// window.location à ajouter pour ne pas quitter la page en mode "preview"...
|
||||
window.location.hash="response";
|
||||
//}
|
||||
// + Affichage des textes d'explications pour chaque question
|
||||
const explanations=document.querySelectorAll(".help");
|
||||
for(let i in explanations)
|
||||
if(explanations[i].style !== undefined) // sinon, la console affiche une erreur "TypeError: explanations[i].style is undefined", bien que tout fonctionne (?)
|
||||
explanations[i].style.display="block";
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
addElement(divResponse, "p", serverError, "", ["error"]);
|
||||
console.error(e);
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
// Fonction vérifiant les précédentes réponses de l'utilisateur.
|
||||
// Utile si connecté lors du premier chargement de la page, puis après une nouvelle réponse.
|
||||
const checkPreviousResponses = (user) =>
|
||||
{
|
||||
const xhrPreviousRes = new XMLHttpRequest();
|
||||
xhrPreviousRes.open("GET", apiUrl+groupRoutes+getPreviousAnswers+user.id+"/"+document.getElementById("groupId").value);
|
||||
xhrPreviousRes.onreadystatechange = function()
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
{
|
||||
let response=JSON.parse(this.responseText);
|
||||
if (this.status === 200)
|
||||
{
|
||||
const nbResponses=response.length;
|
||||
let previousAnswersContent="";
|
||||
addElement(explanationsTitle, "span", previousAnswersTitle.replace("#NOM", user.name));
|
||||
if(nbResponses!==0)
|
||||
{
|
||||
let totNbQuestions=0, totNbCorrectAnswers=0, totDuration=0, mapLineContent;
|
||||
for(let i in response)
|
||||
{
|
||||
totNbQuestions+=response[i].nbQuestions;// ! on ne peut se baser sur la version actuelle du quiz, car le nombre de questions a pu évoluer.
|
||||
totNbCorrectAnswers+=response[i].nbCorrectAnswers;
|
||||
totDuration+=response[i].duration;
|
||||
mapLineContent =
|
||||
{
|
||||
DATEANSWER : dateFormat(response[i].createdAt, lang),
|
||||
NBCORRECTANSWERS : response[i].nbCorrectAnswers,
|
||||
NBQUESTIONS : response[i].nbQuestions,
|
||||
AVGDURATION : response[i].duration
|
||||
};
|
||||
previousAnswersContent+="<li>"+replaceAll(previousAnswersLine, mapLineContent)+"</li>";
|
||||
}
|
||||
mapLineContent =
|
||||
{
|
||||
AVGDURATION : Math.round(totDuration/nbResponses),
|
||||
AVGCORRECTANSWERS : Math.round(totNbCorrectAnswers/totNbQuestions*100)
|
||||
};
|
||||
previousAnswersContent="<h5>"+replaceAll(previousAnswersStats, mapLineContent)+"</h5>"+previousAnswersContent;
|
||||
addElement(explanationsContent, "ul", previousAnswersContent);
|
||||
}
|
||||
else
|
||||
addElement(explanationsContent, "ul", noPreviousAnswer);
|
||||
// dans un cas comme dans l'autre, bouton pour revenir à l'accueil du compte
|
||||
addElement(explanationsContent, "p", "<a href=\"/"+configTemplate.userHomePage+"\" class=\"button cardboard\">"+configTemplate.userHomePageTxt+"</a>", "", ["btn"], "", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
xhrPreviousRes.setRequestHeader("Authorization", "Bearer "+user.token);
|
||||
xhrPreviousRes.send();
|
||||
} */
|
@ -1,259 +0,0 @@
|
||||
// -- GESTION DU FORMULAIRE PERMETTANT D'AFFICHER ET RÉPONDRE À UN QUIZ
|
||||
|
||||
/// Il n'est pas nécessaire d'être connecté pour répondre au quiz et voir son résultat.
|
||||
/// Mais si pas connecté, on propose à l'internaute de se connecter ou de créer un compte pour sauvegarder son résultat.
|
||||
/// Dans ce but son résultat est stocké dans son navigateur.
|
||||
/// Si il est connecté, l'enregistrement de son résultat se fait automatiquement côté serveur et ses éventuels précédents résultats sont affichés.
|
||||
|
||||
// Fichier de configuration tirés du backend :
|
||||
import { apiUrl, availableLangs, theme } from "../../config/instance.js";
|
||||
const lang=availableLangs[0];
|
||||
import { getPreviousAnswers, questionnaireRoutes, saveAnswersRoute } from "../../config/questionnaires.js";
|
||||
const configTemplate = require("../../views/"+theme+"/config/"+lang+".js");
|
||||
|
||||
import { checkAnswerOuput, saveAnswer } from "./tools/answers.js";
|
||||
import { addElement } from "./tools/dom.js";
|
||||
import { helloDev, updateAccountLink } from "./tools/everywhere.js";
|
||||
import { getLocaly } from "./tools/clientstorage.js";
|
||||
import { getDatasFromInputs } from "./tools/forms.js";
|
||||
import { dateFormat, replaceAll } from "../../tools/main";
|
||||
import { loadMatomo } from "./tools/matomo.js";
|
||||
import { checkSession, getTimeDifference } from "./tools/users.js";
|
||||
|
||||
// Dictionnaires :
|
||||
const { noPreviousAnswer, previousAnswersLine, previousAnswersStats, previousAnswersTitle, responseSavedError, wantToSaveResponses } = require("../../lang/"+lang+"/answer");
|
||||
const { serverError } = require("../../lang/"+lang+"/general");
|
||||
|
||||
// Principaux éléments du DOM manipulés :
|
||||
const myForm = document.getElementById("questionnaire");
|
||||
const divResponse = document.getElementById("response");
|
||||
const btnShow = document.getElementById("showQuestionnaire");
|
||||
const btnSubmit = document.getElementById("checkResponses");
|
||||
const explanationsTitle = document.getElementById("explanationsTitle");
|
||||
const explanationsContent = document.getElementById("explanationsContent");
|
||||
|
||||
let isConnected, user;
|
||||
const initialise = async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
btnShow.style.display="inline";// bouton caché si JS inactif, car JS nécessaire pour vérifier les réponses
|
||||
/*
|
||||
isConnected=await checkSession(["user"]);// "user" car seuls les utilisateurs de base peuvent enregistrer leurs réponses aux quizs
|
||||
// Si l'utilisateur est connecté et a déjà répondu à ce quiz, on affiche ses précédentes réponses à la place du texte servant à expliquer le topo aux nouveaux
|
||||
if(isConnected)
|
||||
{
|
||||
user=getLocaly("user", true);
|
||||
updateAccountLink(user.status, configTemplate);// lien vers le compte adapté pour les utilisateurs connectés
|
||||
checkPreviousResponses(user);
|
||||
}
|
||||
else */
|
||||
loadMatomo();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
initialise();
|
||||
helloDev();
|
||||
|
||||
// Affichage du questionnaire quand l'utilisateur clique sur le bouton ou si l'id du formulaire est passée par l'url.
|
||||
// Déclenche en même temps le chronomètre mesurant la durée de la réponse aux questions.
|
||||
const showQuestionnaire = () =>
|
||||
{
|
||||
chronoBegin=Date.now();
|
||||
myForm.style.display="block";
|
||||
btnShow.style.display="none";
|
||||
const here=window.location;// window.location à ajouter pour ne pas quitter la page en mode "preview".
|
||||
if(window.location.hash!=="")
|
||||
{
|
||||
window.location.hash="";// ! le "#" reste
|
||||
window.location.assign(here+"questionnaire");
|
||||
}
|
||||
else
|
||||
window.location.assign(here+"#questionnaire");
|
||||
}
|
||||
let chronoBegin=0;
|
||||
btnShow.addEventListener("click", function(e)
|
||||
{
|
||||
try
|
||||
{
|
||||
e.preventDefault();
|
||||
showQuestionnaire();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
addElement(divResponse, "p", serverError, "", ["error"]);
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
// Lien passé par mail pour voir directement le quiz
|
||||
if(location.hash!="" && location.hash==="#questionnaire")
|
||||
showQuestionnaire();
|
||||
|
||||
// Traitement de l'envoi de la réponse de l'utilisateur :
|
||||
let answer = {};
|
||||
myForm.addEventListener("submit", function(e)
|
||||
{
|
||||
try
|
||||
{
|
||||
e.preventDefault();
|
||||
btnSubmit.style.display="none";// seulement un envoi à la fois, SVP :)
|
||||
divResponse.innerHTML="";// supprime les éventuels messages déjà affichés
|
||||
const userResponses=getDatasFromInputs(myForm);
|
||||
answer.duration=Math.round((Date.now()-chronoBegin)/1000);
|
||||
answer.nbQuestions=0;
|
||||
answer.nbCorrectAnswers=0;
|
||||
answer.QuestionnaireId=document.getElementById("questionnaireId").value;
|
||||
// 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 cocher toutes les bonnes réponses (si QCM) à la question ET cocher aucune des mauvaises.
|
||||
let idChoice, idQuestion="", goodResponse=false;
|
||||
for(let item in userResponses)
|
||||
{
|
||||
if(item.startsWith("isCorrect_response_"))// = Nouvelle réponse possible.
|
||||
{
|
||||
idChoice = item.substring(item.lastIndexOf("_") + 1);
|
||||
// si on change de queston
|
||||
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 bonne réponse 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++;
|
||||
|
||||
// Affichage du résultat, suivant si l'utilisateur est connecté ou pas et son score :
|
||||
let getOuput=checkAnswerOuput(answer);
|
||||
/*
|
||||
if(isConnected)
|
||||
{
|
||||
// Si l'utilisateur est connecté, on enregistre son résultat sur le serveur.
|
||||
const xhrSaveAnswer = new XMLHttpRequest();
|
||||
xhrSaveAnswer.open("POST", apiUrl+questionnaireRoutes+saveAnswersRoute);
|
||||
xhrSaveAnswer.onreadystatechange = function()
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
{
|
||||
let xhrResponse=JSON.parse(this.responseText);
|
||||
if (this.status === 201 && (xhrResponse.message))
|
||||
{
|
||||
getOuput+="<br>"+xhrResponse.message.replace("#URL", configTemplate.userHomePage);
|
||||
checkPreviousResponses(user);
|
||||
}
|
||||
else
|
||||
getOuput+="<br>"+responseSavedError.replace("#URL", configTemplate.userHomePage);
|
||||
// on redirige vers le résultat
|
||||
window.location.hash="";
|
||||
const here=window.location;// window.location à ajouter pour ne pas quitter la page en mode "preview"...
|
||||
window.location.assign(here+"explanations");
|
||||
}
|
||||
}
|
||||
xhrSaveAnswer.setRequestHeader("Authorization", "Bearer "+user.token);
|
||||
xhrSaveAnswer.setRequestHeader("Content-Type", "application/json");
|
||||
answer.timeDifference=getTimeDifference();// on en profite pour mettre les pendules à l'heure.
|
||||
xhrSaveAnswer.send(JSON.stringify(answer));
|
||||
}
|
||||
else
|
||||
{ // si pas connecté, on enregistre le résultat côté client pour permettre de le retrouver au moment de la création du compte ou de la connexion.
|
||||
if(saveAnswer(answer))
|
||||
{
|
||||
getOuput+="<br><br>"+wantToSaveResponses;
|
||||
addElement(divResponse, "p", getOuput, "", ["info"]);
|
||||
document.querySelector(".subscribeBtns").style.display="block";
|
||||
}
|
||||
else // inutile de proposer de créer un compte si le stockage local ne fonctionne pas*/
|
||||
addElement(divResponse, "p", getOuput, "", ["info"]);
|
||||
// on redirige vers le résultat
|
||||
window.location.hash="";
|
||||
const here=window.location;// window.location à ajouter pour ne pas quitter la page en mode "preview"...
|
||||
window.location.assign(here+"response");
|
||||
//}
|
||||
// + affichage des textes d'explications pour chaque question
|
||||
const explanations=document.querySelectorAll(".help");
|
||||
for(let i in explanations)
|
||||
{
|
||||
if(explanations[i].style!=undefined) // sinon, la console affiche une erreur "TypeError: explanations[i].style is undefined", bien que tout fonctionne (?)
|
||||
explanations[i].style.display="block";
|
||||
}
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
addElement(divResponse, "p", serverError, "", ["error"]);
|
||||
console.error(e);
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
// Fonction vérifiant les précédentes réponses de l'utilisateur
|
||||
// Utile si connecté lors du premier chargement de la page, puis après une nouvelle réponse
|
||||
const checkPreviousResponses = (user) =>
|
||||
{
|
||||
const xhrPreviousRes = new XMLHttpRequest();
|
||||
xhrPreviousRes.open("GET", apiUrl+questionnaireRoutes+getPreviousAnswers+user.id+"/"+document.getElementById("questionnaireId").value);
|
||||
xhrPreviousRes.onreadystatechange = function()
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
{
|
||||
let response=JSON.parse(this.responseText);
|
||||
if (this.status === 200)
|
||||
{
|
||||
const nbResponses=response.length;
|
||||
let previousAnswersContent="";
|
||||
addElement(explanationsTitle, "span", previousAnswersTitle.replace("#NOM", user.name));
|
||||
if(nbResponses!==0)
|
||||
{
|
||||
let totNbQuestions=0, totNbCorrectAnswers=0, totDuration=0, mapLineContent;
|
||||
for(let i in response)
|
||||
{
|
||||
totNbQuestions+=response[i].nbQuestions;// ! on ne peut se baser sur la version actuelle du quiz, car le nombre de questions a pu évoluer.
|
||||
totNbCorrectAnswers+=response[i].nbCorrectAnswers;
|
||||
totDuration+=response[i].duration;
|
||||
mapLineContent =
|
||||
{
|
||||
DATEANSWER : dateFormat(response[i].createdAt, lang),
|
||||
NBCORRECTANSWERS : response[i].nbCorrectAnswers,
|
||||
NBQUESTIONS : response[i].nbQuestions,
|
||||
AVGDURATION : response[i].duration
|
||||
};
|
||||
previousAnswersContent+="<li>"+replaceAll(previousAnswersLine, mapLineContent)+"</li>";
|
||||
}
|
||||
mapLineContent =
|
||||
{
|
||||
AVGDURATION : Math.round(totDuration/nbResponses),
|
||||
AVGCORRECTANSWERS : Math.round(totNbCorrectAnswers/totNbQuestions*100)
|
||||
};
|
||||
previousAnswersContent="<h5>"+replaceAll(previousAnswersStats, mapLineContent)+"</h5>"+previousAnswersContent;
|
||||
addElement(explanationsContent, "ul", previousAnswersContent);
|
||||
}
|
||||
else
|
||||
addElement(explanationsContent, "ul", noPreviousAnswer);
|
||||
// dans un cas comme dans l'autre, bouton pour revenir à l'accueil du compte
|
||||
addElement(explanationsContent, "p", "<a href=\"/"+configTemplate.userHomePage+"\" class=\"button cardboard\">"+configTemplate.userHomePageTxt+"</a>", "", ["btn"], "", false);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
xhrPreviousRes.setRequestHeader("Authorization", "Bearer "+user.token);
|
||||
xhrPreviousRes.send();
|
||||
}*/
|
219
front/src/quiz.js
Normal file
219
front/src/quiz.js
Normal file
@ -0,0 +1,219 @@
|
||||
// -- GESTION DU FORMULAIRE PERMETTANT D'AFFICHER ET RÉPONDRE À UN QUIZ
|
||||
|
||||
/// 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, on propose au répondant de sauvegarder son résultat.
|
||||
/// Dans ce but son résultat est 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, theme } from "../../config/instance.js";
|
||||
const lang=availableLangs[0];
|
||||
const configTemplate=require("../../views/"+theme+"/config/"+lang+".js");
|
||||
|
||||
// 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");
|
||||
|
||||
// Informations du quiz, utile pour enregistrement dans base de donnée locale :
|
||||
const myForm=document.getElementById("quiz");// quiz
|
||||
const quizInfos=
|
||||
{
|
||||
url: window.location.pathname,
|
||||
GroupId: document.getElementById("groupId").value,
|
||||
QuestionnaireId: document.getElementById("questionnaireId").value,
|
||||
title: myForm.dataset.title
|
||||
};
|
||||
|
||||
// Autres éléments du DOM manipulés :
|
||||
const propose2Save=document.getElementById("propose2Save");
|
||||
const btnSave=document.getElementById("want2Save");
|
||||
const btnSubmit=document.getElementById("checkResponses");
|
||||
const divResponse=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
|
||||
|
||||
let userDB, allPreviousAnswers=[];
|
||||
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)
|
||||
{
|
||||
// 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);
|
||||
if(allPreviousAnswers.length !== 0)
|
||||
await showPreviousResults(userDB, quizInfos.QuestionnaireId, quizInfos.GroupId);
|
||||
}
|
||||
}
|
||||
loadMatomo();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
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.
|
||||
const showQuestionnaire = () =>
|
||||
{
|
||||
chronoBegin=Date.now();
|
||||
myForm.style.display="block";
|
||||
btnShow.style.display="none";
|
||||
const here=window.location; // window.location à ajouter pour ne pas quitter la page en mode "preview".
|
||||
if(window.location.hash !== "")
|
||||
{
|
||||
window.location.hash="";// ! le "#" reste
|
||||
window.location.assign(here+"questionnaire");
|
||||
}
|
||||
else
|
||||
window.location.assign(here+"#questionnaire");
|
||||
}
|
||||
|
||||
// L'utilisateur demande à voir le quiz :
|
||||
let chronoBegin=0;
|
||||
if(btnShow)
|
||||
{
|
||||
btnShow.addEventListener("click", function(e)
|
||||
{
|
||||
try
|
||||
{
|
||||
e.preventDefault();
|
||||
showQuestionnaire();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
addElement(divResponse, "p", serverError, "", ["error"]);
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
// Un lien peut être passé pour voir directement le quiz :
|
||||
if(location.hash !== "" && location.hash === "#questionnaire")
|
||||
showQuestionnaire();
|
||||
}
|
||||
|
||||
// Dans le cas d'un quiz groupé, le chrono est lancé dès l'affichage :
|
||||
if(quizInfos.GroupId != "0")
|
||||
{
|
||||
chronoBegin=Date.now();
|
||||
btnSubmit.style.display="block";
|
||||
}
|
||||
|
||||
// Traitement de l'envoi de la réponse de l'utilisateur :
|
||||
let answer;
|
||||
myForm.addEventListener("submit", async function(e)
|
||||
{
|
||||
try
|
||||
{
|
||||
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);
|
||||
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);
|
||||
// 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)
|
||||
{
|
||||
const saveResponses=await saveResult(userDB, answer);
|
||||
if(saveResponses)
|
||||
await showPreviousResults(userDB, quizInfos.QuestionnaireId, quizInfos.GroupId);
|
||||
getOuput+="<br><br>"+wantToSeaPreviousResults.replace("URL","#explanations");
|
||||
// Nouveau quiz pour cette personne ?
|
||||
await saveNewQuiz(userDB, 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))
|
||||
{
|
||||
getOuput+="<br><br>"+wantToSaveResponses;
|
||||
propose2Save.style.display="block";
|
||||
}
|
||||
}
|
||||
addElement(divResponse, "p", getOuput, "", ["info"]);
|
||||
// On redirige vers le résultat :
|
||||
const here=window.location; // window.location à ajouter pour ne pas quitter la page en mode "preview".
|
||||
if(window.location.hash !== "")
|
||||
{
|
||||
window.location.hash=""; // ! le "#" reste
|
||||
window.location.assign(here+"response");
|
||||
}
|
||||
else
|
||||
window.location.assign(here+"#response");
|
||||
// + Affichage des textes d'explication pour chaque question
|
||||
const explanations=document.querySelectorAll(".help");
|
||||
for(let i in explanations)
|
||||
{
|
||||
if(explanations[i].style != undefined) // sinon, la console affiche une erreur "TypeError: explanations[i].style is undefined", bien que tout fonctionne (?)
|
||||
explanations[i].style.display="block";
|
||||
}
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
addElement(divResponse, "p", serverError, "", ["error"]);
|
||||
console.error(e);
|
||||
}
|
||||
})
|
||||
|
||||
// L'utilisateur demande à sauvegarder son résultat :
|
||||
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...
|
||||
{
|
||||
const saveResponses=await saveResult(userDB, 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);
|
||||
// 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 !== "")
|
||||
{
|
||||
window.location.hash="";// ! le "#" reste
|
||||
window.location.assign(here+"explanations");
|
||||
}
|
||||
else
|
||||
window.location.assign(here+"#explanations");
|
||||
}
|
||||
propose2Save.style.display="none";
|
||||
}
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
addElement(divResponse, "p", serverError, "", ["error"]);
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
});
|
@ -1,46 +1,350 @@
|
||||
const configFrontEnd = require("../config/general");
|
||||
// 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";
|
||||
|
||||
import { saveLocaly } from "./clientstorage.js";
|
||||
import { isEmpty, replaceAll } from "../../../tools/main";
|
||||
// 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");
|
||||
|
||||
const txt = require("../../../lang/"+configFrontEnd.lang+"/answer");
|
||||
|
||||
// Enregistrement côté client du dernier résultat à un quiz en attendant d'être connecté
|
||||
export const saveAnswer = (answer) =>
|
||||
// Vérification des réponses de l'utilisateur au quiz
|
||||
export const checkUserAnswers = (myForm) =>
|
||||
{
|
||||
if(!isEmpty(answer.duration) && !isEmpty(answer.nbCorrectAnswers) && !isEmpty(answer.nbQuestions) && (!isEmpty(answer.QuestionnaireId) || !isEmpty(answer.GroupId)))
|
||||
// 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)
|
||||
{
|
||||
saveLocaly("lastAnswer", answer);
|
||||
return true;
|
||||
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;
|
||||
}
|
||||
|
||||
// Retourne un texte suivant le nombre de bonnes réponses
|
||||
export const checkAnswerOuput = (answer) =>
|
||||
// Enregistrement temporaire du dernier résultat à un quiz en attendant de savoir si l'utilisateur souhaite une sauvegarde durable :
|
||||
export const saveResultTemp = (result) =>
|
||||
{
|
||||
if(!isEmpty(answer.duration) && !isEmpty(answer.nbCorrectAnswers) && !isEmpty(answer.nbQuestions))
|
||||
if(checkIfResultIsComplete(result))
|
||||
{
|
||||
const ratio=answer.nbCorrectAnswers/answer.nbQuestions;
|
||||
const mapObj=
|
||||
{
|
||||
DURATION: answer.duration,
|
||||
NBCORRECTANSWERS: answer.nbCorrectAnswers,
|
||||
NBQUESTIONS: answer.nbQuestions
|
||||
}
|
||||
let output="";
|
||||
if(ratio < 0.4)
|
||||
output=replaceAll(txt.checkResponsesOuputFail, mapObj);
|
||||
else if(ratio < 0.8)
|
||||
output=replaceAll(txt.checkResponsesOuputMedium, mapObj);
|
||||
else
|
||||
output=replaceAll(txt.checkResponsesOuputSuccess, mapObj);
|
||||
if(output)
|
||||
return output;
|
||||
else
|
||||
return "";
|
||||
saveLocaly("lastResult", result); // écrasera l'éventuel résultat précédent.
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return "";
|
||||
{
|
||||
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+="<li>"+replaceAll(previousResultsLine, mapLineContent)+"</li>";
|
||||
}
|
||||
mapLineContent=
|
||||
{
|
||||
AVGDURATION: Math.round(totDuration/nbPrevious),
|
||||
AVGCORRECTANSWERS: Math.round(totNbCorrectAnswers/totNbQuestions*100)
|
||||
};
|
||||
previousResultsContent="<h5>"+replaceAll(previousResultsStats, mapLineContent)+"</h5>"+previousResultsContent;
|
||||
addElement(explanationsContent, "ul", previousResultsContent);
|
||||
}
|
||||
else
|
||||
addElement(explanationsContent, "ul", noPreviousResults);
|
||||
/// Revoir : ajouter un lien vers la page listant les quizs auxquels l'utilisateur a répondu
|
||||
/// addElement(explanationsContent, "p", "<a href=\"/"+configTemplate.userHomePage+"\" class=\"button cardboard\">"+configTemplate.userHomePageTxt+"</a>", "", ["btn"], "", false);
|
||||
}
|
||||
}
|
||||
|
||||
// Recherche de tous les résultats déjà enregistrés
|
||||
export const checkAllPreviousResults = (db) =>
|
||||
{
|
||||
return new Promise( (resolve, reject) =>
|
||||
{
|
||||
const resultsStore=getStore(db, "userResults", "readonly");
|
||||
const getResults=resultsStore.getAll();
|
||||
getResults.onsuccess = (e) =>
|
||||
{
|
||||
resolve(e.target.result);
|
||||
};
|
||||
getResults.onerror = (e) =>
|
||||
{
|
||||
console.error(e);
|
||||
reject(e);
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
// Tentative d'enregistrement d'un résultat :
|
||||
export const saveResult = (db, result) =>
|
||||
{
|
||||
return new Promise( (resolve, reject) =>
|
||||
{
|
||||
if(checkIfResultIsComplete(result))
|
||||
{
|
||||
const resultsStore=getStore(db, "userResults", "readwrite");
|
||||
let req;
|
||||
try
|
||||
{
|
||||
result.date=new Date();
|
||||
req=resultsStore.add(result);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.error(e);
|
||||
reject(e);
|
||||
}
|
||||
req.onsuccess= (e) =>
|
||||
{
|
||||
resolve(true);
|
||||
};
|
||||
req.onerror= (e) =>
|
||||
{
|
||||
console.error(e);
|
||||
reject(e);
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
console.error(localDBNeedDatas);
|
||||
reject(localDBNeedDatas);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Fonction retournant tous les quizs enregistrés pour cette personne
|
||||
export const getAllQuizs = (db) =>
|
||||
{
|
||||
return new Promise( (resolve, reject) =>
|
||||
{
|
||||
const quizsStore=getStore(db, "userQuizs", "readonly");
|
||||
const getquizs=quizsStore.getAll();
|
||||
getquizs.onsuccess = (e) =>
|
||||
{
|
||||
resolve(e.target.result);
|
||||
};
|
||||
getquizs.onerror= (e) =>
|
||||
{
|
||||
console.error(e);
|
||||
reject(e);
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
// Ajout le quiz à la base de donnée de l'utilisateur, s'il n'y existe pas déjà
|
||||
export const saveNewQuiz = async (userDB, quizInfos) =>
|
||||
{
|
||||
const getUserQuizs=await getAllQuizs(userDB);
|
||||
const checkQuizExist=getUserQuizs.find(quiz => quiz.url == quizInfos.url);
|
||||
if(checkQuizExist === undefined)
|
||||
await saveQuiz(userDB, quizInfos);
|
||||
}
|
||||
|
||||
// Enregistre le quiz parmi ceux auxquels l'internaute a répondu, s'il n'y est pas déjà.
|
||||
export const saveQuiz = (db, quiz) =>
|
||||
{
|
||||
return new Promise( (resolve, reject) =>
|
||||
{
|
||||
if(!isEmpty(quiz.url) && !isEmpty(quiz.title) && (!isEmpty(quiz.QuestionnaireId) || !isEmpty(quiz.GroupId)))
|
||||
{
|
||||
const quizsStore=getStore(db, "userQuizs", "readwrite");
|
||||
let req;
|
||||
try
|
||||
{
|
||||
req=quizsStore.add(quiz);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.error(e);
|
||||
reject(e);
|
||||
}
|
||||
req.onsuccess= (e) =>
|
||||
{
|
||||
resolve(true);
|
||||
};
|
||||
req.onerror= (e) =>
|
||||
{
|
||||
console.error(e);
|
||||
reject(e);
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
console.error(localDBNeedDatas);
|
||||
reject(localDBNeedDatas);
|
||||
}
|
||||
})
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
// FONCTIONS UTILES AU STOCKAGE LOCAL (SESSION, COOKIES, INDEXDB, ETC.)
|
||||
// Revenir pour gérer le cas où local.storage n'est pas connu pour utiliser cookie
|
||||
|
||||
// Éviter sessionStorage dont le contenu n'est pas gardé d'un onglet à l'autre : https://developer.mozilla.org/fr/docs/Web/API/Window/sessionStorage
|
||||
|
||||
export const saveLocaly = (name, data) =>
|
||||
{
|
||||
localStorage.setItem(name, JSON.stringify(data));
|
||||
@ -17,4 +17,18 @@ export const getLocaly = (name, json=false) =>
|
||||
export const removeLocaly = (name) =>
|
||||
{
|
||||
localStorage.removeItem(name);
|
||||
}
|
||||
}
|
||||
|
||||
export const saveIsReady = () =>
|
||||
{
|
||||
if (!window.indexedDB)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
export const getStore = (db, store_name, mode) =>
|
||||
{
|
||||
const tx=db.transaction(store_name, mode);
|
||||
return tx.objectStore(store_name);
|
||||
}
|
@ -53,6 +53,9 @@ export const checkAnswerDatas = (datas) =>
|
||||
return datas;
|
||||
}
|
||||
|
||||
|
||||
/// La suite est toujours utile pour l'admin.
|
||||
|
||||
// Cette fonction teste la connexion de l'utilisateur d'une page
|
||||
// On peut fournis une liste de statuts acceptés (si vide = tous), ainsi qu'une url de redirection si non connecté, un message d'erreur à afficher sur la page de destination et l'url sur laquelle revenir une fois connecté
|
||||
export const checkSession = async (status=[], urlRedirection, message, urlWanted) =>
|
||||
|
@ -9,7 +9,6 @@ module.exports =
|
||||
connection: "./src/connection.js",
|
||||
deconnection: "./src/deconnection.js",
|
||||
deleteValidation: "./src/deleteValidation.js",
|
||||
group: "./src/group.js",
|
||||
groupElement: "./src/groupElement.js",
|
||||
homeManager: "./src/homeManager.js",
|
||||
homeUser: "./src/homeUser.js",
|
||||
@ -21,7 +20,7 @@ module.exports =
|
||||
newLoginValidation: "./src/newLoginValidation.js",
|
||||
paymentPage: "./src/paymentPage.js",
|
||||
polyfill: "babel-polyfill",
|
||||
questionnaire: "./src/questionnaire.js",
|
||||
quiz: "./src/quiz.js",
|
||||
subscribe: "./src/subscribe.js",
|
||||
subscribeValidation: "./src/subscribeValidation.js",
|
||||
unsubscribe: "./src/unsubscribe.js"
|
||||
|
@ -1,8 +1,8 @@
|
||||
module.exports =
|
||||
{
|
||||
checkResponsesOuputFail : "Vous avez répondu en DURATION secondes et avez <u><b>NBCORRECTANSWERS bonne(s) réponse(s) sur NBQUESTIONS questions</b></u>. C'est certain, vous ferez mieux la prochaine fois !",
|
||||
checkResponsesOuputMedium : "Vous avez répondu en DURATION secondes et avez <u><b>NBCORRECTANSWERS bonne(s) réponse(s) sur NBQUESTIONS questions</b></u>. C'est pas mal du tout !",
|
||||
checkResponsesOuputSuccess : "Vous avez répondu en DURATION secondes et avez <u><b>NBCORRECTANSWERS bonne(s) réponse(s) sur NBQUESTIONS questions</b></u>. Bravo ! Rien ne vous échappe !",
|
||||
localDBGetPreviousResultsFail: "Bug de récupération des résultats précédents.",
|
||||
localDBNeedDatas: "Il manque des données nécessaires à l'enregistrement.",
|
||||
localDBNeedQuizId: "Aucun identifiant n'a été fourni pour le quiz.",
|
||||
needIntegerNumberCorrectResponses : "Le nombre de réponses correctes doit être un nombre entier.",
|
||||
needIntegerNumberSecondesResponse : "La durée de la réponse doit être un nombre entier de secondes.",
|
||||
needIntegerNumberUserResponses : "Le nombre de questions auxquelles l'utilisateur a répondu doit être un nombre entier.",
|
||||
@ -13,12 +13,16 @@ module.exports =
|
||||
needMaxNumberCorrectResponses : "Le nombre de réponses correctes ne peut être supérieur au nombre de questions.",
|
||||
needMinNumberCorrectResponses : "Le nombre de réponses correctes ne peut être négatif.",
|
||||
needMinNumberSecondesResponse : "La durée de la réponse ne peut être négative.",
|
||||
noPreviousAnswer: "On dirait que c'est la première fois que vous répondez à ce quiz. Bonne lecture !",
|
||||
previousAnswersLine: "Le DATEANSWER, vous avez répondu correctement à NBCORRECTANSWERS questions sur NBQUESTIONS en AVGDURATION secondes.",
|
||||
previousAnswersStats: "En moyenne, vous avez répondu à ce quiz en AVGDURATION secondes, en ayant <b>AVGCORRECTANSWERS % de bonnes réponses</b>.",
|
||||
previousAnswersTitle: "Bonjour #NOM, voici vos précédents résultats à ce quiz",
|
||||
noPreviousResults: "On dirait que c'est la première fois que vous répondez à ce quiz. Bonne lecture !",
|
||||
previousResultsLine: "Le DATEANSWER, vous avez répondu correctement à NBCORRECTANSWERS questions sur NBQUESTIONS en AVGDURATION secondes.",
|
||||
previousResultsStats: "En moyenne, vous avez répondu à ce quiz en AVGDURATION secondes, en ayant <b>AVGCORRECTANSWERS % de bonnes réponses</b>.",
|
||||
previousResultsTitle: "Voici vos précédents résultats à ce quiz",
|
||||
responseSavedError : "Cependant une erreur a été rencontrée durant l'enregistrement de votre résultat. <a href='/#URL'>Accèder à tous vos quizs</a>.",
|
||||
responseSavedMessage : "Votre résultat a été enregistré. <a href='/#URL'>Accèder à tous vos quizs</a>.",
|
||||
statsUser: "Vous avez enregistré <b>NBANSWERS réponseS1 à NBQUESTIONNAIRES quizS2 différentS3</b> sur les NBTOTQUESTIONNAIRES proposéS4 par le site.<br>En moyenne, vous avez mis AVGDURATION secondes à répondre et avez <b>correctement répondu à AVGCORRECTANSWERS % des questions</b>.",
|
||||
wantToSaveResponses: "Si vous le souhaitez, vous pouvez <u><b>sauvegarder votre résultat</b></u> en créant votre compte ci-dessous. Cela vous permettra aussi de <u><b>recevoir régulièrement de nouvelles \"graines de culture\"</b></u> directement sur votre e-mail.",
|
||||
userAnswersFail : "Vous avez répondu en DURATION secondes et avez <u><b>NBCORRECTANSWERS bonne(s) réponse(s) sur NBQUESTIONS questions</b></u>. C'est certain, vous ferez mieux la prochaine fois !",
|
||||
userAnswersMedium : "Vous avez répondu en DURATION secondes et avez <u><b>NBCORRECTANSWERS bonne(s) réponse(s) sur NBQUESTIONS questions</b></u>. C'est pas mal du tout !",
|
||||
userAnswersSuccess : "Vous avez répondu en DURATION secondes et avez <u><b>NBCORRECTANSWERS bonne(s) réponse(s) sur NBQUESTIONS questions</b></u>. Bravo ! Rien ne vous échappe !",
|
||||
wantToSaveResponses: "Si vous le souhaitez, vous pouvez <b>sauvegarder vos résultats</b> (il n'est pas nécessaire de créer un compte) :",
|
||||
wantToSeaPreviousResults: "<a href='URL'>Cliquez ici</a> pour tous vos résultats et vos statistiques de réussite à ce quiz."
|
||||
};
|
@ -16,6 +16,7 @@ module.exports =
|
||||
failAuthHeader : "Absence de header Authorization.",
|
||||
failAuthId : "Identifiant non valide : ",
|
||||
failAuthToken : "Token invalide ou utilisateur non trouvé.",
|
||||
localDBConnexionFail : "La connexion à la base de donnée locale est impossible.",
|
||||
neededParams : "Des paramètres nécessaires manquants sont manquants.",
|
||||
nextPage : "Page suivante",
|
||||
notAllowed : "Vous n'avez pas les droits nécessaires pour cette action.",
|
||||
|
@ -2,6 +2,7 @@ module.exports =
|
||||
{
|
||||
btnProposeConnection: "Je me connecte.",// déplacé dans general.js
|
||||
btnProposeSubscribe: "Je crée mon compte.",//idem
|
||||
btnProposeSave: "Enregistrer mes réponses.",//idem
|
||||
btnSendResponse: "Testez vos réponses.",
|
||||
btnShareQuizTxt: "Partager via ",
|
||||
btnShareQuizMailBody: "Bonjour,%0A%0AVoici%20un%20lien%20internet%20qui%20devrait%20t'intéresser :%0A",
|
||||
|
@ -1,7 +1,7 @@
|
||||
extends layout.pug
|
||||
block append scripts
|
||||
script(src="/JS/polyfill.app.js" defer)
|
||||
script(src="/JS/group.app.js" defer)
|
||||
script(src="/JS/quiz.app.js" defer)
|
||||
block content
|
||||
-
|
||||
if(group.Questionnaires.length !==0)
|
||||
@ -41,14 +41,12 @@ block content
|
||||
noscript
|
||||
div
|
||||
strong #{configTpl.noJSNotification}
|
||||
form(id="group" method="POST")
|
||||
form(id="quiz" method="POST" data-title=group.Group.title)
|
||||
h2 #{group.Group.title}
|
||||
div#response
|
||||
// div(class="subscribeBtns")
|
||||
// p
|
||||
// a(class="button cardboard" href=configTpl.subscribePage) #{txtGeneral.btnProposeSubscribe}
|
||||
// p
|
||||
// a(class="button cardboard" href=configTpl.connectionPage) #{txtGeneral.btnProposeConnection}
|
||||
div(id="propose2Save" class="needJS")
|
||||
span(class="input_wrapper")
|
||||
input(class="button cardboard" id="want2Save" type="button" value=txtQuestionnaire.btnProposeSave)
|
||||
for questionnaire in group.Questionnaires
|
||||
for question in questionnaire.Questions
|
||||
p(id="question_"+question.Question.id) #{question.Question.text}
|
||||
@ -69,6 +67,7 @@ block content
|
||||
em #{response.text}
|
||||
input(type="hidden" name="isCorrect_response_"+response.id id="isCorrect_response_"+response.id value=""+response.isCorrect)
|
||||
input(type="hidden" name="question_id_response_"+response.id id="question_id_response_"+response.id value=question.Question.id)
|
||||
input(name="questionnaireId" id="questionnaireId" value="0" type="hidden")
|
||||
input(name="groupId" id="groupId" value=group.Group.id type="hidden")
|
||||
p
|
||||
span(class="input_wrapper")
|
||||
|
@ -1,7 +1,7 @@
|
||||
extends layout.pug
|
||||
block append scripts
|
||||
script(src="/JS/polyfill.app.js" defer)
|
||||
script(src="/JS/questionnaire.app.js" defer)
|
||||
script(src="/JS/quiz.app.js" defer)
|
||||
block content
|
||||
div(id="tags" class="cardboard")
|
||||
ul
|
||||
@ -52,9 +52,12 @@ block content
|
||||
noscript
|
||||
div
|
||||
strong #{configTpl.noJSNotification}
|
||||
form(id="questionnaire" method="POST" class="needJS")
|
||||
form(id="quiz" method="POST" class="needJS" data-title=questionnaire.Questionnaire.title)
|
||||
h2 #{questionnaire.Questionnaire.title}
|
||||
div#response
|
||||
div(id="propose2Save" class="needJS")
|
||||
span(class="input_wrapper")
|
||||
input(class="button cardboard" id="want2Save" type="button" value=txtQuestionnaire.btnProposeSave)
|
||||
for question in questionnaire.Questions
|
||||
p(id="question_"+question.Question.id) #{question.Question.text}
|
||||
if(question.Question.explanation)
|
||||
@ -73,9 +76,10 @@ block content
|
||||
input(type="hidden" name="isCorrect_response_"+reponse.id id="isCorrect_response_"+reponse.id value=""+reponse.isCorrect)
|
||||
input(type="hidden" name="question_id_response_"+reponse.id id="question_id_response_"+reponse.id value=question.Question.id)
|
||||
input(name="questionnaireId" id="questionnaireId" value=questionnaire.Questionnaire.id type="hidden")
|
||||
input(name="groupId" id="groupId" value="0" type="hidden")
|
||||
p
|
||||
span(class="input_wrapper")
|
||||
input(id="checkResponses" type="submit" value=txtQuestionnaire.btnSendResponse class="cardboard" title=txtQuestionnaire.btnSendResponse)
|
||||
input(id="checkResponses" type="submit" value=txtQuestionnaire.btnSendResponse class="cardboard")
|
||||
div#licence
|
||||
p !{configTpl.licenceTxt}
|
||||
div#zerozozio
|
||||
|
Loading…
Reference in New Issue
Block a user