2020-10-22 17:47:05 +02:00
// -- GESTION DU FORMULAIRE PERMETTANT D'AFFICHER ET RÉPONDRE À UN GROUPE DE QUIZS
2020-10-12 17:51:35 +02:00
2020-10-20 11:01:52 +02:00
/// 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.
2020-10-12 17:51:35 +02:00
// Fichier de configuration tirés du backend :
import { apiUrl , availableLangs , theme } from "../../config/instance.js" ;
const lang = availableLangs [ 0 ] ;
2020-10-22 17:47:05 +02:00
import { getPreviousAnswers , groupRoutes , saveAnswersRoute } from "../../config/questionnaires.js" ;
2020-10-20 11:01:52 +02:00
const configTemplate = require ( "../../views/" + theme + "/config/" + lang + ".js" ) ;
2020-10-12 17:51:35 +02:00
2020-10-20 11:01:52 +02:00
import { checkAnswerOuput , saveAnswer } from "./tools/answers.js" ;
2020-10-12 17:51:35 +02:00
import { addElement } from "./tools/dom.js" ;
2020-10-20 11:01:52 +02:00
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-10-12 17:51:35 +02:00
import { loadMatomo } from "./tools/matomo.js" ;
2020-10-20 11:01:52 +02:00
import { checkSession , getTimeDifference } from "./tools/users.js" ;
2020-10-12 17:51:35 +02:00
// Dictionnaires :
2020-10-20 11:01:52 +02:00
const { noPreviousAnswer , previousAnswersLine , previousAnswersStats , previousAnswersTitle , responseSavedError , wantToSaveResponses } = require ( "../../lang/" + lang + "/answer" ) ;
const { serverError } = require ( "../../lang/" + lang + "/general" ) ;
2020-10-12 17:51:35 +02:00
// Principaux éléments du DOM manipulés :
2020-10-20 11:01:52 +02:00
const btnSubmit = document . getElementById ( "checkResponses" ) ;
2020-10-22 17:47:05 +02:00
const divResponse = document . getElementById ( "response" ) ;
2020-10-20 11:01:52 +02:00
const explanationsTitle = document . getElementById ( "explanationsTitle" ) ;
const explanationsContent = document . getElementById ( "explanationsContent" ) ;
2020-10-22 17:47:05 +02:00
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" ;
const here = window . location ; // window.location à ajouter pour ne pas quitter la page en mode "preview".
}
2020-10-12 17:51:35 +02:00
2020-10-20 11:01:52 +02:00
let isConnected , user ;
2020-10-12 17:51:35 +02:00
const initialise = async ( ) =>
{
try
{
2020-10-22 17:47:05 +02:00
// Si JS activé, on affiche le bouton de soumission du formulaire :
beginAnswer ( ) ;
2020-10-20 11:01:52 +02:00
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
2020-10-12 17:51:35 +02:00
if ( isConnected )
{
2020-10-20 11:01:52 +02:00
user = getLocaly ( "user" , true ) ;
updateAccountLink ( user . status , configTemplate ) ; // lien vers le compte adapté pour les utilisateurs connectés
checkPreviousResponses ( user ) ;
2020-10-12 17:51:35 +02:00
}
else
loadMatomo ( ) ;
}
catch ( e )
{
console . error ( e ) ;
}
}
initialise ( ) ;
2020-10-20 11:01:52 +02:00
helloDev ( ) ;
2020-10-12 17:51:35 +02:00
2020-10-20 11:01:52 +02:00
// Traitement de l'envoi de la réponse de l'utilisateur :
let answer = { } ;
2020-10-12 17:51:35 +02:00
myForm . addEventListener ( "submit" , function ( e )
{
try
{
2020-10-20 11:01:52 +02:00
e . preventDefault ( ) ;
btnSubmit . style . display = "none" ; // seulement un envoi à la fois, SVP :)
2020-10-22 17:47:05 +02:00
divResponse . innerHTML = "" ; // supprime les éventuels messages déjà affichés.
2020-10-20 11:01:52 +02:00
const userResponses = getDatasFromInputs ( myForm ) ;
answer . duration = Math . round ( ( Date . now ( ) - chronoBegin ) / 1000 ) ;
answer . nbQuestions = 0 ;
answer . nbCorrectAnswers = 0 ;
2020-10-22 17:47:05 +02:00
answer . GroupId = document . getElementById ( "groupId" ) . value ;
2020-10-20 11:01:52 +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.
let idChoice , idQuestion = "" , goodResponse = false ;
for ( let item in userResponses )
2020-10-12 17:51:35 +02:00
{
2020-10-20 11:01:52 +02:00
if ( item . startsWith ( "isCorrect_response_" ) ) // = Nouvelle réponse possible.
2020-10-12 17:51:35 +02:00
{
2020-10-20 11:01:52 +02:00
idChoice = item . substring ( item . lastIndexOf ( "_" ) + 1 ) ;
2020-10-22 17:47:05 +02:00
if ( userResponses [ "question_id_response_" + idChoice ] != idQuestion ) // = on commence à traiter une nouvelle question.
2020-10-12 17:51:35 +02:00
{
2020-10-20 11:01:52 +02:00
idQuestion = userResponses [ "question_id_response_" + idChoice ] ;
answer . nbQuestions ++ ;
2020-10-22 17:47:05 +02:00
if ( goodResponse ) // = pas d'erreur à la question précédente
2020-10-20 11:01:52 +02:00
answer . nbCorrectAnswers ++ ;
2020-10-22 17:47:05 +02:00
goodResponse = true ; // La réponse est considérée comme bonne, jusqu'à la première erreur...
2020-10-12 17:51:35 +02:00
}
2020-10-22 17:47:05 +02:00
if ( userResponses [ item ] == "true" )
2020-10-12 17:51:35 +02:00
{
2020-10-20 11:01:52 +02:00
document . getElementById ( "response_" + idChoice ) . parentNode . classList . add ( "isCorrect" ) ;
2020-10-22 17:47:05 +02:00
if ( userResponses [ "response_" + idChoice ] === undefined ) // = une bonne réponse n'a pas été sélectionnée
2020-10-20 11:01:52 +02:00
goodResponse = false ;
2020-10-12 17:51:35 +02:00
}
else
2020-10-20 11:01:52 +02:00
{
2020-10-22 17:47:05 +02:00
if ( userResponses [ "response_" + idChoice ] === "on" )
2020-10-20 11:01:52 +02:00
{
2020-10-22 17:47:05 +02:00
goodResponse = false ; // = une mauvaise réponse a été sélectionnée
2020-10-20 11:01:52 +02:00
document . getElementById ( "response_" + idChoice ) . parentNode . classList . add ( "isNotCorrect" ) ;
}
}
2020-10-12 17:51:35 +02:00
}
}
2020-10-22 17:47:05 +02:00
// Si j'ai bien répondu à la dernière question, il faut le compter ici, car on est sorti de la boucle :
2020-10-20 11:01:52 +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-10-12 17:51:35 +02:00
{
2020-10-22 17:47:05 +02:00
// Si l'utilisateur est connecté, on passe son résultat au serveur pour le sauvegarder.
2020-10-20 11:01:52 +02:00
const xhrSaveAnswer = new XMLHttpRequest ( ) ;
2020-10-22 17:47:05 +02:00
xhrSaveAnswer . open ( "POST" , apiUrl + groupRoutes + saveAnswersRoute ) ;
2020-10-20 11:01:52 +02:00
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 ) ;
2020-10-22 17:47:05 +02:00
// Puis on le redirige vers son résultat :
2020-10-20 11:01:52 +02:00
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" ) ;
2020-10-22 17:47:05 +02:00
answer . timeDifference = getTimeDifference ( ) ; // On en profite pour mettre les pendules à l'heure.
2020-10-20 11:01:52 +02:00
xhrSaveAnswer . send ( JSON . stringify ( answer ) ) ;
}
else
2020-10-22 17:47:05 +02:00
{ // 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.
2020-10-20 11:01:52 +02:00
if ( saveAnswer ( answer ) )
{
2020-10-22 17:47:05 +02:00
getOuput += "</p><p>" + wantToSaveResponses + "</p>" ;
addElement ( divResponse , "p" , getOuput , "" , [ "success" ] ) ;
2020-10-20 11:01:52 +02:00
document . querySelector ( ".subscribeBtns" ) . style . display = "block" ;
}
2020-10-22 17:47:05 +02:00
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 :
2020-10-20 11:01:52 +02:00
window . location . hash = "" ;
const here = window . location ; // window.location à ajouter pour ne pas quitter la page en mode "preview"...
window . location . assign ( here + "response" ) ;
}
2020-10-22 17:47:05 +02:00
// + Affichage des textes d'explications pour chaque question
2020-10-20 11:01:52 +02:00
const explanations = document . querySelectorAll ( ".help" ) ;
for ( let i in explanations )
2020-10-22 17:47:05 +02:00
if ( explanations [ i ] . style !== undefined ) // sinon, la console affiche une erreur "TypeError: explanations[i].style is undefined", bien que tout fonctionne (?)
2020-10-20 11:01:52 +02:00
explanations [ i ] . style . display = "block" ;
2020-10-12 17:51:35 +02:00
}
catch ( e )
{
addElement ( divResponse , "p" , serverError , "" , [ "error" ] ) ;
console . error ( e ) ;
}
2020-10-20 11:01:52 +02:00
} )
2020-10-22 17:47:05 +02:00
// 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.
2020-10-20 11:01:52 +02:00
const checkPreviousResponses = ( user ) =>
{
const xhrPreviousRes = new XMLHttpRequest ( ) ;
2020-10-22 17:47:05 +02:00
xhrPreviousRes . open ( "GET" , apiUrl + groupRoutes + getPreviousAnswers + user . id + "/" + document . getElementById ( "groupId" ) . value ) ;
2020-10-20 11:01:52 +02:00
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 ( ) ;
}