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