Then create a course using the appropriate button in the middle of the screen.
Finally, create some exams ("new assessment" button). The syntax for a series of questions is described by the following example:
- > Global (HTML) introduction [optional]
-
Question 1 text or introduction (optional if there are subquestions)
Text for question 1.1 (index detected from indentation)
watch: ['./','config','utils','routes','models','entities']
};
-// TODO: tasks for uglify / sass / use webpack
-
gulp.task('server', function () {
nodemon(nodemonOptions)
.on('restart', function () {
const UserModel = require("../models/user");
-const AssessmentModel = require("../models/assessment");
+const EvaluationModel = require("../models/evaluation");
const db = require("../utils/database");
const CourseModel =
CourseModel.getById(cid, (err,course) => {
if (!!err || !course || !course.uid.equals(uid))
return cb({errmsg:"Not your course"},{});
- // 2) remove all associated assessments
- AssessmentModel.removeGroup(cid, (err2,ret) => {
+ // 2) remove all associated evaluations
+ EvaluationModel.removeGroup(cid, (err2,ret) => {
if (!!err)
return cb(err,{});
// 3) remove course (with its students)
const TokenGen = require("../utils/tokenGenerator");
const db = require("../utils/database");
-const AssessmentModel =
+const EvaluationModel =
{
/*
* Structure:
* options: array of varchar --> if present, question type == quiz!
* fixed: bool, options in fixed order (default: false)
* answer: array of integers (for quiz) or html text (for paper); striped in exam mode
- * active: boolean, is question in current assessment?
+ * active: boolean, is question in current evaluation?
* points: points for this question (default 1)
* param: parameter (if applicable)
* papers : array of
//////////////////
// BASIC FUNCTIONS
- getById: function(aid, callback)
+ getById: function(eid, callback)
{
- db.assessments.findOne(
- { _id: aid },
+ db.evaluations.findOne(
+ { _id: eid },
callback
);
},
getByPath: function(cid, name, callback)
{
- db.assessments.findOne(
+ db.evaluations.findOne(
{
cid: cid,
name: name,
insert: function(cid, name, callback)
{
- db.assessments.insert(
+ db.evaluations.insert(
{
name: name,
cid: cid,
getByCourse: function(cid, callback)
{
- db.assessments.find(
+ db.evaluations.find(
{ cid: cid },
callback
);
},
- // arg: full assessment without _id field
- replace: function(aid, assessment, cb)
+ // arg: full evaluation without _id field
+ replace: function(eid, evaluation, cb)
{
// Should be: (but unsupported by mongojs)
-// db.assessments.replaceOne(
-// { _id: aid },
-// assessment,
+// db.evaluations.replaceOne(
+// { _id: eid },
+// evaluation,
// cb
// );
// Temporary workaround:
- db.assessments.update(
- { _id: aid },
- { $set: assessment },
+ db.evaluations.update(
+ { _id: eid },
+ { $set: evaluation },
cb
);
},
- getQuestions: function(aid, callback)
+ getQuestions: function(eid, callback)
{
- db.assessments.findOne(
+ db.evaluations.findOne(
{
- _id: aid,
+ _id: eid,
display: "all",
},
{ questions: 1},
);
},
- getQuestion: function(aid, index, callback)
+ getQuestion: function(eid, index, callback)
{
- db.assessments.findOne(
+ db.evaluations.findOne(
{
- _id: aid,
+ _id: eid,
display: "one",
},
{ questions: 1},
);
},
- getPaperByNumber: function(aid, number, callback)
+ getPaperByNumber: function(eid, number, callback)
{
- db.assessments.findOne(
+ db.evaluations.findOne(
{
- _id: aid,
+ _id: eid,
"papers.number": number,
},
(err,a) => {
// NOTE: no callbacks for 2 next functions, failures are not so important
// (because monitored: teachers can see what's going on)
- addDisco: function(aid, number, deltaTime)
+ addDisco: function(eid, number, deltaTime)
{
- db.assessments.update(
+ db.evaluations.update(
{
- _id: aid,
+ _id: eid,
"papers.number": number,
},
{ $inc: {
);
},
- setDiscoTime: function(aid, number)
+ setDiscoTime: function(eid, number)
{
- db.assessments.update(
+ db.evaluations.update(
{
- _id: aid,
+ _id: eid,
"papers.number": number,
},
{ $set: { "papers.$.discoTime": Date.now() } }
);
},
- getDiscoTime: function(aid, number, cb)
+ getDiscoTime: function(eid, number, cb)
{
- db.assessments.findOne(
- { _id: aid },
+ db.evaluations.findOne(
+ { _id: eid },
(err,a) => {
if (!!err)
return cb(err, null);
);
},
- hasInput: function(aid, number, password, idx, cb)
+ hasInput: function(eid, number, password, idx, cb)
{
- db.assessments.findOne(
+ db.evaluations.findOne(
{
- _id: aid,
+ _id: eid,
"papers.number": number,
"papers.password": password,
},
},
// https://stackoverflow.com/questions/27874469/mongodb-push-in-nested-array
- setInput: function(aid, number, password, input, callback) //input: index + arrayOfInt (or txt)
+ setInput: function(eid, number, password, input, callback) //input: index + arrayOfInt (or txt)
{
- db.assessments.update(
+ db.evaluations.update(
{
- _id: aid,
+ _id: eid,
"papers.number": number,
"papers.password": password,
},
);
},
- endAssessment: function(aid, number, password, callback)
+ endEvaluation: function(eid, number, password, callback)
{
- db.assessments.update(
+ db.evaluations.update(
{
- _id: aid,
+ _id: eid,
"papers.number": number,
"papers.password": password,
},
);
},
- remove: function(aid, cb)
+ remove: function(eid, cb)
{
- db.assessments.remove(
- { _id: aid },
+ db.evaluations.remove(
+ { _id: eid },
cb
);
},
removeGroup: function(cid, cb)
{
- db.assessments.remove(
+ db.evaluations.remove(
{ cid: cid },
cb
);
CourseModel.getByPath(user._id, code, (err2,course) => {
if (!!err2 || !course)
return cb(err2 || {errmsg: "Course not found"});
- AssessmentModel.getByPath(course._id, name, (err3,assessment) => {
- if (!!err3 || !assessment)
- return cb(err3 || {errmsg: "Assessment not found"});
- cb(null,assessment);
+ EvaluationModel.getByPath(course._id, name, (err3,evaluation) => {
+ if (!!err3 || !evaluation)
+ return cb(err3 || {errmsg: "Evaluation not found"});
+ cb(null,evaluation);
});
});
});
},
- checkPassword: function(aid, number, password, cb)
+ checkPassword: function(eid, number, password, cb)
{
- AssessmentModel.getById(aid, (err,assessment) => {
- if (!!err || !assessment)
- return cb(err, assessment);
- const paperIdx = assessment.papers.findIndex( item => { return item.number == number; });
+ EvaluationModel.getById(eid, (err,evaluation) => {
+ if (!!err || !evaluation)
+ return cb(err, evaluation);
+ const paperIdx = evaluation.papers.findIndex( item => { return item.number == number; });
if (paperIdx === -1)
return cb({errmsg: "Paper not found"}, false);
- cb(null, assessment.papers[paperIdx].password == password);
+ cb(null, evaluation.papers[paperIdx].password == password);
});
},
return cb({errmsg: "Course retrieval failure"});
if (!course.uid.equals(uid))
return cb({errmsg:"Not your course"},undefined);
- // 2) Insert new blank assessment
- AssessmentModel.insert(cid, name, cb);
+ // 2) Insert new blank evaluation
+ EvaluationModel.insert(cid, name, cb);
});
},
- update: function(uid, assessment, cb)
+ update: function(uid, evaluation, cb)
{
- const aid = ObjectId(assessment._id);
- // 1) Check that assessment is owned by user of ID uid
- AssessmentModel.getById(aid, (err,assessmentOld) => {
- if (!!err || !assessmentOld)
- return cb({errmsg: "Assessment retrieval failure"});
- CourseModel.getById(ObjectId(assessmentOld.cid), (err2,course) => {
+ const eid = ObjectId(evaluation._id);
+ // 1) Check that evaluation is owned by user of ID uid
+ EvaluationModel.getById(eid, (err,evaluationOld) => {
+ if (!!err || !evaluationOld)
+ return cb({errmsg: "Evaluation retrieval failure"});
+ CourseModel.getById(ObjectId(evaluationOld.cid), (err2,course) => {
if (!!err2 || !course)
return cb({errmsg: "Course retrieval failure"});
if (!course.uid.equals(uid))
return cb({errmsg:"Not your course"},undefined);
- // 2) Replace assessment
- delete assessment["_id"];
- assessment.cid = ObjectId(assessment.cid);
- AssessmentModel.replace(aid, assessment, cb);
+ // 2) Replace evaluation
+ delete evaluation["_id"];
+ evaluation.cid = ObjectId(evaluation.cid);
+ EvaluationModel.replace(eid, evaluation, cb);
});
});
},
// Set password in responses collection
- startSession: function(aid, number, password, cb)
+ startSession: function(eid, number, password, cb)
{
- AssessmentModel.getPaperByNumber(aid, number, (err,paper) => {
+ EvaluationModel.getPaperByNumber(eid, number, (err,paper) => {
if (!!err)
return cb(err,null);
if (!paper && !!password)
if (paper.password != password)
return cb({errmsg: "Wrong password"});
}
- AssessmentModel.getQuestions(aid, (err2,questions) => {
+ EvaluationModel.getQuestions(eid, (err2,questions) => {
if (!!err2)
return cb(err2,null);
if (!!paper)
return cb(null,{paper:paper});
const pwd = TokenGen.generate(12); //arbitrary number, 12 seems enough...
- db.assessments.update(
- { _id: aid },
+ db.evaluations.update(
+ { _id: eid },
{ $push: { papers: {
number: number,
startTime: Date.now(),
});
},
- newAnswer: function(aid, number, password, input, cb)
+ newAnswer: function(eid, number, password, input, cb)
{
// Check that student hasn't already answered
- AssessmentModel.hasInput(aid, number, password, input.index, (err,ret) => {
+ EvaluationModel.hasInput(eid, number, password, input.index, (err,ret) => {
if (!!err)
return cb(err,null);
if (!!ret)
return cb({errmsg:"Question already answered"},null);
- AssessmentModel.setInput(aid, number, password, input, (err2,ret2) => {
+ EvaluationModel.setInput(eid, number, password, input, (err2,ret2) => {
if (!!err2 || !ret2)
return cb(err2,ret2);
return cb(null,ret2);
// NOTE: no callbacks for next function, failures are not so important
// (because monitored: teachers can see what's going on)
- newConnection: function(aid, number)
+ newConnection: function(eid, number)
{
//increment discoCount, reset discoTime to NULL, update totalDisco
- AssessmentModel.getDiscoTime(aid, number, (err,discoTime) => {
+ EvaluationModel.getDiscoTime(eid, number, (err,discoTime) => {
if (!!discoTime)
- AssessmentModel.addDisco(aid, number, Date.now() - discoTime);
+ EvaluationModel.addDisco(eid, number, Date.now() - discoTime);
});
},
}
-module.exports = AssessmentModel;
+module.exports = EvaluationModel;
$$\begin{matrix}7 & x\\y & -3\end{matrix}$$</div>
* ...
++ fixed + question time (syntax ?)
+
--> input of type text (number, or vector, or matrix e.g. in R syntax)
--> parameter stored in question.param (TODO)
"input",
{
domProps: {
- checked: this.inputs[q.index][idx],
- disabled: monitoring,
+ checked: !!this.inputs && this.inputs[q.index][idx],
+ disabled: monitoring || this.display == "solution",
},
attrs: {
id: this.inputId(q.index,idx),
"class": {
option: true,
choiceCorrect: this.display == "solution" && q.answer.includes(idx),
- choiceWrong: this.display == "solution" && this.inputs[q.index][idx] && !q.answer.includes(idx),
+ choiceWrong: this.display == "solution" && !!this.inputs && this.inputs[q.index][idx] && !q.answer.includes(idx),
},
},
option
/*Draft format (compiled to json)
-> Some global (HTML) intro
-
<some html question (or/+ exercise intro)>
<some html subQuestion>
new Vue({
el: '#course',
data: {
- display: "assessments", //or "students", or "grades" (admin mode)
+ display: "evaluations", //or "students", or "grades" (admin mode)
course: course,
- mode: "view", //or "edit" (some assessment)
monitorPwd: "",
- newAssessment: { name: "" },
- assessmentArray: assessmentArray,
- assessmentIndex: 0, //current edited assessment index
- assessment: { }, //copy of assessment at editing index in array
- assessmentText: "", //questions in an assessment, in text format
+ newEvaluation: { name: "" },
+ evaluationArray: evaluationArray,
+ mode: "view", //or "edit" (some evaluation)
+ evaluationIndex: 0, //current edited evaluation index
+ evaluation: { }, //copy of evaluation at editing index in array
+ questionsText: "", //questions in an evaluation, in text format
},
mounted: function() {
+
+
+
$('.modal').each( (i,elem) => {
- if (elem.id != "assessmentEdit")
+ if (elem.id != "evaluationEdit")
$(elem).modal();
});
- $('ul.tabs').tabs();
- $('#assessmentEdit').modal({
+ $('ul.tabs').tabs(); //--> migrate to grade.js
+
+
+
+ $('#evaluationEdit').modal({
complete: () => {
- this.parseAssessment();
- Vue.nextTick( () => {
- $("#questionList").find("code[class^=language-]").each( (i,elem) => {
- Prism.highlightElement(elem);
- });
- MathJax.Hub.Queue(["Typeset",MathJax.Hub,"questionList"]);
- });
+ this.parseEvaluation();
+ Vue.nextTick(statementsLibsRefresh);
},
});
},
},
});
},
- // ASSESSMENT:
- addAssessment: function() {
+ // evaluation:
+ addEvaluation: function() {
if (!admin)
return;
// modal, fill code and description
- let error = Validator.checkObject(this.newAssessment, "Assessment");
+ let error = Validator.checkObject(this.newEvaluation, "Evaluation");
if (!!error)
return alert(error);
else
- $('#newAssessment').modal('close');
- $.ajax("/assessments",
+ $('#newEvaluation').modal('close');
+ $.ajax("/evaluations",
{
method: "POST",
data: {
- name: this.newAssessment.name,
+ name: this.newEvaluation.name,
cid: course._id,
},
dataType: "json",
success: res => {
if (!res.errmsg)
{
- this.newAssessment["name"] = "";
- this.assessmentArray.push(res);
+ this.newEvaluation["name"] = "";
+ this.evaluationArray.push(res);
}
else
alert(res.errmsg);
$("#" + id).modal("open");
Materialize.updateTextFields(); //textareas, time field...
},
- updateAssessment: function() {
- $.ajax("/assessments", {
+ updateEvaluation: function() {
+ $.ajax("/evaluations", {
method: "PUT",
- data: {assessment: JSON.stringify(this.assessment)},
+ data: {evaluation: JSON.stringify(this.evaluation)},
dataType: "json",
success: res => {
if (!res.errmsg)
{
- this.assessmentArray[this.assessmentIndex] = this.assessment;
+ this.evaluationArray[this.evaluationIndex] = this.evaluation;
this.mode = "view";
}
else
},
});
},
- deleteAssessment: function(assessment) {
+ deleteEvaluation: function(evaluation) {
if (!admin)
return;
- if (confirm("Delete assessment '" + assessment.name + "' ?"))
+ if (confirm("Delete evaluation '" + evaluation.name + "' ?"))
{
- $.ajax("/assessments",
+ $.ajax("/evaluations",
{
method: "DELETE",
- data: { qid: this.assessment._id },
+ data: { qid: this.evaluation._id },
dataType: "json",
success: res => {
if (!res.errmsg)
- this.assessmentArray.splice( this.assessmentArray.findIndex( item => {
- return item._id == assessment._id;
+ this.evaluationArray.splice( this.evaluationArray.findIndex( item => {
+ return item._id == evaluation._id;
}), 1 );
else
alert(res.errmsg);
}
},
toggleState: function(questionIndex) {
- // add or remove from activeSet of current assessment
- let activeIndex = this.assessment.activeSet.findIndex( item => { return item == questionIndex; });
+ // add or remove from activeSet of current evaluation
+ let activeIndex = this.evaluation.activeSet.findIndex( item => { return item == questionIndex; });
if (activeIndex >= 0)
- this.assessment.activeSet.splice(activeIndex, 1);
+ this.evaluation.activeSet.splice(activeIndex, 1);
else
- this.assessment.activeSet.push(questionIndex);
+ this.evaluation.activeSet.push(questionIndex);
},
- setAssessmentText: function() {
+ setEvaluationText: function() {
let txt = "";
- this.assessment.questions.forEach( q => {
+ this.evaluation.questions.forEach( q => {
txt += q.wording; //already ended by \n
q.options.forEach( (o,i) => {
let symbol = q.answer.includes(i) ? "+" : "-";
});
txt += "\n"; //separate questions by new line
});
- this.assessmentText = txt;
+ this.questionsText = txt;
},
- parseAssessment: function() {
+ parseEvaluation: function() {
let questions = [ ];
- let lines = this.assessmentText.split("\n").map( L => { return L.trim(); })
+ let lines = this.questionsText.split("\n").map( L => { return L.trim(); })
lines.push(""); //easier parsing
let emptyQuestion = () => {
return {
}
}
});
- this.assessment.questions = questions;
+ this.evaluation.questions = questions;
},
- actionAssessment: function(index) {
+ actionEvaluation: function(index) {
if (admin)
{
// Edit screen
- this.assessmentIndex = index;
- this.assessment = $.extend(true, {}, this.assessmentArray[index]);
- this.setAssessmentText();
+ this.evaluationIndex = index;
+ this.evaluation = $.extend(true, {}, this.evaluationArray[index]);
+ this.setEvaluationText();
this.mode = "edit";
- Vue.nextTick( () => {
- $("#questionList").find("code[class^=language-]").each( (i,elem) => {
- Prism.highlightElement(elem);
- });
- MathJax.Hub.Queue(["Typeset",MathJax.Hub,"questionList"]);
- });
+ Vue.nextTick(statementsLibsRefresh);
}
- else //external user: show assessment
- this.redirect(this.assessmentArray[index].name);
+ else //external user: show evaluation
+ this.redirect(this.evaluationArray[index].name);
},
- redirect: function(assessmentName) {
- document.location.href = "/" + initials + "/" + course.code + "/" + assessmentName;
+ redirect: function(evaluationName) {
+ document.location.href = "/" + initials + "/" + course.code + "/" + evaluationName;
},
setPassword: function() {
let hashPwd = Sha1.Compute(this.monitorPwd);
let socket = null; //monitor answers in real time
-if (assessment.mode == "secure" && !checkWindowSize())
+if (evaluation.mode == "secure" && !checkWindowSize())
document.location.href= "/fullscreen";
function checkWindowSize()
}
new Vue({
- el: "#assessment",
+ el: "#evaluation",
data: {
- assessment: assessment,
+ evaluation: evaluation,
answers: { }, //filled later with answering parameters
student: { }, //filled later (name, password)
// Stage 0: unauthenticated (number),
// 2: locked: password set, exam started
// 3: completed
// 4: show answers
- remainingTime: assessment.time, //integer or array
- stage: assessment.mode != "open" ? 0 : 1,
+ remainingTime: evaluation.time, //integer or array
+ stage: evaluation.mode != "open" ? 0 : 1,
warnMsg: "",
},
computed: {
countdown: function() {
- const remainingTime = assessment.display == "one" && _.isArray(assessment.time)
+ const remainingTime = evaluation.display == "one" && _.isArray(evaluation.time)
? this.remainingTime[this.answers.index]
: this.remainingTime;
let seconds = remainingTime % 60;
},
mounted: function() {
$(".modal").modal();
- if (["exam","open"].includes(assessment.mode))
+ if (["exam","open"].includes(evaluation.mode))
return;
window.addEventListener("blur", () => {
if (this.stage != 2)
return;
- if (assessment.mode == "secure")
+ if (evaluation.mode == "secure")
{
this.sendAnswer();
document.location.href= "/noblur";
else //"watch" mode
socket.emit(message.studentBlur, {number:this.student.number});
}, false);
- if (assessment.mode == "watch")
+ if (evaluation.mode == "watch")
{
window.addEventListener("focus", () => {
if (this.stage != 2)
window.addEventListener("resize", e => {
if (this.stage != 2)
return;
- if (assessment.mode == "secure")
+ if (evaluation.mode == "secure")
{
this.sendAnswer();
document.location.href = "/fullscreen";
method: "GET",
data: {
number: this.student.number,
- cid: assessment.cid,
+ cid: evaluation.cid,
},
dataType: "json",
success: s => {
this.stage = 0;
},
// stage 1 --> 2 (get all questions, set password)
- startAssessment: function() {
+ startEvaluation: function() {
let initializeStage2 = paper => {
$("#leftButton, #rightButton").hide();
// Initialize structured answer(s) based on questions type and nesting (TODO: more general)
if (!!questions)
- assessment.questions = questions;
+ evaluation.questions = questions;
this.answers.inputs = [ ];
- for (let q of assessment.questions)
+ for (let q of evaluation.questions)
this.answers.inputs.push( _(q.options.length).times( _.constant(false) ) );
if (!paper)
{
- this.answers.indices = assessment.fixed
- ? _.range(assessment.questions.length)
- : _.shuffle( _.range(assessment.questions.length) );
+ this.answers.indices = evaluation.fixed
+ ? _.range(evaluation.questions.length)
+ : _.shuffle( _.range(evaluation.questions.length) );
}
else
{
// Resuming
let indices = paper.inputs.map( input => { return input.index; });
- let remainingIndices = _.difference( _.range(assessment.questions.length).map(String), indices );
+ let remainingIndices = _.difference( _.range(evaluation.questions.length).map(String), indices );
this.answers.indices = indices.concat( _.shuffle(remainingIndices) );
}
- if (assessment.time > 0)
+ if (evaluation.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.remainingTime = evaluation.time * 60 - Math.round(deltaTime / 1000);
this.runTimer();
}
this.answers.index = !!paper ? paper.inputs.length : 0;
- this.answers.displayAll = assessment.display == "all";
+ this.answers.displayAll = evaluation.display == "all";
this.answers.showSolution = false;
this.stage = 2;
};
- if (assessment.mode == "open")
+ if (evaluation.mode == "open")
return initializeStage2();
- $.ajax("/assessments/start", {
+ $.ajax("/evaluations/start", {
method: "PUT",
data: {
number: this.student.number,
- aid: assessment._id
+ aid: evaluation._id
},
dataType: "json",
success: s => {
// action (power failure, computer down, ...)
}
socket = io.connect("/", {
- query: "aid=" + assessment._id + "&number=" + this.student.number + "&password=" + this.student.password
+ query: "aid=" + evaluation._id + "&number=" + this.student.number + "&password=" + this.student.password
});
socket.on(message.allAnswers, this.setAnswers);
initializeStage2(s.questions, s.paper);
// stage 2
runGlobalTimer: function() {
- if (assessment.time <= 0)
+ if (evaluation.time <= 0)
return;
let self = this;
setInterval( function() {
if (self.remainingTime <= 0)
{
if (self.stage == 2)
- self.endAssessment();
+ self.endEvaluation();
clearInterval(this);
}
}, 1000);
},
runQuestionTimer: function(idx) {
- if (assessment.questions[idx].time <= 0)
+ if (evaluation.questions[idx].time <= 0)
return;
let self = this; //TODO: question remaining time
setInterval( function() {
if (self.remainingTime <= 0)
{
if (self.stage == 2)
- self.endAssessment();
+ self.endEvaluation();
clearInterval(this);
}
}, 1000);
sendOneAnswer: function() {
const realIndex = this.answers.indices[this.answers.index];
let gotoNext = () => {
- if (this.answers.index == assessment.questions.length - 1)
- this.endAssessment();
+ if (this.answers.index == evaluation.questions.length - 1)
+ this.endEvaluation();
else
this.answers.index++;
this.$children[0].$forceUpdate(); //TODO: bad HACK, and shouldn't be required...
};
- if (assessment.mode == "open")
+ if (evaluation.mode == "open")
return gotoNext(); //only local
let answerData = {
- aid: assessment._id,
+ aid: evaluation._id,
answer: JSON.stringify({
index: realIndex.toString(),
input: this.answers.inputs[realIndex]
number: this.student.number,
password: this.student.password,
};
- $.ajax("/assessments/answer", {
+ $.ajax("/evaluations/answer", {
method: "PUT",
data: answerData,
dataType: "json",
},
// TODO: I don't like that + sending should not be definitive in exam mode with display = all
sendAnswer: function() {
- if (assessment.display == "one")
+ if (evaluation.display == "one")
this.sendOneAnswer();
else
- assessment.questions.forEach(this.sendOneAnswer);
+ evaluation.questions.forEach(this.sendOneAnswer);
},
// stage 2 --> 3 (or 4)
// from a message by statements component, or time over
- endAssessment: function() {
+ endEvaluation: function() {
// Set endTime, destroy password
$("#leftButton, #rightButton").show();
- if (assessment.mode == "open")
+ if (evaluation.mode == "open")
{
this.stage = 4;
this.answers.showSolution = true;
this.answers.displayAll = true;
return;
}
- $.ajax("/assessments/end", {
+ $.ajax("/evaluations/end", {
method: "PUT",
data: {
- aid: assessment._id,
+ aid: evaluation._id,
number: this.student.number,
password: this.student.password,
},
setAnswers: function(m) {
const answers = JSON.parse(m.answers);
for (let i=0; i<answers.length; i++)
- assessment.questions[i].answer = answers[i];
+ evaluation.questions[i].answer = answers[i];
this.answers.showSolution = true;
this.answers.displayAll = true;
this.stage = 4;
new Vue({
el: '#grade',
data: {
- assessmentArray: assessmentArray,
+ evaluationArray: evaluationArray,
settings: {
totalPoints: 20,
halfPoints: false,
let gradesCount = 0;
if (!!this.grades[s.number])
{
- Object.keys(this.grades[s.number]).forEach( assessmentName => {
- s[assessmentName] = this.grades[s.number][assessmentName];
- if (_.isNumeric(s[assessmentName]) && !isNaN(s[assessmentName]))
+ Object.keys(this.grades[s.number]).forEach( evaluationName => {
+ s[evaluationName] = this.grades[s.number][evaluationName];
+ if (_.isNumeric(s[evaluationName]) && !isNaN(s[evaluationName]))
{
- finalGrade += s[assessmentName];
+ finalGrade += s[evaluationName];
gradesCount++;
}
if (gradesCount >= 1)
finalGrade /= gradesCount;
- s["final"] = finalGrade; //TODO: forbid "final" as assessment name
+ s["final"] = finalGrade; //TODO: forbid "final" as evaluation name
});
}
data.push(s); //number,name,group,assessName1...assessNameN,final
});
return _.range(1,maxGrp+1);
},
- grade: function(assessmentIndex, studentNumber) {
- if (!this.grades[assessmentIndex] || !this.grades[assessmentIndex][studentNumber])
+ grade: function(evaluationIndex, studentNumber) {
+ if (!this.grades[evaluationIndex] || !this.grades[evaluationIndex][studentNumber])
return ""; //no grade yet
- return this.grades[assessmentIndex][studentNumber];
+ return this.grades[evaluationIndex][studentNumber];
},
groupId: function(group, prefix) {
return (prefix || "") + "group" + group;
el: "#monitor",
data: {
password: "", //from password field
- assessment: { }, //obtained after authentication
+ evaluation: { }, //obtained after authentication
// Stage 0: unauthenticated (password),
// 1: authenticated (password hash validated), start monitoring
stage: 0,
index : -1,
},
students: [ ], //to know their names
- display: "assessment", //or student's answers
+ display: "evaluation", //or student's answers
},
methods: {
// TODO: redundant code, next 4 funcs already exist in course.js
{
if (!s.present)
continue;
- const paperIdx = this.assessment.papers.findIndex( item => { return item.number == s.number; });
+ const paperIdx = this.evaluation.papers.findIndex( item => { return item.number == s.number; });
if (paperIdx === -1)
return false;
- const paper = this.assessment.papers[paperIdx];
- if (paper.inputs.length < this.assessment.questions.length)
+ const paper = this.evaluation.papers[paperIdx];
+ if (paper.inputs.length < this.evaluation.questions.length)
return false;
}
return true;
getColor: function(number, qIdx) {
// For the moment, green if correct and red if wrong; grey if unanswered yet
// TODO: in-between color for partially right (especially for multi-questions)
- const paperIdx = this.assessment.papers.findIndex( item => { return item.number == number; });
+ const paperIdx = this.evaluation.papers.findIndex( item => { return item.number == number; });
if (paperIdx === -1)
return "grey"; //student didn't start yet
- const inputIdx = this.assessment.papers[paperIdx].inputs.findIndex( item => {
+ const inputIdx = this.evaluation.papers[paperIdx].inputs.findIndex( item => {
const qNum = parseInt(item.index.split(".")[0]); //indexes separated by dots
return qIdx == qNum;
});
if (inputIdx === -1)
return "grey";
- if (_.isEqual(this.assessment.papers[paperIdx].inputs[inputIdx].input, this.assessment.questions[qIdx].answer))
+ if (_.isEqual(this.evaluation.papers[paperIdx].inputs[inputIdx].input, this.evaluation.questions[qIdx].answer))
return "green";
return "red";
},
},
// stage 0 --> 1
startMonitoring: function() {
- $.ajax("/assessments/monitor", {
+ $.ajax("/evaluations/monitor", {
method: "GET",
data: {
password: Sha1.Compute(this.password),
success: s => {
if (!!s.errmsg)
return alert(s.errmsg);
- this.assessment = s.assessment;
- this.answers.inputs = s.assessment.questions.map( q => {
+ this.evaluation = s.evaluation;
+ this.answers.inputs = s.evaluation.questions.map( q => {
let input = _(q.options.length).times( _.constant(false) );
q.answer.forEach( idx => { input[idx] = true; });
return input;
this.students.forEach( s => { s.present = true; }); //a priori...
this.stage = 1;
socket = io.connect("/", {
- query: "aid=" + this.assessment._id + "&secret=" + s.secret
+ query: "aid=" + this.evaluation._id + "&secret=" + s.secret
});
socket.on(message.studentBlur, m => {
const sIdx = this.students.findIndex( item => { return item.number == m.number; });
this.students[sIdx].disco = false;
});
socket.on(message.newAnswer, m => {
- let paperIdx = this.assessment.papers.findIndex( item => {
+ let paperIdx = this.evaluation.papers.findIndex( item => {
return item.number == m.number;
});
if (paperIdx === -1)
{
// First answer
- paperIdx = this.assessment.papers.length;
- this.assessment.papers.push({
+ paperIdx = this.evaluation.papers.length;
+ this.evaluation.papers.push({
number: m.number,
inputs: [ ], //other fields irrelevant here
});
}
// TODO: notations not coherent (input / answer... when, which ?)
- this.assessment.papers[paperIdx].inputs.push(JSON.parse(m.answer)); //input+index
+ this.evaluation.papers[paperIdx].inputs.push(JSON.parse(m.answer)); //input+index
});
},
});
// TODO: disable this button until everyone finished (need ability to mark absents)
socket.emit(
message.allAnswers,
- { answers: JSON.stringify(this.assessment.questions.map( q => { return q.answer; })) }
+ { answers: JSON.stringify(this.evaluation.questions.map( q => { return q.answer; })) }
);
},
},
let Validator = { };
-// Cell in assessment.questions array
+// Cell in evaluation.questions array
Validator.Question = {
"index": "section", //"2.2.1", "3.2", "1" ...etc
"wording": "string",
"password": "password",
};
-Validator.Assessment = {
+Validator.Evaluation = {
"_id": "bson",
"cid": "bson",
"name": "code",
// AJAX requests:
router.use("/", require("./users"));
router.use("/", require("./courses"));
-router.use("/", require("./assessments"));
+router.use("/", require("./evaluations"));
// Pages:
router.use("/", require("./pages"));
let router = require("express").Router();
const access = require("../utils/access");
const UserModel = require("../models/user");
-const AssessmentModel = require("../models/assessment");
+const EvaluationModel = require("../models/evaluation");
const CourseModel = require("../models/course");
const params = require("../config/parameters");
const validator = require("../public/javascripts/utils/validation");
},
};
-router.post("/assessments", access.ajax, access.logged, (req,res) => {
+router.post("/evaluations", access.ajax, access.logged, (req,res) => {
const name = req.body["name"];
const cid = req.body["cid"];
- let error = validator({cid:cid, name:name}, "Assessment");
+ let error = validator({cid:cid, name:name}, "Evaluation");
if (error.length > 0)
return res.json({errmsg:error});
- AssessmentModel.add(req.user._id, ObjectId(cid), name, (err,assessment) => {
- access.checkRequest(res, err, assessment, "Assessment addition failed", () => {
- res.json(assessment);
+ EvaluationModel.add(req.user._id, ObjectId(cid), name, (err,evaluation) => {
+ access.checkRequest(res, err, evaluation, "Evaluation addition failed", () => {
+ res.json(evaluation);
});
});
});
-router.put("/assessments", access.ajax, access.logged, (req,res) => {
- const assessment = JSON.parse(req.body["assessment"]);
- let error = validator(assessment, "Assessment");
+router.put("/evaluations", access.ajax, access.logged, (req,res) => {
+ const evaluation = JSON.parse(req.body["evaluation"]);
+ let error = validator(evaluation, "Evaluation");
if (error.length > 0)
return res.json({errmsg:error});
- assessment.introduction = sanitizeHtml(assessment.introduction, sanitizeOpts);
- assessment.questions.forEach( q => {
+ evaluation.introduction = sanitizeHtml(evaluation.introduction, sanitizeOpts);
+ evaluation.questions.forEach( q => {
q.wording = sanitizeHtml(q.wording, sanitizeOpts);
//q.answer = sanitizeHtml(q.answer); //if text (TODO: it's an array in this case?!)
for (let i=0; i<q.options.length; i++) //if QCM
q.options[i] = sanitizeHtml(q.options[i], sanitizeOpts);
});
- AssessmentModel.update(req.user._id, assessment, (err,ret) => {
- access.checkRequest(res, err, ret, "Assessment update failed", () => {
+ EvaluationModel.update(req.user._id, evaluation, (err,ret) => {
+ access.checkRequest(res, err, ret, "Evaluation update failed", () => {
res.json({});
});
});
});
// Generate and set student password, return it
-router.put("/assessments/start", access.ajax, (req,res) => {
+router.put("/evaluations/start", access.ajax, (req,res) => {
let number = req.body["number"];
- let aid = req.body["aid"];
+ let eid = req.body["eid"];
let password = req.cookies["password"]; //potentially from cookies, resuming
- let error = validator({ _id:aid, papers:[{number:number,password:password || "samplePwd"}] }, "Assessment");
+ let error = validator({ _id:eid, papers:[{number:number,password:password || "samplePwd"}] }, "Evaluation");
if (error.length > 0)
return res.json({errmsg:error});
- AssessmentModel.startSession(ObjectId(aid), number, password, (err,ret) => {
+ EvaluationModel.startSession(ObjectId(eid), number, password, (err,ret) => {
access.checkRequest(res,err,ret,"Failed session initialization", () => {
if (!password)
{
});
});
-router.get("/assessments/monitor", access.ajax, (req,res) => {
+router.get("/evaluations/monitor", access.ajax, (req,res) => {
const password = req.query["password"];
const examName = req.query["aname"];
const courseCode = req.query["ccode"];
access.checkRequest(res,err,course,"Course not found", () => {
if (password != course.password)
return res.json({errmsg: "Wrong password"});
- AssessmentModel.getByRefs(initials, courseCode, examName, (err2,assessment) => {
- access.checkRequest(res,err2,assessment,"Assessment not found", () => {
+ EvaluationModel.getByRefs(initials, courseCode, examName, (err2,evaluation) => {
+ access.checkRequest(res,err2,evaluation,"Evaluation not found", () => {
res.json({
students: course.students,
- assessment: assessment,
+ evaluation: evaluation,
secret: params.secret,
});
});
});
});
-router.put("/assessments/answer", access.ajax, (req,res) => {
- let aid = req.body["aid"];
+router.put("/evaluations/answer", access.ajax, (req,res) => {
+ let eid = req.body["eid"];
let number = req.body["number"];
let password = req.body["password"];
let input = JSON.parse(req.body["answer"]);
- let error = validator({ _id:aid, papers:[{number:number,password:password,inputs:[input]}] }, "Assessment");
+ let error = validator({ _id:eid, papers:[{number:number,password:password,inputs:[input]}] }, "Evaluation");
if (error.length > 0)
return res.json({errmsg:error});
- AssessmentModel.newAnswer(ObjectId(aid), number, password, input, (err,ret) => {
+ EvaluationModel.newAnswer(ObjectId(eid), number, password, input, (err,ret) => {
access.checkRequest(res,err,ret,"Cannot send answer", () => {
res.json({});
});
});
});
-router.put("/assessments/end", access.ajax, (req,res) => {
- let aid = req.body["aid"];
+router.put("/evaluations/end", access.ajax, (req,res) => {
+ let eid = req.body["eid"];
let number = req.body["number"];
let password = req.body["password"];
- let error = validator({ _id:aid, papers:[{number:number,password:password}] }, "Assessment");
+ let error = validator({ _id:eid, papers:[{number:number,password:password}] }, "Evaluation");
if (error.length > 0)
return res.json({errmsg:error});
// Destroy pwd, set endTime
- AssessmentModel.endAssessment(ObjectId(aid), number, password, (err,ret) => {
- access.checkRequest(res,err,ret,"Cannot end assessment", () => {
+ EvaluationModel.endEvaluation(ObjectId(eid), number, password, (err,ret) => {
+ access.checkRequest(res,err,ret,"Cannot end evaluation", () => {
res.clearCookie('password');
res.json({});
});
let router = require("express").Router();
const access = require("../utils/access");
const UserModel = require("../models/user");
-const AssessmentModel = require("../models/assessment");
+const EvaluationModel = require("../models/evaluation");
const CourseModel = require("../models/course");
// Actual pages (least specific last)
-// List initials and count assessments
+// List initials and count evaluations
router.get("/", (req,res) => {
UserModel.getAll( (err,userArray) => {
if (!!err)
let code = req.params["courseCode"];
CourseModel.getByRefs(initials, code, (err,course) => {
access.checkRequest(res, err, course, "Course not found", () => {
- AssessmentModel.getByCourse(course._id, (err2,assessmentArray) => {
+ EvaluationModel.getByCourse(course._id, (err2,evaluationArray) => {
if (!!err)
return res.json(err);
access.getUser(req, res, (err2,user) => {
res.render("course", {
title: "course " + initials + "/" + code,
course: course,
- assessmentArray: assessmentArray,
+ evaluationArray: evaluationArray,
teacher: isTeacher,
initials: initials,
});
});
});
-// Display assessment (exam or open status)
-router.get("/:initials([a-z0-9]+)/:courseCode([a-z0-9._-]+)/:assessmentName([a-z0-9._-]+)", (req,res) => {
+// Display evaluation (exam or open status)
+router.get("/:initials([a-z0-9]+)/:courseCode([a-z0-9._-]+)/:evaluationName([a-z0-9._-]+)", (req,res) => {
let initials = req.params["initials"];
let code = req.params["courseCode"];
- let name = req.params["assessmentName"];
- AssessmentModel.getByRefs(initials, code, name, (err,assessment) => {
- access.checkRequest(res, err, assessment, "Assessment not found", () => {
- if (!assessment.active)
- return res.json({errmsg: "Assessment is idle"});
- delete assessment["papers"]; //always remove recorded students answers
- if (assessment.mode == "exam")
+ let name = req.params["evaluationName"];
+ EvaluationModel.getByRefs(initials, code, name, (err,evaluation) => {
+ access.checkRequest(res, err, evaluation, "Evaluation not found", () => {
+ if (!evaluation.active)
+ return res.json({errmsg: "Evaluation is idle"});
+ delete evaluation["papers"]; //always remove recorded students answers
+ if (evaluation.mode == "exam")
{
if (!!req.headers['user-agent'].match(/(SpecialAgent|HeadlessChrome|PhantomJS)/))
{
return res.json({errmsg: "Headless browser detected"});
}
// Strip questions if exam mode (stepwise process)
- delete assessment["questions"];
+ delete evaluation["questions"];
}
- res.render("assessment", {
- title: "assessment " + initials + "/" + code + "/" + name,
- assessment: assessment,
+ res.render("evaluation", {
+ title: "evaluation " + initials + "/" + code + "/" + name,
+ evaluation: evaluation,
});
});
});
});
// Monitor: --> after identification (password), always send secret with requests
-router.get("/:initials([a-z0-9]+)/:courseCode([a-z0-9._-]+)/:assessmentName([a-z0-9._-]+)/monitor", (req,res) => {
+router.get("/:initials([a-z0-9]+)/:courseCode([a-z0-9._-]+)/:evaluationName([a-z0-9._-]+)/monitor", (req,res) => {
let initials = req.params["initials"];
let code = req.params["courseCode"];
- let name = req.params["assessmentName"];
+ let name = req.params["evaluationName"];
// TODO: if (main) teacher, also send secret, saving one request
res.render("monitor", {
- title: "monitor assessment " + code + "/" + name,
+ title: "monitor evaluation " + code + "/" + name,
initials: initials,
courseCode: code,
examName: name,
// courses
// unique (code,uid)
// index (code,uid)
-// assessments
+// evaluations
// unique (cid, name)
// index (cid, name)
-// db.assessments.createIndex( { cid: 1, name: 1 } );
+// db.evaluations.createIndex( { cid: 1, name: 1 } );
// https://docs.mongodb.com/manual/core/index-compound/
const message = require("./public/javascripts/utils/socketMessages");
const params = require("./config/parameters");
-const AssessmentModel = require("./models/assessment");
+const EvaluationModel = require("./models/evaluation");
const ObjectId = require("bson-objectid");
module.exports = function(io)
{
const number = socket.handshake.query.number;
const password = socket.handshake.query.password;
- AssessmentModel.checkPassword(ObjectId(aid), number, password, (err,ret) => {
+ EvaluationModel.checkPassword(ObjectId(aid), number, password, (err,ret) => {
if (!!err || !ret)
return; //wrong password, or some unexpected error...
- AssessmentModel.newConnection(ObjectId(aid), number);
+ EvaluationModel.newConnection(ObjectId(aid), number);
socket.broadcast.to(aid + "_teacher").emit(message.studentConnect, {number: number});
socket.join(aid + "_student");
socket.on(message.newAnswer, m => { //got answer from student client
socket.broadcast.to(aid + "_teacher").emit(message.studentFullscreen, m);
});
socket.on("disconnect", () => { //notify monitor + server
- AssessmentModel.setDiscoTime(ObjectId(aid), number);
+ EvaluationModel.setDiscoTime(ObjectId(aid), number);
socket.broadcast.to(aid + "_teacher").emit(message.studentDisconnect, {number: number});
});
});
block content
.container#course
if teacher
- #newAssessment.modal
+ #newevaluation.modal
.modal-content
- form(@submit.prevent="addAssessment")
+ form(@submit.prevent="addevaluation")
.input-field
- input#assessmentName(type="text" v-model="newAssessment.name" required)
- label(for="assessmentName") Name
+ input#evaluationName(type="text" v-model="newEvaluation.name" required)
+ label(for="evaluationName") Name
.modal-footer
.center-align
- a.waves-effect.waves-light.btn(href="#!" @click="addAssessment()")
+ a.waves-effect.waves-light.btn(href="#!" @click="addEvaluation()")
span Submit
i.material-icons.right send
- .row(v-show="mode=='view'")
-
-
-
-
-
-
-
-
- #assessmentEdit
- form
- p
- input#active(type="checkbox" v-model="assessment.active")
- label(for="active") Assessment is active
- div
- h4 Questions mode:
- span(title="Exam mode, secured (class only): students cannot lose focus or exit fullscreen")
- input#secure(name="status" type="radio" value="secure" v-model="assessment.mode")
- label(for="secure") secure
- span(title="Exam mode, watched (class only): teachers are notified when students lose focus or resize window")
- input#watch(name="status" type="radio" value="watch" v-model="assessment.mode")
- label(for="watch") watch
- span(title="Exam mode, unwatched: students can browse the web freely")
- input#exam(name="status" type="radio" value="exam" v-model="assessment.mode")
- label(for="exam") exam
- span(title="Questions list open to the world (useful mode after an exam, or for a 'questions bank'")
- input#open(name="status" type="radio" value="open" v-model="assessment.mode")
- label(for="open") open
- p
- input#fixed(type="checkbox" v-model="assessment.fixed")
- label(for="fixed") Fixed questions order
- div
- h4 Display type:
- span(title="Show only one question at a time (with potential sub-questions)")
- input#displayOne(name="display" type="radio" value="one" v-model="assessment.display")
- label(for="displayOne") one
- span(title="Always show all questions (with an optional navigator)")
- input#displayAll(name="display" type="radio" value="all" v-model="assessment.display")
- label(for="displayAll") all
- .input-field
- input#time(type="number" v-model.number="assessment.time")
- label(for="time") Time (minutes)
- .input-field
- textarea#introduction.materialize-textarea(v-model="assessment.introduction")
- label(for="introduction") Introduction
- .input-field
- textarea#assessmentEdition.materialize-textarea(v-model="assessmentText")
- label(for="assessmentEdition") Assessment in text format
-
-
-
-
-
-
-
- .col.s12.m10.offset-m1
- if teacher
+ .row
+ .col.s12.m10.offset-m1
h4.title(@click="toggleDisplay('students')") Students
.card(v-show="display=='students'")
.center-align
td {{ student.number }}
td {{ student.name }}
td {{ student.group }}
- h4.title(@click="toggleDisplay('assessments')") Assessments
- .card(v-show="display=='assessments'")
+ .row
+ .col.s12.m10.offset-m1
+ h4.title(@click="toggleDisplay('evaluations')") evaluations
+ .card(v-show="display=='evaluations'")
if teacher
.center-align
- a.on-left.waves-effect.waves-light.btn.modal-trigger(href="#newAssessment") New assessment
+ a.on-left.waves-effect.waves-light.btn.modal-trigger(href="#newevaluation") New evaluation
input#password(type="password" v-model="monitorPwd" @keyup.enter="setPassword"
placeholder="Password" title="Monitoring password")
table
th Name
th Mode
th #Questions
- th Time
tbody
- tr.assessment(v-for="(assessment,i) in assessmentArray" :class="{idle:!assessment.active}"
- @click.left="actionAssessment(i)" @contextmenu.prevent="deleteAssessment(assessment)")
- td {{ assessment.name }}
- td {{ assessment.mode }}
- td {{ assessment.questions.reduce( (a,b) => { return b.active ? a+1 : a; }, 0) }}
- td {{ assessment.time }}
+ tr.evaluation(v-for="(evaluation,i) in evaluationArray" :class="{idle:!evaluation.active}"
+ @click.left="actionevaluation(i)" @contextmenu.prevent="deleteevaluation(evaluation)")
+ td {{ evaluation.name }}
+ td {{ evaluation.mode }}
+ td {{ evaluation.questions.length }}
if teacher
- .row(v-show="mode=='edit'")
+ .row
.col.s12.m10.offset-m1
-
-
-
-
- // TODO: always edit mode, with a modal preview
-
-
-
- h4 {{ assessment.name }}
- .card
- .center-align
- button.waves-effect.waves-light.btn.on-left(@click="materialOpenModal('assessmentSettings')") Settings
- button.waves-effect.waves-light.btn.on-left(@click="materialOpenModal('assessmentEdit')") Content
- button.waves-effect.waves-light.btn(@click="redirect(assessment.name)") View
- #questionList
- .introduction(v-html="assessment.introduction")
- .question(v-for="(question,i) in assessment.questions" :class="{questionInactive:!question.active}")
- .wording(v-html="question.wording")
- .option(v-for="(option,j) in question.options" :class="{choiceCorrect:question.answer.includes(j)}" v-html="option")
- p
- input(:id="checkboxFixedId(i)" type="checkbox" v-model="question.fixed")
- label.on-left(:for="checkboxFixedId(i)") Fixed
- input(:id="checkboxActiveId(i)" type="checkbox" v-model="question.active")
- label(:for="checkboxActiveId(i)") Active
- input(time default 0 = untimed)
- label Time for the question
+ h4 {{ evaluation.name }}
+ .card(v-show="mode=='view'")
.center-align
- button.waves-effect.waves-light.btn.on-left(@click="mode='view'") Cancel
- button.waves-effect.waves-light.btn(@click="updateAssessment") Send
+ button.waves-effect.waves-light.btn.on-left(@click="mode='edit'") Edit
+ button.waves-effect.waves-light.btn(@click="redirect(evaluation.name)") View
+ div
+ .introduction(v-html="evaluation.introduction")
+ statements(:questions="evaluation.questions" :display="solution")
+ .card(v-show="mode=='edit'")
+ form(@submit.prevent)
+ p
+ input#active(type="checkbox" v-model="evaluation.active")
+ label(for="active") evaluation is active
+ div
+ h4 Questions mode:
+ span(title="Exam mode, secured (class only): students cannot lose focus or exit fullscreen")
+ input#secure(name="status" type="radio" value="secure" v-model="evaluation.mode")
+ label(for="secure") secure
+ span(title="Exam mode, watched (class only): teachers are notified when students lose focus or resize window")
+ input#watch(name="status" type="radio" value="watch" v-model="evaluation.mode")
+ label(for="watch") watch
+ span(title="Exam mode, unwatched: students can browse the web freely")
+ input#exam(name="status" type="radio" value="exam" v-model="evaluation.mode")
+ label(for="exam") exam
+ span(title="Questions list open to the world (useful mode after an exam, or for a 'questions bank'")
+ input#open(name="status" type="radio" value="open" v-model="evaluation.mode")
+ label(for="open") open
+ p
+ input#fixed(type="checkbox" v-model="evaluation.fixed")
+ label(for="fixed") Fixed questions order
+ div
+ h4 Display type:
+ span(title="Show only one question at a time (with potential sub-questions)")
+ input#displayOne(name="display" type="radio" value="one" v-model="evaluation.display")
+ label(for="displayOne") one
+ span(title="Always show all questions (with an optional navigator)")
+ input#displayAll(name="display" type="radio" value="all" v-model="evaluation.display")
+ label(for="displayAll") all
+ .input-field
+ input#time(type="number" v-model.number="evaluation.time")
+ label(for="time") Time (minutes)
+ .input-field
+ textarea#introduction.materialize-textarea(v-model="evaluation.introduction")
+ label(for="introduction") Introduction
+ .input-field
+ textarea#evaluationEdition.materialize-textarea(v-model="evaluationText")
+ label(for="evaluationEdition") evaluation in text format
+ .center-align
+ button.waves-effect.waves-light.btn.on-left(@click="updateEvaluation()") Send
+ button.waves-effect.waves-light.btn(@click="mode='view'") Cancel
block append javascripts
script(src="//cdnjs.cloudflare.com/ajax/libs/PapaParse/4.3.6/papaparse.min.js")
script.
- let assessmentArray = !{JSON.stringify(assessmentArray)};
+ let evaluationArray = !{JSON.stringify(evaluationArray)};
const course = !{JSON.stringify(course)};
const initials = "#{initials}";
const admin = #{teacher};
script(src="/javascripts/utils/sha1.js")
script(src="/javascripts/utils/validation.js")
+ script(src="/javascripts/components/statements.js")
script(src="/javascripts/course.js")
thead
tr
th Number
- th(v-for="assessment in assessmentArray") {{ assessment.name }}
+ th(v-for="evaluation in evaluationArray") {{ evaluation.name }}
tbody
tr.grade(v-for="student in studentList(group)")
td {{ student.number }}
- td(v-for="(assessment,i) in assessmentArray" @click="togglePresence(student.number,i)")
+ td(v-for="(evaluation,i) in evaluationArray" @click="togglePresence(student.number,i)")
| {{ grade(i,student.number) }}
.modal-footer
.center-align
thead
tr
th Name
- th(v-for="(q,i) in assessment.questions") Q.{{ (i+1) }}
+ th(v-for="(q,i) in evaluation.questions") Q.{{ (i+1) }}
tbody
- tr.assessment(v-for="s in studentList(group)")
+ tr.evaluation(v-for="s in studentList(group)")
td {{ s.name }}
- td(v-for="(q,i) in assessment.questions" :style="{background-color: getColor(number,i)}" @click="seeDetails(number,i)")
- h4.title(@click="toggleDisplay('assessment')") Assessment
- div(v-show="display=='assessment'")
+ td(v-for="(q,i) in evaluation.questions" :style="{background-color: getColor(number,i)}" @click="seeDetails(number,i)")
+ h4.title(@click="toggleDisplay('evaluation')") evaluation
+ div(v-show="display=='evaluation'")
.card
- .introduction(v-html="assessment.introduction")
+ .introduction(v-html="evaluation.introduction")
.card
- statements(:questions="assessment.questions" :answers:"answers")
+ statements(:questions="evaluation.questions" :answers:"answers")
thead
tr
th Name
- th(v-for="(q,i) in assessment.questions") Q.{{ (i+1) }}
+ th(v-for="(q,i) in evaluation.questions") Q.{{ (i+1) }}
tbody
- tr.assessment(v-for="s in studentList(group)" :class="{absent:!s.present}")
+ tr.evaluation(v-for="s in studentList(group)" :class="{absent:!s.present}")
td(:class="{blur:!!s.blur,resize:!!s.resize,disconnect:!!s.disco}" @click="togglePresence(s)") {{ s.name }}
- td(v-for="(q,i) in assessment.questions" :style="{backgroundColor: getColor(s.number,i)}" @click="seeDetails(s.number,i)")
- h4.title(@click="toggleDisplay('assessment')") Assessment
- div(v-show="display=='assessment'")
+ td(v-for="(q,i) in evaluation.questions" :style="{backgroundColor: getColor(s.number,i)}" @click="seeDetails(s.number,i)")
+ h4.title(@click="toggleDisplay('evaluation')") evaluation
+ div(v-show="display=='evaluation'")
.card
- .introduction(v-html="assessment.introduction")
+ .introduction(v-html="evaluation.introduction")
.card
- statements(:questions="assessment.questions" :answers="answers")
+ statements(:questions="evaluation.questions" :answers="answers")
block append javascripts
script.