liquid_feedback_frontend
view app/main/vote/list.lua @ 1062:f03fbffc1800
New layout for suggestion details view
| author | bsw | 
|---|---|
| date | Wed Jul 16 21:52:17 2014 +0200 (2014-07-16) | 
| parents | 701a5cf6b067 | 
| 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(" ")
   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(" ")
   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 )
