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