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.
- data/CHANGELOG +27 -0
- data/HELP.textile +71 -0
- data/LICENSE +21 -0
- data/README.textile +112 -0
- data/Rakefile +129 -0
- data/VERSION +1 -0
- data/app/controllers/admin/polls_controller.rb +25 -0
- data/app/controllers/poll_response_controller.rb +35 -0
- data/app/models/option.rb +22 -0
- data/app/models/poll.rb +67 -0
- data/app/views/admin/polls/_form.html.haml +67 -0
- data/app/views/admin/polls/_option.html.haml +9 -0
- data/app/views/admin/polls/edit.html.haml +5 -0
- data/app/views/admin/polls/index.html.haml +72 -0
- data/app/views/admin/polls/new.html.haml +5 -0
- data/app/views/admin/polls/remove.html.haml +17 -0
- data/config/locales/en.yml +25 -0
- data/config/locales/en_available_tags.yml +187 -0
- data/config/routes.rb +6 -0
- data/db/migrate/001_create_polls.rb +12 -0
- data/db/migrate/002_create_options.rb +14 -0
- data/db/migrate/003_add_start_date_to_polls.rb +9 -0
- data/lib/poll_process.rb +23 -0
- data/lib/poll_tags.rb +408 -0
- data/lib/tasks/polls_extension_tasks.rake +56 -0
- data/polls_extension.rb +51 -0
- data/public/images/admin/new-poll.png +0 -0
- data/public/images/admin/poll.png +0 -0
- data/public/images/admin/recycle.png +0 -0
- data/public/images/admin/reset.png +0 -0
- data/public/javascripts/admin/date_selector.js +177 -0
- data/public/javascripts/admin/polls.js +47 -0
- data/public/javascripts/poll_check.js +52 -0
- data/public/stylesheets/admin/polls.css +93 -0
- data/public/stylesheets/polls.css +9 -0
- data/radiant-polls-extension.gemspec +83 -0
- data/spec/controllers/admin/polls_controller_spec.rb +16 -0
- data/spec/controllers/poll_response_controller_spec.rb +149 -0
- data/spec/integration/page_caching_spec.rb +76 -0
- data/spec/lib/poll_tags_spec.rb +415 -0
- data/spec/models/poll_spec.rb +50 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +42 -0
- metadata +141 -0
data/app/models/poll.rb
ADDED
@@ -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,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,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…"} 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 \"« Previous\"</li>
|
164
|
+
<li>@next_label@ override the default of \"Next »\"</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=\"« Previous\"]
|
181
|
+
[next_label=\"Next »\"]
|
182
|
+
[inner_window=\"4\"]
|
183
|
+
[outer_window=\"1\"]
|
184
|
+
[separator=\" \"]
|
185
|
+
[page_links=\"true\"]
|
186
|
+
[container=\"true\" />
|
187
|
+
</r:polls></code></pre>"
|