Classe ValidationZones
Cette classe permet de valider la (ou les) zone(s) de saisie ainsi que les éventuelles listes déroulantes.
La construction se fait le plus souvent à la fin de la partie "enonce" (juste avant l’appel me.finEnonce()) :
stor.fctsValid = new ValidationZones(zonesOptions);
où stor correspond à me.storage.
zonesOptions contient trois propriétés :
-
zonesqui est un tableau d’inputs, d’inputs mathquill, de listes déroulantes, que l’élève doit compléter (les ids sont tolérés). -
validePersoqui est un tableau d’éléments dont la validation ne peut se faire automatique car parfois liée à une autre zone de réponse (pas exemple pour citer un couple de droites parallèles, cas concrets plus bas).Cette propriété peut être omise si ce tableau est vide.
-
parcoursqui permet de récupérer des éléments du parcours
L’objet retourné stor.fctsValid contient un certain nombre de fonctions :
reponseValide()permet de vérifier si toutes les zones sont complétées (appelée parvalidationGlobale());validationGlobale()permet de valider entièrement toutes les zones qui ne sont pas dansvalidePerso;valideUneZone(laZone,laReponse)permet de valider une zone particulière (utile pour celles qui sont dansvalidePerso).laZoneest l’élément html (ou son id);couleurZones()met en couleur (puis barre les réponses fausses si l’élève n’a plus d’essai) les zones dont l'ID n’est pas dans validePerso ; elle appelle la méthodecoloreUneZone;coloreUneZone(laZone)met en couleur (puis barre les réponses fausses si l’élève n’a plus d’essai) la zonelaZone(ou son id) ; c'est utile pour les zones qui sont dansvalidePerso;redonneFocus()utilisé parvalidationGlobale; elle sert aussi dans le cas d’une validation personnelle à redonner le focus à la première zone fausse.
Pour expliquer le fonctionnement, voici quelques exemples classés par ordre de complexité croissante.
Pour chacun d’eux, nous partirons de sectionmodeleEx.js dans laquelle il faut modifer la fonction enonceEnd(me), à partir de la ligne :
stor.elts.divEnonce = j3pAddElt(me.zonesElts.MG, 'div', { style: me.styles.etendre('petit.enonce', { padding: '10px' }) })
par le code proposé.
Cette classe entre en action lors de la correction, il sera nécessaire, pour les tests, d’insérer le code proposé pour la correction dans les fonctions getScore(me), correction(me) et afficheCorrection(me) (cette dernière fonction ne servant qu'à afficher les explications).
À la fin de cette page, j'ai ajouté des remarques pour obtenir un comportement des zones moins conventionnel.
EXEMPLE 1 : Validation d’un texte
Dans la fonction enonceEnd(me)
Tout d’abord, on aura défini le texte dans la fonction getDonnees() :
textes : {
titreExo: 'Exemple de création d’une section',
consigne1: 'Dans la zone @1@, entre le mot "proportionnel"',
...
}
Remarques :
- Prendre l'habitude d’externaliser également le titre de l’exo;
- Pour s'y retrouver appeler "consigne1", "consigne2", ... les textes correspondant à la consigne, "comment1", "comment2", ... ceux correspondant à ce qu'on peut ajouter comme information lors de la validation et enfin "corr1", corr2", ... pour la correction.
stor.elts.divEnonce = j3pAddElt(me.zonesElts.MG, 'div', { style: me.styles.etendre('petit.enonce', { padding: '10px' }) })
const eltCons1 = afficheBloc(stor.elts.divEnonce, ds.textes.consigne1, {
input1: { texte: '', dynamique: true, width: '12px'}
})
stor.zoneInput = eltCons1.inputList[0]
j3pRestriction(stor.zoneInput, j3pRestriction.lettresAccents)
stor.zoneInput.typeReponse = ['texte']
stor.zoneInput.reponse = ["proportionnel","proportionnelle","proportionel"];
stor.fctsValid = new ValidationZones({parcours: me, zone:[stor.zoneInput]})
L’exécution de ce code donne l’affichage suivant : 
La première ligne permet de créer un bloc conteneur (nommé stor.elts.divEnonce).
On y met ensuite un nouveau bloc contenant la consigne avec la zone de saisie à compléter. eltCons1 récupère les informations de ce bloc (parent, zone de saisie, listes déroulantes, ...).
On récupère alors l’élément html correspondant à la zone de saisie qui est un input sans mathquill (présent dans eltCons1.inputList).
Pour faciliter l’utilisation de cette seule zone de saisie, on la stocke dans la variable stor.zoneInput pour l’utiliser au cours de la correction (cette uniformisation permet de s'y retrouver dans n’importe quelle section).
j3pRestriction permet de limiter les caractères acceptés dans la zone de saisie. j3pRestriction.lettresAccents est une variable globale qui permet d’autoriser tous les minuscules avec les accents possibles et imaginables dans une langue latine.
Pour la validation, nous précisons alors que le type de réponse sera du texte. Cela se fait par un tableau nommé typeReponse déclaré comme une propriété de stor.zoneInput.
Ce tableau ne contient qu'un élément : "texte".
Il est possible de donner un seuil de tolérance sur l’orthographe d’un mot en acceptant des réponses "quasiment" bonnes.
On répertorie alors l’ensemble des réponses acceptées dans un tableau nommé reponse qui est encore une propriété de stor.zoneInput. Il est préférable de mettre ces réponses acceptées dans textes défini dans getDonnees().
Le premier élément du tableau sera toujours la bonne réponse. Si l’élève donne une des autres réponses du tableau, celle-ci sera acceptée et remplacée par reponse[0].
Dans la fonction getScore(me) :
const stor = me.storage
const reponse = stor.fctsValid.validationGlobale()
if (reponse.aRepondu) return (reponse.bonneReponse) ? 1 : 0
return null
La suite de la validation de l’élève avec prise en compte de la qualité de sa réponse est assurée dans la fonction correction(me).
Le seul appel de la méthode validationGlobale() fait tout le travail. De plus elle renvoie reponse.aRepondu qui vaut true si l’élève a bien complété la zone. Si c'est le cas, elle renvoie aussi reponse.bonneReponse qui vaut true si l’élève a correctement répondu.
Étapes de la méthode validationGlobale() :
-
elle vérifie que la zone est complète. Si ce n’est pas le cas, on obtient :

Ici, on a validé sans rien saisir dans l’input. On indique qu'une réponse doit être saisie (c'est la même chose s'il existe plusieurs zones de saisie et que l’une d’entre elles est vide). On redonne alors le focus à la première zone de saisie non remplie.
-
On complète alors par l’une des réponses acceptées : "proportionnelle" par exemple.

La réponse est acceptée. Elle est de plus remplacée par la réponse exacte ("proportionnel").
-
Si on ne répond pas correctement,
- soit l’élève a encore une chance de corriger son erreur. Dans ce cas la zone est en rouge et on lui redonne le focus ;

- soit l’élève a épuisé toutes ses chances. On barre alors la réponse donnée.
On affiche alors la correction en faisant appel à la fonction afficheCorrection(me)présente dans le modèle :
- soit l’élève a encore une chance de corriger son erreur. Dans ce cas la zone est en rouge et on lui redonne le focus ;
function afficheCorrection (me) {
const stor = me.storage
const ds = me.DonneesSection
const zoneExpli = j3pAddElt(stor.elts.divEnonce, 'div', '', { style: me.styles.petit.correction })
// La couleur de la correction sera fonction de la qualité de la réponse de l’élève
j3pStyle(zoneExpli, { paddingTop: '10px', color: (stor.score === 1) ? me.styles.cbien : me.styles.toutpetit.correction.color })
phraseBloc(zoneExpli, ds.textes.corr1)
}
EXEMPLE 2 : Validation d’un ou plusieurs nombres
Dans la fonction enonceEnd(me)
stor.elts.divEnonce = j3pAddElt(me.zonesElts.MG, 'div', { style: me.styles.etendre('petit.enonce', { padding: '10px' }) })
const eltCons1 = afficheBloc(stor.elts.divEnonce, ds.textes.consigne1, {
input1: { texte: '', dynamique: true, width: '12px'}
})
const eltCons2 = afficheBloc(stor.elts.divEnonce, ds.textes.consigne2, {
inputmq1: { texte: ''}
})
stor.zonesInput = [eltCons1.inputList[0], eltCons2.inputmqList[0]]
j3pRestriction(stor.zonesInput[0], '0-9,.')
stor.zonesInput[0].addEventListener('input', j3pRemplacePoint)
mqRestriction(stor.zonesInput[1], '\\d.,-/+', { commandes: ['fraction'] })
stor.elts.laPalette = j3pAddElt(stor.elts.divEnonce, 'div')
j3pStyle(stor.elts.laPalette, { paddingTop: '10px' })
j3pPaletteMathquill(stor.elts.laPalette, stor.zonesInput[3], { liste: ['fraction'] })
stor.zonesInput[0].typeReponse = ['nombre', 'approche', 0.001]
stor.zonesInput[0].reponse = [Math.PI]
stor.zonesInput[1].typeReponse = ["nombre", "exact"]
stor.zonesInput[1].solutionSimplifiee = ["reponse fausse","non valide"]//on peut mettre "valide et on accepte", "non valide" ou "reponse fausse";
stor.zonesInput[1].reponse = [9/7]
stor.fctsValid = new ValidationZones({zones:stor.zonesInput,parcours: me})
j3pFocus(stor.zonesInput[0])
On obtient l’énoncé suivant : 
Les consignes 1 et 2 ayant été définies par :
consigne1: "Une valeur approchée à $10^{-3}$ près de $\\pi$ vaut @1@.",
consigne2: "Écris $1+\\frac{2}{7}$ sous forme réduite : &1&."
La première zone de saisie est définie par @1@ : c'est une zone classique input. La seconde est définie par &1& : c'est une zone mathquill inputmq. Pour que le point soit converti en virgule lors de la saisie dans la zone input, on écrit :
stor.zonesInput[0].addEventListener('input', j3pRemplacePoint)
stor.zonesInput[0].typeReponse indique qu'on attend un nombre qui sera une valeur approchée de la réponse attendue avec la précision demandée.
Il est aussi possible de demander l’arrondi d’un nombre avec une certaine précision. Dans cas, on remplace "approche" par "arrondi".
stor.zonesInput[1].typeReponse indique qu'on attend la valeur exacte du nombre demandé (on doit donc écrire une fraction, ce qui explique qu'on ait choisi une zone mathquill).
mqRestriction permet d’ajouter le clavier virtuel sur cette zone. On ajoute également un bouton permettant de créer une fraction (bouton également présent dans le clavier virtuel, mais on peut aussi utiliser la touche / du clavier car elle a été autorisée).
stor.zonesInput[0].reponse et stor.zonesInput[1].reponse sont des tableaux contenant un seul élément : la valeur exacte de la réponse attendue.
- Dans la zone input, on attend véritablement un nombre : 3,141 et non le calcul 4,141-1 qui sera considéré comme faux.
-
Dans la zone inputmq, par défaut, on déterminer la valeur du nombre ou calcul écrit, puis on le compare à la solution attendue.
La propriété
solutionSimplifieeest un tableau de 2 valeurs qui vaut par défaut["valide et on accepte","valide et on accepte"].solutionSimplifiee[i]peut prendre trois valeurs (chaînes de caractères) possibles : "valide et on accepte", "non valide" ou "reponse fausse":- "valide et on accepte" (valeur par défaut) : en [0], cela signifie qu'on accepte des expressions non simplifiées. En [1], cela signifie qu'on accepte les fractions réductibles (selon toute logique, ce doit être le cas si ça l’est en [0]).
- "non valide" : en [0], cela signifie qu'on renverra
aRepondu=falsesi la réponse n’est pas simplifiée. En [1], on renvoitaRepondu=falsesi une fraction est réductible (et qu'en [0] on n’a pas répondu false). - "reponse fausse" : en [0], cela signifie qu'on renverra
aRepondu=trueetbonneReponse=falsesi la réponse n’est pas simplifiée. En [1], on renvoitaRepondu=trueetbonneReponse=falsesi une fraction est réductible.
Dans notre exemple :
stor.zonesInput[1].solutionSimplifiee = ["reponse fausse","non valide"]signifie que si l’élève écrit par exemple
ou
, la réponse n’étant pas simplifiée, on considèrera la réponse comme fausse.S'il écrit
, la réponse est considérée comme simplifiée. Par contre cette fraction n’est pas irréductible, donc on considère la réponse comme non valide.
-
Dans la fonction getScore(me) :
const stor = me.storage
const reponse = stor.fctsValid.validationGlobale()
if (reponse.aRepondu) return (reponse.bonneReponse) ? 1 : 0
return null
Ces lignes donnent le même résultat que précédemment.
Se reporter à l’exemple 1 pour les différents affichages au cours de la validation des réponses (réponse complète, puis bonne réponse ou réponse fausse).
Quelques compléments cependant au sujet des réponses simplifiées
En effet, si l’élève donne dans la zone 2 (stor.zonesInput[1]) une fraction irréductible, sa réponse sera considérée comme non valide. Il faut donc qu'on change le message :
if (score === null && !me.isElapsed) {
let msgReponseManquante
if (!stor.fctsValid.zones.reponseSimplifiee[1][1]) {
// c'est que la fraction de la dernière zone de saisie est réductible
msgReponseManquante = ds.textes.comment1
}
me.reponseManquante(stor.zoneCorr, msgReponseManquante)
return
}
Et on obtient : 
Si la réponse donnée dans la zone 2 n’est pas simplifiée (autre que fractions irréductibles), alors on compte faux. On change le message dans le cas d’une réponse fausse :
if (score !== 1){
if(!stor.fctsValid.zones.repSimplifiee[1][0]){
//c'est que l’expression entrée dans la deuxième zone de saisie est simplifiable
me.reponseKo(stor.zoneCorr, ds.textes.comment2, true)
}
}
On obtient : 
Il est aussi possible de récupérer des paramètres propres à chaque zone de saisie :
stor.fctsValid.zones.bonneReponse[i]renvoie true si la zone placée en index i dansstor.fctsValidest correctement complétée (reponse.detailsreponses[stor.zonesInput[...].id].correctdonne le même résultat mais en se servant de l’id de l’input - index à préciser);stor.fctsValid.zones.reponseSaisie[i]permet de récupérer le contenu de la zone placée en index i (avec l’id, on a aussireponse.detailsreponses[stor.zonesInput[...].id].valeur- index à préciser).
Enfin, pour l’affichage de la correction (ou explication), on complète la fonction afficheCorrection :
phraseBloc(zoneExpli, ds.textes.corr1)
phraseBloc(zoneExpli, ds.textes.corr2, {r:"\\frac{7}{7}+\\frac{2}{7}",t:"\\frac{9}{7}"})
Remarque : r et t sont des variables présentes dans le texte corr2 :
corr2 : "En mettant au même dénominateur, on obtient : <br>$1+\\frac{2}{7}=£r=£t$."
On obtient alors : 
EXEMPLE 3 : Validation d’une liste déroulante
Dans la fonction enonceEnd(me)
stor.elts.divEnonce = j3pAddElt(me.zonesElts.MG, 'div', { style: me.styles.etendre('petit.enonce', { padding: '10px' }) })
phraseBloc(stor.elts.divEnonce, ds.textes.consigne1)
const tabProp = j3pMelangeitemliste(["","parallèles","parrallèles", "paralèles","parralèles"])
// Mélange les items et permet de récupérer la place où ils apparaissent (voir documentation)
const eltCons1 = afficheBloc(stor.elts.divEnonce, ds.textes.consigne1, {
liste1:{ texte:tabProp[0] }
})
stor.zoneSelect = eltCons1.selectList[0]
stor.zoneSelect.typeReponse = [true]
stor.zoneSelect.reponse = tabProp[1][1]
//le second 1 est la position de la bonne réponse dans le tableau initial
stor.fctsValid = new ValidationZones({zones:[stor.zoneSelect],parcours: me})
On obtient l’énoncé suivant : 
stor.zoneSelect.typeReponse[0] égal à true nous indique que la valeur par défaut de la liste (ici "") doit être changée.
stor.zoneSelect.reponse est la position de la bonne réponse dans la liste tabProp[0].
Dans la partie correction, var reponse = stor.fctsValid.validationGlobale() permet une nouvelle fois de valider la liste.
EXEMPLE 4 : Validation classique d’une zone et personnalisée pour d’autres
Dans certains cas, stor.fctsValid.validationGlobale() ne permet pas la validation de toutes les zones de saisie.
C'est le cas par exemple pour citer des droites parallèles ou comme ci-dessous les diagonales d’un parallélogramme.
Dans la fonction enonceEnd(me)
stor.elts.divEnonce = j3pAddElt(me.zonesElts.MG, 'div', { style: me.styles.etendre('petit.enonce', { padding: '10px' }) })
phraseBloc(stor.elts.divEnonce, ds.textes.consigne1)
const eltCons1 = afficheBloc(stor.elts.divEnonce, ds.textes.consigne2, {
input1: { texte: '', dynamique: true, width: '12px', maxchars:2},
input2: { texte: '', dynamique: true, width: '12px', maxchars:2},
input3: { texte: '', dynamique: true, width: '12px'}
})
stor.zonesInput = [].concat(eltCons1.inputList) // ici je stocke les zone de saisie dans un array
stor.zonesInput.forEach((zone, index) => {
if (index === 2) {
j3pRestriction(zone, j3pRestriction.lettresAccents)
} else {
j3pRestriction(zone, "A-Za-z") // ou j3pRestriction.lettres
zone.addEventListener('input', (e) => { e.target.value = e.target.value.toUpperCase() })
}
zone.typeReponse = ["texte"]
})
stor.zonesInput[2].reponse = ['milieu', 'millieu'] // encore une tolérance sur l’orthographe (voir EXEMPLE 1)
stor.fctsValid = new ValidationZones({parcours: me, zones:stor.zonesInput,validePerso:stor.zonesInput.slice(0,2)})

Ici la validation de la troisième zone de saisie se fera comme dans l’exemple 1, mais cela ne sera pas le cas pour les deux premières.
En effet, si on écrit AC dans la première zone, dans la deuxième, on devra avoir BD ou DB. Mais si on écrit BD dans la première zone, on devra écrire AC ou CA dans la deuxième.
La validation des deux première zones de saisie se fera donc à part, ce qui explique : stor.zonesInput.slice(0,2)̀.
Remarque : ̀zone.addEventListener('input', (e) => { e.target.value = e.target.value.toUpperCase() }) permet d’autoriser les minuscules qui seront converties à la saisie en majuscules.
Dans la fonction getScore(me) :
const stor = me.storage
const reponse = stor.fctsValid.validationGlobale()
if (reponse.aRepondu) {
// il faut aussi que je teste les deux zones dont la validation est perso
let bonneRep2 = false
const tabRepPossibles = [
[['AC', 'CA'], ['BD', 'DB']],
[['BD', 'DB'], ['AC', 'CA']]
]
tabRepPossibles.forEach(tab => {
bonneRep2 = bonneRep2 || (stor.fctsValid.valideUneZone(stor.zonesInput[0], tab[0]).bonneReponse && stor.fctsValid.valideUneZone(stor.zonesInput[1], tab[1]).bonneReponse)
})
reponse.bonneReponse = (reponse.bonneReponse && bonneRep2)
if (!bonneRep2) {
stor.fctsValid.zones.bonneReponse[0] = false
stor.fctsValid.zones.bonneReponse[1] = false
// si les 2 zones de validePerso ne sont pas bonnes, on met false dans stor.fctsValid.zones.bonneReponse[i]
// où i est la position de la zone dans le tableau zonesSaisie déclaré lors de la construction de stor.fctsValid
}
// on appelle alors la fonction qui va mettre en couleur les réponses bonnes ou fausses de ces 2 zones
stor.fctsValid.coloreUneZone(stor.zonesInput[0])
stor.fctsValid.coloreUneZone(stor.zonesInput[1])
}
if (reponse.aRepondu) return (reponse.bonneReponse) ? 1 : 0
return null
stor.fctsValid.validationGlobale() vérifie tout d’abord que les trois zones ont bien été complétées. Si ce n’est pas le cas, le message "Il faut donner une réponse !" apparaît et la validation s'arrête.
Si toutes les zones ont bien été complétées, la méthode validationGlobale() ne valide que la troisième réponse.
reponse.bonneReponse contient true ou false suivant que la dernière zone est bonne ou fausse.
La validation des deux autres zones se fait dans le bloc
if(reponse.aRepondu){
...
}
On déclare un nouveau booléen bonneRep2 pour savoir si les deux autres zones sont correctes.
On vérifie ensuite si on a écrit AC ou CA dans la zone 1 et BD ou DB dans la zone 2 (premier cas où la réponse sera bonne) ou bien si on a écrit AC ou CA dans la zone 2 et BD ou DB dans la zone 1 (deuxième cas possible pour bien répondre).
Enfin après la ligne reponse.bonneReponse = (reponse.bonneReponse && bonneRep2), reponse.bonneReponse contient true si toutes les réponses sont bonnes.
À cet instant, s'il y a une erreur dans les deux premières zones, celles-ci ne sont toujours pas en rouge (voire barrées). Il reste donc un petit travail à faire :
stor.fctsValid.zones.bonneReponse[0] = false indique que la première zone du tableau stor.zonesInput est fausse.
Il ne reste plus qu'à mettre les deux zones stor.zonesInput[0] et stor.zonesInput[1] en couleur suivant que bonneRep2 vaut true ou false. Cela se fait par l’appel à la méthode coloreUneZone.
La suite du code de la fonction correction(me) reste par contre toujours la même.
REMARQUES : Modifier le comportement classique d’une zone de saisie pendant ou après la validation
Dans certains cas, on peut envisager de ne pas barrer la zone de saisie même quand me.essaiCourant vaut ds.nbchances. Pour cela, il suffit d’ajouter la propriété "barrer" à l’objet zone de saisie.
stor.zonesInput[0].barrer = false;
Dans ce cas, la zone stor.zonesInput[0] ne sera pas barrée à l’issue de la dernière tentative. Si cette propriété n’est pas présente ou vaut true, alors on retrouvera le comportement habituel de la zone de saisie.
On peut aussi envisager de choisir une couleur différente que le rouge indiquant que c'est faux à l’issue de toutes les tentatives (surtout si on choisit de ne pas barrer la réponse). On peut alors ajouter la propriété "couleur" à l’objet zone de saisie.
stor.zonesInput[0].couleur = "#00FF00";
Dans ce cas, le texte de la zone stor.zonesInput[0] sera en vert. Si cette propriété n’est pas présente, alors on retrouvera le comportement habituel de la zone de saisie.
Ces deux propriétés sont vraiment à n’utiliser que dans le cas d’un exercice très particulier. Dans les exercices classiques, il convient de conserver un comportement uniforme des zones de saisie.