1 let socket
= null; //monitor answers in real time
3 if (assessment
.mode
== "secure" && !checkWindowSize())
4 document
.location
.href
= "/fullscreen";
6 function checkWindowSize()
8 // NOTE: temporarily accept smartphone (security hole: pretend being a smartphone on desktop browser...)
9 if (navigator
.userAgent
.match(/(iPhone|iPod|iPad|Android|BlackBerry)/))
11 // 3 is arbitrary, but a small tolerance is required (e.g. in Firefox)
12 return window
.innerWidth
>= screen
.width
-3 && window
.innerHeight
>= screen
.height
-3;
18 assessment: assessment
,
19 answers: { }, //filled later with answering parameters
20 student: { }, //filled later (name, password)
21 // Stage 0: unauthenticated (number),
22 // 1: authenticated (got a name, unvalidated)
23 // 2: locked: password set, exam started
26 stage: assessment
.mode
!= "open" ? 0 : 1,
27 remainingTime: 0, //global, in seconds
31 countdown: function() {
32 let seconds
= this.remainingTime
% 60;
33 let minutes
= Math
.floor(this.remainingTime
/ 60);
34 return this.padWithZero(minutes
) + ":" + this.padWithZero(seconds
);
36 showAnswers: function() {
37 return this.stage
== 4;
42 if (["exam","open"].includes(assessment
.mode
))
44 window
.addEventListener("blur", () => {
47 if (assessment
.mode
== "secure")
49 this.trySendCurrentAnswer();
50 document
.location
.href
= "/noblur";
53 socket
.emit(message
.studentBlur
, {number:this.student
.number
});
55 if (assessment
.mode
== "watch")
57 window
.addEventListener("focus", () => {
60 socket
.emit(message
.studentFocus
, {number:this.student
.number
});
63 window
.addEventListener("resize", e
=> {
66 if (assessment
.mode
== "secure")
68 this.trySendCurrentAnswer();
69 document
.location
.href
= "/fullscreen";
73 if (checkWindowSize())
74 socket
.emit(message
.studentFullscreen
, {number:this.student
.number
});
76 socket
.emit(message
.studentResize
, {number:this.student
.number
});
81 // In case of AJAX errors
82 showWarning: function(message
) {
83 this.warnMsg
= message
;
84 $("#warning").modal("open");
86 padWithZero: function(x
) {
91 trySendCurrentAnswer: function() {
96 getStudent: function(cb
) {
97 $.ajax("/get/student", {
100 number: this.student
.number
,
106 return this.showWarning(s
.errmsg
);
108 this.student
= s
.student
;
109 Vue
.nextTick( () => { Materialize
.updateTextFields(); });
116 cancelStudent: function() {
119 // stage 1 --> 2 (get all questions, set password)
120 startAssessment: function() {
121 let initializeStage2
= (questions
,paper
) => {
122 $("#leftButton, #rightButton").hide();
123 if (assessment
.time
> 0)
126 // TODO: distinguish total exam time AND question time
128 const deltaTime
= !!paper
? Date
.now() - paper
.startTime : 0;
129 this.remainingTime
= assessment
.time
* 60 - Math
.round(deltaTime
/ 1000);
132 // Initialize structured answer(s) based on questions type and nesting (TODO: more general)
134 assessment
.questions
= questions
;
135 this.answers
.inputs
= [ ];
136 for (let q
of assessment
.questions
)
137 this.answers
.inputs
.push( _(q
.options
.length
).times( _
.constant(false) ) );
140 this.answers
.indices
= assessment
.fixed
141 ? _
.range(assessment
.questions
.length
)
142 : _
.shuffle( _
.range(assessment
.questions
.length
) );
147 let indices
= paper
.inputs
.map( input
=> { return input
.index
; });
148 let remainingIndices
= _
.difference( _
.range(assessment
.questions
.length
).map(String
), indices
);
149 this.answers
.indices
= indices
.concat( _
.shuffle(remainingIndices
) );
151 this.answers
.index
= !!paper
? paper
.inputs
.length : 0;
152 this.answers
.displayAll
= assessment
.display
== "all";
153 this.answers
.showSolution
= false;
156 if (assessment
.mode
== "open")
157 return initializeStage2();
158 $.ajax("/start/assessment", {
161 number: this.student
.number
,
167 return this.showWarning(s
.errmsg
);
170 // Resuming: receive stored answers + startTime
171 this.student
.password
= s
.paper
.password
;
172 this.answers
.inputs
= s
.paper
.inputs
.map( inp
=> { return inp
.input
; });
176 this.student
.password
= s
.password
;
177 // Got password: students answers locked to this page until potential teacher
178 // action (power failure, computer down, ...)
180 socket
= io
.connect("/", {
181 query: "aid=" + assessment
._id
+ "&number=" + this.student
.number
+ "&password=" + this.student
.password
183 socket
.on(message
.allAnswers
, this.setAnswers
);
184 initializeStage2(s
.questions
, s
.paper
);
191 runGlobalTimer: function() {
192 if (assessment
.time
<= 0)
195 setInterval( function() {
196 self
.remainingTime
--;
197 if (self
.remainingTime
<= 0)
200 self
.endAssessment();
205 runQuestionTimer: function(idx
) {
206 if (assessment
.questions
[idx
].time
<= 0)
208 let self
= this; //TODO: question remaining time
209 setInterval( function() {
210 self
.remainingTime
--;
211 if (self
.remainingTime
<= 0)
214 self
.endAssessment();
220 //TODO: get question after sending answer
223 sendOneAnswer: function() {
224 const realIndex
= this.answers
.indices
[this.answers
.index
];
225 let gotoNext
= () => {
226 if (this.answers
.index
== assessment
.questions
.length
- 1)
227 this.endAssessment();
229 this.answers
.index
++;
230 this.$children
[0].$forceUpdate(); //TODO: bad HACK, and shouldn't be required...
232 if (assessment
.mode
== "open")
233 return gotoNext(); //only local
236 answer: JSON
.stringify({
237 index: realIndex
.toString(),
238 input: this.answers
.inputs
[realIndex
]
239 .map( (tf
,i
) => { return {val:tf
,idx:i
}; } )
240 .filter( item
=> { return item
.val
; })
241 .map( item
=> { return item
.idx
; })
243 number: this.student
.number
,
244 password: this.student
.password
,
246 $.ajax("/send/answer", {
252 return this.showWarning(ret
.errmsg
);
254 socket
.emit(message
.newAnswer
, answerData
);
258 // TODO: I don't like that + sending should not be definitive in exam mode with display = all
259 sendAnswer: function() {
260 if (assessment
.display
== "one")
261 this.sendOneAnswer();
263 assessment
.questions
.forEach(this.sendOneAnswer
);
265 // stage 2 --> 3 (or 4)
266 // from a message by statements component, or time over
267 endAssessment: function() {
268 // Set endTime, destroy password
269 $("#leftButton, #rightButton").show();
270 if (assessment
.mode
== "open")
273 this.answers
.showSolution
= true;
274 this.answers
.displayAll
= true;
277 $.ajax("/end/assessment", {
281 number: this.student
.number
,
282 password: this.student
.password
,
287 return this.showWarning(ret
.errmsg
);
289 delete this.student
["password"]; //unable to send new answers now
293 // stage 3 --> 4 (on socket message "feedback")
294 setAnswers: function(m
) {
295 const answers
= JSON
.parse(m
.answers
);
296 for (let i
=0; i
<answers
.length
; i
++)
297 assessment
.questions
[i
].answer
= answers
[i
];
298 this.answers
.showSolution
= true;
299 this.answers
.displayAll
= true;