liquid_feedback_frontend
view app/main/vote/list.lua @ 159:5d797c6706d5
implement quorum display
show the initiative quorum as a small 1px line in bargraph
allow to update your support on the diff page
better linked title in diff page
show absolute quorum numbers in detail pages of issue and initiative
show the initiative quorum as a small 1px line in bargraph
allow to update your support on the diff page
better linked title in diff page
show absolute quorum numbers in detail pages of issue and initiative
author | Daniel Poelzleithner <poelzi@poelzi.org> |
---|---|
date | Sat Oct 09 03:42:48 2010 +0200 (2010-10-09) |
parents | 39d49cf933d8 |
children | 068ad175ebd7 |
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
6 local readonly = false
8 if member_id then
9 if not issue.closed then
10 error("access denied")
11 end
12 member = Member:by_id(member_id)
13 readonly = true
14 end
16 if issue.closed then
17 if not member then
18 slot.put_into("error", _"This issue is already closed.")
19 end
20 if not member then
21 member = app.session.member
22 end
23 readonly = true
24 end
26 if member then
27 local str = _("Ballot of '#{member_name}' for issue ##{issue_id}",
28 {member_name = string.format('<a href="%s">%s</a>',
29 encode.url{
30 module = "member",
31 view = "show",
32 id = member.id,
33 },
34 encode.html(member.name)),
35 issue_id = string.format('<a href="%s">%s</a>',
36 encode.url{
37 module = "issue",
38 view = "show",
39 id = issue.id,
40 },
41 encode.html(tostring(issue.id)))
42 }
43 )
44 slot.put_into("title", str)
45 else
46 member = app.session.member
47 slot.put_into("title", _"Voting")
49 slot.select("actions", function()
50 ui.link{
51 content = function()
52 ui.image{ static = "icons/16/cancel.png" }
53 slot.put(_"Cancel")
54 end,
55 module = "issue",
56 view = "show",
57 id = issue.id
58 }
59 ui.link{
60 text = _"Discard voting",
61 content = function()
62 ui.image{ static = "icons/16/email_delete.png" }
63 slot.put(_"Discard voting")
64 end,
65 module = "vote",
66 action = "update",
67 params = {
68 issue_id = issue.id,
69 discard = true
70 },
71 routing = {
72 default = {
73 mode = "redirect",
74 module = "issue",
75 view = "show",
76 id = issue.id
77 }
78 }
79 }
80 end)
81 end
84 local warning_text = _"Some JavaScript based functions (voting in particular) will not work.\nFor this beta, please use a current version of Firefox, Safari, Opera(?), Konqueror or another (more) standard compliant browser.\nAlternative access without JavaScript will be available soon."
86 ui.script{ static = "js/browser_warning.js" }
87 ui.script{ script = "checkBrowser(" .. encode.json(_"Your web browser is not fully supported yet." .. " " .. warning_text:gsub("\n", "\n\n")) .. ");" }
90 local tempvoting_string = param.get("scoring")
92 local tempvotings = {}
93 if tempvoting_string then
94 for match in tempvoting_string:gmatch("([^;]+)") do
95 for initiative_id, grade in match:gmatch("([^:;]+):([^:;]+)") do
96 tempvotings[tonumber(initiative_id)] = tonumber(grade)
97 end
98 end
99 end
101 local initiatives = issue:get_reference_selector("initiatives"):add_where("initiative.admitted"):add_order_by("initiative.satisfied_supporter_count DESC"):exec()
103 local min_grade = -1;
104 local max_grade = 1;
106 for i, initiative in ipairs(initiatives) do
107 -- TODO performance
108 initiative.vote = Vote:by_pk(initiative.id, member.id)
109 if tempvotings[initiative.id] then
110 initiative.vote = {}
111 initiative.vote.grade = tempvotings[initiative.id]
112 end
113 if initiative.vote then
114 if initiative.vote.grade > max_grade then
115 max_grade = initiative.vote.grade
116 end
117 if initiative.vote.grade < min_grade then
118 min_grade = initiative.vote.grade
119 end
120 end
121 end
123 local sections = {}
124 for i = min_grade, max_grade do
125 sections[i] = {}
126 for j, initiative in ipairs(initiatives) do
127 if (initiative.vote and initiative.vote.grade == i) or (not initiative.vote and i == 0) then
128 sections[i][#(sections[i])+1] = initiative
129 end
130 end
131 end
133 local approval_count, disapproval_count = 0, 0
134 for i = min_grade, -1 do
135 if #sections[i] > 0 then
136 disapproval_count = disapproval_count + 1
137 end
138 end
139 local approval_count = 0
140 for i = 1, max_grade do
141 if #sections[i] > 0 then
142 approval_count = approval_count + 1
143 end
144 end
148 if not readonly then
149 util.help("vote.list", _"Voting")
150 slot.put('<script src="' .. request.get_relative_baseurl() .. 'static/js/dragdrop.js"></script>')
151 slot.put('<script src="' .. request.get_relative_baseurl() .. 'static/js/voting.js"></script>')
152 end
154 ui.script{
155 script = function()
156 slot.put(
157 "voting_text_approval_single = ", encode.json(_"Approval [single entry]"), ";\n",
158 "voting_text_approval_multi = ", encode.json(_"Approval [many entries]"), ";\n",
159 "voting_text_first_preference_single = ", encode.json(_"Approval (first preference) [single entry]"), ";\n",
160 "voting_text_first_preference_multi = ", encode.json(_"Approval (first preference) [many entries]"), ";\n",
161 "voting_text_second_preference_single = ", encode.json(_"Approval (second preference) [single entry]"), ";\n",
162 "voting_text_second_preference_multi = ", encode.json(_"Approval (second preference) [many entries]"), ";\n",
163 "voting_text_third_preference_single = ", encode.json(_"Approval (third preference) [single entry]"), ";\n",
164 "voting_text_third_preference_multi = ", encode.json(_"Approval (third preference) [many entries]"), ";\n",
165 "voting_text_numeric_preference_single = ", encode.json(_"Approval (#th preference) [single entry]"), ";\n",
166 "voting_text_numeric_preference_multi = ", encode.json(_"Approval (#th preference) [many entries]"), ";\n",
167 "voting_text_abstention_single = ", encode.json(_"Abstention [single entry]"), ";\n",
168 "voting_text_abstention_multi = ", encode.json(_"Abstention [many entries]"), ";\n",
169 "voting_text_disapproval_above_one_single = ", encode.json(_"Disapproval (prefer to lower block) [single entry]"), ";\n",
170 "voting_text_disapproval_above_one_multi = ", encode.json(_"Disapproval (prefer to lower block) [many entries]"), ";\n",
171 "voting_text_disapproval_above_many_single = ", encode.json(_"Disapproval (prefer to lower blocks) [single entry]"), ";\n",
172 "voting_text_disapproval_above_many_multi = ", encode.json(_"Disapproval (prefer to lower blocks) [many entries]"), ";\n",
173 "voting_text_disapproval_above_last_single = ", encode.json(_"Disapproval (prefer to last block) [single entry]"), ";\n",
174 "voting_text_disapproval_above_last_multi = ", encode.json(_"Disapproval (prefer to last block) [many entries]"), ";\n",
175 "voting_text_disapproval_single = ", encode.json(_"Disapproval [single entry]"), ";\n",
176 "voting_text_disapproval_multi = ", encode.json(_"Disapproval [many entries]"), ";\n"
177 )
178 end
179 }
181 ui.form{
182 attr = {
183 id = "voting_form",
184 class = readonly and "voting_form_readonly" or "voting_form_active"
185 },
186 module = "vote",
187 action = "update",
188 params = { issue_id = issue.id },
189 routing = {
190 default = {
191 mode = "redirect",
192 module = "issue",
193 view = "show",
194 id = issue.id
195 }
196 },
197 content = function()
198 if not readonly then
199 local scoring = param.get("scoring")
200 if not scoring then
201 for i, initiative in ipairs(initiatives) do
202 local vote = initiative.vote
203 if vote then
204 tempvotings[initiative.id] = vote.grade
205 end
206 end
207 local tempvotings_list = {}
208 for key, val in pairs(tempvotings) do
209 tempvotings_list[#tempvotings_list+1] = tostring(key) .. ":" .. tostring(val)
210 end
211 if #tempvotings_list > 0 then
212 scoring = table.concat(tempvotings_list, ";")
213 else
214 scoring = ""
215 end
216 end
217 slot.put('<input type="hidden" name="scoring" value="' .. scoring .. '"/>')
218 -- TODO abstrahieren
219 ui.tag{
220 tag = "input",
221 attr = {
222 type = "submit",
223 class = "voting_done",
224 value = _"Finish voting"
225 }
226 }
227 end
228 ui.container{
229 attr = { id = "voting" },
230 content = function()
231 local approval_index, disapproval_index = 0, 0
232 for grade = max_grade, min_grade, -1 do
233 local entries = sections[grade]
234 local class
235 if grade > 0 then
236 class = "approval"
237 elseif grade < 0 then
238 class = "disapproval"
239 else
240 class = "abstention"
241 end
242 if
243 #entries > 0 or
244 (grade == 1 and not approval_used) or
245 (grade == -1 and not disapproval_used) or
246 grade == 0
247 then
248 ui.container{
249 attr = { class = class },
250 content = function()
251 local heading
252 if class == "approval" then
253 approval_used = true
254 approval_index = approval_index + 1
255 if approval_count > 1 then
256 if approval_index == 1 then
257 if #entries == 1 then
258 heading = _"Approval (first preference) [single entry]"
259 else
260 heading = _"Approval (first preference) [many entries]"
261 end
262 elseif approval_index == 2 then
263 if #entries == 1 then
264 heading = _"Approval (second preference) [single entry]"
265 else
266 heading = _"Approval (second preference) [many entries]"
267 end
268 elseif approval_index == 3 then
269 if #entries == 1 then
270 heading = _"Approval (third preference) [single entry]"
271 else
272 heading = _"Approval (third preference) [many entries]"
273 end
274 else
275 if #entries == 1 then
276 heading = _"Approval (#th preference) [single entry]"
277 else
278 heading = _"Approval (#th preference) [many entries]"
279 end
280 end
281 else
282 if #entries == 1 then
283 heading = _"Approval [single entry]"
284 else
285 heading = _"Approval [many entries]"
286 end
287 end
288 elseif class == "abstention" then
289 if #entries == 1 then
290 heading = _"Abstention [single entry]"
291 else
292 heading = _"Abstention [many entries]"
293 end
294 elseif class == "disapproval" then
295 disapproval_used = true
296 disapproval_index = disapproval_index + 1
297 if disapproval_count > disapproval_index + 1 then
298 if #entries == 1 then
299 heading = _"Disapproval (prefer to lower blocks) [single entry]"
300 else
301 heading = _"Disapproval (prefer to lower blocks) [many entries]"
302 end
303 elseif disapproval_count == 2 and disapproval_index == 1 then
304 if #entries == 1 then
305 heading = _"Disapproval (prefer to lower block) [single entry]"
306 else
307 heading = _"Disapproval (prefer to lower block) [many entries]"
308 end
309 elseif disapproval_index == disapproval_count - 1 then
310 if #entries == 1 then
311 heading = _"Disapproval (prefer to last block) [single entry]"
312 else
313 heading = _"Disapproval (prefer to last block) [many entries]"
314 end
315 else
316 if #entries == 1 then
317 heading = _"Disapproval [single entry]"
318 else
319 heading = _"Disapproval [many entries]"
320 end
321 end
322 end
323 ui.tag {
324 tag = "div",
325 attr = { class = "cathead" },
326 content = heading
327 }
328 for i, initiative in ipairs(entries) do
329 ui.container{
330 attr = {
331 class = "movable",
332 id = "entry_" .. tostring(initiative.id)
333 },
334 content = function()
335 local initiators_selector = initiative:get_reference_selector("initiating_members")
336 :add_where("accepted")
337 local initiators = initiators_selector:exec()
338 local initiator_names = {}
339 for i, initiator in ipairs(initiators) do
340 initiator_names[#initiator_names+1] = initiator.name
341 end
342 local initiator_names_string = table.concat(initiator_names, ", ")
343 ui.container{
344 attr = { style = "float: right;" },
345 content = function()
346 ui.link{
347 attr = { class = "clickable" },
348 content = _"Show",
349 module = "initiative",
350 view = "show",
351 id = initiative.id
352 }
353 slot.put(" ")
354 ui.link{
355 attr = { class = "clickable", target = "_blank" },
356 content = _"(new window)",
357 module = "initiative",
358 view = "show",
359 id = initiative.id
360 }
361 if not readonly then
362 slot.put(" ")
363 ui.image{ attr = { class = "grabber" }, static = "icons/grabber.png" }
364 end
365 end
366 }
367 if not readonly then
368 ui.container{
369 attr = { style = "float: left;" },
370 content = function()
371 ui.tag{
372 tag = "input",
373 attr = {
374 onclick = "voting_moveUp(this.parentNode.parentNode); return(false);",
375 name = "move_up",
376 value = initiative.id,
377 class = not disabled and "clickable" or nil,
378 type = "image",
379 src = encode.url{ static = "icons/move_up.png" },
380 alt = _"Move up"
381 }
382 }
383 slot.put(" ")
384 ui.tag{
385 tag = "input",
386 attr = {
387 onclick = "voting_moveDown(this.parentNode.parentNode); return(false);",
388 name = "move_down",
389 value = initiative.id,
390 class = not disabled and "clickable" or nil,
391 type = "image",
392 src = encode.url{ static = "icons/move_down.png" },
393 alt = _"Move down"
394 }
395 }
396 slot.put(" ")
397 end
398 }
399 end
400 ui.container{
401 content = function()
402 slot.put(encode.html(initiative.shortened_name))
403 if #initiators > 1 then
404 ui.container{
405 attr = { style = "font-size: 80%;" },
406 content = _"Initiators" .. ": " .. initiator_names_string
407 }
408 else
409 ui.container{
410 attr = { style = "font-size: 80%;" },
411 content = _"Initiator" .. ": " .. initiator_names_string
412 }
413 end
414 end
415 }
416 end
417 }
418 end
419 end
420 }
421 end
422 end
423 end
424 }
425 if not readonly then
426 ui.tag{
427 tag = "input",
428 attr = {
429 type = "submit",
430 class = "voting_done",
431 value = _"Finish voting"
432 }
433 }
434 end
435 end
436 }