2020-08-07 12:23:59 +02:00
// -- 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 ] ;
2020-08-10 16:27:45 +02:00
import { getPreviousAnswers , questionnaireRoutes , saveAnswersRoute } from "../../config/questionnaires.js" ;
2020-08-07 12:23:59 +02:00
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" ;
2020-08-13 12:12:29 +02:00
import { loadMatomo } from "./tools/matomo.js" ;
2020-08-07 12:23:59 +02:00
import { checkSession , getTimeDifference } from "./tools/users.js" ;
// Dictionnaires :
2020-10-06 16:34:17 +02:00
const { noPreviousAnswer , previousAnswersLine , previousAnswersStats , previousAnswersTitle , responseSavedError , wantToSaveResponses } = require ( "../../lang/" + lang + "/answer" ) ;
const { serverError } = require ( "../../lang/" + lang + "/general" ) ;
2020-08-07 12:23:59 +02:00
// 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
2020-08-10 16:27:45 +02:00
isConnected = await checkSession ( [ "user" ] ) ; // "user" car seuls les utilisateurs de base peuvent enregistrer leurs réponses aux quizs
2020-08-07 12:23:59 +02:00
// 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 ) ;
2020-08-10 16:27:45 +02:00
updateAccountLink ( user . status , configTemplate ) ; // lien vers le compte adapté pour les utilisateurs connectés
2020-08-07 12:23:59 +02:00
checkPreviousResponses ( user ) ;
}
2020-08-13 12:12:29 +02:00
else
loadMatomo ( ) ;
2020-08-07 12:23:59 +02:00
}
catch ( e )
{
console . error ( e ) ;
}
}
initialise ( ) ;
helloDev ( ) ;
2020-08-10 16:27:45 +02:00
// Affichage du questionnaire quand l'utilisateur clique sur le bouton ou si l'id du formulaire est passée par l'url.
2020-08-07 12:23:59 +02:00
// 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" ;
2020-08-10 16:27:45 +02:00
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" ) ;
2020-08-07 12:23:59 +02:00
}
let chronoBegin = 0 ;
btnShow . addEventListener ( "click" , function ( e )
{
try
{
e . preventDefault ( ) ;
showQuestionnaire ( ) ;
}
catch ( e )
{
2020-10-06 16:34:17 +02:00
addElement ( divResponse , "p" , serverError , "" , [ "error" ] ) ;
2020-08-07 12:23:59 +02:00
console . error ( e ) ;
}
} ) ;
// Lien passé par mail pour voir directement le quiz
2020-08-10 16:27:45 +02:00
if ( location . hash != "" && location . hash === "#questionnaire" )
2020-08-07 12:23:59 +02:00
showQuestionnaire ( ) ;
// Traitement de l'envoi de la réponse de l'utilisateur :
let answer = { } ;
myForm . addEventListener ( "submit" , function ( e )
{
try
{
e . preventDefault ( ) ;
2020-08-10 16:27:45 +02:00
btnSubmit . style . display = "none" ; // seulement un envoi à la fois, SVP :)
2020-08-07 12:23:59 +02:00
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 ;
2020-08-10 16:27:45 +02:00
// 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.
2020-08-07 12:23:59 +02:00
let idChoice , idQuestion = "" , goodResponse = false ;
for ( let item in userResponses )
{
2020-08-10 16:27:45 +02:00
if ( item . startsWith ( "isCorrect_response_" ) ) // = Nouvelle réponse possible.
2020-08-07 12:23:59 +02:00
{
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 ++ ;
2020-08-10 16:27:45 +02:00
if ( goodResponse ) // résultat de la question précédente
2020-08-07 12:23:59 +02:00
answer . nbCorrectAnswers ++ ;
goodResponse = true ; // réponse bonne jusqu'à la première erreur...
}
if ( userResponses [ item ] == "true" )
{
document . getElementById ( "response_" + idChoice ) . parentNode . classList . add ( "isCorrect" ) ;
2020-08-10 16:27:45 +02:00
if ( userResponses [ "response_" + idChoice ] === undefined ) // une bonne réponse n'a pas été sélectionnée
2020-08-07 12:23:59 +02:00
goodResponse = false ;
}
else
{
2020-08-10 16:27:45 +02:00
if ( userResponses [ "response_" + idChoice ] === "on" ) // réponse cochée ne faisant pas partie des bonnes
2020-08-07 12:23:59 +02:00
{
goodResponse = false ;
document . getElementById ( "response_" + idChoice ) . parentNode . classList . add ( "isNotCorrect" ) ;
}
}
}
}
2020-08-10 16:27:45 +02:00
// si j'ai bien répondu à la dernière question, il faut le compter ici, car je suis sorti de la boucle :
2020-08-07 12:23:59 +02:00
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 )
{
2020-08-10 16:27:45 +02:00
// Si l'utilisateur est connecté, on enregistre son résultat sur le serveur.
2020-08-07 12:23:59 +02:00
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
2020-10-06 16:34:17 +02:00
getOuput += "<br>" + responseSavedError . replace ( "#URL" , configTemplate . userHomePage ) ;
2020-09-08 17:03:11 +02:00
// 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" ) ;
2020-08-07 12:23:59 +02:00
}
}
xhrSaveAnswer . setRequestHeader ( "Authorization" , "Bearer " + user . token ) ;
xhrSaveAnswer . setRequestHeader ( "Content-Type" , "application/json" ) ;
2020-08-10 16:27:45 +02:00
answer . timeDifference = getTimeDifference ( ) ; // on en profite pour mettre les pendules à l'heure.
2020-08-07 12:23:59 +02:00
xhrSaveAnswer . send ( JSON . stringify ( answer ) ) ;
}
else
2020-08-10 16:27:45 +02:00
{ // 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.
2020-08-07 12:23:59 +02:00
if ( saveAnswer ( answer ) )
{
2020-10-06 16:34:17 +02:00
getOuput += "<br><br>" + wantToSaveResponses ;
2020-08-07 12:23:59 +02:00
addElement ( divResponse , "p" , getOuput , "" , [ "info" ] ) ;
2020-08-10 16:27:45 +02:00
document . querySelector ( ".subscribeBtns" ) . style . display = "block" ;
2020-08-07 12:23:59 +02:00
}
2020-08-10 16:27:45 +02:00
else // inutile de proposer de créer un compte si le stockage local ne fonctionne pas
2020-08-07 12:23:59 +02:00
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"...
2020-09-08 17:03:11 +02:00
window . location . assign ( here + "response" ) ;
2020-08-07 12:23:59 +02:00
}
2020-08-10 16:27:45 +02:00
// + affichage des textes d'explications pour chaque question
2020-08-07 12:23:59 +02:00
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 )
{
2020-10-06 16:34:17 +02:00
addElement ( divResponse , "p" , serverError , "" , [ "error" ] ) ;
2020-08-07 12:23:59 +02:00
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 = "" ;
2020-10-06 16:34:17 +02:00
addElement ( explanationsTitle , "span" , previousAnswersTitle . replace ( "#NOM" , user . name ) ) ;
2020-08-07 12:23:59 +02:00
if ( nbResponses !== 0 )
{
let totNbQuestions = 0 , totNbCorrectAnswers = 0 , totDuration = 0 , mapLineContent ;
for ( let i in response )
{
2020-08-10 16:27:45 +02:00
totNbQuestions += response [ i ] . nbQuestions ; // ! on ne peut se baser sur la version actuelle du quiz, car le nombre de questions a pu évoluer.
2020-08-07 12:23:59 +02:00
totNbCorrectAnswers += response [ i ] . nbCorrectAnswers ;
totDuration += response [ i ] . duration ;
mapLineContent =
{
2020-08-10 16:27:45 +02:00
DATEANSWER : dateFormat ( response [ i ] . createdAt , lang ) ,
2020-08-07 12:23:59 +02:00
NBCORRECTANSWERS : response [ i ] . nbCorrectAnswers ,
2020-09-08 17:03:11 +02:00
NBQUESTIONS : response [ i ] . nbQuestions ,
2020-08-07 12:23:59 +02:00
AVGDURATION : response [ i ] . duration
} ;
2020-10-06 16:34:17 +02:00
previousAnswersContent += "<li>" + replaceAll ( previousAnswersLine , mapLineContent ) + "</li>" ;
2020-08-07 12:23:59 +02:00
}
mapLineContent =
{
AVGDURATION : Math . round ( totDuration / nbResponses ) ,
AVGCORRECTANSWERS : Math . round ( totNbCorrectAnswers / totNbQuestions * 100 )
} ;
2020-10-06 16:34:17 +02:00
previousAnswersContent = "<h5>" + replaceAll ( previousAnswersStats , mapLineContent ) + "</h5>" + previousAnswersContent ;
2020-08-07 12:23:59 +02:00
addElement ( explanationsContent , "ul" , previousAnswersContent ) ;
}
else
2020-10-06 16:34:17 +02:00
addElement ( explanationsContent , "ul" , noPreviousAnswer ) ;
2020-09-08 17:03:11 +02:00
// 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 ) ;
2020-08-07 12:23:59 +02:00
}
}
}
xhrPreviousRes . setRequestHeader ( "Authorization" , "Bearer " + user . token ) ;
xhrPreviousRes . send ( ) ;
}