|
1 IriSP.Widgets.Quiz = function(player, config) { |
|
2 IriSP.Widgets.Widget.call(this, player, config); |
|
3 } |
|
4 |
|
5 IriSP.Widgets.Quiz.prototype = new IriSP.Widgets.Widget(); |
|
6 |
|
7 IriSP.Widgets.Quiz.prototype.defaults = { |
|
8 // annotation_type: "at_quiz", |
|
9 quiz_activated: true, |
|
10 api_serializer: "ldt_annotate", |
|
11 analytics_api: "", |
|
12 api_method: "POST", |
|
13 user: "", |
|
14 userid:"" |
|
15 } |
|
16 |
|
17 IriSP.Widgets.Quiz.prototype.template = '<div class="Ldt-Quiz-Container">' |
|
18 + '<div class="Ldt-Quiz-Header">' |
|
19 + ' <div class="Ldt-Quiz-Index"></div><div class="Ldt-Quiz-Score"></div>' |
|
20 + '</div>' |
|
21 + '<div class="Ldt-Quiz-Content">' |
|
22 + ' <h1 class="Ldt-Quiz-Title">{{question}}</h1>' |
|
23 + ' <div class="Ldt-Quiz-Questions">' |
|
24 + ' </div>' |
|
25 + '</div>' |
|
26 + '<div class="Ldt-Quiz-Footer">' |
|
27 + ' <div class="Ldt-Quiz-Votes">' |
|
28 + ' <span class="Ldt-Quiz-Votes-Question">Avez-vous trouvé cette question utile ?</span>' |
|
29 + ' <div class="Ldt-Quiz-Votes-Buttons">' |
|
30 + ' <div class="Ldt-Quiz-Vote-Skip-Block"><a href="#" class="Ldt-Quiz-Vote-Skip">Passer</a></div>' |
|
31 + ' <div><input type="button" value="Non" class="Ldt-Quiz-Vote-Useless" /></div>' |
|
32 + ' <div><input type="button" value="Oui" class="Ldt-Quiz-Vote-Useful" /></div>' |
|
33 + ' </div>' |
|
34 + ' </div>' |
|
35 + ' <div class="Ldt-Quiz-Submit">' |
|
36 + ' <div class="Ldt-Quiz-Submit-Button"><input type="button" value="Valider" /></div>' |
|
37 + ' <div class="Ldt-Quiz-Submit-Skip-Link"><a href="#">Passer</a></div><div style="clear:both;"></div>' |
|
38 + ' </div>' |
|
39 + ' <div class="Ldt-Quiz-Result">Bonne réponse</div>' |
|
40 + '</div>' |
|
41 + '</div>'; |
|
42 |
|
43 IriSP.Widgets.Quiz.prototype.annotationTemplate = ''; |
|
44 |
|
45 IriSP.Widgets.Quiz.prototype.update = function(annotation) { |
|
46 var _this = this; |
|
47 |
|
48 if (this.quiz_activated && |
|
49 this.correct[annotation.id] != 1 && |
|
50 this.correct[annotation.id] != 0) { |
|
51 |
|
52 _this.quiz_displayed = true; |
|
53 |
|
54 //Pause the current video |
|
55 this.media.pause(); |
|
56 |
|
57 this.annotation = annotation; |
|
58 |
|
59 var question = annotation.content.data.question; |
|
60 var answers = annotation.content.data.answers; |
|
61 var resource = annotation.content.data.resource; |
|
62 |
|
63 $(".Ldt-Quiz-Votes").hide(); |
|
64 $(".Ldt-Pause-Add-Question").hide(); |
|
65 $(".Ldt-Quiz-Container .Ldt-Quiz-Title").html(question); |
|
66 |
|
67 var i = 0; |
|
68 |
|
69 var score = Mustache.to_html('<span class="Ldt-Quiz-Correct-Answer">{{ correctness.0 }}</span> / <span class="Ldt-Quiz-Incorrect-Answer">{{ correctness.1 }}</span>', { correctness: this.globalScore() }); |
|
70 $(".Ldt-Quiz-Index").html(Mustache.to_html("Q{{index}}/{{total}}", { index: annotation.number + 1, |
|
71 total: this.totalAmount })); |
|
72 $(".Ldt-Quiz-Score").html(score); |
|
73 this.question = new IriSP.Widgets.UniqueChoiceQuestion(annotation); |
|
74 this.resource = new IriSP.Widgets.UniqueChoiceQuestion(resource); |
|
75 |
|
76 if (annotation.content.data.type == "multiple_choice") { |
|
77 this.question = new IriSP.Widgets.MultipleChoiceQuestion(annotation); |
|
78 this.resource = new IriSP.Widgets.MultipleChoiceQuestion(resource); |
|
79 } |
|
80 else if (annotation.content.data.type == "unique_choice") { |
|
81 this.question = new IriSP.Widgets.UniqueChoiceQuestion(annotation); |
|
82 this.resource = new IriSP.Widgets.UniqueChoiceQuestion(resource); |
|
83 } |
|
84 |
|
85 var output = ""; |
|
86 for (i = 0; i < answers.length; i++) { |
|
87 output += '<div class="quiz-question-block"><p>' + this.question.renderQuizTemplate(answers[i], i) + '<span class="quiz-question-label">'+ answers[i].content + '</span></p></div>'; |
|
88 } |
|
89 |
|
90 |
|
91 var QR = ''; |
|
92 //If there is an attached resource, display it on the resources overlay |
|
93 if (resource != null) { |
|
94 QR = '<div class="quiz-resource-block" id="resource" >' + resource + '</div>'; |
|
95 }; |
|
96 $(".Ldt-Quiz-Questions").html(QR + output); |
|
97 $(".Ldt-Quiz-Overlay").fadeIn(); |
|
98 |
|
99 $(".Ldt-Quiz-Submit").fadeIn(); |
|
100 |
|
101 //Let's automatically check the checkbox/radio if we click on the label |
|
102 $(".quiz-question-label").click(function() { |
|
103 var input = $(this).siblings("input"); |
|
104 if (input.prop('checked') && input.prop('type') == 'radio') { |
|
105 // Already checked. Consider a double click on unique question as a validation. |
|
106 _this.answer(); |
|
107 } else { |
|
108 input.prop('checked', !input.prop('checked')); |
|
109 } |
|
110 }); |
|
111 |
|
112 //In case we click on the first "Skip" link |
|
113 $(".Ldt-Quiz-Submit-Skip-Link").click({media: this.media}, function(event) { |
|
114 _this.hide(); |
|
115 _this.player.trigger("QuizCreator.skip"); |
|
116 event.data.media.play(); |
|
117 }); |
|
118 } |
|
119 }; |
|
120 |
|
121 IriSP.Widgets.Quiz.prototype.hide = function() { |
|
122 var _this = this; |
|
123 |
|
124 $(".Ldt-Quiz-Votes").hide(); |
|
125 $(".Ldt-Quiz-Overlay").hide(); |
|
126 $(".Ldt-Pause-Add-Question").hide(); |
|
127 _this.quiz_displayed = false; |
|
128 } |
|
129 |
|
130 IriSP.Widgets.Quiz.prototype.answer = function() { |
|
131 var _this = this; |
|
132 |
|
133 function insert_timecode_links (s) { |
|
134 return (s || "").replace(/\s(\d+:\d+)/, function (match, timecode) { |
|
135 return ' <a href="#t=' + (IriSP.timestamp2ms(timecode) / 1000) + '">' + timecode + '</a>'; |
|
136 }); |
|
137 }; |
|
138 |
|
139 var answers = _this.annotation.content.data.answers; |
|
140 |
|
141 // Augment answers with the correct feedback |
|
142 var i = 0; |
|
143 var wrong = 0; |
|
144 // Signature is an array giving the answers signature: 1 for checked, 0 for unchecked |
|
145 // We cannot simply store the right answer index, since there may be multiple-choice questions |
|
146 var signature = []; |
|
147 _this.$.find(".Ldt-Quiz-Question-Check").each( function (code) { |
|
148 var checked = $(this).is(":checked"); |
|
149 signature.push(checked ? 1 : 0); |
|
150 var ans = answers[i]; |
|
151 if ((ans.correct && !checked) |
|
152 || (!ans.correct && checked)) { |
|
153 wrong += 1; |
|
154 IriSP.jQuery(this).parents(".quiz-question-block").append('<div class="quiz-question-feedback quiz-question-incorrect-feedback">'+ insert_timecode_links(ans.feedback) +'</div>'); |
|
155 } else { |
|
156 IriSP.jQuery(this).parents(".quiz-question-block").append('<div class="quiz-question-feedback quiz-question-correct-feedback">'+ insert_timecode_links(ans.feedback) +'</div>'); |
|
157 } |
|
158 i++; |
|
159 }); |
|
160 |
|
161 if (wrong) { |
|
162 $(".Ldt-Quiz-Result").html("Mauvaise réponse"); |
|
163 $(".Ldt-Quiz-Result").css({"background-color" : "red"}); |
|
164 this.correct[this.annotation.id] = 0; |
|
165 } else { |
|
166 $(".Ldt-Quiz-Result").html("Bonne réponse !"); |
|
167 $(".Ldt-Quiz-Result").css({"background-color" : "green"}); |
|
168 this.correct[this.annotation.id] = 1; |
|
169 } |
|
170 |
|
171 $(".Ldt-Quiz-Result").animate({height:"100%"},500, "linear", function() { |
|
172 $(".Ldt-Quiz-Result").delay(2000).animate({ height:"0%" }, 500); |
|
173 }); |
|
174 |
|
175 var question_number = this.annotation.number + 1; |
|
176 var correctness = this.globalScore(); |
|
177 var score = ""; |
|
178 score += '<span class="Ldt-Quiz-Correct-Answer">' + correctness[0] +'</span> / <span class="Ldt-Quiz-Incorrect-Answer">' + correctness[1] + '</span>'; |
|
179 $(".Ldt-Quiz-Index").html("Q"+ question_number + "/" + this.totalAmount); |
|
180 $(".Ldt-Quiz-Score").html(score); |
|
181 |
|
182 this.submit(this.user, this.userid, this.annotation.id, wrong ? 'wrong_answer' : 'right_answer', signature.join("")); |
|
183 |
|
184 //Hide the "Validate" button and display the UI dedicated to votes |
|
185 $(".Ldt-Quiz-Submit").fadeOut(400, function () { |
|
186 $(".Ldt-Quiz-Votes").show(); |
|
187 }); |
|
188 }; |
|
189 |
|
190 IriSP.Widgets.Quiz.prototype.globalScore = function() { |
|
191 // Return 2 variables to know how many right and wrong answers there are |
|
192 var values = _.values(this.correct); |
|
193 var ok = values.filter( function (s) { return s == 1; }).length; |
|
194 var not_ok = values.filter( function (s) { return s == 0; }).length; |
|
195 return [ok, not_ok]; |
|
196 }; |
|
197 |
|
198 IriSP.Widgets.Quiz.prototype.refresh = function() { |
|
199 var _annotations = this.getWidgetAnnotations().sortBy(function(_annotation) { |
|
200 return _annotation.begin; |
|
201 }); |
|
202 |
|
203 var _this = this; |
|
204 |
|
205 _this.totalAmount = _annotations.length; |
|
206 _this.number = 0; |
|
207 _this.correct = {}; |
|
208 _this.keys = {}; |
|
209 |
|
210 _annotations.forEach(function(_a) { |
|
211 //Fix each annotation as "non-answered yet" |
|
212 _this.correct[_a.id] = -1; |
|
213 _this.keys[_this.number] = _a.id; |
|
214 _a.number = _this.number++; |
|
215 }); |
|
216 |
|
217 } |
|
218 |
|
219 IriSP.Widgets.Quiz.prototype.draw = function() { |
|
220 var _this = this; |
|
221 _this.quiz_displayed = false; |
|
222 this.onMediaEvent("enter-annotation", function (annotation) { |
|
223 var an = _this.getWidgetAnnotations().filter( function (a) { return a === annotation; }); |
|
224 if (an.number === undefined) { |
|
225 _this.refresh(); |
|
226 } |
|
227 if (an.length) { |
|
228 _this.update(an[0]); |
|
229 }; |
|
230 }); |
|
231 this.onMdpEvent("Quiz.activate", function() { |
|
232 _this.quiz_activated = true; |
|
233 }); |
|
234 |
|
235 this.onMdpEvent("Quiz.deactivate", function() { |
|
236 _this.quiz_activated = false; |
|
237 _this.hide(); |
|
238 }); |
|
239 |
|
240 this.onMdpEvent("Quiz.hide", function() { |
|
241 _this.hide(); |
|
242 }); |
|
243 |
|
244 this.onMdpEvent("Quiz.refresh", function() { |
|
245 _this.refresh(); |
|
246 }); |
|
247 |
|
248 this.onMediaEvent("pause", function() { |
|
249 if (! _this.quiz_displayed) { |
|
250 $(".Ldt-Pause-Add-Question").show(); |
|
251 } |
|
252 }); |
|
253 |
|
254 this.onMediaEvent("play", function() { |
|
255 $(".Ldt-Pause-Add-Question").hide(); |
|
256 }); |
|
257 |
|
258 // Add Ldt-Quiz-Overlay widget on top of video player |
|
259 _this.overlay = $("<div class='Ldt-Quiz-Overlay'></div>").appendTo($('#' + _this.container)); |
|
260 _this.PauseAddQuestion = $("<div class='Ldt-Pause-Add-Question' title='Ajoutez une question !'>") |
|
261 .on("click", function() { _this.player.trigger("QuizCreator.create"); }) |
|
262 .appendTo($('#' + _this.container)); |
|
263 _this.overlay.html(this.template); |
|
264 |
|
265 $(".Ldt-Quiz-Overlay").hide(); |
|
266 |
|
267 $(".Ldt-Quiz-Submit input").click(function() { |
|
268 _this.answer(); |
|
269 }); |
|
270 |
|
271 //In case we click on the first "Skip" link |
|
272 $(".Ldt-Quiz-Submit-Skip-Link").click({ media: this.media }, function(event) { |
|
273 _this.submit(_this.user, _this.userid, _this.annotation.id, "skipped_answer", 0); |
|
274 _this.hide(); |
|
275 _this.player.trigger("QuizCreator.skip"); |
|
276 event.data.media.play(); |
|
277 }); |
|
278 |
|
279 $(".Ldt-Quiz-Votes-Buttons input[type=\"button\"], .Ldt-Quiz-Votes-Buttons a").click({media: this.media}, function(event) { |
|
280 var vote_prop, vote_val; |
|
281 |
|
282 if ($(this).hasClass("Ldt-Quiz-Vote-Useful")) { |
|
283 vote_prop = "useful"; |
|
284 vote_val = 1; |
|
285 } else if ($(this).hasClass("Ldt-Quiz-Vote-Useless")) { |
|
286 vote_prop = "useless"; |
|
287 vote_val = -1; |
|
288 |
|
289 $(".Ldt-Ctrl-Quiz-Create").addClass("button_highlight").delay(5000).queue(function() { |
|
290 $(this).removeClass("button_highlight").dequeue(); |
|
291 }); |
|
292 }else{ |
|
293 vote_prop = "skipped_vote"; |
|
294 vote_val = 0; |
|
295 } |
|
296 |
|
297 _this.submit(_this.user, _this.userid, _this.annotation.id, vote_prop, vote_val); |
|
298 |
|
299 //Resume the current video |
|
300 event.data.media.play(); |
|
301 |
|
302 _this.hide(); |
|
303 $(".Ldt-Pause-Add-Question").hide(); |
|
304 |
|
305 _this.player.trigger("QuizCreator.skip"); |
|
306 }); |
|
307 |
|
308 _this.refresh(); |
|
309 }; |
|
310 |
|
311 //Generates uid |
|
312 //source : http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript |
|
313 IriSP.Widgets.Widget.prototype.generateUid = function () { |
|
314 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { |
|
315 var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8); |
|
316 return v.toString(16); |
|
317 }); |
|
318 } |
|
319 |
|
320 //UniqueChoice Question |
|
321 IriSP.Widgets.UniqueChoiceQuestion = function(annotation) { |
|
322 this.annotation = annotation; |
|
323 } |
|
324 |
|
325 IriSP.Widgets.UniqueChoiceQuestion.prototype = new IriSP.Widgets.Widget(); |
|
326 |
|
327 IriSP.Widgets.UniqueChoiceQuestion.prototype.renderQuizTemplate = function(answer, identifier) { |
|
328 return '<input type="radio" class="quiz-question Ldt-Quiz-Question-Check Ldt-Quiz-Question-Check-' + identifier + '" name="question" data-question="' + identifier + '" value="' + identifier + '" />'; |
|
329 } |
|
330 |
|
331 IriSP.Widgets.UniqueChoiceQuestion.prototype.renderTemplate = function(answer, identifier) { |
|
332 var id = this.generateUid(); |
|
333 return '<input type="radio" id="' + id + '" class="quiz-question-edition Ldt-Quiz-Question-Check Ldt-Quiz-Question-Check-'+ identifier +'" name="question" data-question="'+ identifier +'" value="' + identifier + '" /><label for="'+ id +'" title="Veuillez sélectionner la réponse correcte"></label>'; |
|
334 } |
|
335 |
|
336 IriSP.Widgets.UniqueChoiceQuestion.prototype.renderFullTemplate = function(answer, identifier) { |
|
337 var correct = (answer && answer.correct) ? "checked" : ""; |
|
338 var id = this.generateUid(); |
|
339 return '<input type="radio" id="'+ id +'" '+ correct +' class="quiz-question-edition Ldt-Quiz-Question-Check Ldt-Quiz-Question-Check-'+ identifier +'" name="question" data-question="'+ identifier +'" value="' + identifier + '" /><label for="'+ id +'"></label>'; |
|
340 } |
|
341 |
|
342 |
|
343 //MultipleChoice Question |
|
344 IriSP.Widgets.MultipleChoiceQuestion = function(annotation) { |
|
345 this.annotation = annotation; |
|
346 } |
|
347 |
|
348 IriSP.Widgets.MultipleChoiceQuestion.prototype = new IriSP.Widgets.Widget(); |
|
349 |
|
350 IriSP.Widgets.MultipleChoiceQuestion.prototype.renderQuizTemplate = function(answer, identifier) { |
|
351 return '<input type="checkbox" class="quiz-question Ldt-Quiz-Question-Check Ldt-Quiz-Question-Check-'+ identifier + '" name="question['+ identifier +']" data-question="'+ identifier +'" value="' + identifier + '" /> '; |
|
352 } |
|
353 |
|
354 IriSP.Widgets.MultipleChoiceQuestion.prototype.renderTemplate = function(answer, identifier) { |
|
355 var id = this.generateUid(); |
|
356 return '<input type="checkbox" id="'+ id +'" class="quiz-question-edition Ldt-Quiz-Question-Check" name="question['+ identifier +']" data-question="'+ identifier +'" value="' + identifier + '" /><label for="'+ id +'" title="Veuillez sélectionner la ou les réponses correctes"></label>'; |
|
357 } |
|
358 |
|
359 IriSP.Widgets.MultipleChoiceQuestion.prototype.renderFullTemplate = function(answer, identifier) { |
|
360 var correct = (answer && answer.correct) ? "checked" : ""; |
|
361 var id = this.generateUid(); |
|
362 return '<input type="checkbox" id="'+ id +'" '+ correct +' class="quiz-question-edition Ldt-Quiz-Question-Check" name="question['+ identifier +']" data-question="'+ identifier +'" value="' + identifier + '" /><label for="'+ id +'"></label> '; |
|
363 } |
|
364 |
|
365 IriSP.Widgets.Quiz.prototype.submit = function(user,user_id,question,prop,val) { |
|
366 var _this = this; |
|
367 var _url = Mustache.to_html(this.analytics_api, {id: this.source.projectId}), |
|
368 donnees = { |
|
369 "username": user, |
|
370 "useruuid": user_id, |
|
371 "subject": question, |
|
372 "property": prop, |
|
373 "value": val, |
|
374 "session": _this.session_id |
|
375 }; |
|
376 |
|
377 IriSP.jQuery.ajax({ |
|
378 url: _url, |
|
379 type: this.api_method, |
|
380 contentType: 'application/json', |
|
381 data: JSON.stringify(donnees), |
|
382 success: function(_data) { |
|
383 }, |
|
384 error: function(_xhr, _error, _thrown) { |
|
385 IriSP.log("Error when sending annotation", _thrown); |
|
386 } |
|
387 }); |
|
388 } |