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