afe75d0c53cc60782a7fc1f1a9ac3eb18dde5981
[redakcja.git] / redakcja / static / edumed / js / edumed.coffee
1
2 $ = jQuery
3
4 class Binding
5   constructor: (@handler, @element) ->
6     $(@element).data(@handler, this)
7
8
9 class EduModule extends Binding
10   constructor: (element) ->
11     super 'edumodule', element
12
13     # $("[name=teacher-toggle]").change (ev) =>
14     #   if $(ev.target).is(":checked")
15     #     $(".teacher", @element).addClass "show"
16     #   else
17     #     $(".teacher", @element).removeClass "show"
18
19
20 class Exercise extends Binding
21   constructor: (element) ->
22     super 'exercise', element
23     # just save the html to reset the exercise
24     $(@element).data("exercise-html", $(@element).html())
25
26     $(".check", @element).click (ev) =>
27       @check()
28       $(ev.target).next(".retry").show()
29       $(ev.target).hide()
30     $(".retry", @element).click (ev) =>
31       $(".correct, .incorrect", @element).removeClass("correct incorrect")
32       $(ev.target).prev(".check").show()
33       $(ev.target).hide()
34     $('.solutions', @element).click =>
35       @show_solutions()
36       $(".comment", @element).show()
37     $('.reset', @element).click =>
38       @reset()
39
40   reset: ->
41     $(@element).html($(@element).data('exercise-html'))
42     exercise @element
43
44   piece_correct: (qpiece) ->
45     $(qpiece).removeClass('incorrect').addClass('correct')
46
47   piece_incorrect: (qpiece) ->
48     $(qpiece).removeClass('correct').addClass('incorrect')
49
50   check: ->
51     scores = []
52     $(".question", @element).each (i, question) =>
53       scores.push(@check_question question)
54
55     score = [0, 0]
56     $.each scores, (i, s) ->
57       score[0] += s[0]
58       score[1] += s[1]
59     @show_score(score)
60
61   show_solutions: ->
62     @reset()
63     $(".question", @element).each (i, question) =>
64       @solve_question question
65
66   # Parses a list of values, separated by space or comma.
67   # The list is read from data attribute of elem using data_key
68   # Returns a list with elements
69   # eg.: things_i_need: "house bike tv playstation"
70   # yields ["house", "bike", "tv", "playstation"]
71   # If optional numbers argument is true, returns list of numbers
72   # instead of strings
73   get_value_list: (elem, data_key, numbers) ->
74     vl = $(elem).attr("data-" + data_key).split(/[ ,]+/).map($.trim) #.map((x) -> parseInt(x))
75     if numbers
76       vl = vl.map((x) -> parseInt(x))
77     return vl
78
79   # Parses a list of values, separated by space or comma.
80   # The list is read from data attribute of elem using data_key
81   # Returns a 2-element list with mandatory and optional
82   # items. optional items are marked with a question mark on the end
83   # eg.: things_i_need: "house bike tv? playstation?"
84   # yields [[ "house", "bike"], ["tv", "playstation"]]
85   get_value_optional_list: (elem, data_key) ->
86     vals = @get_value_list(elem, data_key)
87     mandat = []
88     opt = []
89     for v in vals
90       if v.slice(-1) == "?"
91         opt.push v.slice(0, -1)
92       else
93         mandat.push v
94     return [mandat, opt]
95
96   show_score: (score) ->
97     $(".message", @element).text("Wynik: #{score[0]} / #{score[1]}")
98
99
100   draggable_equal: ($draggable1, $draggable2) ->
101     return false
102
103   draggable_accept: ($draggable, $droppable) ->
104     dropped = $droppable.closest("ul, ol").find(".draggable")
105     for d in dropped
106       if @draggable_equal $draggable, $(d)
107         return false
108     return true
109
110   draggable_move: ($draggable, $placeholder, ismultiple) ->
111     $added = $draggable.clone()
112     $added.data("original", $draggable.get(0))
113     if not ismultiple
114       $draggable.addClass('disabled').draggable('disable')
115
116     $placeholder.after($added)
117     if not $placeholder.hasClass('multiple')
118       $placeholder.hide()
119     if $added.is(".add-li")
120       $added.wrap("<li/>")
121
122     $added.append('<span class="remove">x</span>')
123     $('.remove', $added).click (ev) =>
124       if not ismultiple
125         $($added.data('original')).removeClass('disabled').draggable('enable')
126
127       if $added.is(".add-li")
128         $added = $added.closest('li')
129       $added.prev(".placeholder:not(.multiple)").show()
130       $added.remove()
131
132
133 ## XXX co z issortable?
134   dragging: (ismultiple, issortable) ->
135     $(".question", @element).each (i, question) =>
136       draggable_opts =
137         revert: 'invalid'
138         helper: 'clone'
139
140       $(".draggable", question).draggable(draggable_opts)
141       self = this
142       $(".placeholder", question).droppable
143         accept: (draggable) ->
144           $draggable = $(draggable)
145           is_accepted = true
146
147           if not $draggable.is(".draggable")
148             is_accepted = false
149
150           if is_accepted
151             is_accepted= self.draggable_accept $draggable, $(this)
152
153           if is_accepted
154             $(this).addClass 'accepting'
155           else
156             $(this).removeClass 'accepting'
157           return is_accepted
158
159         drop: (ev, ui) =>
160           $(ev.target).removeClass 'accepting dragover'
161
162           @draggable_move $(ui.draggable), $(ev.target), ismultiple
163
164           # $added = $(ui.draggable).clone()
165           # $added.data("original", ui.draggable)
166           # if not ismultiple
167           #   $(ui.draggable).addClass('disabled').draggable('disable')
168
169           # $(ev.target).after(added)
170           # if not $(ev.target).hasClass('multiple')
171           #   $(ev.target).hide()
172           # $added.append('<span class="remove">x</span>')
173           # $('.remove', added).click (ev) =>
174           #   $added.prev(".placeholder:not(.multiple)").show()
175           #   if not ismultiple
176           #     $added.data('original').removeClass('disabled').draggable('enable')
177           #   $(added).remove()
178
179         over: (ev, ui) ->
180           $(ev.target).addClass 'dragover'
181
182
183         out: (ev, ui) ->
184           $(ev.target).removeClass 'dragover'
185
186
187
188 class Wybor extends Exercise
189   constructor: (element) ->
190     super element
191
192
193   check_question: (question) ->
194     all = 0
195     good = 0
196     solution = @get_value_list(question, 'solution')
197     $(".question-piece", question).each (i, qpiece) =>
198       piece_no = $(qpiece).attr 'data-no'
199       piece_name = $(qpiece).attr 'data-name'
200       if piece_name
201         should_be_checked = solution.indexOf(piece_name) >= 0
202       else
203         should_be_checked = solution.indexOf(piece_no) >= 0
204       is_checked = $("input", qpiece).is(":checked")
205
206       if should_be_checked
207         all += 1
208
209       if is_checked
210         if should_be_checked
211           good += 1
212           @piece_correct qpiece
213         else
214           @piece_incorrect qpiece
215       else
216         $(qpiece).removeClass("correct,incorrect")
217
218     return [good, all]
219
220   solve_question: (question) ->
221     solution = @get_value_list(question, 'solution')
222     $(".question-piece", question).each (i, qpiece) =>
223       piece_no = $(qpiece).attr 'data-no'
224       piece_name = $(qpiece).attr 'data-name'
225       if piece_name
226         should_be_checked = solution.indexOf(piece_name) >= 0
227       else
228         should_be_checked = solution.indexOf(piece_no) >= 0
229       console.log("check " + $("input[type=checkbox]", qpiece).attr("id") + " -> " + should_be_checked)
230       $("input[type=checkbox]", qpiece).prop 'checked', should_be_checked
231
232
233
234 class Uporzadkuj extends Exercise
235   constructor: (element) ->
236     super element
237     $('ol, ul', @element).sortable({ items: "> li" })
238
239   check_question: (question) ->
240     positions = @get_value_list(question, 'original', true)
241     sorted = positions.sort()
242     pkts = $('.question-piece', question)
243
244     correct = 0
245     all = 0
246
247     for pkt in [0...pkts.length]
248       all += 1
249       if pkts.eq(pkt).data('pos') == sorted[pkt]
250         correct += 1
251         @piece_correct pkts.eq(pkt)
252       else
253         @piece_incorrect pkts.eq(pkt)
254     return [correct, all]
255
256   solve_question: (question) ->
257     positions = @get_value_list(question, 'original', true)
258     sorted = positions.sort()
259     pkts = $('.question-piece', question)
260     pkts.sort (a, b) ->
261       q = $(a).data('pos')
262       w = $(b).data('pos')
263       return 1 if q < w
264       return -1 if q > w
265       return 0
266
267     parent = pkts.eq(0).parent()
268     for p in pkts
269       parent.prepend(p)
270
271
272 # XXX propozycje="1/0"
273 class Luki extends Exercise
274   constructor: (element) ->
275     super element
276     @dragging false, false
277
278   check: ->
279     all = 0
280     correct = 0
281     $(".placeholder + .question-piece", @element).each (i, qpiece) =>
282       $placeholder = $(qpiece).prev(".placeholder")
283       if $placeholder.data('solution') == $(qpiece).data('no')
284         @piece_correct qpiece
285         correct += 1
286       else
287         @piece_incorrect qpiece
288       all += 1
289
290     @show_score [correct, all]
291
292   solve_question: (question) ->
293     $(".placeholder", question).each (i, placeholder) =>
294       $qp = $(".question-piece[data-no=" + $(placeholder).data('solution') + "]", question)
295       @draggable_move $qp, $(placeholder), false
296
297
298 class Zastap extends Exercise
299   constructor: (element) ->
300     super element
301     $(".paragraph", @element).each (i, par) =>
302       @wrap_words $(par), $('<span class="placeholder zastap"/>')
303     @dragging false, false
304
305   check: ->
306     all = 0
307     correct = 0
308
309     $(".paragraph", @element).each (i, par) =>
310       $(".placeholder", par).each (j, qpiece) =>
311         $qp = $(qpiece)
312         $dragged = $qp.next(".draggable")
313         if $qp.data("solution")
314           if $dragged and $qp.data("solution") == $dragged.data("no")
315             @piece_correct $dragged
316             correct += 1
317 #          else -- we dont mark enything here, so not to hint user about solution. He sees he hasn't used all the draggables
318
319           all += 1
320
321     @show_score [correct, all]
322
323   show_solutions: ->
324     @reset()
325     $(".paragraph", @element).each (i, par) =>
326       $(".placeholder[data-solution]", par).each (j, qpiece) =>
327         $qp = $(qpiece)
328         $dr = $(".draggable[data-no=" + $qp.data('solution') + "]", @element)
329         @draggable_move $dr, $qp, false
330
331
332   wrap_words: (element, wrapper) ->
333     # This function wraps each word of element in wrapper, but does not descend into child-tags of element.
334     # It doesn't wrap things between words (defined by ignore RE below). Warning - ignore must begin with ^
335     ignore = /^[ \t.,:;()]+/
336
337     insertWrapped = (txt, elem) ->
338       nw = wrapper.clone()
339       $(document.createTextNode(txt))
340         .wrap(nw).parent().attr("data-original", txt).insertBefore(elem)
341
342     for j in [element.get(0).childNodes.length-1..0]
343       chld = element.get(0).childNodes[j]
344       if chld.nodeType == document.TEXT_NODE
345         len = chld.textContent.length
346         wordb = 0
347         i = 0
348         while i < len
349           space = ignore.exec(chld.textContent.substr(i))
350           if space?
351             if wordb < i
352               insertWrapped(chld.textContent.substr(wordb, i-wordb), chld)
353
354             $(document.createTextNode(space[0])).insertBefore(chld)
355             i += space[0].length
356             wordb = i
357           else
358             i = i + 1
359         if wordb < len - 1
360           insertWrapped(chld.textContent.substr(wordb, len - 1 - wordb), chld)
361         $(chld).remove()
362
363
364 class Przyporzadkuj extends Exercise
365   is_multiple: ->
366     for qp in $(".question-piece", @element)
367       if $(qp).data('solution').split(/[ ,]+/).length > 1
368         return true
369     return false
370
371   constructor: (element) ->
372     super element
373
374     @multiple = @is_multiple()
375
376     @dragging @multiple, true
377
378   draggable_equal: (d1, d2) ->
379     return d1.data("no") == d2.data("no")
380
381
382   check_question: (question) ->
383     # subjects placed in predicates
384     minimum = $(question).data("minimum")
385     count = 0
386     all = 0
387     if not minimum
388       all = $(".subjects .question-piece", question).length
389
390     for pred in $(".predicate [data-predicate]", question)
391       pn = $(pred).attr('data-predicate')
392       if minimum?
393         all += minimum
394
395       for qp in $(".question-piece", pred)
396         v = @get_value_optional_list qp, 'solution'
397         mandatory = v[0]
398         optional = v[1]
399
400         if mandatory.indexOf(pn) >= 0 or (minimum and optional.indexOf(pn) >= 0)
401           count += 1
402           @piece_correct qp
403         else
404           @piece_incorrect qp
405
406     return [count, all]
407
408   solve_question: (question) ->
409     minimum = $(question).data("min")
410
411     for qp in $(".subject .question-piece", question)
412       v = @get_value_optional_list qp, 'solution'
413       mandatory = v[0]
414       optional = v[1]
415
416       if minimum
417         draggables = mandatory.count(optional)[0...minimum]
418       else
419         draggables = mandatory
420       for m in draggables
421         $pr = $(".predicate [data-predicate=" + m + "]", question)
422         $ph = $pr.find ".placeholder:visible"
423         @draggable_move $(qp), $ph, @multiple
424
425
426
427 class PrawdaFalsz extends Exercise
428   constructor: (element) ->
429     super element
430
431     for qp in $(".question-piece", @element)
432       $(".true", qp).click (ev) ->
433         ev.preventDefault()
434         $(this).closest(".question-piece").data("value", "true")
435         $(this).addClass('chosen').siblings('a').removeClass('chosen')
436       $(".false", qp).click (ev) ->
437         ev.preventDefault()
438         $(this).closest(".question-piece").data("value", "false")
439         $(this).addClass('chosen').siblings('a').removeClass('chosen')
440
441
442   check_question: ->
443     all = 0
444     good = 0
445     for qp in $(".question-piece", @element)
446       if $(qp).data("solution").toString() == $(qp).data("value")
447         good += 1
448         @piece_correct qp
449       else
450         @piece_incorrect qp
451
452       all += 1
453
454     return [good, all]
455
456   show_solutions: ->
457     @reset()
458     for qp in $(".question-piece", @element)
459       if $(qp).data('solution') == 'true'
460         $(".true", qp).click()
461       else
462         $(".false", qp).click()
463
464
465 ##########
466
467 exercise = (ele) ->
468   es =
469     wybor: Wybor
470     uporzadkuj: Uporzadkuj
471     luki: Luki
472     zastap: Zastap
473     przyporzadkuj: Przyporzadkuj
474     prawdafalsz: PrawdaFalsz
475
476
477   cls = es[$(ele).attr('data-type')]
478   new cls(ele)
479
480
481 window.edumed =
482   'EduModule': EduModule
483
484
485
486
487 $(document).ready () ->
488   new EduModule($("#book-text"))
489
490   $(".exercise").each (i, el) ->
491     exercise(this)