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
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",

Impressum / About Us