radiant-polls-extension 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. data/CHANGELOG +27 -0
  2. data/HELP.textile +71 -0
  3. data/LICENSE +21 -0
  4. data/README.textile +112 -0
  5. data/Rakefile +129 -0
  6. data/VERSION +1 -0
  7. data/app/controllers/admin/polls_controller.rb +25 -0
  8. data/app/controllers/poll_response_controller.rb +35 -0
  9. data/app/models/option.rb +22 -0
  10. data/app/models/poll.rb +67 -0
  11. data/app/views/admin/polls/_form.html.haml +67 -0
  12. data/app/views/admin/polls/_option.html.haml +9 -0
  13. data/app/views/admin/polls/edit.html.haml +5 -0
  14. data/app/views/admin/polls/index.html.haml +72 -0
  15. data/app/views/admin/polls/new.html.haml +5 -0
  16. data/app/views/admin/polls/remove.html.haml +17 -0
  17. data/config/locales/en.yml +25 -0
  18. data/config/locales/en_available_tags.yml +187 -0
  19. data/config/routes.rb +6 -0
  20. data/db/migrate/001_create_polls.rb +12 -0
  21. data/db/migrate/002_create_options.rb +14 -0
  22. data/db/migrate/003_add_start_date_to_polls.rb +9 -0
  23. data/lib/poll_process.rb +23 -0
  24. data/lib/poll_tags.rb +408 -0
  25. data/lib/tasks/polls_extension_tasks.rake +56 -0
  26. data/polls_extension.rb +51 -0
  27. data/public/images/admin/new-poll.png +0 -0
  28. data/public/images/admin/poll.png +0 -0
  29. data/public/images/admin/recycle.png +0 -0
  30. data/public/images/admin/reset.png +0 -0
  31. data/public/javascripts/admin/date_selector.js +177 -0
  32. data/public/javascripts/admin/polls.js +47 -0
  33. data/public/javascripts/poll_check.js +52 -0
  34. data/public/stylesheets/admin/polls.css +93 -0
  35. data/public/stylesheets/polls.css +9 -0
  36. data/radiant-polls-extension.gemspec +83 -0
  37. data/spec/controllers/admin/polls_controller_spec.rb +16 -0
  38. data/spec/controllers/poll_response_controller_spec.rb +149 -0
  39. data/spec/integration/page_caching_spec.rb +76 -0
  40. data/spec/lib/poll_tags_spec.rb +415 -0
  41. data/spec/models/poll_spec.rb +50 -0
  42. data/spec/spec.opts +6 -0
  43. data/spec/spec_helper.rb +42 -0
  44. metadata +141 -0
