'update'
[qomet.git] / public / javascripts / components / statements.js
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 * ...
17
18 --> input of type text (number, or vector, or matrix e.g. in R syntax)
19 --> parameter stored in question.param (TODO)
20
21 */
22
23 Vue.component("statements", {
24 // 'inputs': object with key = question index and value = text or boolean array
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 }
33 // Full questions tree is rendered, but some parts hidden depending on display settings
34 render(h) {
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 }
63 },
64 q.index
65 )
66 );
67 questionContent.push(
68 h(
69 "div",
70 {
71 "class": {
72 wording: true,
73 },
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 },
104 },
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 },
130 },
131 option
132 )
133 );
134 });
135 questionContent.push(
136 h(
137 "div",
138 {
139 "class": {
140 optionList: true,
141 },
142 },
143 optionList
144 )
145 );
146 }
147 const depth = (q.index.match(/\./g) || []).length;
148 return h(
149 "div",
150 {
151 "class": {
152 "question": true,
153 "depth" + depth: true,
154 },
155 },
156 questionContent
157 );
158 });
159 return h(
160 "div",
161 {
162 "class": {
163 "questionGroup": true,
164 "hide": this.display == "one" && this.iidx != i,
165 },
166 }
167 qgDom
168 );
169 });
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") ]
191 ),
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 );
223 return h(
224 "div",
225 {
226 attrs: {
227 id: "statements",
228 },
229 },
230 domTree
231 );
232 },
233 mounted: function() {
234 statementsLibsRefresh();
235 },
236 updated: function() {
237 statementsLibsRefresh();
238 },
239 methods: {
240 inputId: function(i,j) {
241 return "q" + i + "_" + "input" + j;
242 },
243 },
244 });