Commit | Line | Data |
---|---|---|
43828378 BA |
1 | /*Draft format (compiled to json) |
2 | ||
3 | > Some global (HTML) intro | |
4 | ||
5 | <some html question (or/+ exercise intro)> | |
6 | ||
7 | <some html subQuestion> | |
8 | * some answer [trigger input/index in answers] | |
9 | ||
10 | <another subquestion> | |
11 | ||
12 | <sub-subQuestion> | |
13 | + choix1 | |
14 | - choix 2 | |
15 | + choix 3 | |
16 | - choix4 | |
17 | ||
18 | <another sub sub> | |
19 | * answer 2 (which can | |
20 | be on | |
21 | several lines) | |
22 | ||
23 | <Some second question> | |
24 | * With answer | |
25 | */ | |
e99c53fb | 26 | |
8a2b3260 BA |
27 | new Vue({ |
28 | el: '#course', | |
29 | data: { | |
30 | display: "assessments", //or "students", or "grades" (admin mode) | |
31 | course: course, | |
32 | mode: "view", //or "edit" (some assessment) | |
8a2b3260 BA |
33 | monitorPwd: "", |
34 | newAssessment: { name: "" }, | |
35 | assessmentArray: assessmentArray, | |
36 | assessmentIndex: 0, //current edited assessment index | |
37 | assessment: { }, //copy of assessment at editing index in array | |
38 | assessmentText: "", //questions in an assessment, in text format | |
8a2b3260 BA |
39 | }, |
40 | mounted: function() { | |
41 | $('.modal').each( (i,elem) => { | |
42 | if (elem.id != "assessmentEdit") | |
43 | $(elem).modal(); | |
44 | }); | |
45 | $('ul.tabs').tabs(); | |
46 | $('#assessmentEdit').modal({ | |
47 | complete: () => { | |
48 | this.parseAssessment(); | |
49 | Vue.nextTick( () => { | |
50 | $("#questionList").find("code[class^=language-]").each( (i,elem) => { | |
51 | Prism.highlightElement(elem); | |
e99c53fb | 52 | }); |
8a2b3260 | 53 | MathJax.Hub.Queue(["Typeset",MathJax.Hub,"questionList"]); |
e99c53fb BA |
54 | }); |
55 | }, | |
8a2b3260 BA |
56 | }); |
57 | }, | |
58 | methods: { | |
59 | // GENERAL: | |
60 | toggleDisplay: function(area) { | |
61 | if (this.display == area) | |
62 | this.display = ""; | |
63 | else | |
64 | this.display = area; | |
65 | }, | |
66 | studentList: function(group) { | |
67 | return this.course.students | |
68 | .filter( s => { return group==0 || s.group == group; }) | |
69 | .map( s => { return Object.assign({}, s); }) //not altering initial array | |
70 | .sort( (a,b) => { return a.name.localeCompare(b.name); }) | |
71 | }, | |
72 | // STUDENTS: | |
73 | uploadTrigger: function() { | |
74 | $("#upload").click(); | |
75 | }, | |
76 | upload: function(e) { | |
77 | let file = (e.target.files || e.dataTransfer.files)[0]; | |
78 | Papa.parse(file, { | |
79 | header: true, | |
80 | skipEmptyLines: true, | |
81 | complete: (results,file) => { | |
82 | let students = [ ]; | |
83 | // Post-process: add group/number if missing | |
84 | let number = 1; | |
85 | results.data.forEach( d => { | |
86 | if (!d.group) | |
87 | d.group = 1; | |
88 | if (!d.number) | |
89 | d.number = number++; | |
90 | if (typeof d.number !== "string") | |
91 | d.number = d.number.toString(); | |
92 | students.push(d); | |
93 | }); | |
94 | $.ajax("/import/students", { | |
95 | method: "POST", | |
e99c53fb | 96 | data: { |
8a2b3260 BA |
97 | cid: this.course._id, |
98 | students: JSON.stringify(students), | |
e99c53fb BA |
99 | }, |
100 | dataType: "json", | |
101 | success: res => { | |
102 | if (!res.errmsg) | |
8a2b3260 | 103 | this.course.students = students; |
e99c53fb BA |
104 | else |
105 | alert(res.errmsg); | |
106 | }, | |
8a2b3260 BA |
107 | }); |
108 | }, | |
109 | }); | |
110 | }, | |
111 | // ASSESSMENT: | |
112 | addAssessment: function() { | |
113 | if (!admin) | |
114 | return; | |
115 | // modal, fill code and description | |
116 | let error = Validator.checkObject(this.newAssessment, "Assessment"); | |
117 | if (!!error) | |
118 | return alert(error); | |
119 | else | |
120 | $('#newAssessment').modal('close'); | |
121 | $.ajax("/add/assessment", | |
122 | { | |
123 | method: "GET", | |
124 | data: { | |
125 | name: this.newAssessment.name, | |
126 | cid: course._id, | |
127 | }, | |
e99c53fb BA |
128 | dataType: "json", |
129 | success: res => { | |
130 | if (!res.errmsg) | |
131 | { | |
8a2b3260 BA |
132 | this.newAssessment["name"] = ""; |
133 | this.assessmentArray.push(res); | |
e99c53fb BA |
134 | } |
135 | else | |
136 | alert(res.errmsg); | |
137 | }, | |
e99c53fb | 138 | } |
8a2b3260 BA |
139 | ); |
140 | }, | |
141 | materialOpenModal: function(id) { | |
142 | $("#" + id).modal("open"); | |
143 | Materialize.updateTextFields(); //textareas, time field... | |
144 | }, | |
145 | updateAssessment: function() { | |
146 | $.ajax("/update/assessment", { | |
147 | method: "POST", | |
148 | data: {assessment: JSON.stringify(this.assessment)}, | |
149 | dataType: "json", | |
150 | success: res => { | |
151 | if (!res.errmsg) | |
e99c53fb | 152 | { |
8a2b3260 BA |
153 | this.assessmentArray[this.assessmentIndex] = this.assessment; |
154 | this.mode = "view"; | |
e99c53fb BA |
155 | } |
156 | else | |
8a2b3260 BA |
157 | alert(res.errmsg); |
158 | }, | |
159 | }); | |
160 | }, | |
161 | deleteAssessment: function(assessment) { | |
162 | if (!admin) | |
163 | return; | |
164 | if (confirm("Delete assessment '" + assessment.name + "' ?")) | |
165 | { | |
166 | $.ajax("/remove/assessment", | |
e99c53fb BA |
167 | { |
168 | method: "GET", | |
8a2b3260 | 169 | data: { qid: this.assessment._id }, |
e99c53fb BA |
170 | dataType: "json", |
171 | success: res => { | |
172 | if (!res.errmsg) | |
8a2b3260 BA |
173 | this.assessmentArray.splice( this.assessmentArray.findIndex( item => { |
174 | return item._id == assessment._id; | |
175 | }), 1 ); | |
e99c53fb BA |
176 | else |
177 | alert(res.errmsg); | |
178 | }, | |
179 | } | |
180 | ); | |
8a2b3260 BA |
181 | } |
182 | }, | |
183 | toggleState: function(questionIndex) { | |
184 | // add or remove from activeSet of current assessment | |
185 | let activeIndex = this.assessment.activeSet.findIndex( item => { return item == questionIndex; }); | |
186 | if (activeIndex >= 0) | |
187 | this.assessment.activeSet.splice(activeIndex, 1); | |
188 | else | |
189 | this.assessment.activeSet.push(questionIndex); | |
190 | }, | |
191 | setAssessmentText: function() { | |
192 | let txt = ""; | |
193 | this.assessment.questions.forEach( q => { | |
194 | txt += q.wording; //already ended by \n | |
195 | q.options.forEach( (o,i) => { | |
196 | let symbol = q.answer.includes(i) ? "+" : "-"; | |
197 | txt += symbol + " " + o + "\n"; | |
198 | }); | |
199 | txt += "\n"; //separate questions by new line | |
200 | }); | |
201 | this.assessmentText = txt; | |
202 | }, | |
203 | parseAssessment: function() { | |
204 | let questions = [ ]; | |
205 | let lines = this.assessmentText.split("\n").map( L => { return L.trim(); }) | |
206 | lines.push(""); //easier parsing | |
207 | let emptyQuestion = () => { | |
208 | return { | |
209 | wording: "", | |
210 | options: [ ], | |
211 | answer: [ ], | |
212 | active: true, //default | |
213 | }; | |
214 | }; | |
215 | let q = emptyQuestion(); | |
216 | lines.forEach( L => { | |
217 | if (L.length > 0) | |
218 | { | |
219 | if (['+','-'].includes(L.charAt(0))) | |
e99c53fb | 220 | { |
8a2b3260 BA |
221 | if (L.charAt(0) == '+') |
222 | q.answer.push(q.options.length); | |
223 | q.options.push(L.slice(1).trim()); | |
e99c53fb | 224 | } |
8a2b3260 BA |
225 | else if (L.charAt(0) == '*') |
226 | { | |
227 | // TODO: read current + next lines into q.answer (HTML, 1-elem array) | |
228 | } | |
229 | else | |
230 | q.wording += L + "\n"; | |
231 | } | |
232 | else | |
233 | { | |
234 | // Flush current question (if any) | |
235 | if (q.wording.length > 0) | |
236 | { | |
237 | questions.push(q); | |
238 | q = emptyQuestion(); | |
239 | } | |
240 | } | |
241 | }); | |
242 | this.assessment.questions = questions; | |
243 | }, | |
244 | actionAssessment: function(index) { | |
245 | if (admin) | |
246 | { | |
247 | // Edit screen | |
248 | this.assessmentIndex = index; | |
249 | this.assessment = $.extend(true, {}, this.assessmentArray[index]); | |
250 | this.setAssessmentText(); | |
251 | this.mode = "edit"; | |
252 | Vue.nextTick( () => { | |
253 | $("#questionList").find("code[class^=language-]").each( (i,elem) => { | |
254 | Prism.highlightElement(elem); | |
255 | }); | |
256 | MathJax.Hub.Queue(["Typeset",MathJax.Hub,"questionList"]); | |
e99c53fb | 257 | }); |
8a2b3260 BA |
258 | } |
259 | else //external user: show assessment | |
260 | this.redirect(this.assessmentArray[index].name); | |
e99c53fb | 261 | }, |
8a2b3260 BA |
262 | redirect: function(assessmentName) { |
263 | document.location.href = "/" + initials + "/" + course.code + "/" + assessmentName; | |
264 | }, | |
265 | setPassword: function() { | |
266 | let hashPwd = Sha1.Compute(this.monitorPwd); | |
267 | let error = Validator.checkObject({password:hashPwd}, "Course"); | |
268 | if (error.length > 0) | |
269 | return alert(error); | |
270 | $.ajax("/set/password", | |
271 | { | |
272 | method: "GET", | |
273 | data: { | |
274 | cid: this.course._id, | |
275 | pwd: hashPwd, | |
276 | }, | |
277 | dataType: "json", | |
278 | success: res => { | |
279 | if (!res.errmsg) | |
280 | alert("Password saved!"); | |
281 | else | |
282 | alert(res.errmsg); | |
283 | }, | |
284 | } | |
285 | ); | |
286 | }, | |
287 | // NOTE: artifact required for Vue v-model to behave well | |
43828378 | 288 | checkboxFixedId: function(i) { |
8a2b3260 BA |
289 | return "questionFixed" + i; |
290 | }, | |
43828378 | 291 | checkboxActiveId: function(i) { |
8a2b3260 BA |
292 | return "questionActive" + i; |
293 | }, | |
8a2b3260 BA |
294 | }, |
295 | }); |