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