decidim-forms 0.22.0 → 0.23.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.
- checksums.yaml +4 -4
- data/app/assets/config/admin/decidim_forms_manifest.css +3 -0
- data/app/assets/config/decidim_forms_manifest.css +1 -0
- data/app/assets/javascripts/decidim/forms/admin/auto_select_options_from_url.component.js.es6 +40 -0
- data/app/assets/javascripts/decidim/forms/admin/forms.js.es6 +181 -25
- data/app/assets/javascripts/decidim/forms/autosortable_checkboxes.component.js.es6 +54 -34
- data/app/assets/javascripts/decidim/forms/display_conditions.component.js.es6 +204 -0
- data/app/assets/javascripts/decidim/forms/forms.js.es6 +8 -1
- data/app/assets/stylesheets/decidim/forms/questionnaire-answers-pdf.scss +69 -0
- data/app/commands/decidim/forms/admin/update_questionnaire.rb +25 -1
- data/app/commands/decidim/forms/answer_questionnaire.rb +1 -1
- data/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb +47 -1
- data/app/controllers/decidim/forms/admin/concerns/has_questionnaire_answers.rb +97 -0
- data/app/forms/decidim/forms/admin/display_condition_form.rb +100 -0
- data/app/forms/decidim/forms/admin/question_form.rb +1 -0
- data/app/forms/decidim/forms/answer_form.rb +15 -0
- data/app/helpers/decidim/forms/admin/application_helper.rb +21 -0
- data/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_pagination_helper.rb +49 -0
- data/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_url_helper.rb +40 -0
- data/app/helpers/decidim/forms/admin/questionnaire_answers_helper.rb +27 -0
- data/app/jobs/decidim/forms/export_questionnaire_answers_job.rb +19 -0
- data/app/models/decidim/forms/answer.rb +0 -3
- data/app/models/decidim/forms/answer_option.rb +14 -0
- data/app/models/decidim/forms/display_condition.rb +51 -0
- data/app/models/decidim/forms/question.rb +34 -0
- data/app/models/decidim/forms/question_matrix_row.rb +3 -0
- data/app/models/decidim/forms/questionnaire.rb +11 -1
- data/app/presenters/decidim/forms/admin/questionnaire_answer_presenter.rb +43 -0
- data/app/presenters/decidim/forms/admin/questionnaire_participant_presenter.rb +60 -0
- data/app/presenters/decidim/forms/answer_option_presenter.rb +20 -0
- data/app/presenters/decidim/forms/question_presenter.rb +16 -0
- data/app/queries/decidim/forms/questionnaire_participant.rb +35 -0
- data/app/queries/decidim/forms/questionnaire_participants.rb +43 -0
- data/app/views/decidim/forms/admin/questionnaires/_display_condition.html.erb +88 -0
- data/app/views/decidim/forms/admin/questionnaires/_display_condition_template.html.erb +7 -0
- data/app/views/decidim/forms/admin/questionnaires/_form.html.erb +30 -4
- data/app/views/decidim/forms/admin/questionnaires/_question.html.erb +19 -3
- data/app/views/decidim/forms/admin/questionnaires/answers/export/_answer.html.erb +31 -0
- data/app/views/decidim/forms/admin/questionnaires/answers/export/pdf.html.erb +13 -0
- data/app/views/decidim/forms/admin/questionnaires/answers/index.html.erb +53 -0
- data/app/views/decidim/forms/admin/questionnaires/answers/show.html.erb +48 -0
- data/app/views/decidim/forms/admin/questionnaires/edit.html.erb +9 -5
- data/app/views/decidim/forms/questionnaires/_answer.html.erb +1 -1
- data/app/views/decidim/forms/questionnaires/show.html.erb +22 -2
- data/app/views/layouts/decidim/forms/admin/questionnaires/questionnaire_answers.html.erb +12 -0
- data/config/initializers/wicked_pdf.rb +25 -0
- data/config/locales/am-ET.yml +1 -0
- data/config/locales/ar.yml +0 -1
- data/config/locales/bg-BG.yml +0 -2
- data/config/locales/bg.yml +14 -0
- data/config/locales/ca.yml +56 -4
- data/config/locales/cs.yml +54 -2
- data/config/locales/da.yml +1 -0
- data/config/locales/de.yml +51 -1
- data/config/locales/el.yml +0 -2
- data/config/locales/en.yml +54 -2
- data/config/locales/eo.yml +1 -0
- data/config/locales/es-MX.yml +53 -1
- data/config/locales/es-PY.yml +53 -1
- data/config/locales/es.yml +54 -2
- data/config/locales/et.yml +1 -0
- data/config/locales/eu.yml +0 -1
- data/config/locales/fi-plain.yml +53 -1
- data/config/locales/fi.yml +53 -1
- data/config/locales/fr-CA.yml +54 -2
- data/config/locales/fr.yml +54 -2
- data/config/locales/gl.yml +0 -1
- data/config/locales/hr.yml +1 -0
- data/config/locales/hu.yml +0 -2
- data/config/locales/id-ID.yml +0 -1
- data/config/locales/is.yml +1 -0
- data/config/locales/it.yml +53 -1
- data/config/locales/ja-JP.yml +51 -1
- data/config/locales/ja.yml +170 -0
- data/config/locales/ko-KR.yml +1 -0
- data/config/locales/ko.yml +1 -0
- data/config/locales/lt.yml +1 -0
- data/config/locales/{lv-LV.yml → lv.yml} +0 -1
- data/config/locales/mt.yml +1 -0
- data/config/locales/nl.yml +59 -7
- data/config/locales/no.yml +47 -2
- data/config/locales/om-ET.yml +1 -0
- data/config/locales/pl.yml +52 -3
- data/config/locales/pt-BR.yml +0 -1
- data/config/locales/pt.yml +51 -2
- data/config/locales/ro-RO.yml +51 -2
- data/config/locales/ru.yml +0 -1
- data/config/locales/sk.yml +0 -2
- data/config/locales/sl.yml +7 -0
- data/config/locales/so-SO.yml +1 -0
- data/config/locales/sv.yml +51 -2
- data/config/locales/ti-ER.yml +1 -0
- data/config/locales/tr-TR.yml +0 -1
- data/config/locales/vi-VN.yml +1 -0
- data/config/locales/vi.yml +1 -0
- data/config/locales/zh-CN.yml +172 -0
- data/config/locales/zh-TW.yml +1 -0
- data/db/migrate/20200130194123_create_decidim_forms_display_conditions.rb +20 -0
- data/lib/decidim/exporters/form_pdf.rb +33 -0
- data/lib/decidim/exporters/form_pdf_controller_helper.rb +11 -0
- data/lib/decidim/forms.rb +5 -0
- data/lib/decidim/forms/admin_engine.rb +1 -1
- data/lib/decidim/forms/test.rb +6 -0
- data/lib/decidim/forms/test/factories.rb +30 -0
- data/lib/decidim/forms/test/shared_examples/has_questionnaire.rb +619 -38
- data/lib/decidim/forms/test/shared_examples/manage_questionnaire_answers.rb +108 -0
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires.rb +21 -894
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_display_conditions.rb +179 -0
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_questions.rb +463 -0
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_display_conditions.rb +93 -0
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_questions.rb +461 -0
- data/lib/decidim/forms/version.rb +1 -1
- metadata +91 -11
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/* eslint-disable no-ternary, no-plusplus */
|
|
2
|
+
|
|
3
|
+
((exports) => {
|
|
4
|
+
class DisplayCondition {
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
this.wrapperField = options.wrapperField;
|
|
7
|
+
this.type = options.type;
|
|
8
|
+
this.conditionQuestion = options.conditionQuestion;
|
|
9
|
+
this.answerOption = options.answerOption;
|
|
10
|
+
this.mandatory = options.mandatory;
|
|
11
|
+
this.value = options.value;
|
|
12
|
+
this.onFulfilled = options.onFulfilled;
|
|
13
|
+
this.bindEvent();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
bindEvent() {
|
|
17
|
+
this.checkCondition();
|
|
18
|
+
this.getInputsToListen().on("change", this.checkCondition.bind(this));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getInputValue() {
|
|
22
|
+
const $conditionWrapperField = $(`.question[data-question-id='${this.conditionQuestion}']`);
|
|
23
|
+
const $textInput = $conditionWrapperField.find("textarea, input[type='text']:not([name$=\\[custom_body\\]])");
|
|
24
|
+
|
|
25
|
+
if ($textInput.length) {
|
|
26
|
+
return $textInput.val();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let multipleInput = [];
|
|
30
|
+
|
|
31
|
+
$conditionWrapperField.find(".radio-button-collection, .check-box-collection").find(".collection-input").each((idx, el) => {
|
|
32
|
+
const $label = $(el).find("label");
|
|
33
|
+
const checked = !$label.find("input[type='hidden']").is("[disabled]");
|
|
34
|
+
|
|
35
|
+
if (checked) {
|
|
36
|
+
const $textField = $(el).find("input[name$=\\[custom_body\\]]");
|
|
37
|
+
const text = $textField.val();
|
|
38
|
+
const value = $label.find("input:not([type='hidden'])").val();
|
|
39
|
+
const id = $label.find("input[type='hidden']").val();
|
|
40
|
+
|
|
41
|
+
multipleInput.push({ id, value, text });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return multipleInput;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getInputsToListen() {
|
|
49
|
+
const $conditionWrapperField = $(`.question[data-question-id='${this.conditionQuestion}']`);
|
|
50
|
+
const $textInput = $conditionWrapperField.find("textarea, input[type='text']:not([name$=\\[custom_body\\]])");
|
|
51
|
+
|
|
52
|
+
if ($textInput.length) {
|
|
53
|
+
return $textInput;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return $conditionWrapperField.find(".collection-input").find("input:not([type='hidden'])");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
checkAnsweredCondition(value) {
|
|
60
|
+
if (typeof (value) !== "object") {
|
|
61
|
+
return Boolean(value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return Boolean(value.some((it) => it.value));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
checkNotAnsweredCondition(value) {
|
|
68
|
+
return !this.checkAnsweredCondition(value);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
checkEqualCondition(value) {
|
|
72
|
+
if (value.length) {
|
|
73
|
+
return value.some((it) => it.id === this.answerOption.toString());
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
checkNotEqualCondition(value) {
|
|
79
|
+
if (value.length) {
|
|
80
|
+
return value.every((it) => it.id !== this.answerOption.toString());
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
checkMatchCondition(value) {
|
|
86
|
+
let regexp = new RegExp(this.value, "i");
|
|
87
|
+
|
|
88
|
+
if (typeof (value) !== "object") {
|
|
89
|
+
return Boolean(value.match(regexp));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return value.some(function (it) {
|
|
93
|
+
return it.text
|
|
94
|
+
? it.text.match(regexp)
|
|
95
|
+
: it.value.match(regexp)
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
checkCondition() {
|
|
100
|
+
const value = this.getInputValue();
|
|
101
|
+
let fulfilled = false;
|
|
102
|
+
|
|
103
|
+
switch (this.type) {
|
|
104
|
+
case "answered":
|
|
105
|
+
fulfilled = this.checkAnsweredCondition(value);
|
|
106
|
+
break;
|
|
107
|
+
case "not_answered":
|
|
108
|
+
fulfilled = this.checkNotAnsweredCondition(value);
|
|
109
|
+
break;
|
|
110
|
+
case "equal":
|
|
111
|
+
fulfilled = this.checkEqualCondition(value);
|
|
112
|
+
break;
|
|
113
|
+
case "not_equal":
|
|
114
|
+
fulfilled = this.checkNotEqualCondition(value);
|
|
115
|
+
break;
|
|
116
|
+
case "match":
|
|
117
|
+
fulfilled = this.checkMatchCondition(value);
|
|
118
|
+
break;
|
|
119
|
+
default:
|
|
120
|
+
fulfilled = false;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.onFulfilled(fulfilled);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
class DisplayConditionsComponent {
|
|
129
|
+
constructor(options = {}) {
|
|
130
|
+
this.wrapperField = options.wrapperField;
|
|
131
|
+
this.conditions = {};
|
|
132
|
+
this.showCount = 0;
|
|
133
|
+
this.initializeConditions();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
initializeConditions() {
|
|
137
|
+
const $conditionElements = this.wrapperField.find(".display-condition");
|
|
138
|
+
|
|
139
|
+
$conditionElements.each((idx, el) => {
|
|
140
|
+
const $condition = $(el);
|
|
141
|
+
const id = $condition.data("id");
|
|
142
|
+
this.conditions[id] = {};
|
|
143
|
+
|
|
144
|
+
this.conditions[id] = new DisplayCondition({
|
|
145
|
+
wrapperField: this.wrapperField,
|
|
146
|
+
type: $condition.data("type"),
|
|
147
|
+
conditionQuestion: $condition.data("condition"),
|
|
148
|
+
answerOption: $condition.data("option"),
|
|
149
|
+
mandatory: $condition.data("mandatory"),
|
|
150
|
+
value: $condition.data("value"),
|
|
151
|
+
onFulfilled: (fulfilled) => {
|
|
152
|
+
this.onFulfilled(id, fulfilled);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
mustShow() {
|
|
159
|
+
const conditions = Object.values(this.conditions);
|
|
160
|
+
const mandatoryConditions = conditions.filter((condition) => condition.mandatory);
|
|
161
|
+
const nonMandatoryConditions = conditions.filter((condition) => !condition.mandatory);
|
|
162
|
+
|
|
163
|
+
if (mandatoryConditions.length) {
|
|
164
|
+
return mandatoryConditions.every((condition) => condition.fulfilled);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return nonMandatoryConditions.some((condition) => condition.fulfilled);
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
onFulfilled(id, fulfilled) {
|
|
172
|
+
this.conditions[id].fulfilled = fulfilled;
|
|
173
|
+
|
|
174
|
+
if (this.mustShow()) {
|
|
175
|
+
this.showQuestion();
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
this.hideQuestion();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
showQuestion() {
|
|
183
|
+
this.wrapperField.fadeIn();
|
|
184
|
+
this.wrapperField.find("input, textarea").prop("disabled", null);
|
|
185
|
+
this.showCount++;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
hideQuestion() {
|
|
189
|
+
if (this.showCount) {
|
|
190
|
+
this.wrapperField.fadeOut();
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
this.wrapperField.hide();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
this.wrapperField.find("input, textarea").prop("disabled", "disabled");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
exports.Decidim = exports.Decidim || {};
|
|
201
|
+
exports.Decidim.createDisplayConditions = (options) => {
|
|
202
|
+
return new DisplayConditionsComponent(options);
|
|
203
|
+
};
|
|
204
|
+
})(window);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// = require ./option_attached_inputs.component
|
|
2
2
|
// = require ./autosortable_checkboxes.component
|
|
3
|
+
// = require ./display_conditions.component
|
|
3
4
|
// = require ./max_choices_alert.component
|
|
4
5
|
|
|
5
6
|
((exports) => {
|
|
6
|
-
const { createOptionAttachedInputs, createAutosortableCheckboxes, createMaxChoicesAlertComponent } = exports.Decidim;
|
|
7
|
+
const { createOptionAttachedInputs, createAutosortableCheckboxes, createMaxChoicesAlertComponent, createDisplayConditions } = exports.Decidim;
|
|
7
8
|
|
|
8
9
|
$(".radio-button-collection, .check-box-collection").each((idx, el) => {
|
|
9
10
|
createOptionAttachedInputs({
|
|
@@ -32,6 +33,12 @@
|
|
|
32
33
|
})
|
|
33
34
|
});
|
|
34
35
|
|
|
36
|
+
$(".answer-questionnaire .question[data-conditioned='true']").each((idx, el) => {
|
|
37
|
+
createDisplayConditions({
|
|
38
|
+
wrapperField: $(el)
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
35
42
|
const $form = $("form.answer-questionnaire");
|
|
36
43
|
if ($form.length > 0) {
|
|
37
44
|
$form.find("input, textarea, select").on("change", () => {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
.questionnaire-answers{
|
|
2
|
+
.header{
|
|
3
|
+
h1{
|
|
4
|
+
margin: 0;
|
|
5
|
+
padding: 25px;
|
|
6
|
+
background: rgb(59, 69, 87);
|
|
7
|
+
color: white;
|
|
8
|
+
border-radius: 4px 4px 0 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.description{
|
|
12
|
+
margin: 0;
|
|
13
|
+
padding: 25px;
|
|
14
|
+
background: #f6f6f6;
|
|
15
|
+
color: #202751;
|
|
16
|
+
border-radius: 0 0 4px 4px;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.answer{
|
|
21
|
+
margin-top: 25px;
|
|
22
|
+
background: #f6f6f6;
|
|
23
|
+
border-radius: 0 0 4px 4px;
|
|
24
|
+
|
|
25
|
+
.title{
|
|
26
|
+
page-break-inside: avoid;
|
|
27
|
+
border-radius: 4px 4px 0 0;
|
|
28
|
+
padding: 10px 25px;
|
|
29
|
+
background: rgb(59, 69, 87);
|
|
30
|
+
color: white;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.participant-info{
|
|
34
|
+
margin-top: 0;
|
|
35
|
+
padding: 10px 25px;
|
|
36
|
+
width: 100%;
|
|
37
|
+
text-align: center;
|
|
38
|
+
background: #eee;
|
|
39
|
+
color: #202751;
|
|
40
|
+
page-break-inside: avoid;
|
|
41
|
+
|
|
42
|
+
th:first-child{
|
|
43
|
+
text-align: left;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
td:first-child{
|
|
47
|
+
text-align: left;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
th:last-child{
|
|
51
|
+
text-align: right;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
td:last-child{
|
|
55
|
+
text-align: right;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.answers{
|
|
60
|
+
padding: 25px;
|
|
61
|
+
|
|
62
|
+
.question{
|
|
63
|
+
page-break-inside: avoid;
|
|
64
|
+
font-size: inherit;
|
|
65
|
+
color: #202751;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -22,7 +22,11 @@ module Decidim
|
|
|
22
22
|
return broadcast(:invalid) if @form.invalid?
|
|
23
23
|
|
|
24
24
|
Decidim::Forms::Questionnaire.transaction do
|
|
25
|
-
|
|
25
|
+
if @questionnaire.questions_editable?
|
|
26
|
+
update_questionnaire_questions
|
|
27
|
+
delete_answers unless @questionnaire.published?
|
|
28
|
+
end
|
|
29
|
+
|
|
26
30
|
update_questionnaire
|
|
27
31
|
end
|
|
28
32
|
|
|
@@ -57,6 +61,22 @@ module Decidim
|
|
|
57
61
|
update_nested_model(form_answer_option, answer_option_attributes, question.answer_options)
|
|
58
62
|
end
|
|
59
63
|
|
|
64
|
+
form_question.display_conditions.each do |form_display_condition|
|
|
65
|
+
type = form_display_condition.condition_type
|
|
66
|
+
|
|
67
|
+
display_condition_attributes = {
|
|
68
|
+
condition_question: form_display_condition.condition_question,
|
|
69
|
+
condition_type: form_display_condition.condition_type,
|
|
70
|
+
condition_value: type == "match" ? form_display_condition.condition_value : nil,
|
|
71
|
+
answer_option: %w(equal not_equal).include?(type) ? form_display_condition.answer_option : nil,
|
|
72
|
+
mandatory: form_display_condition.mandatory
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
next if form_display_condition.deleted? && form_display_condition.id.blank?
|
|
76
|
+
|
|
77
|
+
update_nested_model(form_display_condition, display_condition_attributes, question.display_conditions)
|
|
78
|
+
end
|
|
79
|
+
|
|
60
80
|
form_question.matrix_rows.each do |form_matrix_row|
|
|
61
81
|
matrix_row_attributes = {
|
|
62
82
|
body: form_matrix_row.body
|
|
@@ -88,6 +108,10 @@ module Decidim
|
|
|
88
108
|
description: @form.description,
|
|
89
109
|
tos: @form.tos)
|
|
90
110
|
end
|
|
111
|
+
|
|
112
|
+
def delete_answers
|
|
113
|
+
@questionnaire.answers.destroy_all
|
|
114
|
+
end
|
|
91
115
|
end
|
|
92
116
|
end
|
|
93
117
|
end
|
|
@@ -30,7 +30,7 @@ module Decidim
|
|
|
30
30
|
|
|
31
31
|
def answer_questionnaire
|
|
32
32
|
Answer.transaction do
|
|
33
|
-
form.responses_by_step.flatten.each do |form_answer|
|
|
33
|
+
form.responses_by_step.flatten.select(&:display_conditions_fulfilled?).each do |form_answer|
|
|
34
34
|
answer = Answer.new(
|
|
35
35
|
user: @current_user,
|
|
36
36
|
questionnaire: @questionnaire,
|
|
@@ -15,7 +15,21 @@ module Decidim
|
|
|
15
15
|
|
|
16
16
|
included do
|
|
17
17
|
helper Decidim::Forms::Admin::ApplicationHelper
|
|
18
|
-
helper_method :questionnaire_for, :questionnaire, :blank_question, :blank_answer_option, :blank_matrix_row,
|
|
18
|
+
helper_method :questionnaire_for, :questionnaire, :blank_question, :blank_answer_option, :blank_matrix_row,
|
|
19
|
+
:blank_display_condition, :question_types, :display_condition_types, :update_url, :public_url, :answer_options_url
|
|
20
|
+
|
|
21
|
+
if defined? Decidim::Templates::Admin
|
|
22
|
+
include Decidim::Templates::Admin::Concerns::Templatable
|
|
23
|
+
helper Decidim::Templates::Admin::TemplatesHelper
|
|
24
|
+
|
|
25
|
+
def templatable_type
|
|
26
|
+
"Decidim::Forms::Questionnaire"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def templatable
|
|
30
|
+
questionnaire
|
|
31
|
+
end
|
|
32
|
+
end
|
|
19
33
|
|
|
20
34
|
def edit
|
|
21
35
|
enforce_permission_to :update, :questionnaire, questionnaire: questionnaire
|
|
@@ -46,6 +60,16 @@ module Decidim
|
|
|
46
60
|
end
|
|
47
61
|
end
|
|
48
62
|
|
|
63
|
+
def answer_options
|
|
64
|
+
respond_to do |format|
|
|
65
|
+
format.json do
|
|
66
|
+
question_id = params["id"]
|
|
67
|
+
question = Question.find_by(id: question_id)
|
|
68
|
+
render json: question.answer_options.map { |answer_option| AnswerOptionPresenter.new(answer_option).as_json } if question.present?
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
49
73
|
# Public: The only method to be implemented at the controller. You need to
|
|
50
74
|
# return the object that will hold the questionnaire.
|
|
51
75
|
def questionnaire_for
|
|
@@ -64,6 +88,18 @@ module Decidim
|
|
|
64
88
|
url_for(questionnaire.questionnaire_for)
|
|
65
89
|
end
|
|
66
90
|
|
|
91
|
+
# Implement this method in your controller to set the URL
|
|
92
|
+
# where the questionnaire can be answered.
|
|
93
|
+
def public_url
|
|
94
|
+
raise "#{self.class.name} is expected to implement #public_url"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Returns the url to get the answer options json (for the display conditions form)
|
|
98
|
+
# for the question with id = params[:id]
|
|
99
|
+
def answer_options_url(params)
|
|
100
|
+
url_for([questionnaire.questionnaire_for, action: :answer_options, format: :json, **params])
|
|
101
|
+
end
|
|
102
|
+
|
|
67
103
|
private
|
|
68
104
|
|
|
69
105
|
def i18n_flashes_scope
|
|
@@ -82,6 +118,10 @@ module Decidim
|
|
|
82
118
|
@blank_answer_option ||= Admin::AnswerOptionForm.new
|
|
83
119
|
end
|
|
84
120
|
|
|
121
|
+
def blank_display_condition
|
|
122
|
+
@blank_display_condition ||= Admin::DisplayConditionForm.new
|
|
123
|
+
end
|
|
124
|
+
|
|
85
125
|
def blank_matrix_row
|
|
86
126
|
@blank_matrix_row ||= Admin::QuestionMatrixRowForm.new
|
|
87
127
|
end
|
|
@@ -91,6 +131,12 @@ module Decidim
|
|
|
91
131
|
[question_type, I18n.t("decidim.forms.question_types.#{question_type}")]
|
|
92
132
|
end
|
|
93
133
|
end
|
|
134
|
+
|
|
135
|
+
def display_condition_types
|
|
136
|
+
@display_condition_types ||= DisplayCondition.condition_types.keys.map do |condition_type|
|
|
137
|
+
[condition_type, I18n.t("decidim.forms.admin.questionnaires.display_condition.condition_types.#{condition_type}")]
|
|
138
|
+
end
|
|
139
|
+
end
|
|
94
140
|
end
|
|
95
141
|
end
|
|
96
142
|
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Forms
|
|
5
|
+
module Admin
|
|
6
|
+
module Concerns
|
|
7
|
+
# Questionnaires can be related to any class in Decidim. In order to
|
|
8
|
+
# manage the questionnaires answers for a given type, you should create a new
|
|
9
|
+
# controller and include the HasQuestionnaire concern as well as this one.
|
|
10
|
+
#
|
|
11
|
+
# In the controller that includes this concern, you should define a
|
|
12
|
+
# `questionnaire_for` method that returns an instance of the model that the
|
|
13
|
+
# questionnaire belongs to. You should also define the routes for:
|
|
14
|
+
# `index_<model>_url` and `export_<model>_url` as well as
|
|
15
|
+
# `show_<model>_url` and `export_response_<model>_url` (which are passed
|
|
16
|
+
# a `:session_token` parameter)
|
|
17
|
+
module HasQuestionnaireAnswers
|
|
18
|
+
extend ActiveSupport::Concern
|
|
19
|
+
|
|
20
|
+
included do
|
|
21
|
+
include Decidim::Paginable
|
|
22
|
+
include Decidim::Forms::Admin::Concerns::HasQuestionnaireAnswersUrlHelper
|
|
23
|
+
include Decidim::Forms::Admin::Concerns::HasQuestionnaireAnswersPaginationHelper
|
|
24
|
+
|
|
25
|
+
helper Decidim::Forms::Admin::QuestionnaireAnswersHelper
|
|
26
|
+
|
|
27
|
+
def index
|
|
28
|
+
enforce_permission_to :index, :questionnaire_answers
|
|
29
|
+
|
|
30
|
+
@query = paginate(collection)
|
|
31
|
+
@participants = participants(@query)
|
|
32
|
+
@total = participants_query.count_participants
|
|
33
|
+
|
|
34
|
+
render template: "decidim/forms/admin/questionnaires/answers/index"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def show
|
|
38
|
+
enforce_permission_to :show, :questionnaire_answers
|
|
39
|
+
|
|
40
|
+
@participant = participant(participants_query.participant(params[:session_token]))
|
|
41
|
+
|
|
42
|
+
render template: "decidim/forms/admin/questionnaires/answers/show"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def export_response
|
|
46
|
+
enforce_permission_to :export_response, :questionnaire_answers
|
|
47
|
+
|
|
48
|
+
session_token = params[:session_token]
|
|
49
|
+
answers = QuestionnaireUserAnswers.for(questionnaire)
|
|
50
|
+
|
|
51
|
+
# i18n-tasks-use t("decidim.forms.admin.questionnaires.answers.export_response.title")
|
|
52
|
+
title = t("export_response.title", scope: i18n_scope, token: session_token)
|
|
53
|
+
|
|
54
|
+
Decidim::Forms::ExportQuestionnaireAnswersJob.perform_later(current_user, title, answers.select { |a| a.first.session_token == session_token })
|
|
55
|
+
|
|
56
|
+
flash[:notice] = t("decidim.admin.exports.notice")
|
|
57
|
+
|
|
58
|
+
redirect_back(fallback_location: questionnaire_participant_answers_url(session_token))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Public: The only method to be implemented at the controller. You need to
|
|
62
|
+
# return the object that will hold the questionnaire.
|
|
63
|
+
def questionnaire_for
|
|
64
|
+
raise "#{self.class.name} is expected to implement #questionnaire_for"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def i18n_scope
|
|
70
|
+
"decidim.forms.admin.questionnaires.answers"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def questionnaire
|
|
74
|
+
@questionnaire ||= Questionnaire.find_by(questionnaire_for: questionnaire_for)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def participants_query
|
|
78
|
+
QuestionnaireParticipants.new(questionnaire)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def collection
|
|
82
|
+
@collection ||= participants_query.participants
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def participant(answer)
|
|
86
|
+
Decidim::Forms::Admin::QuestionnaireParticipantPresenter.new(participant: answer)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def participants(query)
|
|
90
|
+
query.map { |answer| participant(answer) }
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|