liquid_feedback_frontend
view app/main/vote/list.lua @ 19:00d1004545f1
Dynamic interface using XMLHttpRequests, and many other changes
Bugfixes:
- Only allow voting on admitted initiatives
- Repaired issue search
- Don't display delegations for closed issues on member page
- Don't show revoke link in initiative, when issue is already half_frozen
- Localization for voting JavaScript
- Display author of suggestions
Disclosure of voting data after voting is finished:
- Possibility to inspect every ballot including preferences
- Show number of voters preferring one initiative to another initiative
Interface behaviour changes:
- Reversed default order of drafts
- Default order of suggestions changed
- Show new drafts of initiatives only once per day in timeline
Accessibility:
- Barrier-free voting implemented
- POST links are now accessible without JavaScript
- Changed gray for unsatisfied supporters in bar graph to a lighter gray
Other interface improvements:
- Optical enhancements
- Dynamic interface using XMLHttpRequests
- Show usage terms in about section
- Show own membership in area listing
- Show uninformed supporters greyed out and marked with yellow question mark
- Warning box in non-admitted initiatives
- When voted, don't display voting notice and change label of voting link
- Show object counts in more tabulator heads
- Enlarged member statement input field
Miscellaneous:
- Code cleanup
- Added README file containing installation instructions
- Use new WebMCP function ui.filters{...} instead of own ui.filter and ui.order functions
Bugfixes:
- Only allow voting on admitted initiatives
- Repaired issue search
- Don't display delegations for closed issues on member page
- Don't show revoke link in initiative, when issue is already half_frozen
- Localization for voting JavaScript
- Display author of suggestions
Disclosure of voting data after voting is finished:
- Possibility to inspect every ballot including preferences
- Show number of voters preferring one initiative to another initiative
Interface behaviour changes:
- Reversed default order of drafts
- Default order of suggestions changed
- Show new drafts of initiatives only once per day in timeline
Accessibility:
- Barrier-free voting implemented
- POST links are now accessible without JavaScript
- Changed gray for unsatisfied supporters in bar graph to a lighter gray
Other interface improvements:
- Optical enhancements
- Dynamic interface using XMLHttpRequests
- Show usage terms in about section
- Show own membership in area listing
- Show uninformed supporters greyed out and marked with yellow question mark
- Warning box in non-admitted initiatives
- When voted, don't display voting notice and change label of voting link
- Show object counts in more tabulator heads
- Enlarged member statement input field
Miscellaneous:
- Code cleanup
- Added README file containing installation instructions
- Use new WebMCP function ui.filters{...} instead of own ui.filter and ui.order functions
| author | bsw/jbe | 
|---|---|
| date | Sat Feb 20 22:10:31 2010 +0100 (2010-02-20) | 
| parents | 77d58efe99fd | 
| children | 3036f2732b83 | 
 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
     7 if member_id then
     8   if not issue.closed then
     9     error("access denied")
    10   end
    11   member = Member:by_id(member_id)
    12   readonly = true
    13 end
    15 if member then
    16   slot.put_into("title", _("Ballot of '#{member_name}' for issue ##{issue_id}", {
    17     member_name = member.name,
    18     issue_id = issue.id
    19   }))
    20 else
    21   member = app.session.member
    22   slot.put_into("title", _"Voting")
    24   slot.select("actions", function()
    25     ui.link{
    26       content = function()
    27           ui.image{ static = "icons/16/cancel.png" }
    28           slot.put(_"Cancel")
    29       end,
    30       module = "issue",
    31       view = "show",
    32       id = issue.id
    33     }
    34   end)
    36 end
    39 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."
    41 ui.script{ static = "js/browser_warning.js" }
    42 ui.script{ script = "checkBrowser(" .. encode.json(_"Your web browser is not fully supported yet." .. " " .. warning_text:gsub("\n", "\n\n")) .. ");" }
    45 local tempvoting_string = param.get("scoring")
    47 local tempvotings = {}
    48 if tempvoting_string then
    49   for match in tempvoting_string:gmatch("([^;]+)") do
    50     for initiative_id, grade in match:gmatch("([^:;]+):([^:;]+)") do
    51       tempvotings[tonumber(initiative_id)] = tonumber(grade)
    52     end
    53   end
    54 end
    56 local initiatives = issue:get_reference_selector("initiatives"):add_where("initiative.admitted"):exec()
    58 local min_grade = -1;
    59 local max_grade = 1;
    61 for i, initiative in ipairs(initiatives) do
    62   -- TODO performance
    63   initiative.vote = Vote:by_pk(initiative.id, member.id)
    64   if tempvotings[initiative.id] then
    65     initiative.vote = {}
    66     initiative.vote.grade = tempvotings[initiative.id]
    67   end
    68   if initiative.vote then
    69     if initiative.vote.grade > max_grade then
    70       max_grade = initiative.vote.grade
    71     end
    72     if initiative.vote.grade < min_grade then
    73       min_grade = initiative.vote.grade
    74     end
    75   end
    76 end
    78 local sections = {}
    79 for i = min_grade, max_grade do
    80   sections[i] = {}
    81   for j, initiative in ipairs(initiatives) do
    82     if (initiative.vote and initiative.vote.grade == i) or (not initiative.vote and i == 0) then
    83       sections[i][#(sections[i])+1] = initiative
    84     end
    85   end
    86 end
    88 local approval_count, disapproval_count = 0, 0
    89 for i = min_grade, -1 do
    90   if #sections[i] > 0 then
    91     disapproval_count = disapproval_count + 1
    92   end
    93 end
    94 local approval_count = 0
    95 for i = 1, max_grade do
    96   if #sections[i] > 0 then
    97     approval_count = approval_count + 1
    98   end
    99 end
   103 if not readonly then
   104   util.help("vote.list", _"Voting")
   105   slot.put('<script src="' .. request.get_relative_baseurl() .. 'static/js/dragdrop.js"></script>')
   106   slot.put('<script src="' .. request.get_relative_baseurl() .. 'static/js/voting.js"></script>')
   107 end
   109 ui.script{
   110   script = function()
   111     slot.put(
   112       "voting_text_approval_single               = ", encode.json(_"Approval [single entry]"), ";\n",
   113       "voting_text_approval_multi                = ", encode.json(_"Approval [many entries]"), ";\n",
   114       "voting_text_first_preference_single       = ", encode.json(_"Approval (first preference) [single entry]"), ";\n",
   115       "voting_text_first_preference_multi        = ", encode.json(_"Approval (first preference) [many entries]"), ";\n",
   116       "voting_text_second_preference_single      = ", encode.json(_"Approval (second preference) [single entry]"), ";\n",
   117       "voting_text_second_preference_multi       = ", encode.json(_"Approval (second preference) [many entries]"), ";\n",
   118       "voting_text_third_preference_single       = ", encode.json(_"Approval (third preference) [single entry]"), ";\n",
   119       "voting_text_third_preference_multi        = ", encode.json(_"Approval (third preference) [many entries]"), ";\n",
   120       "voting_text_numeric_preference_single     = ", encode.json(_"Approval (#th preference) [single entry]"), ";\n",
   121       "voting_text_numeric_preference_multi      = ", encode.json(_"Approval (#th preference) [many entries]"), ";\n",
   122       "voting_text_abstention_single             = ", encode.json(_"Abstention [single entry]"), ";\n",
   123       "voting_text_abstention_multi              = ", encode.json(_"Abstention [many entries]"), ";\n",
   124       "voting_text_disapproval_above_one_single  = ", encode.json(_"Disapproval (prefer to lower block) [single entry]"), ";\n",
   125       "voting_text_disapproval_above_one_multi   = ", encode.json(_"Disapproval (prefer to lower block) [many entries]"), ";\n",
   126       "voting_text_disapproval_above_many_single = ", encode.json(_"Disapproval (prefer to lower blocks) [single entry]"), ";\n",
   127       "voting_text_disapproval_above_many_multi  = ", encode.json(_"Disapproval (prefer to lower blocks) [many entries]"), ";\n",
   128       "voting_text_disapproval_above_last_single = ", encode.json(_"Disapproval (prefer to last block) [single entry]"), ";\n",
   129       "voting_text_disapproval_above_last_multi  = ", encode.json(_"Disapproval (prefer to last block) [many entries]"), ";\n",
   130       "voting_text_disapproval_single            = ", encode.json(_"Disapproval [single entry]"), ";\n",
   131       "voting_text_disapproval_multi             = ", encode.json(_"Disapproval [many entries]"), ";\n"
   132     )
   133   end
   134 }
   136 ui.form{
   137   attr = {
   138     id = "voting_form",
   139     class = readonly and "voting_form_readonly" or "voting_form_active"
   140   },
   141   module = "vote",
   142   action = "update",
   143   params = { issue_id = issue.id },
   144   routing = {
   145     default = {
   146       mode = "redirect",
   147       module = "issue",
   148       view = "show",
   149       id = issue.id
   150     }
   151   },
   152   content = function()
   153     if not readonly then
   154       local scoring = param.get("scoring")
   155       if not scoring then
   156         for i, initiative in ipairs(initiatives) do
   157           local vote = initiative.vote
   158           if vote then
   159             tempvotings[initiative.id] = vote.grade
   160           end
   161         end
   162         local tempvotings_list = {}
   163         for key, val in pairs(tempvotings) do
   164           tempvotings_list[#tempvotings_list+1] = tostring(key) .. ":" .. tostring(val)
   165         end
   166         if #tempvotings_list > 0 then
   167           scoring = table.concat(tempvotings_list, ";")
   168         else
   169           scoring = ""
   170         end
   171       end
   172       slot.put('<input type="hidden" name="scoring" value="' .. scoring .. '"/>')
   173       -- TODO abstrahieren
   174       ui.tag{
   175         tag = "input",
   176         attr = {
   177           type = "button",
   178           class = "voting_done",
   179           value = _"Finish voting"
   180         }
   181       }
   182     end
   183     ui.container{
   184       attr = { id = "voting" },
   185       content = function()
   186         local approval_index, disapproval_index = 0, 0
   187         for grade = max_grade, min_grade, -1 do 
   188           local entries = sections[grade]
   189           local class
   190           if grade > 0 then
   191             class = "approval"
   192           elseif grade < 0 then
   193             class = "disapproval"
   194           else
   195             class = "abstention"
   196           end
   197           if
   198             #entries > 0 or
   199             (grade == 1 and not approval_used) or
   200             (grade == -1 and not disapproval_used) or
   201             grade == 0
   202           then
   203             ui.container{
   204               attr = { class = class },
   205               content = function()
   206                 local heading
   207                 if class == "approval" then
   208                   approval_used = true
   209                   approval_index = approval_index + 1
   210                   if approval_count > 1 then
   211                     if approval_index == 1 then
   212                       if #entries == 1 then
   213                         heading = _"Approval (first preference) [single entry]"
   214                       else
   215                         heading = _"Approval (first preference) [many entries]"
   216                       end
   217                     elseif approval_index == 2 then
   218                       if #entries == 1 then
   219                         heading = _"Approval (second preference) [single entry]"
   220                       else
   221                         heading = _"Approval (second preference) [many entries]"
   222                       end
   223                     elseif approval_index == 3 then
   224                       if #entries == 1 then
   225                         heading = _"Approval (third preference) [single entry]"
   226                       else
   227                         heading = _"Approval (third preference) [many entries]"
   228                       end
   229                     else
   230                       if #entries == 1 then
   231                         heading = _"Approval (#th preference) [single entry]"
   232                       else
   233                         heading = _"Approval (#th preference) [many entries]"
   234                       end
   235                     end
   236                   else
   237                     if #entries == 1 then
   238                       heading = _"Approval [single entry]"
   239                     else
   240                       heading = _"Approval [many entries]"
   241                     end
   242                   end
   243                 elseif class == "abstention" then
   244                     if #entries == 1 then
   245                       heading = _"Abstention [single entry]"
   246                     else
   247                       heading = _"Abstention [many entries]"
   248                     end
   249                 elseif class == "disapproval" then
   250                   disapproval_used = true
   251                   disapproval_index = disapproval_index + 1
   252                   if disapproval_count > disapproval_index + 1 then
   253                     if #entries == 1 then
   254                       heading = _"Disapproval (prefer to lower blocks) [single entry]"
   255                     else
   256                       heading = _"Disapproval (prefer to lower blocks) [many entries]"
   257                     end
   258                   elseif disapproval_count == 2 and disapproval_index == 1 then
   259                     if #entries == 1 then
   260                       heading = _"Disapproval (prefer to lower block) [single entry]"
   261                     else
   262                       heading = _"Disapproval (prefer to lower block) [many entries]"
   263                     end
   264                   elseif disapproval_index == disapproval_count - 1 then
   265                     if #entries == 1 then
   266                       heading = _"Disapproval (prefer to last block) [single entry]"
   267                     else
   268                       heading = _"Disapproval (prefer to last block) [many entries]"
   269                     end
   270                   else
   271                     if #entries == 1 then
   272                       heading = _"Disapproval [single entry]"
   273                     else
   274                       heading = _"Disapproval [many entries]"
   275                     end
   276                   end
   277                 end
   278                 ui.tag {
   279                   tag     = "div",
   280                   attr    = { class = "cathead" },
   281                   content = heading
   282                 }
   283                 for i, initiative in ipairs(entries) do
   284                   ui.container{
   285                     attr = {
   286                       class = "movable",
   287                       id = "entry_" .. tostring(initiative.id)
   288                     },
   289                     content = function()
   290                       local initiators_selector = initiative:get_reference_selector("initiating_members")
   291                         :add_where("accepted")
   292                       local initiators = initiators_selector:exec()
   293                       local initiator_names = {}
   294                       for i, initiator in ipairs(initiators) do
   295                         initiator_names[#initiator_names+1] = initiator.name
   296                       end
   297                       local initiator_names_string = table.concat(initiator_names, ", ")
   298                       ui.container{
   299                         attr = { style = "float: right;" },
   300                         content = function()
   301                           ui.link{
   302                             attr = { class = "clickable" },
   303                             content = _"Show",
   304                             module = "initiative",
   305                             view = "show",
   306                             id = initiative.id
   307                           }
   308                           slot.put(" ")
   309                           ui.link{
   310                             attr = { class = "clickable", target = "_blank" },
   311                             content = _"(new window)",
   312                             module = "initiative",
   313                             view = "show",
   314                             id = initiative.id
   315                           }
   316                           if not readonly then
   317                             slot.put(" ")
   318                             ui.image{ attr = { class = "grabber" }, static = "icons/grabber.png" }
   319                           end
   320                         end
   321                       }
   322                       if not readonly then
   323                         ui.container{
   324                           attr = { style = "float: left;" },
   325                           content = function()
   326                             ui.tag{
   327                               tag = "input",
   328                               attr = {
   329                                 onclick = "voting_moveUp(this.parentNode.parentNode); return(false);",
   330                                 name = "move_up",
   331                                 value = initiative.id,
   332                                 class = not disabled and "clickable" or nil,
   333                                 type = "image",
   334                                 src = encode.url{ static = "icons/move_up.png" },
   335                                 alt = _"Move up"
   336                               }
   337                             }
   338                             slot.put(" ")
   339                             ui.tag{
   340                               tag = "input",
   341                               attr = {
   342                                 onclick = "voting_moveDown(this.parentNode.parentNode); return(false);",
   343                                 name = "move_down",
   344                                 value = initiative.id,
   345                                 class = not disabled and "clickable" or nil,
   346                                 type = "image",
   347                                 src = encode.url{ static = "icons/move_down.png" },
   348                                 alt = _"Move down"
   349                               }
   350                             }
   351                             slot.put(" ")
   352                           end
   353                         }
   354                       end
   355                       ui.container{
   356                         content = function()
   357                           slot.put(encode.html(initiative.shortened_name))
   358                           if #initiators > 1 then
   359                             ui.container{
   360                               attr = { style = "font-size: 80%;" },
   361                               content = _"Initiators" .. ": " .. initiator_names_string
   362                             }
   363                           else
   364                             ui.container{
   365                               attr = { style = "font-size: 80%;" },
   366                               content = _"Initiator" .. ": " .. initiator_names_string
   367                             }
   368                           end
   369                         end
   370                       }
   371                     end
   372                   }
   373                 end
   374               end
   375             }
   376           end
   377         end
   378       end
   379     }
   380     if not readonly then
   381       ui.tag{
   382         tag = "input",
   383         attr = {
   384           type = "button",
   385           class = "voting_done",
   386           value = _"Finish voting"
   387         }
   388       }
   389     end
   390   end
   391 }
