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