liquid_feedback_frontend
diff app/main/initiative/show.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 | 7d0f4721d2f3 |
line diff
1.1 --- a/app/main/initiative/show.lua Tue Feb 02 00:31:06 2010 +0100 1.2 +++ b/app/main/initiative/show.lua Sat Feb 20 22:10:31 2010 +0100 1.3 @@ -1,535 +1,36 @@ 1.4 -local initiative = Initiative:new_selector():add_where{ "id = ?", param.get_id()}:single_object_mode():exec() 1.5 - 1.6 -slot.select("actions", function() 1.7 - ui.link{ 1.8 - content = function() 1.9 - ui.image{ static = "icons/16/script.png" } 1.10 - slot.put(_"Show all initiatives") 1.11 - end, 1.12 - module = "issue", 1.13 - view = "show", 1.14 - id = initiative.issue.id 1.15 - } 1.16 -end) 1.17 - 1.18 -execute.view{ 1.19 - module = "issue", 1.20 - view = "_show_head", 1.21 - params = { issue = initiative.issue } 1.22 -} 1.23 - 1.24 -if initiative.revoked then 1.25 - ui.container{ 1.26 - attr = { class = "revoked_info" }, 1.27 - content = function() 1.28 - slot.put(_("This initiative has been revoked at #{revoked}", { revoked = format.timestamp(initiative.revoked) })) 1.29 - local suggested_initiative = initiative.suggested_initiative 1.30 - if suggested_initiative then 1.31 - slot.put("<br /><br />") 1.32 - slot.put(_("The initiators suggest to support the following initiative:")) 1.33 - slot.put("<br />") 1.34 - ui.link{ 1.35 - content = _("Issue ##{id}", { id = suggested_initiative.issue.id } ) .. ": " .. encode.html(suggested_initiative.name), 1.36 - module = "initiative", 1.37 - view = "show", 1.38 - id = suggested_initiative.id 1.39 - } 1.40 - end 1.41 - end 1.42 - } 1.43 -end 1.44 - 1.45 -local initiator = Initiator:by_pk(initiative.id, app.session.member.id) 1.46 - 1.47 ---slot.put_into("html_head", '<link rel="alternate" type="application/rss+xml" title="RSS" href="../show/' .. tostring(initiative.id) .. '.rss" />') 1.48 - 1.49 - 1.50 -slot.select("actions", function() 1.51 - if not initiative.issue.fully_frozen and not initiative.issue.closed then 1.52 - ui.link{ 1.53 - attr = { class = "action" }, 1.54 - content = function() 1.55 - ui.image{ static = "icons/16/script_add.png" } 1.56 - slot.put(_"Create alternative initiative") 1.57 - end, 1.58 - module = "initiative", 1.59 - view = "new", 1.60 - params = { issue_id = initiative.issue.id } 1.61 - } 1.62 - end 1.63 -end) 1.64 - 1.65 -slot.put_into("sub_title", encode.html(_"Initiative: '#{name}'":gsub("#{name}", initiative.shortened_name) )) 1.66 - 1.67 -slot.select("support", function() 1.68 - ui.container{ 1.69 - attr = { class = "actions" }, 1.70 - content = function() 1.71 - execute.view{ 1.72 - module = "supporter", 1.73 - view = "_show_box", 1.74 - params = { initiative = initiative } 1.75 - } 1.76 - if initiator and initiator.accepted and not initiative.issue.fully_frozen and not initiative.issue.closed and not initiative.revoked then 1.77 - ui.link{ 1.78 - attr = { class = "action", style = "float: left;" }, 1.79 - content = function() 1.80 - ui.image{ static = "icons/16/script_delete.png" } 1.81 - slot.put(_"Revoke initiative") 1.82 - end, 1.83 - module = "initiative", 1.84 - view = "revoke", 1.85 - id = initiative.id 1.86 - } 1.87 - end 1.88 - end 1.89 - } 1.90 -end) 1.91 - 1.92 -util.help("initiative.show") 1.93 +local initiative = param.get("initiative", "table") 1.94 1.95 -if initiator and initiator.accepted == nil then 1.96 - ui.container{ 1.97 - attr = { class = "initiator_invite_info" }, 1.98 - content = function() 1.99 - slot.put(_"You are invited to become initiator of this initiative.") 1.100 - slot.put(" ") 1.101 - ui.link{ 1.102 - content = function() 1.103 - ui.image{ static = "icons/16/tick.png" } 1.104 - slot.put(_"Accept invitation") 1.105 - end, 1.106 - module = "initiative", 1.107 - action = "accept_invitation", 1.108 - id = initiative.id, 1.109 - routing = { 1.110 - default = { 1.111 - mode = "redirect", 1.112 - module = request.get_module(), 1.113 - view = request.get_view(), 1.114 - id = param.get_id_cgi(), 1.115 - params = param.get_all_cgi() 1.116 - } 1.117 - } 1.118 - } 1.119 - slot.put(" ") 1.120 - ui.link{ 1.121 - content = function() 1.122 - ui.image{ static = "icons/16/cross.png" } 1.123 - slot.put(_"Refuse invitation") 1.124 - end, 1.125 - module = "initiative", 1.126 - action = "reject_initiator_invitation", 1.127 - params = { 1.128 - initiative_id = initiative.id, 1.129 - member_id = app.session.member.id 1.130 - }, 1.131 - routing = { 1.132 - default = { 1.133 - mode = "redirect", 1.134 - module = request.get_module(), 1.135 - view = request.get_view(), 1.136 - id = param.get_id_cgi(), 1.137 - params = param.get_all_cgi() 1.138 - } 1.139 - } 1.140 - } 1.141 - end 1.142 - } 1.143 - slot.put("<br />") 1.144 -end 1.145 - 1.146 -if (initiative.discussion_url and #initiative.discussion_url > 0) 1.147 - or (initiator and initiator.accepted and not initiative.issue.half_frozen and not initiative.issue.closed and not initiative.revoked) then 1.148 - ui.container{ 1.149 - attr = { class = "vertical" }, 1.150 - content = function() 1.151 - ui.container{ 1.152 - attr = { class = "ui_field_label" }, 1.153 - content = _"Discussion with initiators" 1.154 - } 1.155 - ui.tag{ 1.156 - tag = "span", 1.157 - content = function() 1.158 - if initiative.discussion_url:find("^https?://") then 1.159 - if initiative.discussion_url and #initiative.discussion_url > 0 then 1.160 - ui.link{ 1.161 - attr = { 1.162 - class = "actions", 1.163 - target = "_blank", 1.164 - title = initiative.discussion_url 1.165 - }, 1.166 - content = function() 1.167 - slot.put(encode.html(initiative.discussion_url)) 1.168 - end, 1.169 - external = initiative.discussion_url 1.170 - } 1.171 - end 1.172 - else 1.173 - slot.put(encode.html(initiative.discussion_url)) 1.174 - end 1.175 - slot.put(" ") 1.176 - if initiator and initiator.accepted and not initiative.issue.half_frozen and not initiative.issue.closed and not initiative.revoked then 1.177 - ui.link{ 1.178 - attr = { class = "actions" }, 1.179 - content = _"(change URL)", 1.180 - module = "initiative", 1.181 - view = "edit", 1.182 - id = initiative.id 1.183 - } 1.184 - end 1.185 - end 1.186 - } 1.187 - end 1.188 - } 1.189 +if not initiative then 1.190 + initiative = Initiative:new_selector():add_where{ "id = ?", param.get_id()}:single_object_mode():exec() 1.191 end 1.192 1.193 - 1.194 -ui.container{ 1.195 - attr = { id = "add_suggestion_form", class = "hidden_inline_form" }, 1.196 - content = function() 1.197 - 1.198 - ui.link{ 1.199 - content = _"Close", 1.200 - attr = { 1.201 - onclick = "document.getElementById('add_suggestion_form').style.display='none';return(false)", 1.202 - style = "float: right;" 1.203 - } 1.204 - } 1.205 - 1.206 - ui.field.text{ attr = { class = "head" }, value = _"Add new suggestion" } 1.207 - 1.208 - 1.209 - ui.form{ 1.210 - module = "suggestion", 1.211 - action = "add", 1.212 - params = { initiative_id = initiative.id }, 1.213 - routing = { 1.214 - default = { 1.215 - mode = "redirect", 1.216 - module = "initiative", 1.217 - view = "show", 1.218 - id = initiative.id, 1.219 - params = { tab = "suggestion" } 1.220 - } 1.221 - }, 1.222 - attr = { class = "vertical" }, 1.223 - content = function() 1.224 - local supported = Supporter:by_pk(initiative.id, app.session.member.id) and true or false 1.225 - if not supported then 1.226 - ui.field.text{ 1.227 - attr = { class = "warning" }, 1.228 - value = _"You are currently not supporting this initiative. By adding suggestions to this initiative you will automatically become a potential supporter." 1.229 - } 1.230 - end 1.231 - ui.field.text{ label = _"Title (80 chars max)", name = "name" } 1.232 - ui.field.text{ label = _"Description", name = "description", multiline = true } 1.233 - ui.field.select{ 1.234 - label = _"Degree", 1.235 - name = "degree", 1.236 - foreign_records = { 1.237 - { id = 1, name = _"should"}, 1.238 - { id = 2, name = _"must"}, 1.239 - }, 1.240 - foreign_id = "id", 1.241 - foreign_name = "name" 1.242 - } 1.243 - ui.submit{ text = _"Commit suggestion" } 1.244 - end 1.245 - } 1.246 - end 1.247 -} 1.248 - 1.249 -local supporter = app.session.member:get_reference_selector("supporters") 1.250 - :add_where{ "initiative_id = ?", initiative.id } 1.251 - :optional_object_mode() 1.252 - :exec() 1.253 - 1.254 -if supporter then 1.255 - local old_draft_id = supporter.draft_id 1.256 - local new_draft_id = initiative.current_draft.id 1.257 - if old_draft_id ~= new_draft_id then 1.258 - ui.container{ 1.259 - attr = { class = "draft_updated_info" }, 1.260 - content = function() 1.261 - slot.put(_"The draft of this initiative has been updated!") 1.262 - slot.put(" ") 1.263 - ui.link{ 1.264 - content = _"Show diff", 1.265 - module = "draft", 1.266 - view = "diff", 1.267 - params = { 1.268 - old_draft_id = old_draft_id, 1.269 - new_draft_id = new_draft_id 1.270 - } 1.271 - } 1.272 - slot.put(" ") 1.273 - ui.link{ 1.274 - content = _"Refresh support to current draft", 1.275 - module = "initiative", 1.276 - action = "add_support", 1.277 - id = initiative.id, 1.278 - routing = { 1.279 - default = { 1.280 - mode = "redirect", 1.281 - module = "initiative", 1.282 - view = "show", 1.283 - id = initiative.id 1.284 - } 1.285 - } 1.286 - } 1.287 - end 1.288 - } 1.289 - end 1.290 -end 1.291 - 1.292 - 1.293 -local current_draft_name = _"Current draft" 1.294 -if initiative.issue.half_frozen then 1.295 - current_draft_name = _"Voting proposal" 1.296 -end 1.297 - 1.298 -if initiative.issue.state == "finished" then 1.299 - current_draft_name = _"Voted proposal" 1.300 -end 1.301 - 1.302 -local tabs = { 1.303 - { 1.304 - name = "current_draft", 1.305 - label = current_draft_name, 1.306 - content = function() 1.307 - if initiator and initiator.accepted and not initiative.issue.half_frozen and not initiative.issue.closed and not initiative.revoked then 1.308 - ui.link{ 1.309 - content = function() 1.310 - ui.image{ static = "icons/16/script_add.png" } 1.311 - slot.put(_"Edit draft") 1.312 - end, 1.313 - module = "draft", 1.314 - view = "new", 1.315 - params = { initiative_id = initiative.id } 1.316 - } 1.317 - end 1.318 - execute.view{ module = "draft", view = "_show", params = { draft = initiative.current_draft } } 1.319 - end 1.320 - } 1.321 -} 1.322 - 1.323 -if initiative.issue.ranks_available then 1.324 - tabs[#tabs+1] = { 1.325 - name = "voter", 1.326 - label = _"Voter", 1.327 - content = function() 1.328 - execute.view{ 1.329 - module = "member", 1.330 - view = "_list", 1.331 - params = { 1.332 - initiative = initiative, 1.333 - members_selector = initiative.issue:get_reference_selector("direct_voters") 1.334 - :left_join("vote", nil, { "vote.initiative_id = ? AND vote.member_id = member.id", initiative.id }) 1.335 - :add_field("direct_voter.weight as voter_weight") 1.336 - :add_field("coalesce(vote.grade, 0) as grade") 1.337 - } 1.338 - } 1.339 - end 1.340 - } 1.341 -end 1.342 - 1.343 -local suggestion_count = initiative:get_reference_selector("suggestions"):count() 1.344 - 1.345 -tabs[#tabs+1] = { 1.346 - name = "suggestion", 1.347 - label = _"Suggestions" .. " (" .. tostring(suggestion_count) .. ")", 1.348 - content = function() 1.349 - execute.view{ 1.350 - module = "suggestion", 1.351 - view = "_list", 1.352 - params = { 1.353 - initiative = initiative, 1.354 - suggestions_selector = initiative:get_reference_selector("suggestions") 1.355 - } 1.356 +if request.get_json_request_slots() then 1.357 + execute.view{ 1.358 + module = "initiative", 1.359 + view = "show_partial", 1.360 + params = { 1.361 + initiative = initiative 1.362 } 1.363 - slot.put("<br />") 1.364 - if not initiative.issue.fully_frozen and not initiative.issue.closed and not initiative.revoked then 1.365 - ui.link{ 1.366 - content = function() 1.367 - ui.image{ static = "icons/16/comment_add.png" } 1.368 - slot.put(_"Add new suggestion") 1.369 - end, 1.370 - attr = { onclick = "document.getElementById('add_suggestion_form').style.display='block';return(false)" }, 1.371 - static = "#" 1.372 - } 1.373 - end 1.374 - end 1.375 -} 1.376 - 1.377 -local members_selector = initiative:get_reference_selector("supporting_members_snapshot") 1.378 - :join("issue", nil, "issue.id = direct_supporter_snapshot.issue_id") 1.379 - :join("direct_interest_snapshot", nil, "direct_interest_snapshot.event = issue.latest_snapshot_event AND direct_interest_snapshot.issue_id = issue.id AND direct_interest_snapshot.member_id = member.id") 1.380 - :add_field("direct_interest_snapshot.weight") 1.381 - :add_where("direct_supporter_snapshot.event = issue.latest_snapshot_event") 1.382 - :add_where("direct_supporter_snapshot.satisfied") 1.383 - 1.384 -local tmp = db:query("SELECT count(1) AS count, sum(weight) AS weight FROM (" .. tostring(members_selector) .. ") as subquery", "object") 1.385 -local direct_satisfied_supporter_count = tmp.count 1.386 -local indirect_satisfied_supporter_count = (tmp.weight or 0) - tmp.count 1.387 - 1.388 -local count_string 1.389 -if indirect_satisfied_supporter_count > 0 then 1.390 - count_string = "(" .. tostring(direct_satisfied_supporter_count) .. "+" .. tostring(indirect_satisfied_supporter_count) .. ")" 1.391 -else 1.392 - count_string = "(" .. tostring(direct_satisfied_supporter_count) .. ")" 1.393 -end 1.394 - 1.395 -tabs[#tabs+1] = { 1.396 - name = "satisfied_supporter", 1.397 - label = _"Supporter" .. " " .. count_string, 1.398 - content = function() 1.399 - execute.view{ 1.400 - module = "member", 1.401 - view = "_list", 1.402 - params = { 1.403 - initiative = initiative, 1.404 - members_selector = members_selector 1.405 - } 1.406 - } 1.407 - end 1.408 -} 1.409 - 1.410 -local members_selector = initiative:get_reference_selector("supporting_members_snapshot") 1.411 - :join("issue", nil, "issue.id = direct_supporter_snapshot.issue_id") 1.412 - :join("direct_interest_snapshot", nil, "direct_interest_snapshot.event = issue.latest_snapshot_event AND direct_interest_snapshot.issue_id = issue.id AND direct_interest_snapshot.member_id = member.id") 1.413 - :add_field("direct_interest_snapshot.weight") 1.414 - :add_where("direct_supporter_snapshot.event = issue.latest_snapshot_event") 1.415 - :add_where("NOT direct_supporter_snapshot.satisfied") 1.416 - 1.417 -local tmp = db:query("SELECT count(1) AS count, sum(weight) AS weight FROM (" .. tostring(members_selector) .. ") as subquery", "object") 1.418 -local direct_potential_supporter_count = tmp.count 1.419 -local indirect_potential_supporter_count = (tmp.weight or 0) - tmp.count 1.420 - 1.421 -local count_string 1.422 -if indirect_potential_supporter_count > 0 then 1.423 - count_string = "(" .. tostring(direct_potential_supporter_count) .. "+" .. tostring(indirect_potential_supporter_count) .. ")" 1.424 -else 1.425 - count_string = "(" .. tostring(direct_potential_supporter_count) .. ")" 1.426 -end 1.427 - 1.428 -tabs[#tabs+1] = { 1.429 - name = "supporter", 1.430 - label = _"Potential supporter" .. " " .. count_string, 1.431 - content = function() 1.432 - execute.view{ 1.433 - module = "member", 1.434 - view = "_list", 1.435 - params = { 1.436 - initiative = initiative, 1.437 - members_selector = members_selector 1.438 - } 1.439 + } 1.440 +elseif 1.441 + config.user_tab_mode == "accordeon" or 1.442 + config.user_tab_mode == "accordeon_first_expanded" or 1.443 + config.user_tab_mode == "accordeon_all_expanded" 1.444 +then 1.445 + execute.view{ 1.446 + module = "issue", 1.447 + view = "show", 1.448 + id = initiative.issue_id, 1.449 + params = { 1.450 + for_initiative_id = initiative.id 1.451 } 1.452 - end 1.453 -} 1.454 - 1.455 -local initiator_count = initiative:get_reference_selector("initiators"):add_where("accepted"):count() 1.456 - 1.457 -tabs[#tabs+1] = { 1.458 - name = "initiators", 1.459 - label = _"Initiators" .. " (" .. tostring(initiator_count) .. ")", 1.460 - content = function() 1.461 - if initiator and initiator.accepted and not initiative.issue.fully_frozen and not initiative.issue.closed and not initiative.revoked then 1.462 - ui.link{ 1.463 - attr = { class = "action" }, 1.464 - content = function() 1.465 - ui.image{ static = "icons/16/user_add.png" } 1.466 - slot.put(_"Invite initiator") 1.467 - end, 1.468 - module = "initiative", 1.469 - view = "add_initiator", 1.470 - params = { initiative_id = initiative.id } 1.471 - } 1.472 - if initiator_count > 1 then 1.473 - ui.link{ 1.474 - content = function() 1.475 - ui.image{ static = "icons/16/user_delete.png" } 1.476 - slot.put(_"Remove initiator") 1.477 - end, 1.478 - module = "initiative", 1.479 - view = "remove_initiator", 1.480 - params = { initiative_id = initiative.id } 1.481 - } 1.482 - end 1.483 - end 1.484 - if initiator and initiator.accepted == false then 1.485 - ui.link{ 1.486 - content = function() 1.487 - ui.image{ static = "icons/16/user_delete.png" } 1.488 - slot.put(_"Cancel refuse of invitation") 1.489 - end, 1.490 - module = "initiative", 1.491 - action = "remove_initiator", 1.492 - params = { 1.493 - initiative_id = initiative.id, 1.494 - member_id = app.session.member.id 1.495 - }, 1.496 - routing = { 1.497 - ok = { 1.498 - mode = "redirect", 1.499 - module = "initiative", 1.500 - view = "show", 1.501 - id = initiative.id 1.502 - } 1.503 - } 1.504 - } 1.505 - end 1.506 - local members_selector = initiative:get_reference_selector("initiating_members") 1.507 - :add_field("initiator.accepted", "accepted") 1.508 - if not (initiator and initiator.accepted) then 1.509 - members_selector:add_where("accepted") 1.510 - end 1.511 - execute.view{ 1.512 - module = "member", 1.513 - view = "_list", 1.514 - params = { 1.515 - members_selector = members_selector, 1.516 - initiator = initiator 1.517 - } 1.518 + } 1.519 +else 1.520 + execute.view{ 1.521 + module = "initiative", 1.522 + view = "show_static", 1.523 + params = { 1.524 + initiative = initiative 1.525 } 1.526 - end 1.527 -} 1.528 - 1.529 -local drafts_count = initiative:get_reference_selector("drafts"):count() 1.530 - 1.531 -tabs[#tabs+1] = { 1.532 - name = "drafts", 1.533 - label = _"Draft history" .. " (" .. tostring(drafts_count) .. ")", 1.534 - content = function() 1.535 - execute.view{ module = "draft", view = "_list", params = { drafts = initiative.drafts } } 1.536 - end 1.537 -} 1.538 - 1.539 -tabs[#tabs+1] = { 1.540 - name = "details", 1.541 - label = _"Details", 1.542 - content = function() 1.543 - ui.form{ 1.544 - attr = { class = "vertical" }, 1.545 - record = initiative, 1.546 - readonly = true, 1.547 - content = function() 1.548 - ui.field.text{ label = _"Issue policy", value = initiative.issue.policy.name } 1.549 - ui.field.text{ 1.550 - label = _"Created at", 1.551 - value = tostring(initiative.created) 1.552 - } 1.553 - ui.field.text{ 1.554 - label = _"Created at", 1.555 - value = format.timestamp(initiative.created) 1.556 - } 1.557 --- ui.field.date{ label = _"Revoked at", name = "revoked" } 1.558 - ui.field.boolean{ label = _"Admitted", name = "admitted" } 1.559 - end 1.560 - } 1.561 - end 1.562 -} 1.563 - 1.564 - 1.565 -ui.tabs(tabs) 1.566 - 1.567 + } 1.568 +end 1.569 \ No newline at end of file