liquid_feedback_frontend

view app/main/vote/list.lua @ 1045:701a5cf6b067

Imported LiquidFeedback Frontend 3.0 branch
author bsw
date Thu Jul 10 01:19:48 2014 +0200 (2014-07-10)
parents 37e31dd73e45
children 904f6807f7fa
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") 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 if preview then
25 readonly = true
26 end
28 local submit_button_text = _"Finish voting"
29 local edit_button_text = _"Edit again"
31 if issue.closed then
32 submit_button_text = _"Save voting comment"
33 edit_button_text = _"Edit voting comment"
34 end
36 execute.view {
37 module = "issue", view = "_head", params = { issue = issue }
38 }
40 local direct_voter
42 if member then
43 direct_voter = DirectVoter:by_pk(issue.id, member.id)
44 else
45 member = app.session.member
46 direct_voter = DirectVoter:by_pk(issue.id, member.id)
47 end
51 local tempvoting_string = param.get("scoring")
53 local tempvotings = {}
54 if tempvoting_string then
55 for match in tempvoting_string:gmatch("([^;]+)") do
56 for initiative_id, grade in match:gmatch("([^:;]+):([^:;]+)") do
57 tempvotings[tonumber(initiative_id)] = tonumber(grade)
58 end
59 end
60 end
62 local initiatives = issue:get_reference_selector("initiatives"):add_where("initiative.admitted"):add_order_by("initiative.satisfied_supporter_count DESC"):exec()
64 local min_grade = -1;
65 local max_grade = 1;
67 for i, initiative in ipairs(initiatives) do
68 -- TODO performance
69 initiative.vote = Vote:by_pk(initiative.id, member.id)
70 if tempvotings[initiative.id] then
71 initiative.vote = {}
72 initiative.vote.grade = tempvotings[initiative.id]
73 end
74 if initiative.vote then
75 if initiative.vote.grade > max_grade then
76 max_grade = initiative.vote.grade
77 end
78 if initiative.vote.grade < min_grade then
79 min_grade = initiative.vote.grade
80 end
81 end
82 end
84 local sections = {}
85 for i = min_grade, max_grade do
86 sections[i] = {}
87 for j, initiative in ipairs(initiatives) do
88 if (initiative.vote and initiative.vote.grade == i) or (not initiative.vote and i == 0) then
89 sections[i][#(sections[i])+1] = initiative
90 end
91 end
92 end
94 local approval_count, disapproval_count = 0, 0
95 for i = min_grade, -1 do
96 if #sections[i] > 0 then
97 disapproval_count = disapproval_count + 1
98 end
99 end
100 local approval_count = 0
101 for i = 1, max_grade do
102 if #sections[i] > 0 then
103 approval_count = approval_count + 1
104 end
105 end
107 if not readonly then
108 slot.put('<script src="' .. request.get_relative_baseurl() .. 'static/js/dragdrop.js"></script>')
109 slot.put('<script src="' .. request.get_relative_baseurl() .. 'static/js/voting.js"></script>')
110 end
112 ui.script{
113 script = function()
114 slot.put(
115 "voting_text_approval_single = ", encode.json(_"Approval [single entry]"), ";\n",
116 "voting_text_approval_multi = ", encode.json(_"Approval [many entries]"), ";\n",
117 "voting_text_first_preference_single = ", encode.json(_"Approval (first preference) [single entry]"), ";\n",
118 "voting_text_first_preference_multi = ", encode.json(_"Approval (first preference) [many entries]"), ";\n",
119 "voting_text_second_preference_single = ", encode.json(_"Approval (second preference) [single entry]"), ";\n",
120 "voting_text_second_preference_multi = ", encode.json(_"Approval (second preference) [many entries]"), ";\n",
121 "voting_text_third_preference_single = ", encode.json(_"Approval (third preference) [single entry]"), ";\n",
122 "voting_text_third_preference_multi = ", encode.json(_"Approval (third preference) [many entries]"), ";\n",
123 "voting_text_numeric_preference_single = ", encode.json(_"Approval (#th preference) [single entry]"), ";\n",
124 "voting_text_numeric_preference_multi = ", encode.json(_"Approval (#th preference) [many entries]"), ";\n",
125 "voting_text_abstention_single = ", encode.json(_"Abstention [single entry]"), ";\n",
126 "voting_text_abstention_multi = ", encode.json(_"Abstention [many entries]"), ";\n",
127 "voting_text_disapproval_above_one_single = ", encode.json(_"Disapproval (prefer to lower block) [single entry]"), ";\n",
128 "voting_text_disapproval_above_one_multi = ", encode.json(_"Disapproval (prefer to lower block) [many entries]"), ";\n",
129 "voting_text_disapproval_above_many_single = ", encode.json(_"Disapproval (prefer to lower blocks) [single entry]"), ";\n",
130 "voting_text_disapproval_above_many_multi = ", encode.json(_"Disapproval (prefer to lower blocks) [many entries]"), ";\n",
131 "voting_text_disapproval_above_last_single = ", encode.json(_"Disapproval (prefer to last block) [single entry]"), ";\n",
132 "voting_text_disapproval_above_last_multi = ", encode.json(_"Disapproval (prefer to last block) [many entries]"), ";\n",
133 "voting_text_disapproval_single = ", encode.json(_"Disapproval [single entry]"), ";\n",
134 "voting_text_disapproval_multi = ", encode.json(_"Disapproval [many entries]"), ";\n"
135 )
136 end
137 }
139 if issue.state == "finished_with_winner"
140 or issue.state == "finished_without_winner"
141 then
143 local members_selector = Member:new_selector()
144 :join("delegating_voter", nil, "delegating_voter.member_id = member.id")
145 :add_where{ "delegating_voter.issue_id = ?", issue.id }
146 :add_where{ "delegating_voter.delegate_member_ids[1] = ?", member.id }
147 :add_field("delegating_voter.weight", "voter_weight")
148 :join("issue", nil, "issue.id = delegating_voter.issue_id")
150 ui.sidebar( "tab-members", function()
151 ui.sidebarHead(function()
152 ui.heading{ level = 2, content = _"Incoming delegations" }
153 end)
154 execute.view{
155 module = "member",
156 view = "_list",
157 params = {
158 members_selector = members_selector,
159 trustee = member,
160 issue = issue,
161 initiative = initiative,
162 for_votes = true, no_filter = true,
163 member_class = "sidebarRow sidebarRowNarrow",
164 }
165 }
166 end)
167 end
170 ui.section( function()
172 ui.sectionHead( function()
173 if preview then
174 ui.heading { level = 1, content = _"Preview of voting ballot" }
175 elseif readonly then
176 local str = _("Ballot of '#{member_name}'",
177 {member_name = string.format('<a href="%s">%s</a>',
178 encode.url{
179 module = "member",
180 view = "show",
181 id = member.id,
182 },
183 encode.html(member.name))
184 }
185 )
186 ui.heading { level = 1, content = function () slot.put ( str ) end }
187 else
188 ui.heading { level = 1, content = _"Voting" }
189 end
190 end )
192 ui.sectionRow( function()
194 ui.form{
195 record = direct_voter,
196 attr = {
197 id = "voting_form",
198 class = readonly and "voting_form_readonly" or "voting_form_active"
199 },
200 module = "vote",
201 action = "update",
202 params = { issue_id = issue.id },
203 content = function()
204 if not readonly or preview then
205 local scoring = param.get("scoring")
206 if not scoring then
207 for i, initiative in ipairs(initiatives) do
208 local vote = initiative.vote
209 if vote then
210 tempvotings[initiative.id] = vote.grade
211 else
212 tempvotings[initiative.id] = 0
213 end
214 end
215 local tempvotings_list = {}
216 for key, val in pairs(tempvotings) do
217 tempvotings_list[#tempvotings_list+1] = tostring(key) .. ":" .. tostring(val)
218 end
219 if #tempvotings_list > 0 then
220 scoring = table.concat(tempvotings_list, ";")
221 else
222 scoring = ""
223 end
224 end
225 slot.put('<input type="hidden" name="scoring" value="' .. scoring .. '"/>')
226 end
227 if preview then
228 ui.heading{ level = 2, content = _"Your choice" }
229 elseif not readonly then
230 ui.heading{ level = 2, content = _"Make your choice by placing the initiatives" }
231 end
233 ui.container{
234 attr = { id = "voting" },
235 content = function()
236 local approval_index, disapproval_index = 0, 0
237 for grade = max_grade, min_grade, -1 do
238 local entries = sections[grade]
239 local class
240 if grade > 0 then
241 class = "approval"
242 elseif grade < 0 then
243 class = "disapproval"
244 else
245 class = "abstention"
246 end
247 if
248 #entries > 0 or
249 (grade == 1 and not approval_used) or
250 (grade == -1 and not disapproval_used) or
251 grade == 0
252 then
253 ui.container{
254 attr = { class = class },
255 content = function()
256 local heading
257 if class == "approval" then
258 approval_used = true
259 approval_index = approval_index + 1
260 if approval_count > 1 then
261 if approval_index == 1 then
262 if #entries == 1 then
263 heading = _"Approval (first preference) [single entry]"
264 else
265 heading = _"Approval (first preference) [many entries]"
266 end
267 elseif approval_index == 2 then
268 if #entries == 1 then
269 heading = _"Approval (second preference) [single entry]"
270 else
271 heading = _"Approval (second preference) [many entries]"
272 end
273 elseif approval_index == 3 then
274 if #entries == 1 then
275 heading = _"Approval (third preference) [single entry]"
276 else
277 heading = _"Approval (third preference) [many entries]"
278 end
279 else
280 if #entries == 1 then
281 heading = _"Approval (#th preference) [single entry]"
282 else
283 heading = _"Approval (#th preference) [many entries]"
284 end
285 end
286 else
287 if #entries == 1 then
288 heading = _"Approval [single entry]"
289 else
290 heading = _"Approval [many entries]"
291 end
292 end
293 elseif class == "abstention" then
294 if #entries == 1 then
295 heading = _"Abstention [single entry]"
296 else
297 heading = _"Abstention [many entries]"
298 end
299 elseif class == "disapproval" then
300 disapproval_used = true
301 disapproval_index = disapproval_index + 1
302 if disapproval_count > disapproval_index + 1 then
303 if #entries == 1 then
304 heading = _"Disapproval (prefer to lower blocks) [single entry]"
305 else
306 heading = _"Disapproval (prefer to lower blocks) [many entries]"
307 end
308 elseif disapproval_count == 2 and disapproval_index == 1 then
309 if #entries == 1 then
310 heading = _"Disapproval (prefer to lower block) [single entry]"
311 else
312 heading = _"Disapproval (prefer to lower block) [many entries]"
313 end
314 elseif disapproval_index == disapproval_count - 1 then
315 if #entries == 1 then
316 heading = _"Disapproval (prefer to last block) [single entry]"
317 else
318 heading = _"Disapproval (prefer to last block) [many entries]"
319 end
320 else
321 if #entries == 1 then
322 heading = _"Disapproval [single entry]"
323 else
324 heading = _"Disapproval [many entries]"
325 end
326 end
327 end
328 ui.tag {
329 tag = "div",
330 attr = { class = "cathead" },
331 content = heading
332 }
333 for i, initiative in ipairs(entries) do
334 ui.container{
335 attr = {
336 class = "movable",
337 id = "entry_" .. tostring(initiative.id)
338 },
339 content = function()
340 local initiators_selector = initiative:get_reference_selector("initiating_members")
341 :add_where("accepted")
342 local initiators = initiators_selector:exec()
343 local initiator_names = {}
344 for i, initiator in ipairs(initiators) do
345 initiator_names[#initiator_names+1] = initiator.name
346 end
347 local initiator_names_string = table.concat(initiator_names, ", ")
348 ui.container{
349 attr = { style = "float: right; position: relative;" },
350 content = function()
351 ui.link{
352 attr = { class = "clickable" },
353 content = _"Show",
354 module = "initiative",
355 view = "show",
356 id = initiative.id
357 }
358 slot.put(" ")
359 ui.link{
360 attr = { class = "clickable", target = "_blank" },
361 content = _"(new window)",
362 module = "initiative",
363 view = "show",
364 id = initiative.id
365 }
366 if not readonly then
367 slot.put(" ")
368 ui.image{ attr = { class = "grabber" }, static = "icons/grabber.png" }
369 end
370 end
371 }
372 if not readonly then
373 ui.container{
374 attr = { style = "float: left; position: relative;" },
375 content = function()
376 ui.tag{
377 tag = "input",
378 attr = {
379 onclick = "if (jsFail) return true; voting_moveUp(this.parentNode.parentNode); return(false);",
380 name = "move_up_" .. tostring(initiative.id),
381 class = not disabled and "clickable" or nil,
382 type = "image",
383 src = encode.url{ static = "icons/move_up.png" },
384 alt = _"Move up"
385 }
386 }
387 slot.put("&nbsp;")
388 ui.tag{
389 tag = "input",
390 attr = {
391 onclick = "if (jsFail) return true; voting_moveDown(this.parentNode.parentNode); return(false);",
392 name = "move_down_" .. tostring(initiative.id),
393 class = not disabled and "clickable" or nil,
394 type = "image",
395 src = encode.url{ static = "icons/move_down.png" },
396 alt = _"Move down"
397 }
398 }
399 slot.put("&nbsp;")
400 end
401 }
402 end
403 ui.container{
404 content = function()
405 ui.tag{ content = "i" .. initiative.id .. ": " }
406 ui.tag{ content = initiative.shortened_name }
407 slot.put("<br />")
408 for i, initiator in ipairs(initiators) do
409 ui.link{
410 attr = { class = "clickable" },
411 content = function ()
412 execute.view{
413 module = "member_image",
414 view = "_show",
415 params = {
416 member = initiator,
417 image_type = "avatar",
418 show_dummy = true,
419 class = "micro_avatar",
420 popup_text = text
421 }
422 }
423 end,
424 module = "member", view = "show", id = initiator.id
425 }
426 slot.put(" ")
427 ui.tag{ content = initiator.name }
428 slot.put(" ")
429 end
430 end
431 }
432 end
433 }
434 end
435 end
436 }
437 end
438 end
439 end
440 }
441 if app.session.member_id and preview then
442 local formatting_engine = param.get("formatting_engine") or config.enforce_formatting_engine
443 local comment = param.get("comment")
444 if comment and #comment > 0 then
445 local rendered_comment = format.wiki_text(comment, formatting_engine)
446 ui.heading{ level = "2", content = _"Voting comment" }
447 ui.container { attr = { class = "member_statement" }, content = function()
448 slot.put(rendered_comment)
449 end }
450 slot.put("<br />")
451 end
452 end
453 if (readonly or direct_voter and direct_voter.comment) and not preview and not (app.session.member_id == member.id) then
454 local text
455 if direct_voter and direct_voter.comment_changed then
456 text = _("Voting comment (last updated: #{timestamp})", { timestamp = format.timestamp(direct_voter.comment_changed) })
457 elseif direct_voter and direct_voter.comment then
458 text = _"Voting comment"
459 end
460 if text then
461 ui.heading{ level = "2", content = text }
462 end
463 if direct_voter and direct_voter.comment then
464 local rendered_comment = direct_voter:get_content('html')
465 ui.container { attr = { class = "member_statement" }, content = function()
466 slot.put(rendered_comment)
467 end }
468 slot.put("<br />")
469 end
470 end
471 if app.session.member_id and app.session.member_id == member.id then
472 if (not readonly or direct_voter) and not preview then
473 ui.container{ content = function()
474 if not config.enforce_formatting_engine then
475 ui.field.select{
476 label = _"Wiki engine for statement",
477 name = "formatting_engine",
478 foreign_records = config.formatting_engines,
479 attr = {id = "formatting_engine"},
480 foreign_id = "id",
481 foreign_name = "name",
482 value = param.get("formatting_engine") or direct_voter and direct_voter.formatting_engine
483 }
484 end
485 ui.heading { level = 2, content = _"Voting comment (optional)" }
486 ui.field.text{
487 name = "comment",
488 multiline = true,
489 value = param.get("comment") or direct_voter and direct_voter.comment,
490 attr = { style = "height: 10ex; width: 100%;" },
491 }
492 end }
493 end
495 if preview then
496 if not config.enforce_formatting_engine then
497 ui.field.hidden{ name = "formatting_engine", value = param.get("formatting_engine") }
498 end
499 ui.field.hidden{ name = "comment", value = param.get("comment") or direct_voter and direct_voter.comment }
500 end
502 if not readonly or direct_voter or preview then
503 ui.container{ content = function()
504 if preview then
505 slot.put(" ")
506 ui.tag{
507 tag = "input",
508 attr = {
509 type = "submit",
510 class = "btn btn-default",
511 name = issue.closed and "update_comment" or nil,
512 value = submit_button_text -- finish voting / update comment
513 }
514 }
515 end
516 if not preview then
517 ui.tag{
518 tag = "input",
519 attr = {
520 type = "submit",
521 name = "preview",
522 class = "btn btn-default",
523 value = _"Preview",
524 }
525 }
526 else
527 slot.put(" ")
528 ui.tag{
529 tag = "input",
530 attr = {
531 type = "submit",
532 name = "edit",
533 class = "btn-link",
534 value = edit_button_text,
535 }
536 }
537 end
538 end }
539 end
540 end
541 end
542 }
543 slot.put("<br />")
544 ui.link{
545 text = _"Cancel",
546 module = "issue",
547 view = "show",
548 id = issue.id
549 }
550 if direct_voter then
551 slot.put(" | ")
552 ui.link {
553 module = "vote", action = "update",
554 params = {
555 issue_id = issue.id,
556 discard = true
557 },
558 routing = {
559 default = {
560 mode = "redirect",
561 module = "issue",
562 view = "show",
563 id = issue.id
564 }
565 },
566 text = _"Discard my vote"
567 }
568 end
570 end )
571 end )

Impressum / About Us