@@ -0,0 +1,67 @@
1
+ class Poll < ActiveRecord::Base
2
+
3
+ has_many :options
4
+ after_update :save_options
5
+ validates_presence_of :title
6
+ validates_uniqueness_of :title, :allow_nil => true
7
+ validates_uniqueness_of :start_date, :allow_nil => true
8
+ validates_associated :options
9
+ before_create :set_defaults
10
+
11
+ def self.marker; "<!-- Polls Extension -->" end
12
+
13
+ # Find the current poll. The current poll is defined as the poll that has the
14
+ # latest start date that is no later than the current date. If no poll has a
15
+ # defined start date, then there is no current poll.
16
+ def self.find_current
17
+ Poll.find(:first, :order => 'start_date DESC',
18
+ :conditions => [ 'start_date IS NOT NULL AND start_date <= ?', Date.today ])
19
+ end
20
+
21
+ def option_attributes=(option_attributes)
22
+ option_attributes.each do |attributes|
23
+ if attributes[:id].blank?
24
+ attributes[:response_count] = 0
25
+ options.build(attributes)
26
+ else
27
+ option = options.detect { |a| a.id == attributes[:id].to_i }
28
+ option.attributes = attributes
29
+ end
30
+ end
31
+ end
32
+
33
+ # submit a response to the poll and update
34
+ # the response counts for the poll and the option
35
+ def submit_response(option)
36
+ option.update_attribute(:response_count, option.response_count + 1)
37
+ self.update_attribute(:response_count, self.response_count + 1)
38
+
39
+ return true
40
+ end
41
+
42
+ # clear the number of the responses for the poll
43
+ # and all of its options
44
+ def clear_responses
45
+ self.update_attribute(:response_count, 0)
46
+ self.options.find(:all, :conditions => ["response_count >= 1"]).each do |option|
47
+ option.update_attribute(:response_count, 0)
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def save_options
54
+ options.each do |option|
55
+ if option.should_destroy?
56
+ option.destroy
57
+ else
58
+ option.save(false)
59
+ end
60
+ end
61
+ end
62
+
63
+ def set_defaults
64
+ self.response_count = 0
65
+ end
66
+
67
+ end
@@ -0,0 +1,67 @@
1
+ - include_javascript 'admin/polls'
2
+ - include_javascript 'admin/lowpro'
3
+ - include_javascript 'admin/date_selector'
4
+ - content_for :page_scripts do
5
+ Event.addBehavior({ '#poll_start_date': DateSelector });
6
+
7
+ - first_new_form = @poll.new_record? && params[:poll].nil?
8
+ - form_for [:admin, @poll], :html => {'data-onsubmit_status' => onsubmit_status(@poll)} do |f|
9
+ .form-area
10
+ - render_region :form, :locals => {:f => f} do |form|
11
+ #poll_form_area
12
+ %p.name
13
+ = f.label :title, t('polls_form.label.title')
14
+ = f.text_field :title, :class => 'textbox activate', :style => "width: 50%;"
15
+ %p.title
16
+ = f.label :poll_start_date, t('polls_form.label.start_date')
17
+ = f.text_field :start_date, :size => 15
18
+ %p.title
19
+ %label#options-title{:for => "polls_form.label.poll_options"}
20
+ = t('polls_form.label.options')
21
+ #options
22
+ - if first_new_form
23
+ %p.option
24
+ %input{:name => "poll[option_attributes][][title]", :size => "30", :type => "text"}/
25
+ = render :partial => 'option', :collection => @poll.options
26
+ %p#add-option= link_to_function(t('polls_form.label.add_option'), "add_option('options');")
27
+
28
+ = javascript_tag "document.observe('dom:loaded',initialize_page_view);"
29
+ - if first_new_form
30
+ = javascript_tag "$('poll_title').activate();"
31
+
32
+ %p.buttons{:style=>"clear: left"}
33
+ = save_model_button(@poll)
34
+ = save_model_and_continue_editing_button(@poll)
35
+ = t('or')
36
+ = link_to t('cancel'), admin_polls_url
37
+
38
+ - content_for :page_css do
39
+ :sass
40
+ table.calendar
41
+ background: white
42
+ border: 1px solid black
43
+ th
44
+ background: #eee
45
+ text-align: center
46
+ color: black
47
+ font-weight: bold
48
+ td
49
+ background: white
50
+ border: 1px solid #eee
51
+ padding: 0 2px
52
+ text-align: center
53
+ &.today a
54
+ color: red !important
55
+ &.back, &.forward
56
+ background: #ddd
57
+ a
58
+ color: black
59
+ text-decoration: none
60
+ tbody tr
61
+ &:last-child td
62
+ border-bottom: 1px solid black
63
+ td
64
+ &:first-child
65
+ border-left: 1px solid black
66
+ &:last-child
67
+ border-right: 1px solid black
@@ -0,0 +1,9 @@
1
+ - option_id = option.id || option_counter
2
+ - field_base_name = 'poll[option_attributes][]'
3
+ - fields_for field_base_name, option do |f|
4
+ %p.option{:id => "option_#{option_id}"}
5
+ = f.hidden_field :id, :index => option_id, :name => "#{field_base_name}[id]"
6
+ = f.hidden_field :should_destroy, :index => option_id, :name => "#{field_base_name}[should_destroy]", :class => 'should_destroy'
7
+ - unless object.should_destroy?
8
+ = link_to_function "Delete", "delete_option(#{option_id}, 'options')", { :class => 'delete' }
9
+ = f.text_field :title, :index => option_id, :name => "#{field_base_name}[title]", :size => 30
@@ -0,0 +1,5 @@
1
+ - render_region :main do |main|
2
+ - main.edit_header do
3
+ %h1= t('polls_action.edit')
4
+ - main.edit_form do
5
+ = render :partial => 'form'
@@ -0,0 +1,72 @@
1
+ - @page_title = t('polls_index.title') + ' - ' + default_page_title
2
+ .outset
3
+ = render_region :top
4
+ %table.index#polls
5
+ %thead
6
+ %tr
7
+ - render_region :thead do |thead|
8
+ - thead.name_header do
9
+ %th.name= t('polls_index.column.title')
10
+ - thead.date_header do
11
+ %th.date= t('polls_index.column.start_date')
12
+ - thead.responses_header do
13
+ %th.responses= t('polls_index.column.responses')
14
+ - thead.actions_header do
15
+ %th.actions{:colspan => 2}= t('modify')
16
+ %tbody
17
+ - if @polls.any?
18
+ - @polls.each do |poll|
19
+ %tr[poll]
20
+ - render_region :tbody, :locals => {:poll => poll} do |tbody|
21
+ - tbody.name_cell do
22
+ %td.name
23
+ = image('poll', :alt => '')
24
+ = link_to poll.title, edit_admin_poll_url(poll)
25
+ - tbody.date_cell do
26
+ %td.date{:style=>"white-space:nowrap"}
27
+ = poll.start_date
28
+ - tbody.responses_cell do
29
+ %td.name#responses
30
+ - if poll.response_count > 0
31
+ %strong= link_to_function number_with_delimiter(poll.response_count), :title => "Show / Hide Results for this Poll" do |page| page.visual_effect :toggle_blind, "poll_#{poll.id}_results" end
32
+ - else
33
+ %span.none= poll.response_count
34
+ - tbody.actions_cell do
35
+ %td.actions
36
+ - if poll.response_count > 0
37
+ = link_to image('recycle') + ' ' + t('polls_index.reset'), clear_responses_admin_poll_url(poll), :method => :post, :confirm => t('polls_index.reset_confirm', :poll => poll.title), :class => "action"
38
+ %td.actions
39
+ = link_to image('minus') + ' ' + t('remove'), admin_poll_url(poll), :method => :delete, :confirm => t('polls_index.delete_confirm', :poll => poll.title), :class => "action"
40
+
41
+ - if poll.response_count > 0
42
+ %tr
43
+ %td.blank
44
+ %td{:colspan => 4}
45
+ .results{:id => "poll_#{poll.id}_results", :style => "align:right;display: none;"}
46
+ %strong Results
47
+ %table
48
+ - poll.options.sort{|a,b| b.response_count <=> a.response_count}.each do |option|
49
+ %tr
50
+ %td
51
+ %strong= option.title
52
+ %td= option.response_count
53
+ - else
54
+ %td.empty{:colspan => admin.poll.index.tbody.length}= t('polls_index.empty')
55
+
56
+ - render_region :bottom do |bottom|
57
+ - bottom.new_button do
58
+ #actions
59
+ = pagination_for(@polls)
60
+ %ul
61
+ %li= link_to image('plus') + " " + t('polls_action.new'), new_admin_poll_url, :class => 'action_button'
62
+
63
+ - content_for :page_css do
64
+ :sass
65
+ .index#polls
66
+ th.actions, td.name#responses
67
+ text-align: center
68
+ td.responses a
69
+ font-size: 115%
70
+ font-weight: bold
71
+ color: black
72
+ text-decoration: none
@@ -0,0 +1,5 @@
1
+ - render_region :main do |main|
2
+ - main.edit_header do
3
+ %h1= t('polls_action.new')
4
+ - main.edit_form do
5
+ = render :partial => 'form'
@@ -0,0 +1,17 @@
1
+ %h1= t('remove_poll')
2
+
3
+ %p
4
+ = t('text.polls.remove_warning')
5
+
6
+ %table.index#polls
7
+ %tbody
8
+ %tr.node.level_1
9
+ %td.poll
10
+ = image('poll', :alt => "")
11
+ %span.title= @poll.title
12
+
13
+ - form_for [:admin, @poll], :html => {:method => :delete, 'data-onsubmit_status'=>"Removing poll&#8230;"} do
14
+ .buttons
15
+ %input.button{:type=>"submit", :value => t('delete_poll')}/
16
+ = t('or')
17
+ = link_to t('cancel'), admin_polls_url
@@ -0,0 +1,25 @@
1
+ ---
2
+ en:
3
+ polls_action:
4
+ edit: 'Edit Poll'
5
+ new: 'New Poll'
6
+ polls_form:
7
+ label:
8
+ title: 'Title'
9
+ start_date: 'Start Date'
10
+ options: 'Options'
11
+ add_option: 'Add Poll Option'
12
+ polls_index:
13
+ title: 'Polls'
14
+ instructions: 'Click on a poll name below to edit it, or click <code>Remove</code> to delete it. If a poll has responses, click the responses value to toggle the view of the results. Poll responses can be cleared by clicking <code>Clear responses</code>.'
15
+ column:
16
+ title: 'Title'
17
+ start_date: 'Start Date'
18
+ responses: 'Responses'
19
+ empty: 'You have not created any polls. Click <code>New Poll</code> below to create a new poll.'
20
+ reset: 'Clear responses'
21
+ reset_confirm: "Clear all responses for the poll '%{poll}' ?"
22
+ delete_confirm: "Permanently delete the poll '%{poll}' ?"
23
+ results: 'Results'
24
+ polls_controller:
25
+ responses_cleared: "Responses have been cleared."
@@ -0,0 +1,187 @@
1
+ ---
2
+ en:
3
+ desc:
4
+ poll: "Selects the active poll.
5
+
6
+ *Usage*:
7
+
8
+ <pre><code><r:poll [title=\"Poll Title\"]>
9
+ ...
10
+ </r:poll></code></pre>"
11
+ poll-unless_submitted: "Expands inner tags if the poll has not been submitted yet.
12
+
13
+ *Usage*:
14
+
15
+ <pre><code><r:poll:unless_submitted>
16
+ ...
17
+ </r:poll:unless_submitted></code></pre>"
18
+ poll-if_submitted: "Expands inner tags if the poll has been yet.
19
+
20
+ *Usage*:
21
+
22
+ <pre><code><r:poll:if_submitted>
23
+ ...
24
+ </r:poll:if_submitted></code></pre>"
25
+ poll-title: "Shows the poll title.
26
+
27
+ *Usage*:
28
+
29
+ <pre><code><r:poll><r:title /></r:poll></code></pre>"
30
+ poll-form: "Renders a poll survey form.
31
+
32
+ *Usage*:
33
+
34
+ <pre><code><r:poll:form>
35
+ ...
36
+ </r:poll:form></code></pre>"
37
+ poll-options: "Render a collection of options, optionally sorted by @response_count@
38
+ in @asc@ending, @desc@ending, or @rand@om order. If no order is specified, the
39
+ result will be in whatever order is returned by the SQL query.
40
+
41
+ *Usage*:
42
+
43
+ <pre><code><r:poll:options [order=\"asc|desc|rand\"]>
44
+ ...
45
+ </r:poll:options></code></pre>"
46
+ poll-options-each: "Iterate through each poll option.
47
+
48
+ *Usage:*
49
+
50
+ <pre><code><r:poll:options:each>
51
+ ...
52
+ </r:poll:options:each></code></pre>"
53
+ poll-options-each-if_first: "Render inner content if the current contextual option is the first option.
54
+
55
+ *Usage:*
56
+
57
+ <pre><code><r:poll:options:each:if_first>
58
+ ...
59
+ </r:poll:options:each:if_first></code></pre>"
60
+ poll-options-each-if_last: "Render inner content if the current contextual option is the last option.
61
+
62
+ *Usage:*
63
+
64
+ <pre><code><r:poll:options:each:if_last>
65
+ ...
66
+ </r:poll:options:each:if_last></code></pre>"
67
+ poll-options-each-unless_first: "Render inner content unless the current contextual option is the first option.
68
+
69
+ *Usage:*
70
+
71
+ <pre><code><r:poll:options:each:unless_first>
72
+ ...
73
+ </r:poll:options:each:unless_first></code></pre>"
74
+ poll-options-each-unless_last: "Render inner content unless the current contextual option is the last option.
75
+
76
+ *Usage:*
77
+
78
+ <pre><code><r:poll:options:each:unless_last>
79
+ ...
80
+ </r:poll:options:each:unless_last></code></pre>"
81
+ poll-options-input: "Show the poll option radio button input type.
82
+
83
+ *Usage:*
84
+
85
+ <pre><code><r:poll:form>
86
+ <r:options:each>
87
+ <r:input />
88
+ </r:options:each>
89
+ </r:poll:form></code></pre>"
90
+ poll-options-title: "Show the poll option title.
91
+
92
+ *Usage:*
93
+
94
+ <pre><code><r:poll:form>
95
+ <r:options:each>
96
+ <r:title />
97
+ </r:options:each>
98
+ </r:poll:form></code></pre>"
99
+ poll-submit: "Show a poll form submit button.
100
+ The optional @value@ attribute may be specified to override the default
101
+ button label.
102
+
103
+ *Usage:*
104
+
105
+ <pre><code><r:poll:form>
106
+ <r:options:each>
107
+ <r:input /><r:title />
108
+ </r:options:each>
109
+ <r:submit [value=\"Go!\"] />
110
+ </r:poll:form></code></pre>"
111
+ poll-options-percent_responses: "Show the percentage of responses for an option.
112
+
113
+ *Usage:*
114
+
115
+ <pre><code><r:poll:options:each>
116
+ <r:title /> <r:percent_responses />
117
+ </r:poll:options:each></code</pre>"
118
+ poll-options-number_responded: "Show the number of responses for an option.
119
+
120
+ *Usage:*
121
+
122
+ <pre><code><r:poll:options:each>
123
+ <r:title /> <r:number_responded />
124
+ </r:poll:options:each></code</pre>"
125
+ poll-total_responses: "Show the total number of visitors that responded.
126
+
127
+ *Usage:*
128
+
129
+ <pre><code><r:poll:total_responses /></code></pre>"
130
+ polls: "Selects all polls.
131
+
132
+ By default, polls are sorted in ascending order by title and limited to
133
+ 10 per page; the current poll and any future polls are excluded. To
134
+ include the current poll, set @show_current@ to @true@. Any polls where
135
+ @attribute@ is null are also excluded, so if you have a set of polls that
136
+ include polls that do not have a defined start date, then when @by@ is set
137
+ to @start_date@, only those polls with start dates will be shown.
138
+
139
+ *Usage:*
140
+
141
+ <pre><code><r:polls [per_page=\"count\"] [by=\"attribute\"]
142
+ [order=\"asc|desc\"] [show_current=\"true|false\"] /></code></pre>"
143
+ polls-each: "Loops through each poll and renders the contents.
144
+
145
+ *Usage:*
146
+
147
+ <pre><code><r:polls:each>
148
+ ...
149
+ </r:polls:each></code></pre>"
150
+ polls-each-poll: "Creates context for a single poll.
151
+
152
+ *Usage:*
153
+
154
+ <pre><code><r:polls:each:poll>
155
+ ...
156
+ </r:polls:each:poll></code></pre>"
157
+ polls-pages: "Renders pagination links with will_paginate.
158
+ The following optional attributes may be controlled:
159
+
160
+ <ul>
161
+ <li>@id@ the id to apply to the containing div</li>
162
+ <li>@class@ the class to apply to the containing div (default is \"pagination\")</li>
163
+ <li>@previous_label@ override the default of \"&laquo; Previous\"</li>
164
+ <li>@next_label@ override the default of \"Next &raquo;\"</li>
165
+ <li>@inner_window@ the number of pagination links shown around the current page (default is 4)</li>
166
+ <li>@outer_window@ the number of pagination links shown around the first and last page links (default is 1)</li>
167
+ <li>@separator@ the string separator for page HTML elements (default is a single space)</li>
168
+ <li>@page_links@ if false, only previous/next links are rendered (default is true)</li>
169
+ <li>@container@ if false, pagination links are not wrapped in a containing div (default is true)</li>
170
+ </ul>
171
+
172
+ *Usage:*
173
+
174
+ <pre><code><r:polls>
175
+ <r:each:poll>
176
+ ...
177
+ </r:each:poll>
178
+ <r:pages [id=\"\"]
179
+ [class=\"pagination\"]
180
+ [previous_label=\"&laquo; Previous\"]
181
+ [next_label=\"Next &raquo;\"]
182
+ [inner_window=\"4\"]
183
+ [outer_window=\"1\"]
184
+ [separator=\" \"]
185
+ [page_links=\"true\"]
186
+ [container=\"true\" />
187
+ </r:polls></code></pre>"