liquid_feedback_frontend
diff app/main/timeline/index.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 | 53a45356c107 |
line diff
1.1 --- a/app/main/timeline/index.lua Tue Feb 02 00:31:06 2010 +0100 1.2 +++ b/app/main/timeline/index.lua Sat Feb 20 22:10:31 2010 +0100 1.3 @@ -37,13 +37,10 @@ 1.4 if options_string == current_options then 1.5 active = true 1.6 end 1.7 - timeline_params.date = param.get("date") 1.8 ui.link{ 1.9 - attr = { class = active and "action_active" or nil }, 1.10 - content = function() 1.11 - ui.image{ static = "icons/16/time.png" } 1.12 - slot.put(encode.html(name)) 1.13 - end, 1.14 + image = { static = "icons/16/time.png" }, 1.15 + attr = { class = active and "action_active" or nil }, 1.16 + text = name, 1.17 module = 'timeline', 1.18 action = 'update', 1.19 params = { 1.20 @@ -81,68 +78,100 @@ 1.21 action = "update", 1.22 content = function() 1.23 1.24 + ui.container{ 1.25 1.26 - ui.tag{ 1.27 - tag = "label", 1.28 - attr = { style = "font-size: 130%;" }, 1.29 - content = _"Date" .. ":" 1.30 - } 1.31 - slot.put(" ") 1.32 - local date = param.get("date") 1.33 - if not date or #date == 0 then 1.34 - date = tostring(db:query("select now()::date as date")[1].date) 1.35 - end 1.36 - ui.tag{ 1.37 - tag = "input", 1.38 - attr = { 1.39 - type = "text", 1.40 - id = "timeline_search_date", 1.41 - style = "width: 10em;", 1.42 - onchange = "this.form.submit();", 1.43 - name = "date", 1.44 - value = date 1.45 - }, 1.46 - content = function() end 1.47 - } 1.48 - 1.49 - ui.script{ static = "gregor.js/gregor.js" } 1.50 - util.gregor("timeline_search_date", "document.getElementById('timeline_search_date').form.submit();") 1.51 - 1.52 - 1.53 - ui.link{ 1.54 - attr = { style = "margin-left: 1em; font-size: 130%; font-weight: bold;", onclick = "document.getElementById('timeline_search_date').form.submit();return(false);" }, 1.55 content = function() 1.56 - ui.image{ 1.57 - attr = { style = "margin-right: 0.25em;" }, 1.58 - static = "icons/16/magnifier.png" 1.59 + 1.60 + ui.tag{ 1.61 + tag = "input", 1.62 + attr = { 1.63 + type = "radio", 1.64 + id = "timeline_search_last_24h", 1.65 + name = "search_from", 1.66 + value = "last_24h", 1.67 + checked = param.get("date") == "last_24h" and "checked" or nil 1.68 + }, 1.69 } 1.70 - slot.put(_"Search") 1.71 - end, 1.72 - external = "#", 1.73 - } 1.74 - local show_options = param.get("show_options", atom.boolean) 1.75 - ui.link{ 1.76 - attr = { style = "margin-left: 1em; font-size: 130%;", onclick = "el=document.getElementById('timeline_show_options');el.checked=" .. tostring(not show_options) .. ";el.form.submit();return(false);" }, 1.77 - content = function() 1.78 - ui.image{ 1.79 - attr = { style = "margin-right: 0.25em;" }, 1.80 - static = "icons/16/text_list_bullets.png" 1.81 + 1.82 + ui.tag{ 1.83 + tag = "label", 1.84 + attr = { 1.85 + ["for"] = "timeline_search_last_24h" 1.86 + }, 1.87 + content = " " .. _"last 24 hours" .. " " 1.88 + } 1.89 + 1.90 + ui.tag{ 1.91 + tag = "input", 1.92 + attr = { 1.93 + type = "radio", 1.94 + id = "timeline_search_from_date", 1.95 + name = "search_from", 1.96 + value = "date", 1.97 + checked = not (param.get("date") == "last_24h") and "checked" or nil 1.98 + }, 1.99 } 1.100 - slot.put(not show_options and _"Show filter details" or _"Hide filter details") 1.101 - end, 1.102 - external = "#", 1.103 - } 1.104 1.105 - ui.field.boolean{ 1.106 - attr = { id = "timeline_show_options", style = "display: none;", onchange="this.form.submit();" }, 1.107 - name = "show_options", 1.108 - value = param.get("show_options", atom.boolean) 1.109 - } 1.110 + slot.put(" ") 1.111 + local current_date = param.get("date") 1.112 + if not current_date or #current_date == 0 or current_date == "last_24h" then 1.113 + current_date = tostring(db:query("select now()::date as date")[1].date) 1.114 + end 1.115 + ui.tag{ 1.116 + tag = "input", 1.117 + attr = { 1.118 + type = "text", 1.119 + id = "timeline_search_date", 1.120 + style = "width: 10em;", 1.121 + onchange = "this.form.submit();", 1.122 + onclick = "document.getElementById('timeline_search_from_date').checked = true;", 1.123 + name = "date", 1.124 + value = current_date 1.125 + }, 1.126 + content = function() end 1.127 + } 1.128 + 1.129 + ui.script{ static = "gregor.js/gregor.js" } 1.130 + util.gregor("timeline_search_date", "document.getElementById('timeline_search_date').form.submit();") 1.131 + 1.132 + 1.133 + ui.link{ 1.134 + attr = { style = "margin-left: 1em; font-weight: bold;", onclick = "document.getElementById('timeline_search_date').form.submit();return(false);" }, 1.135 + content = function() 1.136 + ui.image{ 1.137 + attr = { style = "margin-right: 0.25em;" }, 1.138 + static = "icons/16/magnifier.png" 1.139 + } 1.140 + slot.put(_"Search") 1.141 + end, 1.142 + external = "#", 1.143 + } 1.144 + local show_options = param.get("show_options", atom.boolean) 1.145 + ui.link{ 1.146 + attr = { style = "margin-left: 1em;", onclick = "el=document.getElementById('timeline_show_options');el.checked=" .. tostring(not show_options) .. ";el.form.submit();return(false);" }, 1.147 + content = function() 1.148 + ui.image{ 1.149 + attr = { style = "margin-right: 0.25em;" }, 1.150 + static = "icons/16/text_list_bullets.png" 1.151 + } 1.152 + slot.put(not show_options and _"Show filter details" or _"Hide filter details") 1.153 + end, 1.154 + external = "#", 1.155 + } 1.156 1.157 - ui.field.boolean{ 1.158 - attr = { id = "timeline_save", style = "display: none;", onchange="this.form.submit();" }, 1.159 - name = "save", 1.160 - value = false 1.161 + ui.field.boolean{ 1.162 + attr = { id = "timeline_show_options", style = "display: none;", onchange="this.form.submit();" }, 1.163 + name = "show_options", 1.164 + value = param.get("show_options", atom.boolean) 1.165 + } 1.166 + 1.167 + ui.field.boolean{ 1.168 + attr = { id = "timeline_save", style = "display: none;", onchange="this.form.submit();" }, 1.169 + name = "save", 1.170 + value = false 1.171 + } 1.172 + 1.173 + end 1.174 } 1.175 1.176 ui.container{ 1.177 @@ -219,8 +248,6 @@ 1.178 } 1.179 } 1.180 1.181 - slot.put("<br />") 1.182 - 1.183 slot.put("<table>") 1.184 1.185 for i_event_group, event_group in ipairs(event_groups) do 1.186 @@ -255,6 +282,7 @@ 1.187 } 1.188 1.189 local date = param.get("date") 1.190 + 1.191 if not date or #date == 0 then 1.192 date = "today" 1.193 end 1.194 @@ -266,24 +294,50 @@ 1.195 if param.get("option_" .. event, atom.boolean) then 1.196 1.197 local tmp = Timeline:new_selector() 1.198 - :add_where{ "occurrence::date = ?", date } 1.199 - 1.200 - :left_join("draft", nil, "draft.id = timeline.draft_id") 1.201 - :left_join("suggestion", nil, "suggestion.id = timeline.suggestion_id") 1.202 - :left_join("initiative", nil, "initiative.id = timeline.initiative_id or initiative.id = draft.initiative_id or initiative.id = suggestion.initiative_id") 1.203 - :left_join("issue", nil, "issue.id = timeline.issue_id or issue.id = initiative.issue_id") 1.204 - :left_join("area", nil, "area.id = issue.area_id") 1.205 + if event == "draft_created" then 1.206 + tmp 1.207 + :reset_fields() 1.208 + :add_field("max(timeline.occurrence)", "occurrence") 1.209 + :add_field("timeline.event", nil, { "grouped" }) 1.210 + :add_field("timeline.issue_id", nil, { "grouped" }) 1.211 + :add_field("timeline.initiative_id", nil, { "grouped" }) 1.212 + :add_field("max(timeline.draft_id)", "draft_id") 1.213 + :add_field("timeline.suggestion_id", nil, { "grouped" }) 1.214 + :add_field("COUNT(*)", "count") 1.215 + else 1.216 + tmp 1.217 + :add_field("1", "count") 1.218 + end 1.219 1.220 - :left_join("interest", "_interest", { "_interest.issue_id = issue.id AND _interest.member_id = ?", app.session.member.id} ) 1.221 - :left_join("membership", "_membership", { "_membership.area_id = area.id AND _membership.member_id = ?", app.session.member.id} ) 1.222 - :left_join("initiator", "_initiator", { "_initiator.initiative_id = initiative.id AND _initiator.member_id = ?", app.session.member.id} ) 1.223 - :left_join("supporter", "_supporter", { "_supporter.initiative_id = initiative.id AND _supporter.member_id = ?", app.session.member.id} ) 1.224 + if date == "last_24h" then 1.225 + tmp:add_where{ "occurrence > now() - '24 hours'::interval" } 1.226 + else 1.227 + tmp:add_where{ "occurrence::date = ?::date", date } 1.228 + end 1.229 + tmp 1.230 + :left_join("draft", nil, "draft.id = timeline.draft_id") 1.231 + :left_join("suggestion", nil, "suggestion.id = timeline.suggestion_id") 1.232 + :left_join("initiative", nil, "initiative.id = timeline.initiative_id or initiative.id = draft.initiative_id or initiative.id = suggestion.initiative_id") 1.233 + :left_join("issue", nil, "issue.id = timeline.issue_id or issue.id = initiative.issue_id") 1.234 + :left_join("area", nil, "area.id = issue.area_id") 1.235 1.236 - :add_field("(_interest.member_id NOTNULL)", "is_interested") 1.237 - :add_field("(_initiator.member_id NOTNULL)", "is_initiator") 1.238 - :add_field({"(_supporter.member_id NOTNULL) AND NOT EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1)", app.session.member.id }, "is_supporter") 1.239 - :add_field({"EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1)", app.session.member.id }, "is_potential_supporter") 1.240 - -- :left_join("member", nil, "member.id = timeline.member_id") 1.241 + :left_join("interest", "_interest", { "_interest.issue_id = issue.id AND _interest.member_id = ?", app.session.member.id} ) 1.242 + :left_join("initiator", "_initiator", { "_initiator.initiative_id = initiative.id AND _initiator.member_id = ?", app.session.member.id} ) 1.243 + :left_join("membership", "_membership", { "_membership.area_id = area.id AND _membership.member_id = ?", app.session.member.id} ) 1.244 + :left_join("supporter", "_supporter", { "_supporter.initiative_id = initiative.id AND _supporter.member_id = ?", app.session.member.id} ) 1.245 + 1.246 + local group 1.247 + if event == "draft_created" then 1.248 + group = { "grouped" } 1.249 + end 1.250 + 1.251 + tmp 1.252 + :add_field("(_interest.member_id NOTNULL)", "is_interested", group) 1.253 + :add_field("(_initiator.member_id NOTNULL)", "is_initiator", group) 1.254 + :add_field({"(_supporter.member_id NOTNULL) AND NOT EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1)", app.session.member.id }, "is_supporter", group) 1.255 + :add_field({"EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1)", app.session.member.id }, "is_potential_supporter", group) 1.256 + -- :left_join("member", nil, "member.id = timeline.member_id", group) 1.257 + 1.258 1.259 tmp:add_where{ "event = ?", event } 1.260 1.261 @@ -310,7 +364,7 @@ 1.262 1.263 if #filters > 0 then 1.264 local filter_string = "(" .. table.concat(filters, ") OR (") .. ")" 1.265 - tmp:add_where{ filter_string, app.session.member.id } 1.266 + tmp:add_where{ filter_string, app.session.member.id, app.session.member.id } 1.267 end 1.268 1.269 if not timeline_selector then 1.270 @@ -327,10 +381,11 @@ 1.271 1.272 local outer_timeline_selector = db:new_selector() 1.273 outer_timeline_selector._class = Timeline 1.274 - outer_timeline_selector:add_field{ "timeline.*" } 1.275 - outer_timeline_selector:from({"($)", { timeline_selector }}, "timeline" ) 1.276 - outer_timeline_selector:add_order_by("occurrence DESC") 1.277 - 1.278 + outer_timeline_selector 1.279 + :add_field{ "timeline.*" } 1.280 + :from({"($)", { timeline_selector }}, "timeline" ) 1.281 + :add_order_by("occurrence DESC") 1.282 + 1.283 slot.put("<br />") 1.284 execute.view{ 1.285 module = "timeline",