radiant-polls-extension 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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>"