2022-03-21 17:08:58 +01:00
// Classe gérant le stockage des résultats aux quizs de l'utilisateur.
// Dépendance assumée à IndexedDB...
// 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 { saveIsReady } from "./clientstorage.js" ;
// Textes :
import { availableLangs } from "../../../../config/instance.js" ;
const lang = availableLangs [ 0 ] ;
2022-03-21 18:33:06 +01:00
const { localDBNeedDatas , localDBNeedQuizId , noPreviousResults , noPreviousResultsAtAll , previousResultsLine , previousResultsTitle , previousResultsStats , userAnswersFail , userAnswersMedium , userAnswersSuccess } = require ( "../../../../lang/" + lang + "/answer" ) ;
const { localDBConnexionFail } = require ( "../../../../lang/" + lang + "/general" ) ;
2022-03-21 17:08:58 +01:00
export class userQuizsResults
{
dbName ;
dbVersion ;
# db ;
# dbIsReady ;
# allQuizs ;
# allResults ;
set db ( value )
{
this . # db = value ;
}
set dbIsReady ( value )
{
this . # dbIsReady = value ;
}
set allQuizs ( array )
{
this . # allQuizs = array ;
}
set allResults ( array )
{
this . # allResults = array ;
}
get db ( )
{
return this . # db ;
}
get dbIsReady ( )
{
return this . # dbIsReady ;
}
get allQuizs ( )
{
return this . # allQuizs ;
}
get allResults ( )
{
return this . # allResults ;
}
// Vérification des réponses de l'utilisateur au quiz
static checkUserAnswers ( myForm )
{
// 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 , result = { nbCorrectAnswers : 0 , nbQuestions : 0 } ;
for ( let item in userResponses )
{
if ( item . startsWith ( "isCorrect_response_" ) ) // = Nouvelle réponse possible.
{
idChoice = item . substring ( item . lastIndexOf ( "_" ) + 1 ) ;
// Si on change de question :
if ( userResponses [ "question_id_response_" + idChoice ] != idQuestion ) // on commence à traiter une nouvelle question
{
idQuestion = userResponses [ "question_id_response_" + idChoice ] ;
result . nbQuestions ++ ;
if ( goodResponse ) // Résultat de la question précédente
result . 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 )
result . nbCorrectAnswers ++ ;
return result ;
}
// Retourne un texte suivant le nombre de bonnes réponses
static 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 "" ;
}
// Ne pouvant déclarer constructor() comme async, on passe par une méthode dédiée :
static async initialise ( dbName , dbVersion )
{
const myInstance = new userQuizsResults ( ) ;
myInstance . dbName = dbName ;
myInstance . dbVersion = dbVersion ;
myInstance . dbIsReady = saveIsReady ( ) ;
if ( myInstance . dbIsReady === true )
{
// On essaye ensuite de se connecter à la base de données (et de la créer, si elle est inexistante) :
await myInstance . getOpenDb ( myInstance . dbName , myInstance . dbVersion ) ;
if ( myInstance . db === undefined )
{
console . error ( localDBConnexionFail ) ; // information mais pas d'exception pour éviter blocage, car on peut répondre aux quizs sans sauvegarder les résultats
myInstance . dbIsReady = false ;
}
else
{
// Récupère la liste des quizs auxquels, cet utilisateur a déjà répondu :
await myInstance . getAllQuizs ( ) ;
// + L'ensemble de ses résultats :
await myInstance . getAllResults ( ) ;
}
}
return myInstance ;
}
// Retourne la base de données de sauvegarde des résultats + la créée ou met à jour si besoin
getOpenDb ( )
{
return new Promise ( ( resolve , reject ) =>
{
let req = indexedDB . open ( this . dbName , this . dbVersion ) ;
req . onupgradeneeded = ( e ) =>
{
if ( e . oldVersion < 1 ) // Voir : https://developer.mozilla.org/en-US/docs/Web/API/IDBOpenDBRequest/onupgradeneeded
{
// Stockage des quizs auxquels l'utilisateur a répondu :
let store = e . currentTarget . result . createObjectStore ( "userQuizs" , { keyPath : "id" , autoIncrement : true } ) ;
store . createIndex ( "url" , "url" , { unique : true } ) ;
store . createIndex ( "QuestionnaireId" , "id" , { unique : false } ) ; // = simple quiz, sinon "0" donc doublons possibles
store . createIndex ( "GroupId" , "id" , { unique : false } ) ; // = quiz après lecture d'un ou +sieurs articles, sinon "0"
store . createIndex ( "title" , "title" , { unique : false } ) ;
// 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 : false } ) ;
store . createIndex ( "nbCorrectAnswers" , "nbCorrectAnswers" , { unique : false } ) ;
store . createIndex ( "nbQuestions" , "nbQuestions" , { unique : false } ) ;
store . createIndex ( "date" , "date" , { unique : false } ) ; // bien que doublons peu probables...
}
} ;
req . onsuccess = ( e ) =>
{
this . db = req . result ;
resolve ( this . db ) ;
} ;
req . onerror = ( e ) =>
{
console . error ( e ) ;
resolve ( undefined ) ;
} ;
} )
}
// Fonction retournant tous les quizs enregistrés pour cette personne
async getAllQuizs ( )
{
await this . getOpenDb ( ) ;
return new Promise ( ( resolve , reject ) =>
{
const quizsStore = getStore ( this . db , "userQuizs" , "readonly" ) ;
const getquizs = quizsStore . getAll ( ) ;
getquizs . onsuccess = ( e ) =>
{
2022-03-21 18:33:06 +01:00
this . allQuizs = e . target . result ;
2022-03-21 17:08:58 +01:00
this . db . close ( ) ;
resolve ( e . target . result ) ;
} ;
getquizs . onerror = ( e ) =>
{
this . db . close ( ) ;
reject ( e ) ;
} ;
} )
}
// Retourne tous les résultats déjà enregistrés
async getAllResults ( )
{
await this . getOpenDb ( ) ;
return new Promise ( ( resolve , reject ) =>
{
const resultsStore = getStore ( this . db , "userResults" , "readonly" ) ;
const getResults = resultsStore . getAll ( ) ;
getResults . onsuccess = ( e ) =>
{
this . allResults = e . target . result ;
this . db . close ( ) ;
resolve ( this . allResults ) ;
} ;
getResults . onerror = ( e ) =>
{
this . db . close ( ) ;
reject ( e ) ;
} ;
} )
}
// Retourne les résultats déjà enregistrés pour un quiz donné :
async checkPreviousResultsForId ( QuestionnaireId = 0 , GroupId = 0 )
{
await this . getOpenDb ( ) ;
return new Promise ( ( resolve , reject ) =>
{
const resultsStore = getStore ( this . 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 ( new Error ( 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
{
this . db . close ( ) ;
resolve ( answers ) ;
}
} ;
getResults . onerror = ( e ) =>
{
this . db . close ( ) ;
reject ( e ) ;
} ;
} )
}
// Contrôle que les données fournies pour un résultat sont complètes
checkIfResultIsComplete ( result )
{
if ( ! isEmpty ( result . duration ) && ! isEmpty ( result . nbCorrectAnswers ) && ! isEmpty ( result . nbQuestions ) && ( ! isEmpty ( result . QuestionnaireId ) || ! isEmpty ( result . GroupId ) ) )
return true ;
else
return false ;
}
// Enregistrement temporaire du dernier résultat à un quiz.
// En attendant de savoir si l'utilisateur souhaite une sauvegarde durable.
saveResultTemp ( result )
{
if ( this . checkIfResultIsComplete ( result ) )
{
saveLocaly ( "lastResult" , result ) ; // écrasera l'éventuel résultat précédent.
return true ;
}
else
{
throw new Error ( localDBNeedDatas ) ;
return false ;
}
}
// Enregistrement durable d'un résultat :
async addResult ( result )
{
await this . getOpenDb ( ) ;
return new Promise ( ( resolve , reject ) =>
{
if ( this . checkIfResultIsComplete ( result ) )
{
const resultsStore = getStore ( this . db , "userResults" , "readwrite" ) ;
let req ;
try
{
result . date = new Date ( ) ;
req = resultsStore . add ( result ) ;
}
catch ( e )
{
console . error ( e ) ;
this . db . close ( ) ;
reject ( e ) ;
}
req . onsuccess = ( e ) =>
{
this . db . close ( ) ;
resolve ( true ) ;
} ;
req . onerror = ( e ) =>
{
this . db . close ( ) ;
reject ( e ) ;
} ;
}
else
{
this . db . close ( ) ;
reject ( new Error ( localDBNeedDatas ) ) ;
}
} )
}
// Enregistre le quiz, s'il n'existe pas déjà :
async saveNewQuiz ( quizInfos )
{
const getUserQuizs = await this . getAllQuizs ( ) ;
const checkQuizExist = getUserQuizs . find ( quiz => quiz . url == quizInfos . url ) ;
let result ;
if ( checkQuizExist === undefined )
result = await this . saveQuiz ( quizInfos ) ;
return result ;
}
async saveQuiz ( quiz )
{
await this . getOpenDb ( ) ;
return new Promise ( ( resolve , reject ) =>
{
if ( ! isEmpty ( quiz . url ) && ! isEmpty ( quiz . title ) && ( ! isEmpty ( quiz . QuestionnaireId ) || ! isEmpty ( quiz . GroupId ) ) )
{
const quizsStore = getStore ( this . db , "userQuizs" , "readwrite" ) ;
let req ;
try
{
req = quizsStore . add ( quiz ) ;
}
catch ( e )
{
this . db . close ( ) ;
reject ( e ) ;
}
req . onsuccess = ( e ) =>
{
this . db . close ( ) ;
resolve ( true ) ;
} ;
req . onerror = ( e ) =>
{
this . db . close ( ) ;
reject ( e ) ;
} ;
}
else
{
this . db . close ( ) ;
reject ( new Error ( localDBNeedDatas ) ) ;
}
} )
}
// Fonction affichant les précédents résultats connus pour le quiz encours :
async showPreviousResultsForId ( QuestionnaireId , GroupId , txtContentId = "explanationsContent" , txtTitleId = "explanationsTitle" )
{
if ( ( isEmpty ( QuestionnaireId ) && isEmpty ( GroupId ) ) )
throw new Error ( localDBNeedDatas ) ;
// Recherche dans la base de données :
const previousResults = await this . checkPreviousResultsForId ( QuestionnaireId , GroupId ) ;
if ( previousResults === undefined ) // Peut être un tableau vide si ancun résultat enregistré, mais pas undefined.
throw new Error ( localDBGetPreviousResultsFail ) ;
else
{
const explanationsContent = document . getElementById ( txtContentId ) ;
const explanationsTitle = document . getElementById ( txtTitleId ) ;
// 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 ; // ! le nombre de questions peut évoluer, si le quiz est actualisé.
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);
}
}
2022-03-21 18:33:06 +01:00
// Retourne une liste HTML des précédents quizs avec lien vers leur page
showMyQuizs ( listId = "quizsList" )
{
const listElt = document . getElementById ( listId ) ;
// On affiche d'abord les quizs les plus récents :
const myQuizs = this . allQuizs . reverse ( ) ;
let html = "" ;
for ( const quiz of myQuizs )
html += ` <li><a href=" ${ quiz . url } #explanations"> ${ quiz . title } </a></li> ` ;
if ( html !== "" )
addElement ( listElt , "ul" , html + "ici" ) ;
else
addElement ( listElt , "p" , noPreviousResultsAtAll ) ;
}
2022-03-21 17:08:58 +01:00
}