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