É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";
|
// Textes :
|
||||||
import { isEmpty, replaceAll } from "../../../tools/main";
|
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");
|
// Vérification des réponses de l'utilisateur au quiz
|
||||||
|
export const checkUserAnswers = (myForm) =>
|
||||||
// Enregistrement côté client du dernier résultat à un quiz en attendant d'être connecté
|
|
||||||
export const saveAnswer = (answer) =>
|
|
||||||
{
|
{
|
||||||
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);
|
if(item.startsWith("isCorrect_response_")) // = Nouvelle réponse possible.
|
||||||
return true;
|
{
|
||||||
|
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
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retourne un texte suivant le nombre de bonnes réponses
|
// Enregistrement temporaire du dernier résultat à un quiz en attendant de savoir si l'utilisateur souhaite une sauvegarde durable :
|
||||||
export const checkAnswerOuput = (answer) =>
|
export const saveResultTemp = (result) =>
|
||||||
{
|
{
|
||||||
if(!isEmpty(answer.duration) && !isEmpty(answer.nbCorrectAnswers) && !isEmpty(answer.nbQuestions))
|
if(checkIfResultIsComplete(result))
|
||||||
{
|
{
|
||||||
const ratio=answer.nbCorrectAnswers/answer.nbQuestions;
|
saveLocaly("lastResult", result); // écrasera l'éventuel résultat précédent.
|
||||||
const mapObj=
|
return true;
|
||||||
{
|
|
||||||
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 "";
|
|
||||||
}
|
}
|
||||||
else
|
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,5 +1,5 @@
|
|||||||
// FONCTIONS UTILES AU STOCKAGE LOCAL (SESSION, COOKIES, INDEXDB, ETC.)
|
// 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) =>
|
export const saveLocaly = (name, data) =>
|
||||||
{
|
{
|
||||||
@ -18,3 +18,17 @@ export const removeLocaly = (name) =>
|
|||||||
{
|
{
|
||||||
localStorage.removeItem(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;
|
return datas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// La suite est toujours utile pour l'admin.
|
||||||
|
|
||||||
// Cette fonction teste la connexion de l'utilisateur d'une page
|
// 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é
|
// 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) =>
|
export const checkSession = async (status=[], urlRedirection, message, urlWanted) =>
|
||||||
|
@ -9,7 +9,6 @@ module.exports =
|
|||||||
connection: "./src/connection.js",
|
connection: "./src/connection.js",
|
||||||
deconnection: "./src/deconnection.js",
|
deconnection: "./src/deconnection.js",
|
||||||
deleteValidation: "./src/deleteValidation.js",
|
deleteValidation: "./src/deleteValidation.js",
|
||||||
group: "./src/group.js",
|
|
||||||
groupElement: "./src/groupElement.js",
|
groupElement: "./src/groupElement.js",
|
||||||
homeManager: "./src/homeManager.js",
|
homeManager: "./src/homeManager.js",
|
||||||
homeUser: "./src/homeUser.js",
|
homeUser: "./src/homeUser.js",
|
||||||
@ -21,7 +20,7 @@ module.exports =
|
|||||||
newLoginValidation: "./src/newLoginValidation.js",
|
newLoginValidation: "./src/newLoginValidation.js",
|
||||||
paymentPage: "./src/paymentPage.js",
|
paymentPage: "./src/paymentPage.js",
|
||||||
polyfill: "babel-polyfill",
|
polyfill: "babel-polyfill",
|
||||||
questionnaire: "./src/questionnaire.js",
|
quiz: "./src/quiz.js",
|
||||||
subscribe: "./src/subscribe.js",
|
subscribe: "./src/subscribe.js",
|
||||||
subscribeValidation: "./src/subscribeValidation.js",
|
subscribeValidation: "./src/subscribeValidation.js",
|
||||||
unsubscribe: "./src/unsubscribe.js"
|
unsubscribe: "./src/unsubscribe.js"
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
module.exports =
|
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 !",
|
localDBGetPreviousResultsFail: "Bug de récupération des résultats précédents.",
|
||||||
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 !",
|
localDBNeedDatas: "Il manque des données nécessaires à l'enregistrement.",
|
||||||
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 !",
|
localDBNeedQuizId: "Aucun identifiant n'a été fourni pour le quiz.",
|
||||||
needIntegerNumberCorrectResponses : "Le nombre de réponses correctes doit être un nombre entier.",
|
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.",
|
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.",
|
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.",
|
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.",
|
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.",
|
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 !",
|
noPreviousResults: "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.",
|
previousResultsLine: "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>.",
|
previousResultsStats: "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",
|
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>.",
|
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>.",
|
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>.",
|
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.",
|
failAuthHeader : "Absence de header Authorization.",
|
||||||
failAuthId : "Identifiant non valide : ",
|
failAuthId : "Identifiant non valide : ",
|
||||||
failAuthToken : "Token invalide ou utilisateur non trouvé.",
|
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.",
|
neededParams : "Des paramètres nécessaires manquants sont manquants.",
|
||||||
nextPage : "Page suivante",
|
nextPage : "Page suivante",
|
||||||
notAllowed : "Vous n'avez pas les droits nécessaires pour cette action.",
|
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
|
btnProposeConnection: "Je me connecte.",// déplacé dans general.js
|
||||||
btnProposeSubscribe: "Je crée mon compte.",//idem
|
btnProposeSubscribe: "Je crée mon compte.",//idem
|
||||||
|
btnProposeSave: "Enregistrer mes réponses.",//idem
|
||||||
btnSendResponse: "Testez vos réponses.",
|
btnSendResponse: "Testez vos réponses.",
|
||||||
btnShareQuizTxt: "Partager via ",
|
btnShareQuizTxt: "Partager via ",
|
||||||
btnShareQuizMailBody: "Bonjour,%0A%0AVoici%20un%20lien%20internet%20qui%20devrait%20t'intéresser :%0A",
|
btnShareQuizMailBody: "Bonjour,%0A%0AVoici%20un%20lien%20internet%20qui%20devrait%20t'intéresser :%0A",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
extends layout.pug
|
extends layout.pug
|
||||||
block append scripts
|
block append scripts
|
||||||
script(src="/JS/polyfill.app.js" defer)
|
script(src="/JS/polyfill.app.js" defer)
|
||||||
script(src="/JS/group.app.js" defer)
|
script(src="/JS/quiz.app.js" defer)
|
||||||
block content
|
block content
|
||||||
-
|
-
|
||||||
if(group.Questionnaires.length !==0)
|
if(group.Questionnaires.length !==0)
|
||||||
@ -41,14 +41,12 @@ block content
|
|||||||
noscript
|
noscript
|
||||||
div
|
div
|
||||||
strong #{configTpl.noJSNotification}
|
strong #{configTpl.noJSNotification}
|
||||||
form(id="group" method="POST")
|
form(id="quiz" method="POST" data-title=group.Group.title)
|
||||||
h2 #{group.Group.title}
|
h2 #{group.Group.title}
|
||||||
div#response
|
div#response
|
||||||
// div(class="subscribeBtns")
|
div(id="propose2Save" class="needJS")
|
||||||
// p
|
span(class="input_wrapper")
|
||||||
// a(class="button cardboard" href=configTpl.subscribePage) #{txtGeneral.btnProposeSubscribe}
|
input(class="button cardboard" id="want2Save" type="button" value=txtQuestionnaire.btnProposeSave)
|
||||||
// p
|
|
||||||
// a(class="button cardboard" href=configTpl.connectionPage) #{txtGeneral.btnProposeConnection}
|
|
||||||
for questionnaire in group.Questionnaires
|
for questionnaire in group.Questionnaires
|
||||||
for question in questionnaire.Questions
|
for question in questionnaire.Questions
|
||||||
p(id="question_"+question.Question.id) #{question.Question.text}
|
p(id="question_"+question.Question.id) #{question.Question.text}
|
||||||
@ -69,6 +67,7 @@ block content
|
|||||||
em #{response.text}
|
em #{response.text}
|
||||||
input(type="hidden" name="isCorrect_response_"+response.id id="isCorrect_response_"+response.id value=""+response.isCorrect)
|
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(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")
|
input(name="groupId" id="groupId" value=group.Group.id type="hidden")
|
||||||
p
|
p
|
||||||
span(class="input_wrapper")
|
span(class="input_wrapper")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
extends layout.pug
|
extends layout.pug
|
||||||
block append scripts
|
block append scripts
|
||||||
script(src="/JS/polyfill.app.js" defer)
|
script(src="/JS/polyfill.app.js" defer)
|
||||||
script(src="/JS/questionnaire.app.js" defer)
|
script(src="/JS/quiz.app.js" defer)
|
||||||
block content
|
block content
|
||||||
div(id="tags" class="cardboard")
|
div(id="tags" class="cardboard")
|
||||||
ul
|
ul
|
||||||
@ -52,9 +52,12 @@ block content
|
|||||||
noscript
|
noscript
|
||||||
div
|
div
|
||||||
strong #{configTpl.noJSNotification}
|
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}
|
h2 #{questionnaire.Questionnaire.title}
|
||||||
div#response
|
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
|
for question in questionnaire.Questions
|
||||||
p(id="question_"+question.Question.id) #{question.Question.text}
|
p(id="question_"+question.Question.id) #{question.Question.text}
|
||||||
if(question.Question.explanation)
|
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="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(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="questionnaireId" id="questionnaireId" value=questionnaire.Questionnaire.id type="hidden")
|
||||||
|
input(name="groupId" id="groupId" value="0" type="hidden")
|
||||||
p
|
p
|
||||||
span(class="input_wrapper")
|
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
|
div#licence
|
||||||
p !{configTpl.licenceTxt}
|
p !{configTpl.licenceTxt}
|
||||||
div#zerozozio
|
div#zerozozio
|
||||||
|
Loading…
x
Reference in New Issue
Block a user