Prepare monitoring using Statements component (early draft stage)
authorBenjamin Auder <benjamin.auder@somewhere>
Tue, 30 Jan 2018 21:04:05 +0000 (22:04 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Tue, 30 Jan 2018 21:04:05 +0000 (22:04 +0100)
public/javascripts/assessment.js
public/javascripts/components/statements.js [new file with mode: 0644]
public/javascripts/monitor.js
public/javascripts/utils/libsRefresh.js [new file with mode: 0644]
views/assessment.pug
views/monitor.pug

index c014b5a..5019840 100644 (file)
@@ -1,6 +1,3 @@
-// TODO: if display == "all", les envois devraient être non définitifs (possibilité de corriger)
-// Et, blur sur une (sous-)question devrait envoyer la version courante de la sous-question
-
 let socket = null; //monitor answers in real time
 
 if (assessment.mode == "secure" && !checkWindowSize())
@@ -15,15 +12,6 @@ function checkWindowSize()
        return window.innerWidth >= screen.width-3 && window.innerHeight >= screen.height-3;
 };
 
-function libsRefresh()
-{
-       // Run Prism + MathJax on questions text
-       $("#statements").find("code[class^=language-]").each( (i,elem) => {
-               Prism.highlightElement(elem);
-       });
-       MathJax.Hub.Queue(["Typeset",MathJax.Hub,"statements"]);
-}
-
 new Vue({
        el: "#assessment",
        data: {
@@ -39,207 +27,6 @@ new Vue({
                remainingTime: 0, //global, in seconds
                warnMsg: "",
        },
-       components: {
-               "statements": {
-                       props: ['assessment','inputs','student','stage'],
-                       // TODO: general render function for nested exercises
-                       // There should be a questions navigator below, or next (visible if display=='all')
-                       // Full questions tree is rendered, but some parts hidden depending on display settings
-                       render(h) {
-                               let self = this;
-                               let questions = (assessment.questions || [ ]).map( (q,i) => {
-                                       let questionContent = [ ];
-                                       questionContent.push(
-                                               h(
-                                                       "div",
-                                                       {
-                                                               "class": {
-                                                                       wording: true,
-                                                               },
-                                                               domProps: {
-                                                                       innerHTML: q.wording,
-                                                               },
-                                                       }
-                                               )
-                                       );
-                                       let optionsOrder = _.range(q.options.length);
-                                       if (!q.fixed)
-                                               optionsOrder = _.shuffle(optionsOrder);
-                                       let optionList = [ ];
-                                       optionsOrder.forEach( idx => {
-                                               let option = [ ];
-                                               option.push(
-                                                       h(
-                                                               "input",
-                                                               {
-                                                                       domProps: {
-                                                                               checked: this.inputs.length > 0 && this.inputs[i][idx],
-                                                                       },
-                                                                       attrs: {
-                                                                               id: this.inputId(i,idx),
-                                                                               type: "checkbox",
-                                                                       },
-                                                                       on: {
-                                                                               change: e => { this.inputs[i][idx] = e.target.checked; },
-                                                                       },
-                                                               },
-                                                       )
-                                               );
-                                               option.push(
-                                                       h(
-                                                               "label",
-                                                               {
-                                                                       domProps: {
-                                                                               innerHTML: q.options[idx],
-                                                                       },
-                                                                       attrs: {
-                                                                               "for": this.inputId(i,idx),
-                                                                       },
-                                                               }
-                                                       )
-                                               );
-                                               optionList.push(
-                                                       h(
-                                                               "div",
-                                                               {
-                                                                       "class": {
-                                                                               option: true,
-                                                                               choiceCorrect: this.stage == 4 && assessment.questions[i].answer.includes(idx),
-                                                                               choiceWrong: this.stage == 4 && this.inputs[i][idx] && !assessment.questions[i].answer.includes(idx),
-                                                                       },
-                                                               },
-                                                               option
-                                                       )
-                                               );
-                                       });
-                                       questionContent.push(
-                                               h(
-                                                       "div",
-                                                       {
-                                                               "class": {
-                                                                       optionList: true,
-                                                               },
-                                                       },
-                                                       optionList
-                                               )
-                                       );
-                                       return h(
-                                               "div",
-                                               {
-                                                       "class": {
-                                                               "question": true,
-                                                               "hide": this.stage == 2 && assessment.display == 'one' && assessment.indices[assessment.index] != i,
-                                                       },
-                                               },
-                                               questionContent
-                                       );
-                               });
-                               if (this.stage == 2)
-                               {
-                                       questions.unshift(
-                                               h(
-                                                       "button",
-                                                       {
-                                                               "class": {
-                                                                       "waves-effect": true,
-                                                                       "waves-light": true,
-                                                                       "btn": true,
-                                                               },
-                                                               style: {
-                                                                       "display": "block",
-                                                                       "margin-left": "auto",
-                                                                       "margin-right": "auto",
-                                                               },
-                                                               on: {
-                                                                       click: () => this.sendAnswer(assessment.indices[assessment.index]),
-                                                               },
-                                                       },
-                                                       "Send"
-                                               )
-                                       );
-                               }
-                               return h(
-                                       "div",
-                                       {
-                                               attrs: {
-                                                       id: "statements",
-                                               },
-                                       },
-                                       questions
-                               );
-                       },
-                       mounted: function() {
-                               if (assessment.mode != "secure")
-                                       return;
-                               window.addEventListener("keydown", e => {
-                                       // Ignore F12 (avoid accidental window resize due to devtools)
-                                       // NOTE: in Chromium at least, fullscreen mode exit with F11 cannot be prevented.
-                                       // Workaround: disable key at higher level. Possible xbindkey config:
-                                       // "false"
-                                       //   m:0x10 + c:95
-                                       //   Mod2 + F11
-                                       if (e.keyCode == 123)
-                                               e.preventDefault();
-                               }, false);
-                               window.addEventListener("blur", () => {
-                                       this.trySendCurrentAnswer();
-                                       document.location.href= "/noblur";
-                               }, false);
-                               window.addEventListener("resize", e => {
-                                       this.trySendCurrentAnswer();
-                                       document.location.href= "/fullscreen";
-                               }, false);
-                       },
-                       updated: function() {
-                               libsRefresh(); //TODO: shouldn't be required: "MathJax" strings on start and assign them to assessment.questions. ...
-                       },
-                       methods: {
-                               inputId: function(i,j) {
-                                       return "q" + i + "_" + "input" + j;
-                               },
-                               trySendCurrentAnswer: function() {
-                                       if (this.stage == 2)
-                                               this.sendAnswer(assessment.indices[assessment.index]);
-                               },
-                               // stage 2
-                               sendAnswer: function(realIndex) {
-                                       let gotoNext = () => {
-                                               if (assessment.index == assessment.questions.length - 1)
-                                                       this.$emit("gameover");
-                                               else
-                                                       assessment.index++;
-                                               this.$forceUpdate(); //TODO: shouldn't be required
-                                       };
-                                       if (assessment.mode == "open")
-                                               return gotoNext(); //only local
-                                       let answerData = {
-                                               aid: assessment._id,
-                                               answer: JSON.stringify({
-                                                       index:realIndex.toString(),
-                                                       input:this.inputs[realIndex]
-                                                               .map( (tf,i) => { return {val:tf,idx:i}; } )
-                                                               .filter( item => { return item.val; })
-                                                               .map( item => { return item.idx; })
-                                               }),
-                                               number: this.student.number,
-                                               password: this.student.password,
-                                       };
-                                       $.ajax("/send/answer", {
-                                               method: "GET",
-                                               data: answerData,
-                                               dataType: "json",
-                                               success: ret => {
-                                                       if (!!ret.errmsg)
-                                                               return this.$emit("warning", ret.errmsg);
-                                                       else
-                                                               gotoNext();
-                                                       socket.emit(message.newAnswer, answerData);
-                                               },
-                                       });
-                               },
-                       },
-               },
-       },
        computed: {
                countdown: function() {
                        let seconds = this.remainingTime % 60;
@@ -249,6 +36,31 @@ new Vue({
        },
        mounted: function() {
                $(".modal").modal();
+               if (assessment.mode != "secure")
+                       return;
+               window.addEventListener("keydown", e => {
+                       // Ignore F12 (avoid accidental window resize due to devtools)
+                       // NOTE: in Chromium at least, fullscreen mode exit with F11 cannot be prevented.
+                       // Workaround: disable key at higher level. Possible xbindkey config:
+                       // "false"
+                       //   m:0x10 + c:95
+                       //   Mod2 + F11
+                       if (e.keyCode == 123)
+                               e.preventDefault();
+               }, false);
+               window.addEventListener("blur", () => {
+                       this.trySendCurrentAnswer();
+                       document.location.href= "/noblur";
+               }, false);
+               window.addEventListener("resize", e => {
+                       this.trySendCurrentAnswer();
+                       document.location.href= "/fullscreen";
+               }, false);
+       },
+               trySendCurrentAnswer: function() {
+                       if (this.stage == 2)
+                               this.sendAnswer(assessment.indices[assessment.index]);
+               },
        },
        methods: {
                // In case of AJAX errors
@@ -361,6 +173,45 @@ new Vue({
                                        clearInterval(this);
                        }, 1000);
                },
+               // stage 2
+               // TODO: currentIndex ? click: () => this.sendAnswer(assessment.indices[assessment.index]),
+               // De même, cette condition sur le display d'une question doit remonter (résumée dans 'index' property) :
+               // à faire par ici : "hide": this.stage == 2 && assessment.display == 'one' && assessment.indices[assessment.index] != i,
+               sendAnswer: function(realIndex) {
+                       let gotoNext = () => {
+                               if (assessment.index == assessment.questions.length - 1)
+                                       this.$emit("gameover");
+                               else
+                                       assessment.index++;
+                               this.$forceUpdate(); //TODO: shouldn't be required
+                       };
+                       if (assessment.mode == "open")
+                               return gotoNext(); //only local
+                       let answerData = {
+                               aid: assessment._id,
+                               answer: JSON.stringify({
+                                       index:realIndex.toString(),
+                                       input:this.inputs[realIndex]
+                                               .map( (tf,i) => { return {val:tf,idx:i}; } )
+                                               .filter( item => { return item.val; })
+                                               .map( item => { return item.idx; })
+                               }),
+                               number: this.student.number,
+                               password: this.student.password,
+                       };
+                       $.ajax("/send/answer", {
+                               method: "GET",
+                               data: answerData,
+                               dataType: "json",
+                               success: ret => {
+                                       if (!!ret.errmsg)
+                                               return this.$emit("warning", ret.errmsg);
+                                       else
+                                               gotoNext();
+                                       socket.emit(message.newAnswer, answerData);
+                               },
+                       });
+               },
                // stage 2 --> 3 (or 4)
                // from a message by statements component, or time over
                endAssessment: function() {
diff --git a/public/javascripts/components/statements.js b/public/javascripts/components/statements.js
new file mode 100644 (file)
index 0000000..d93a97f
--- /dev/null
@@ -0,0 +1,115 @@
+Vue.component("statements", {
+       props: ['questions','inputs','showAnswers','index'], // index=-1 : show all, otherwise show current question
+       // TODO: general render function for nested exercises
+       // There should be a questions navigator below, or next (visible if display=='all')
+       // Full questions tree is rendered, but some parts hidden depending on display settings
+       render(h) {
+               let domTree = this.questions.map( (q,i) => {
+                       let questionContent = [ ];
+                       questionContent.push(
+                               h(
+                                       "div",
+                                       {
+                                               "class": {
+                                                       wording: true,
+                                               },
+                                               domProps: {
+                                                       innerHTML: q.wording,
+                                               },
+                                       }
+                               )
+                       );
+                       let optionsOrder = _.range(q.options.length);
+                       if (!q.fixed)
+                               optionsOrder = _.shuffle(optionsOrder);
+                       let optionList = [ ];
+                       optionsOrder.forEach( idx => {
+                               let option = [ ];
+                               option.push(
+                                       h(
+                                               "input",
+                                               {
+                                                       domProps: {
+                                                               checked: this.inputs.length > 0 && this.inputs[i][idx],
+                                                               disabled: monitoring,
+                                                       },
+                                                       attrs: {
+                                                               id: this.inputId(i,idx),
+                                                               type: "checkbox",
+                                                       },
+                                                       on: {
+                                                               change: e => { this.inputs[i][idx] = e.target.checked; },
+                                                       },
+                                               },
+                                       )
+                               );
+                               option.push(
+                                       h(
+                                               "label",
+                                               {
+                                                       domProps: {
+                                                               innerHTML: q.options[idx],
+                                                       },
+                                                       attrs: {
+                                                               "for": this.inputId(i,idx),
+                                                       },
+                                               }
+                                       )
+                               );
+                               optionList.push(
+                                       h(
+                                               "div",
+                                               {
+                                                       "class": {
+                                                               option: true,
+                                                               choiceCorrect: showAnswers && this.questions[i].answer.includes(idx),
+                                                               choiceWrong: showAnswers && this.inputs[i][idx] && !questions[i].answer.includes(idx),
+                                                       },
+                                               },
+                                               option
+                                       )
+                               );
+                       });
+                       questionContent.push(
+                               h(
+                                       "div",
+                                       {
+                                               "class": {
+                                                       optionList: true,
+                                               },
+                                       },
+                                       optionList
+                               )
+                       );
+                       return h(
+                               "div",
+                               {
+                                       "class": {
+                                               "question": true,
+                                               "hide": index >= 0 && index != i,
+                                       },
+                               },
+                               questionContent
+                       );
+               });
+               return h(
+                       "div",
+                       {
+                               attrs: {
+                                       id: "statements",
+                               },
+                       },
+                       questions
+               );
+       },
+       updated: function() {
+               // TODO: next line shouldn't be required: questions wordings + answer + options
+               // are processed earlier; their content should be updated at this time.
+               statementsLibsRefresh();
+       },
+       methods: {
+               inputId: function(i,j) {
+                       return "q" + i + "_" + "input" + j;
+               },
+       },
+});
index 1d32d0c..9f43ce0 100644 (file)
@@ -1,5 +1,3 @@
-// UNIMPLEMENTED
-
 // TODO: onglets pour chaque groupe + section déroulante questionnaire (chargé avec réponses)
 //   NOM Prenom (par grp, puis alphabétique)
 //   réponse : vert si OK (+ choix), rouge si faux, gris si texte (clic pour voir)
@@ -10,19 +8,8 @@
 
 // Also buttons "start exam", "end exam" for logged in teacher
 
-// TODO: réutiliser le component... trouver un moyen
-
 let socket = null; //monitor answers in real time
 
-function libsRefresh()
-{
-       // Run Prism + MathJax on questions text
-       $("#statements").find("code[class^=language-]").each( (i,elem) => {
-               Prism.highlightElement(elem);
-       });
-       MathJax.Hub.Queue(["Typeset",MathJax.Hub,"statements"]);
-}
-
 new Vue({
        el: "#monitor",
        data: {
@@ -32,145 +19,6 @@ new Vue({
                //       1: authenticated (password hash validated), start monitoring
                stage: 0,
        },
-       components: {
-               "statements": {
-                       props: ['assessment','inputs','student','stage'],
-                       // TODO: general render function for nested exercises
-                       // There should be a questions navigator below, or next (visible if display=='all')
-                       // Full questions tree is rendered, but some parts hidden depending on display settings
-                       render(h) {
-                               let self = this;
-                               let questions = (assessment.questions || [ ]).map( (q,i) => {
-                                       let questionContent = [ ];
-                                       questionContent.push(
-                                               h(
-                                                       "div",
-                                                       {
-                                                               "class": {
-                                                                       wording: true,
-                                                               },
-                                                               domProps: {
-                                                                       innerHTML: q.wording,
-                                                               },
-                                                       }
-                                               )
-                                       );
-                                       let optionsOrder = _.range(q.options.length);
-                                       if (!q.fixed)
-                                               optionsOrder = _.shuffle(optionsOrder);
-                                       let optionList = [ ];
-                                       optionsOrder.forEach( idx => {
-                                               let option = [ ];
-                                               option.push(
-                                                       h(
-                                                               "input",
-                                                               {
-                                                                       domProps: {
-                                                                               checked: this.inputs.length > 0 && this.inputs[i][idx],
-                                                                       },
-                                                                       attrs: {
-                                                                               id: this.inputId(i,idx),
-                                                                               type: "checkbox",
-                                                                       },
-                                                                       on: {
-                                                                               change: e => { this.inputs[i][idx] = e.target.checked; },
-                                                                       },
-                                                               },
-                                                       )
-                                               );
-                                               option.push(
-                                                       h(
-                                                               "label",
-                                                               {
-                                                                       domProps: {
-                                                                               innerHTML: q.options[idx],
-                                                                       },
-                                                                       attrs: {
-                                                                               "for": this.inputId(i,idx),
-                                                                       },
-                                                               }
-                                                       )
-                                               );
-                                               optionList.push(
-                                                       h(
-                                                               "div",
-                                                               {
-                                                                       "class": {
-                                                                               option: true,
-                                                                               choiceCorrect: this.stage == 4 && assessment.questions[i].answer.includes(idx),
-                                                                               choiceWrong: this.stage == 4 && this.inputs[i][idx] && !assessment.questions[i].answer.includes(idx),
-                                                                       },
-                                                               },
-                                                               option
-                                                       )
-                                               );
-                                       });
-                                       questionContent.push(
-                                               h(
-                                                       "div",
-                                                       {
-                                                               "class": {
-                                                                       optionList: true,
-                                                               },
-                                                       },
-                                                       optionList
-                                               )
-                                       );
-                                       return h(
-                                               "div",
-                                               {
-                                                       "class": {
-                                                               "question": true,
-                                                               "hide": this.stage == 2 && assessment.display == 'one' && assessment.indices[assessment.index] != i,
-                                                       },
-                                               },
-                                               questionContent
-                                       );
-                               });
-                               if (this.stage == 2)
-                               {
-                                       questions.unshift(
-                                               h(
-                                                       "button",
-                                                       {
-                                                               "class": {
-                                                                       "waves-effect": true,
-                                                                       "waves-light": true,
-                                                                       "btn": true,
-                                                               },
-                                                               style: {
-                                                                       "display": "block",
-                                                                       "margin-left": "auto",
-                                                                       "margin-right": "auto",
-                                                               },
-                                                               on: {
-                                                                       click: () => this.sendAnswer(assessment.indices[assessment.index]),
-                                                               },
-                                                       },
-                                                       "Send"
-                                               )
-                                       );
-                               }
-                               return h(
-                                       "div",
-                                       {
-                                               attrs: {
-                                                       id: "statements",
-                                               },
-                                       },
-                                       questions
-                               );
-                       },
-                       mounted: function() {
-                               libsRefresh();
-                       },
-                       methods: {
-                               inputId: function(i,j) {
-                                       return "q" + i + "_" + "input" + j;
-                               },
-                       },
-               },
-       },
        methods: {
                // stage 0 --> 1
                startMonitoring: function() {
diff --git a/public/javascripts/utils/libsRefresh.js b/public/javascripts/utils/libsRefresh.js
new file mode 100644 (file)
index 0000000..4bdb7a0
--- /dev/null
@@ -0,0 +1,8 @@
+function statementsLibsRefresh()
+{
+       // Run Prism + MathJax on questions text
+       $("#statements").find("code[class^=language-]").each( (i,elem) => {
+               Prism.highlightElement(elem);
+       });
+       MathJax.Hub.Queue(["Typeset",MathJax.Hub,"statements"]);
+}
index c5395d6..94b87ae 100644 (file)
@@ -47,7 +47,8 @@ block content
                                                .card
                                                        .timer.center(v-if="stage==2") {{ countdown }}
                                        .card
-                                               statements(:assessment="assessment" :student="student" :stage="stage" :inputs="inputs" @gameover="endAssessment" @warning="warning")
+                                               button.waves-effect.waves-light.btn(style="display:block;margin:0 auto" @click="sendAnswer") Send
+                                               statements(:questions="assessment.questions" :showAnswers="showAnswers" :index="index" :inputs="inputs" @gameover="endAssessment")
                                #stage3(v-show="stage==3")
                                        .card
                                                .finish Exam completed &#9786; ...don't close the window!
@@ -58,4 +59,6 @@ block content
 block append javascripts
        script.
                let assessment = !{JSON.stringify(assessment)};
+               const monitoring = false;
+       script(src="/javascripts/components/statements.js")
        script(src="/javascripts/assessment.js")
index 6c9cf6a..f4f2e3c 100644 (file)
@@ -28,4 +28,7 @@ block content
                                                .conclusion(v-html="assessment.conclusion")
 
 block append javascripts
+       script.
+               const monitoring = true;
+       script(src="/javascripts/components/statements.js")
        script(src="/javascripts/monitor.js")