256 lines
12 KiB
JavaScript
256 lines
12 KiB
JavaScript
// -- 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();
|
|
} |