* _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
* 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}
// 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();
return;
if (assessment.mode == "secure")
{
- this.trySendCurrentAnswer();
+ this.sendAnswer();
document.location.href= "/noblur";
}
else //"watch" mode
return;
if (assessment.mode == "secure")
{
- this.trySendCurrentAnswer();
+ this.sendAnswer();
document.location.href = "/fullscreen";
}
else //"watch" mode
}, false);
},
methods: {
- // In case of AJAX errors
+ // In case of AJAX errors (not blur-ing)
showWarning: function(message) {
this.warnMsg = message;
$("#warning").modal("open");
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: {
this.stage = 1;
this.student = s.student;
Vue.nextTick( () => { Materialize.updateTextFields(); });
- if (!!cb)
- cb();
},
});
},
},
// 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 = [ ];
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;
*/
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,
}
)
);
- 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",
{
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: {