thoughts about time, refactor statements component
authorBenjamin Auder <benjamin.auder@somewhere>
Tue, 13 Feb 2018 22:32:29 +0000 (23:32 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Tue, 13 Feb 2018 22:32:29 +0000 (23:32 +0100)
models/assessment.js
public/javascripts/assessment.js
public/javascripts/components/statements.js

index c7cb0fd..fd0133c 100644 (file)
@@ -11,11 +11,11 @@ const AssessmentModel =
         *   _id: BSON id
         *   cid: course ID
         *   name: varchar
-        *   active: boolean true/false
-        *   mode: secure | exam | open (decreasing security)
+        *   active: boolean
+        *   mode: secure | watch | exam | open (decreasing security)
         *   fixed: bool (questions in fixed order; default: false)
         *   display: "one" or "all" (generally "all" for open questions, but...)
-        *   time: 0, //<=0 means "untimed"; otherwise, time in seconds
+        *   time: 0, global (one vaue) or per question (array of integers)
         *   introduction: "",
         *   coefficient: number, default 1
         *   questions: array of
@@ -26,7 +26,6 @@ const AssessmentModel =
         *     answer: array of integers (for quiz) or html text (for paper); striped in exam mode
         *     active: boolean, is question in current assessment?
         *     points: points for this question (default 1)
-        *     time: 0 (<=0: untimed)
         *   papers : array of
         *     number: student number
         *     inputs: array of {index,answer[array of integers or html text],startTime}
index 93200cc..9275879 100644 (file)
@@ -23,19 +23,19 @@ new Vue({
                //       2: locked: password set, exam started
                //       3: completed
                //       4: show answers
+               remainingTime: assessment.time, //integer or array
                stage: assessment.mode != "open" ? 0 : 1,
-               remainingTime: 0, //global, in seconds
                warnMsg: "",
        },
        computed: {
                countdown: function() {
-                       let seconds = this.remainingTime % 60;
-                       let minutes = Math.floor(this.remainingTime / 60);
+                       const remainingTime = assessment.display == "one" && _.isArray(assessment.time)
+                               ? this.remainingTime[this.answers.index]
+                               : this.remainingTime;
+                       let seconds = remainingTime % 60;
+                       let minutes = Math.floor(remainingTime / 60);
                        return this.padWithZero(minutes) + ":" + this.padWithZero(seconds);
                },
-               showAnswers: function() {
-                       return this.stage == 4;
-               },
        },
        mounted: function() {
                $(".modal").modal();
@@ -46,7 +46,7 @@ new Vue({
                                return;
                        if (assessment.mode == "secure")
                        {
-                               this.trySendCurrentAnswer();
+                               this.sendAnswer();
                                document.location.href= "/noblur";
                        }
                        else //"watch" mode
@@ -65,7 +65,7 @@ new Vue({
                                return;
                        if (assessment.mode == "secure")
                        {
-                               this.trySendCurrentAnswer();
+                               this.sendAnswer();
                                document.location.href = "/fullscreen";
                        }
                        else //"watch" mode
@@ -78,7 +78,7 @@ new Vue({
                }, false);
        },
        methods: {
-               // In case of AJAX errors
+               // In case of AJAX errors (not blur-ing)
                showWarning: function(message) {
                        this.warnMsg = message;
                        $("#warning").modal("open");
@@ -88,12 +88,8 @@ new Vue({
                                return "0" + x;
                        return x;
                },
-               trySendCurrentAnswer: function() {
-                       if (this.stage == 2)
-                               this.sendAnswer();
-               },
                // stage 0 --> 1
-               getStudent: function(cb) {
+               getStudent: function() {
                        $.ajax("/get/student", {
                                method: "GET",
                                data: {
@@ -107,8 +103,6 @@ new Vue({
                                        this.stage = 1;
                                        this.student = s.student;
                                        Vue.nextTick( () => { Materialize.updateTextFields(); });
-                                       if (!!cb)
-                                               cb();
                                },
                        });
                },
@@ -118,18 +112,14 @@ new Vue({
                },
                // stage 1 --> 2 (get all questions, set password)
                startAssessment: function() {
-                       let initializeStage2 = (questions,paper) => {
+                       let initializeStage2 = paper => {
                                $("#leftButton, #rightButton").hide();
-                               if (assessment.time > 0)
-                               {
-
-// TODO: distinguish total exam time AND question time
-
-                                       const deltaTime = !!paper ? Date.now() - paper.startTime : 0;
-                                       this.remainingTime = assessment.time * 60 - Math.round(deltaTime / 1000);
-                                       this.runTimer();
-                               }
                                // Initialize structured answer(s) based on questions type and nesting (TODO: more general)
+                               
+                               // if display == "all" getQuestionS
+                               // otherwise get first question
+                               
+                               
                                if (!!questions)
                                        assessment.questions = questions;
                                this.answers.inputs = [ ];
@@ -148,6 +138,24 @@ new Vue({
                                        let remainingIndices = _.difference( _.range(assessment.questions.length).map(String), indices );
                                        this.answers.indices = indices.concat( _.shuffle(remainingIndices) );
                                }
+
+
+
+
+
+
+
+                               if (assessment.time > 0)
+                               {
+
+// TODO: distinguish total exam time AND question time
+
+                                       const deltaTime = !!paper ? Date.now() - paper.startTime : 0;
+                                       this.remainingTime = assessment.time * 60 - Math.round(deltaTime / 1000);
+                                       this.runTimer();
+                               }
+
+
                                this.answers.index = !!paper ? paper.inputs.length : 0;
                                this.answers.displayAll = assessment.display == "all";
                                this.answers.showSolution = false;
index 0fb5124..900a5e7 100644 (file)
@@ -17,26 +17,37 @@ Imaginary example: (using math.js)
 */
 
 Vue.component("statements", {
-       // 'answers' is an object containing
-       //   'inputs'(array),
-       //   'displayAll'(bool), //TODO: should be in questions!
-       //   'showSolution'(bool),
-       //   'indices': order of appearance
-       //   'index': current integer index (focused question)
-       props: ['questions','answers'],
-       // TODO: general render function for nested exercises
-       // There should be a questions navigator below, or next (visible if display=='all')
+       // 'inputs': array of index (as in questions) + input (text or array of ints)
+       // display: 'all', 'one', 'solution'
+       // iidx: current level-0 integer index (can match a group of questions / inputs)
+       props: ['questions','inputs','display','iidx'],
+       data: function() {
+               return {
+                       displayStyle: "compact", //or "all": all on same page
+               };
+       }
        // Full questions tree is rendered, but some parts hidden depending on display settings
        render(h) {
-               // TODO: render nothing if answers is empty
                let domTree = (this.questions || [ ]).map( (q,i) => {
                        let questionContent = [ ];
+                       questionContent.push(
+                               h(
+                                       "h4",
+                                       {
+                                               "class": {
+                                                       "questionIndex": true,
+                                               }
+                                       },
+                                       q.index
+                               )
+                       );
                        questionContent.push(
                                h(
                                        "div",
                                        {
                                                "class": {
                                                        wording: true,
+
                                                },
                                                domProps: {
                                                        innerHTML: q.wording,
@@ -44,82 +55,141 @@ Vue.component("statements", {
                                        }
                                )
                        );
-                       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.answers.inputs.length > 0 && this.answers.inputs[i][idx],
-                                                               disabled: monitoring,
-                                                       },
-                                                       attrs: {
-                                                               id: this.inputId(i,idx),
-                                                               type: "checkbox",
-                                                       },
-                                                       on: {
-                                                               change: e => { this.answers.inputs[i][idx] = e.target.checked; },
-                                                       },
-                                               },
-                                               [ '' ] //to work in Firefox 45.9 ESR @ ENSTA...
-                                       )
-                               );
-                               option.push(
-                                       h(
-                                               "label",
-                                               {
-                                                       domProps: {
-                                                               innerHTML: q.options[idx],
+                       if (!!q.options)
+                       {
+                               // quiz-like question
+                               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.answers.inputs.length > 0 && this.answers.inputs[i][idx],
+                                                                       disabled: monitoring,
+                                                               },
+                                                               attrs: {
+                                                                       id: this.inputId(i,idx),
+                                                                       type: "checkbox",
+                                                               },
+                                                               on: {
+                                                                       change: e => { this.answers.inputs[i][idx] = e.target.checked; },
+                                                               },
                                                        },
-                                                       attrs: {
-                                                               "for": this.inputId(i,idx),
+                                                       [ '' ] //to work in Firefox 45.9 ESR @ ENSTA...
+                                               )
+                                       );
+                                       option.push(
+                                               h(
+                                                       "label",
+                                                       {
+                                                               domProps: {
+                                                                       innerHTML: q.options[idx],
+                                                               },
+                                                               attrs: {
+                                                                       "for": this.inputId(i,idx),
+                                                               },
+                                                       }
+                                               )
+                                       );
+                                       optionList.push(
+                                               h(
+                                                       "div",
+                                                       {
+                                                               "class": {
+                                                                       option: true,
+                                                                       choiceCorrect: this.answers.showSolution && this.questions[i].answer.includes(idx),
+                                                                       choiceWrong: this.answers.showSolution && this.answers.inputs[i][idx] && !q.answer.includes(idx),
+                                                               },
                                                        },
-                                               }
-                                       )
-                               );
-                               optionList.push(
+                                                       option
+                                               )
+                                       );
+                               });
+                               questionContent.push(
                                        h(
                                                "div",
                                                {
                                                        "class": {
-                                                               option: true,
-                                                               choiceCorrect: this.answers.showSolution && this.questions[i].answer.includes(idx),
-                                                               choiceWrong: this.answers.showSolution && this.answers.inputs[i][idx] && !q.answer.includes(idx),
+                                                               optionList: true,
                                                        },
                                                },
-                                               option
+                                               optionList
                                        )
                                );
-                       });
-                       questionContent.push(
-                               h(
-                                       "div",
-                                       {
-                                               "class": {
-                                                       optionList: true,
-                                               },
-                                       },
-                                       optionList
-                               )
-                       );
-                       if (this.answers.displayAll && i < this.questions.length-1)
+                       }
+                       if (this.display == "all" && !this.navigator && i < this.questions.length-1)
                                questionContent.push( h("hr") );
+                       const depth = (q.index.match(/\./g) || []).length;
                        return h(
                                "div",
                                {
                                        "class": {
                                                "question": true,
-                                               "hide": !this.answers.displayAll && this.answers.indices[this.answers.index] != i,
+                                               "hide": this.display == "one" && this.iidx != i,
+                                               "depth" + depth: true,
                                        },
                                },
                                questionContent
                        );
                });
+               const navigator = h(
+                       "div",
+                       {
+                               "class": {
+                                       "hide": this.displayStyle == "all"
+                               },
+                       },
+                       [
+                               h(
+                                       "button",
+                                       {
+                                               "class": {
+                                                       "btn": true,
+                                               },
+                                               on: {
+                                                       click: () => {
+                                                               this.index = Math.max(0, this.index - 1);
+                                                       },
+                                               },
+                                       },
+                                       [ h("span", { "class": { "material-icon": true } }, "fast_rewind") ]
+                               ), //onclick: index = max(0,index-1)
+                               h("span",{ },(this.iidx+1).toString()),
+                               h(
+                                       "button",
+                                       {
+                                               "class": {
+                                                       "btn": true,
+                                               },
+                                               on: {
+                                                       click: () => {
+                                                               this.index = Math.min(this.index+1, this.questions.length-1)
+                                                       },
+                                               },
+                                       },
+                                       [ h("span", { "class": { "material-icon": true } }, "fast_forward") ]
+                               )
+                       ]
+               );
+               domTree.push(navigator);
+               domTree.push(
+                       h(
+                               "button",
+                               {
+                                       on: {
+                                               click: () => {
+                                                       this.displayStyle = displayStyle == "compact" ? "all" : "compact";
+                                               },
+                                       },
+                               },
+                               this.displayStyle == "compact" ? "Show all" : "Navigator"
+                       )
+               );
                return h(
                        "div",
                        {
@@ -134,8 +204,6 @@ Vue.component("statements", {
                statementsLibsRefresh();
        },
        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: {