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