liquid_feedback_frontend

view app/main/vote/list.lua @ 945:b865f87ea810

Work on robustification of JavaScript voting
author jbe
date Thu Nov 08 13:17:37 2012 +0100 (2012-11-08)
parents e81f35cdc088
children 37e31dd73e45
line source
1 local issue = Issue:by_id(param.get("issue_id"), atom.integer)
3 local member_id = param.get("member_id", atom.integer)
4 local member
5 local readonly = false
7 local preview = param.get("preview") or param.get("preview2") == "1" and true or false
9 if member_id then
10 if not issue.closed then
11 error("access denied")
12 end
13 member = Member:by_id(member_id)
14 readonly = true
15 end
17 if issue.closed then
18 if not member then
19 member = app.session.member
20 end
21 readonly = true
22 end
24 local submit_button_text = _"Finish voting"
26 if issue.closed then
27 submit_button_text = _"Update voting comment"
28 end
30 local direct_voter
32 if member then
33 direct_voter = DirectVoter:by_pk(issue.id, member.id)
34 local str = _("Ballot of '#{member_name}' for issue ##{issue_id}",
35 {member_name = string.format('<a href="%s">%s</a>',
36 encode.url{
37 module = "member",
38 view = "show",
39 id = member.id,
40 },
41 encode.html(member.name)),
42 issue_id = string.format('<a href="%s">%s</a>',
43 encode.url{
44 module = "issue",
45 view = "show",
46 id = issue.id,
47 },
48 encode.html(tostring(issue.id)))
49 }
50 )
51 ui.raw_title(str)
52 else
53 member = app.session.member
55 direct_voter = DirectVoter:by_pk(issue.id, member.id)
57 ui.title(_"Voting")
59 ui.actions(function()
60 ui.link{
61 text = _"Cancel",
62 module = "issue",
63 view = "show",
64 id = issue.id
65 }
66 if direct_voter then
67 slot.put(" &middot; ")
68 ui.link{
69 text = _"Discard voting",
70 module = "vote",
71 action = "update",
72 params = {
73 issue_id = issue.id,
74 discard = true
75 },
76 routing = {
77 default = {
78 mode = "redirect",
79 module = "issue",
80 view = "show",
81 id = issue.id
82 }
83 }
84 }
85 end
86 end)
87 end
91 local tempvoting_string = param.get("scoring")
93 local tempvotings = {}
94 if tempvoting_string then
95 for match in tempvoting_string:gmatch("([^;]+)") do
96 for initiative_id, grade in match:gmatch("([^:;]+):([^:;]+)") do
97 tempvotings[tonumber(initiative_id)] = tonumber(grade)
98 end
99 end
100 end
102 local initiatives = issue:get_reference_selector("initiatives"):add_where("initiative.admitted"):add_order_by("initiative.satisfied_supporter_count DESC"):exec()
104 local min_grade = -1;
105 local max_grade = 1;
107 for i, initiative in ipairs(initiatives) do
108 -- TODO performance
109 initiative.vote = Vote:by_pk(initiative.id, member.id)
110 if tempvotings[initiative.id] then
111 initiative.vote = {}
112 initiative.vote.grade = tempvotings[initiative.id]
113 end
114 if initiative.vote then
115 if initiative.vote.grade > max_grade then
116 max_grade = initiative.vote.grade
117 end
118 if initiative.vote.grade < min_grade then
119 min_grade = initiative.vote.grade
120 end
121 end
122 end
124 local sections = {}
125 for i = min_grade, max_grade do
126 sections[i] = {}
127 for j, initiative in ipairs(initiatives) do
128 if (initiative.vote and initiative.vote.grade == i) or (not initiative.vote and i == 0) then
129 sections[i][#(sections[i])+1] = initiative
130 end
131 end
132 end
134 local approval_count, disapproval_count = 0, 0
135 for i = min_grade, -1 do
136 if #sections[i] > 0 then
137 disapproval_count = disapproval_count + 1
138 end
139 end
140 local approval_count = 0
141 for i = 1, max_grade do
142 if #sections[i] > 0 then
143 approval_count = approval_count + 1
144 end
145 end
149 if not readonly then
150 util.help("vote.list", _"Voting")
151 slot.put('<script src="' .. request.get_relative_baseurl() .. 'static/js/dragdrop.js"></script>')
152 slot.put('<script src="' .. request.get_relative_baseurl() .. 'static/js/voting.js"></script>')
153 end
155 ui.script{
156 script = function()
157 slot.put(
158 "voting_text_approval_single = ", encode.json(_"Approval [single entry]"), ";\n",
159 "voting_text_approval_multi = ", encode.json(_"Approval [many entries]"), ";\n",
160 "voting_text_first_preference_single = ", encode.json(_"Approval (first preference) [single entry]"), ";\n",
161 "voting_text_first_preference_multi = ", encode.json(_"Approval (first preference) [many entries]"), ";\n",
162 "voting_text_second_preference_single = ", encode.json(_"Approval (second preference) [single entry]"), ";\n",
163 "voting_text_second_preference_multi = ", encode.json(_"Approval (second preference) [many entries]"), ";\n",
164 "voting_text_third_preference_single = ", encode.json(_"Approval (third preference) [single entry]"), ";\n",
165 "voting_text_third_preference_multi = ", encode.json(_"Approval (third preference) [many entries]"), ";\n",
166 "voting_text_numeric_preference_single = ", encode.json(_"Approval (#th preference) [single entry]"), ";\n",
167 "voting_text_numeric_preference_multi = ", encode.json(_"Approval (#th preference) [many entries]"), ";\n",
168 "voting_text_abstention_single = ", encode.json(_"Abstention [single entry]"), ";\n",
169 "voting_text_abstention_multi = ", encode.json(_"Abstention [many entries]"), ";\n",
170 "voting_text_disapproval_above_one_single = ", encode.json(_"Disapproval (prefer to lower block) [single entry]"), ";\n",
171 "voting_text_disapproval_above_one_multi = ", encode.json(_"Disapproval (prefer to lower block) [many entries]"), ";\n",
172 "voting_text_disapproval_above_many_single = ", encode.json(_"Disapproval (prefer to lower blocks) [single entry]"), ";\n",
173 "voting_text_disapproval_above_many_multi = ", encode.json(_"Disapproval (prefer to lower blocks) [many entries]"), ";\n",
174 "voting_text_disapproval_above_last_single = ", encode.json(_"Disapproval (prefer to last block) [single entry]"), ";\n",
175 "voting_text_disapproval_above_last_multi = ", encode.json(_"Disapproval (prefer to last block) [many entries]"), ";\n",
176 "voting_text_disapproval_single = ", encode.json(_"Disapproval [single entry]"), ";\n",
177 "voting_text_disapproval_multi = ", encode.json(_"Disapproval [many entries]"), ";\n"
178 )
179 end
180 }
182 ui.form{
183 record = direct_voter,
184 attr = {
185 id = "voting_form",
186 class = readonly and "voting_form_readonly" or "voting_form_active"
187 },
188 module = "vote",
189 action = "update",
190 params = { issue_id = issue.id },
191 content = function()
192 if not readonly or preview then
193 local scoring = param.get("scoring")
194 if not scoring then
195 for i, initiative in ipairs(initiatives) do
196 local vote = initiative.vote
197 if vote then
198 tempvotings[initiative.id] = vote.grade
199 else
200 tempvotings[initiative.id] = 0
201 end
202 end
203 local tempvotings_list = {}
204 for key, val in pairs(tempvotings) do
205 tempvotings_list[#tempvotings_list+1] = tostring(key) .. ":" .. tostring(val)
206 end
207 if #tempvotings_list > 0 then
208 scoring = table.concat(tempvotings_list, ";")
209 else
210 scoring = ""
211 end
212 end
213 slot.put('<input type="hidden" name="scoring" value="' .. scoring .. '"/>')
214 -- TODO abstrahieren
215 ui.tag{
216 tag = "input",
217 attr = {
218 type = "submit",
219 class = "voting_done1",
220 value = submit_button_text
221 }
222 }
223 end
224 ui.container{
225 attr = { id = "voting" },
226 content = function()
227 local approval_index, disapproval_index = 0, 0
228 for grade = max_grade, min_grade, -1 do
229 local entries = sections[grade]
230 local class
231 if grade > 0 then
232 class = "approval"
233 elseif grade < 0 then
234 class = "disapproval"
235 else
236 class = "abstention"
237 end
238 if
239 #entries > 0 or
240 (grade == 1 and not approval_used) or
241 (grade == -1 and not disapproval_used) or
242 grade == 0
243 then
244 ui.container{
245 attr = { class = class },
246 content = function()
247 local heading
248 if class == "approval" then
249 approval_used = true
250 approval_index = approval_index + 1
251 if approval_count > 1 then
252 if approval_index == 1 then
253 if #entries == 1 then
254 heading = _"Approval (first preference) [single entry]"
255 else
256 heading = _"Approval (first preference) [many entries]"
257 end
258 elseif approval_index == 2 then
259 if #entries == 1 then
260 heading = _"Approval (second preference) [single entry]"
261 else
262 heading = _"Approval (second preference) [many entries]"
263 end
264 elseif approval_index == 3 then
265 if #entries == 1 then
266 heading = _"Approval (third preference) [single entry]"
267 else
268 heading = _"Approval (third preference) [many entries]"
269 end
270 else
271 if #entries == 1 then
272 heading = _"Approval (#th preference) [single entry]"
273 else
274 heading = _"Approval (#th preference) [many entries]"
275 end
276 end
277 else
278 if #entries == 1 then
279 heading = _"Approval [single entry]"
280 else
281 heading = _"Approval [many entries]"
282 end
283 end
284 elseif class == "abstention" then
285 if #entries == 1 then
286 heading = _"Abstention [single entry]"
287 else
288 heading = _"Abstention [many entries]"
289 end
290 elseif class == "disapproval" then
291 disapproval_used = true
292 disapproval_index = disapproval_index + 1
293 if disapproval_count > disapproval_index + 1 then
294 if #entries == 1 then
295 heading = _"Disapproval (prefer to lower blocks) [single entry]"
296 else
297 heading = _"Disapproval (prefer to lower blocks) [many entries]"
298 end
299 elseif disapproval_count == 2 and disapproval_index == 1 then
300 if #entries == 1 then
301 heading = _"Disapproval (prefer to lower block) [single entry]"
302 else
303 heading = _"Disapproval (prefer to lower block) [many entries]"
304 end
305 elseif disapproval_index == disapproval_count - 1 then
306 if #entries == 1 then
307 heading = _"Disapproval (prefer to last block) [single entry]"
308 else
309 heading = _"Disapproval (prefer to last block) [many entries]"
310 end
311 else
312 if #entries == 1 then
313 heading = _"Disapproval [single entry]"
314 else
315 heading = _"Disapproval [many entries]"
316 end
317 end
318 end
319 ui.tag {
320 tag = "div",
321 attr = { class = "cathead" },
322 content = heading
323 }
324 for i, initiative in ipairs(entries) do
325 ui.container{
326 attr = {
327 class = "movable",
328 id = "entry_" .. tostring(initiative.id)
329 },
330 content = function()
331 local initiators_selector = initiative:get_reference_selector("initiating_members")
332 :add_where("accepted")
333 local initiators = initiators_selector:exec()
334 local initiator_names = {}
335 for i, initiator in ipairs(initiators) do
336 initiator_names[#initiator_names+1] = initiator.name
337 end
338 local initiator_names_string = table.concat(initiator_names, ", ")
339 ui.container{
340 attr = { style = "float: right; position: relative;" },
341 content = function()
342 ui.link{
343 attr = { class = "clickable" },
344 content = _"Show",
345 module = "initiative",
346 view = "show",
347 id = initiative.id
348 }
349 slot.put(" ")
350 ui.link{
351 attr = { class = "clickable", target = "_blank" },
352 content = _"(new window)",
353 module = "initiative",
354 view = "show",
355 id = initiative.id
356 }
357 if not readonly then
358 slot.put(" ")
359 ui.image{ attr = { class = "grabber" }, static = "icons/grabber.png" }
360 end
361 end
362 }
363 if not readonly then
364 ui.container{
365 attr = { style = "float: left; position: relative;" },
366 content = function()
367 ui.tag{
368 tag = "input",
369 attr = {
370 onclick = "if (jsFail) return true; voting_moveUp(this.parentNode.parentNode); return(false);",
371 name = "move_up_" .. tostring(initiative.id),
372 class = not disabled and "clickable" or nil,
373 type = "image",
374 src = encode.url{ static = "icons/move_up.png" },
375 alt = _"Move up"
376 }
377 }
378 slot.put("&nbsp;")
379 ui.tag{
380 tag = "input",
381 attr = {
382 onclick = "if (jsFail) return true; voting_moveDown(this.parentNode.parentNode); return(false);",
383 name = "move_down_" .. tostring(initiative.id),
384 class = not disabled and "clickable" or nil,
385 type = "image",
386 src = encode.url{ static = "icons/move_down.png" },
387 alt = _"Move down"
388 }
389 }
390 slot.put("&nbsp;")
391 end
392 }
393 end
394 ui.container{
395 content = function()
396 ui.tag{ content = "i" .. initiative.id .. ": " }
397 ui.tag{ content = initiative.shortened_name }
398 slot.put("<br />")
399 for i, initiator in ipairs(initiators) do
400 ui.link{
401 attr = { class = "clickable" },
402 content = function ()
403 execute.view{
404 module = "member_image",
405 view = "_show",
406 params = {
407 member = initiator,
408 image_type = "avatar",
409 show_dummy = true,
410 class = "micro_avatar",
411 popup_text = text
412 }
413 }
414 end,
415 module = "member", view = "show", id = initiator.id
416 }
417 slot.put(" ")
418 ui.tag{ content = initiator.name }
419 slot.put(" ")
420 end
421 end
422 }
423 end
424 }
425 end
426 end
427 }
428 end
429 end
430 end
431 }
432 if app.session.member_id and preview then
433 local formatting_engine = param.get("formatting_engine")
434 local comment = param.get("comment")
435 local rendered_comment = format.wiki_text(comment, formatting_engine)
436 slot.put(rendered_comment)
437 end
438 if (readonly or direct_voter and direct_voter.comment) and not preview then
439 local text
440 if direct_voter and direct_voter.comment_changed then
441 text = _("Voting comment (last updated: #{timestamp})", { timestamp = format.timestamp(direct_voter.comment_changed) })
442 elseif direct_voter and direct_voter.comment then
443 text = _"Voting comment"
444 end
445 if text then
446 ui.heading{ level = "2", content = text }
447 end
448 if direct_voter and direct_voter.comment then
449 local rendered_comment = direct_voter:get_content('html')
450 ui.container{ attr = { class = "member_statement" }, content = function()
451 slot.put(rendered_comment)
452 end }
453 slot.put("<br />")
454 end
455 end
456 if app.session.member_id and app.session.member_id == member.id then
457 if not readonly or direct_voter then
458 ui.field.hidden{ name = "update_comment", value = param.get("update_comment") or issue.closed and "1" }
459 ui.field.select{
460 label = _"Wiki engine for statement",
461 name = "formatting_engine",
462 foreign_records = {
463 { id = "rocketwiki", name = "RocketWiki" },
464 { id = "compat", name = _"Traditional wiki syntax" }
465 },
466 attr = {id = "formatting_engine"},
467 foreign_id = "id",
468 foreign_name = "name",
469 value = param.get("formatting_engine") or direct_voter and direct_voter.formatting_engine
470 }
471 ui.field.text{
472 label = _"Voting comment (optional)",
473 name = "comment",
474 multiline = true,
475 value = param.get("comment") or direct_voter and direct_voter.comment,
476 attr = { style = "height: 20ex;" },
477 }
478 ui.field.hidden{ name = "preview2", attr = { id = "preview2" }, value = "0" }
479 ui.submit{
480 name = "preview",
481 value = _"Preview voting comment",
482 attr = { class = "preview" }
483 }
484 end
485 if not readonly or preview or direct_voter then
486 slot.put(" ")
487 ui.tag{
488 tag = "input",
489 attr = {
490 type = "submit",
491 class = "voting_done2",
492 value = submit_button_text
493 }
494 }
495 end
496 end
497 end
498 }

Impressum / About Us