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