Commit | Line | Data |
---|---|---|
43828378 BA |
1 | /* |
2 | * questions group by index prefix 1.2.3 1.1 ...etc --> '1' | |
3 | ||
4 | NOTE: questions can contain parameterized exercises (how ? | |
5 | --> describe variables (syntax ?) | |
6 | --> write javascript script (OK, users trusted ? ==> safe mode possible if public website) | |
7 | Imaginary example: (using math.js) | |
8 | <params> (avant l'exo) | |
fdf62750 BA |
9 | const x = math.random(); |
10 | const y = math.random(); | |
43828378 | 11 | </params> |
92a6f830 BA |
12 | <result> |
13 | let M = math.matrix([[7, x], [y, -3]]); | |
14 | return math.det(M); | |
15 | </result> | |
43828378 | 16 | <div>Calculer le déterminant de |
92a6f830 | 17 | $$\begin{matrix}7 & £x£\\£y£ & -3\end{matrix}$$</div> |
43828378 | 18 | * ... |
73609d3b | 19 | |
a3080c33 BA |
20 | + fixed + question time (syntax ?) |
21 | ||
73609d3b BA |
22 | --> input of type text (number, or vector, or matrix e.g. in R syntax) |
23 | --> parameter stored in question.param (TODO) | |
24 | ||
43828378 BA |
25 | */ |
26 | ||
435371c7 | 27 | Vue.component("statements", { |
73609d3b | 28 | // 'inputs': object with key = question index and value = text or boolean array |
a80c6a3b BA |
29 | // display: 'all', 'one', 'solution' |
30 | // iidx: current level-0 integer index (can match a group of questions / inputs) | |
14c9c66a | 31 | props: ['questions','inputs','answers','display','iidx'], |
a80c6a3b BA |
32 | data: function() { |
33 | return { | |
34 | displayStyle: "compact", //or "all": all on same page | |
14c9c66a | 35 | parameters: 0, //TODO: DO NOT re-draw parameters for already answered questions |
a80c6a3b | 36 | }; |
cb39647a | 37 | }, |
435371c7 BA |
38 | // Full questions tree is rendered, but some parts hidden depending on display settings |
39 | render(h) { | |
73609d3b BA |
40 | // Prepare questions groups, ordered |
41 | let questions = this.questions || [ ] | |
42 | let questionGroups = _.groupBy(questions, q => { | |
43 | const dotPos = q.index.indexOf("."); | |
44 | return dotPos === -1 ? q.index : q.index.substring(0,dotPos); | |
45 | }); | |
c8f68697 BA |
46 | let domTree = Object.keys(questionGroups).map( idx => { |
47 | let qg = questionGroups[idx]; | |
48 | const i = parseInt(idx); | |
73609d3b BA |
49 | // Re-order questions 1.1.1 then 1.1.2 then... |
50 | const orderedQg = qg.sort( (a,b) => { | |
51 | let aParts = a.split('.').map(Number); | |
52 | let bParts = b.split('.').map(Number); | |
53 | const La = aParts.length, Lb = bParts.length; | |
54 | for (let i=0; i<Math.min(La,Lb); i++) | |
55 | { | |
56 | if (aParts[i] != bParts[i]) | |
57 | return aParts[i] - bParts[i]; | |
58 | } | |
59 | return La - Lb; //the longer should appear after | |
60 | }); | |
61 | let qgDom = orderedQg.map( q => { | |
62 | let questionContent = [ ]; | |
63 | questionContent.push( | |
64 | h( | |
65 | "h4", | |
66 | { | |
67 | "class": { | |
68 | "questionIndex": true, | |
69 | } | |
435371c7 | 70 | }, |
73609d3b BA |
71 | q.index |
72 | ) | |
73 | ); | |
74 | questionContent.push( | |
75 | h( | |
76 | "div", | |
77 | { | |
78 | "class": { | |
79 | wording: true, | |
435371c7 | 80 | }, |
73609d3b BA |
81 | domProps: { |
82 | innerHTML: q.wording, | |
83 | }, | |
84 | } | |
85 | ) | |
86 | ); | |
87 | if (!!q.options) | |
88 | { | |
89 | // quiz-like question | |
90 | let optionsOrder = _.range(q.options.length); | |
91 | if (!q.fixed) | |
92 | optionsOrder = _.shuffle(optionsOrder); | |
93 | let optionList = [ ]; | |
94 | optionsOrder.forEach( idx => { | |
95 | let option = [ ]; | |
96 | option.push( | |
97 | h( | |
98 | "input", | |
99 | { | |
100 | domProps: { | |
a3080c33 BA |
101 | checked: !!this.inputs && this.inputs[q.index][idx], |
102 | disabled: monitoring || this.display == "solution", | |
73609d3b BA |
103 | }, |
104 | attrs: { | |
105 | id: this.inputId(q.index,idx), | |
106 | type: "checkbox", | |
107 | }, | |
108 | on: { | |
109 | change: e => { this.inputs[q.index][idx] = e.target.checked; }, | |
110 | }, | |
a80c6a3b | 111 | }, |
73609d3b BA |
112 | [ '' ] //to work in Firefox 45.9 ESR @ ENSTA... |
113 | ) | |
114 | ); | |
115 | option.push( | |
116 | h( | |
117 | "label", | |
118 | { | |
119 | domProps: { | |
120 | innerHTML: q.options[idx], | |
121 | }, | |
122 | attrs: { | |
123 | "for": this.inputId(q.index,idx), | |
124 | }, | |
125 | } | |
126 | ) | |
127 | ); | |
14c9c66a | 128 | const aIdx = (this.answers || [ ]).findIndex( item => { return item.index == q.index; }); |
73609d3b BA |
129 | optionList.push( |
130 | h( | |
131 | "div", | |
132 | { | |
133 | "class": { | |
134 | option: true, | |
14c9c66a BA |
135 | choiceCorrect: this.display == "solution" && this.answers[aIdx].includes(idx), |
136 | choiceWrong: this.display == "solution" && !!this.inputs && this.inputs[q.index][idx] && !this.answers[aIdx].includes(idx), | |
73609d3b | 137 | }, |
a80c6a3b | 138 | }, |
73609d3b BA |
139 | option |
140 | ) | |
141 | ); | |
142 | }); | |
143 | questionContent.push( | |
a80c6a3b BA |
144 | h( |
145 | "div", | |
146 | { | |
147 | "class": { | |
73609d3b | 148 | optionList: true, |
a80c6a3b | 149 | }, |
435371c7 | 150 | }, |
73609d3b | 151 | optionList |
a80c6a3b BA |
152 | ) |
153 | ); | |
73609d3b | 154 | } |
14c9c66a BA |
155 | else |
156 | { | |
157 | // Open question, or parameterized: TODO | |
158 | } | |
73609d3b BA |
159 | const depth = (q.index.match(/\./g) || []).length; |
160 | return h( | |
161 | "div", | |
162 | { | |
163 | "class": { | |
164 | "question": true, | |
cb39647a | 165 | ["depth" + depth]: true, |
435371c7 | 166 | }, |
73609d3b BA |
167 | }, |
168 | questionContent | |
435371c7 | 169 | ); |
73609d3b | 170 | }); |
435371c7 BA |
171 | return h( |
172 | "div", | |
173 | { | |
174 | "class": { | |
73609d3b | 175 | "questionGroup": true, |
a80c6a3b | 176 | "hide": this.display == "one" && this.iidx != i, |
435371c7 | 177 | }, |
cb39647a | 178 | }, |
73609d3b | 179 | qgDom |
435371c7 BA |
180 | ); |
181 | }); | |
a80c6a3b BA |
182 | const navigator = h( |
183 | "div", | |
184 | { | |
185 | "class": { | |
186 | "hide": this.displayStyle == "all" | |
187 | }, | |
188 | }, | |
189 | [ | |
190 | h( | |
191 | "button", | |
192 | { | |
193 | "class": { | |
194 | "btn": true, | |
195 | }, | |
196 | on: { | |
197 | click: () => { | |
198 | this.index = Math.max(0, this.index - 1); | |
199 | }, | |
200 | }, | |
201 | }, | |
202 | [ h("span", { "class": { "material-icon": true } }, "fast_rewind") ] | |
73609d3b | 203 | ), |
a80c6a3b BA |
204 | h("span",{ },(this.iidx+1).toString()), |
205 | h( | |
206 | "button", | |
207 | { | |
208 | "class": { | |
209 | "btn": true, | |
210 | }, | |
211 | on: { | |
212 | click: () => { | |
213 | this.index = Math.min(this.index+1, this.questions.length-1) | |
214 | }, | |
215 | }, | |
216 | }, | |
217 | [ h("span", { "class": { "material-icon": true } }, "fast_forward") ] | |
218 | ) | |
219 | ] | |
220 | ); | |
221 | domTree.push(navigator); | |
222 | domTree.push( | |
223 | h( | |
224 | "button", | |
225 | { | |
226 | on: { | |
227 | click: () => { | |
228 | this.displayStyle = displayStyle == "compact" ? "all" : "compact"; | |
229 | }, | |
230 | }, | |
231 | }, | |
232 | this.displayStyle == "compact" ? "Show all" : "Navigator" | |
233 | ) | |
234 | ); | |
435371c7 BA |
235 | return h( |
236 | "div", | |
237 | { | |
238 | attrs: { | |
239 | id: "statements", | |
240 | }, | |
241 | }, | |
71d1ca9c | 242 | domTree |
435371c7 BA |
243 | ); |
244 | }, | |
3b8117c5 BA |
245 | mounted: function() { |
246 | statementsLibsRefresh(); | |
247 | }, | |
435371c7 | 248 | updated: function() { |
435371c7 BA |
249 | statementsLibsRefresh(); |
250 | }, | |
251 | methods: { | |
252 | inputId: function(i,j) { | |
253 | return "q" + i + "_" + "input" + j; | |
254 | }, | |
255 | }, | |
256 | }); |