decidim-forms 0.21.0 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/admin/decidim_forms_manifest.js +1 -0
  3. data/app/assets/images/decidim/surveys/icon.svg +1 -19
  4. data/app/assets/javascripts/decidim/forms/admin/collapsible_questions.js.es6 +13 -0
  5. data/app/assets/javascripts/decidim/forms/admin/forms.js.es6 +95 -7
  6. data/app/assets/javascripts/decidim/forms/admin/live_text_update.component.js.es6 +52 -0
  7. data/app/assets/javascripts/decidim/forms/forms.js.es6 +42 -1
  8. data/app/assets/javascripts/decidim/forms/max_choices_alert.component.js.es6 +44 -0
  9. data/app/assets/stylesheets/decidim/forms/forms.scss +39 -0
  10. data/app/cells/decidim/forms/matrix_readonly/show.erb +5 -0
  11. data/app/cells/decidim/forms/matrix_readonly_cell.rb +12 -0
  12. data/app/cells/decidim/forms/question_readonly/show.erb +5 -1
  13. data/app/cells/decidim/forms/question_readonly_cell.rb +5 -0
  14. data/app/cells/decidim/forms/step_navigation/show.erb +35 -0
  15. data/app/cells/decidim/forms/step_navigation_cell.rb +46 -0
  16. data/app/commands/decidim/forms/admin/update_questionnaire.rb +8 -0
  17. data/app/commands/decidim/forms/answer_questionnaire.rb +2 -1
  18. data/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb +6 -2
  19. data/app/controllers/decidim/forms/concerns/has_questionnaire.rb +11 -2
  20. data/app/forms/decidim/forms/admin/question_form.rb +20 -1
  21. data/app/forms/decidim/forms/admin/question_matrix_row_form.rb +26 -0
  22. data/app/forms/decidim/forms/answer_choice_form.rb +1 -0
  23. data/app/forms/decidim/forms/answer_form.rb +16 -2
  24. data/app/forms/decidim/forms/questionnaire_form.rb +25 -3
  25. data/app/helpers/decidim/forms/admin/application_helper.rb +16 -0
  26. data/app/models/decidim/forms/answer_choice.rb +7 -0
  27. data/app/models/decidim/forms/question.rb +18 -2
  28. data/app/models/decidim/forms/question_matrix_row.rb +12 -0
  29. data/app/views/decidim/forms/admin/questionnaires/_answer_option_template.html.erb +1 -1
  30. data/app/views/decidim/forms/admin/questionnaires/_form.html.erb +37 -4
  31. data/app/views/decidim/forms/admin/questionnaires/_matrix_row.html.erb +34 -0
  32. data/app/views/decidim/forms/admin/questionnaires/_matrix_row_template.html.erb +7 -0
  33. data/app/views/decidim/forms/admin/questionnaires/_question.html.erb +29 -6
  34. data/app/views/decidim/forms/admin/questionnaires/_separator.html.erb +41 -0
  35. data/app/views/decidim/forms/questionnaires/_answer.html.erb +21 -92
  36. data/app/views/decidim/forms/questionnaires/answers/_long_answer.html.erb +1 -0
  37. data/app/views/decidim/forms/questionnaires/answers/_matrix_multiple.html.erb +43 -0
  38. data/app/views/decidim/forms/questionnaires/answers/_matrix_single.html.erb +43 -0
  39. data/app/views/decidim/forms/questionnaires/answers/_multiple_option.html.erb +23 -0
  40. data/app/views/decidim/forms/questionnaires/answers/_separator.html.erb +1 -0
  41. data/app/views/decidim/forms/questionnaires/answers/_short_answer.html.erb +1 -0
  42. data/app/views/decidim/forms/questionnaires/answers/_single_option.html.erb +30 -0
  43. data/app/views/decidim/forms/questionnaires/answers/_sorting.html.erb +23 -0
  44. data/app/views/decidim/forms/questionnaires/show.html.erb +57 -25
  45. data/config/locales/ar.yml +7 -3
  46. data/config/locales/bg-BG.yml +16 -0
  47. data/config/locales/ca.yml +35 -3
  48. data/config/locales/cs.yml +35 -3
  49. data/config/locales/da-DK.yml +1 -0
  50. data/config/locales/de.yml +41 -3
  51. data/config/locales/el.yml +119 -0
  52. data/config/locales/en.yml +35 -3
  53. data/config/locales/es-MX.yml +35 -3
  54. data/config/locales/es-PY.yml +35 -3
  55. data/config/locales/es.yml +35 -3
  56. data/config/locales/et-EE.yml +1 -0
  57. data/config/locales/eu.yml +7 -3
  58. data/config/locales/fi-plain.yml +35 -3
  59. data/config/locales/fi.yml +36 -4
  60. data/config/locales/fr-CA.yml +120 -0
  61. data/config/locales/fr.yml +36 -4
  62. data/config/locales/ga-IE.yml +1 -0
  63. data/config/locales/gl.yml +7 -3
  64. data/config/locales/hr-HR.yml +1 -0
  65. data/config/locales/hu.yml +11 -3
  66. data/config/locales/id-ID.yml +7 -3
  67. data/config/locales/it.yml +36 -4
  68. data/config/locales/ja-JP.yml +120 -0
  69. data/config/locales/lt-LT.yml +1 -0
  70. data/config/locales/lv-LV.yml +119 -0
  71. data/config/locales/mt-MT.yml +1 -0
  72. data/config/locales/nl.yml +35 -3
  73. data/config/locales/no.yml +7 -3
  74. data/config/locales/pl.yml +63 -25
  75. data/config/locales/pt-BR.yml +8 -4
  76. data/config/locales/pt.yml +62 -24
  77. data/config/locales/ro-RO.yml +118 -0
  78. data/config/locales/ru.yml +4 -1
  79. data/config/locales/sk-SK.yml +88 -0
  80. data/config/locales/sk.yml +92 -0
  81. data/config/locales/sl.yml +5 -0
  82. data/config/locales/sr-CS.yml +1 -0
  83. data/config/locales/sv.yml +38 -6
  84. data/config/locales/tr-TR.yml +7 -3
  85. data/db/migrate/20200225123810_create_decidim_forms_question_matrix_rows.rb +11 -0
  86. data/db/migrate/20200304152939_add_matrix_row_id_to_decidim_forms_answer_choices.rb +11 -0
  87. data/lib/decidim/forms/test/factories.rb +20 -0
  88. data/lib/decidim/forms/test/shared_examples/has_questionnaire.rb +313 -36
  89. data/lib/decidim/forms/test/shared_examples/manage_questionnaires.rb +346 -15
  90. data/lib/decidim/forms/user_answers_serializer.rb +21 -4
  91. data/lib/decidim/forms/version.rb +1 -1
  92. metadata +45 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 334b51b756960630b366ba46f5a50ecdb927bc65f55c08da3419911312a861f0
4
- data.tar.gz: 5e63218e62c6f71cefcf28c6344f04b559ef0f2f4cef0124d4cadc95eaffd7ab
3
+ metadata.gz: 73b7dd5e0d7e94151c79b989666d5a3d1e3790db9c97cee7a35c587a0a00977e
4
+ data.tar.gz: 4e7f6a2d27f58079d5eed9d08404373a66636f6960a220dc1572809a482dfbb3
5
5
  SHA512:
6
- metadata.gz: 06f69f7b6c6ad38af0da016e76f4b7af11f2a626badb37bfc29ac8c258432e8ed6367ba8ac4e4db39b02bbcfc7dd913672ec789db99650fc52410bf7ccb400d4
7
- data.tar.gz: ee7a451f8bda9e7c28ed27844a79af3e588d29fa635509d410b2b04efd2d5207eba5ec9d9018e0b8c3258520d88ef85982da809bab219a920a0de0486704e551
6
+ metadata.gz: b2eed83cff858299ee6fef944b1951150ad4634a193fec095ca20a393bfec8de0f1a2fe1e09447728d40dbb2898ce4b884d4ab82cdecaf93c6025efd1d677bd5
7
+ data.tar.gz: e1e84f6aa8d5a1cb2ea2fe95593bd0ba0977d536c4abb3a3cc66726cf66a425dd7c1622f462cc9324c5fbf0d375e0dcc43c2844eb0b0fafc5457f97d34a6de70
@@ -1 +1,2 @@
1
1
  //= link decidim/forms/admin/forms.js
2
+ //= link decidim/forms/admin/collapsible_questions.js
@@ -1,19 +1 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
- <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
- viewBox="0 0 36 36" style="enable-background:new 0 0 36 36;" xml:space="preserve">
5
- <path d="M27,28H9c-0.6,0-1-0.4-1-1V11c0-0.6,0.4-1,1-1h3.1c0.6,0,1,0.4,1,1v1.9h9.7V11c0-0.6,0.4-1,1-1H27c0.6,0,1,0.4,1,1v16
6
- C28,27.6,27.6,28,27,28z M10,26h16V12h-1.2v1.9c0,0.6-0.4,1-1,1H12.1c-0.6,0-1-0.4-1-1V12H10V26z"/>
7
- <g>
8
- <g>
9
- <path d="M21,11h-6c-0.6,0-1-0.4-1-1s0.4-1,1-1h6c0.6,0,1,0.4,1,1S21.6,11,21,11z"/>
10
- </g>
11
- <g>
12
- <path d="M18.7,11h-1.5c-0.6,0-1-0.4-1-1V8c0-0.6,0.4-1,1-1h1.5c0.6,0,1,0.4,1,1v2C19.7,10.5,19.3,11,18.7,11z"/>
13
- </g>
14
- </g>
15
- <g>
16
- <path d="M18,36.1c-9.9,0-18-8.1-18-18s8.1-18,18-18s18,8.1,18,18S27.9,36.1,18,36.1z M18,2.1c-8.8,0-16,7.2-16,16s7.2,16,16,16
17
- s16-7.2,16-16S26.8,2.1,18,2.1z"/>
18
- </g>
19
- </svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="Layer_1" x="0" y="0" enable-background="new 0 0 36 36" version="1.1" viewBox="0 0 36 36" xml:space="preserve"><path d="M27,28H9c-0.6,0-1-0.4-1-1V11c0-0.6,0.4-1,1-1h3.1c0.6,0,1,0.4,1,1v1.9h9.7V11c0-0.6,0.4-1,1-1H27c0.6,0,1,0.4,1,1v16 C28,27.6,27.6,28,27,28z M10,26h16V12h-1.2v1.9c0,0.6-0.4,1-1,1H12.1c-0.6,0-1-0.4-1-1V12H10V26z"/><g><g><path d="M21,11h-6c-0.6,0-1-0.4-1-1s0.4-1,1-1h6c0.6,0,1,0.4,1,1S21.6,11,21,11z"/></g><g><path d="M18.7,11h-1.5c-0.6,0-1-0.4-1-1V8c0-0.6,0.4-1,1-1h1.5c0.6,0,1,0.4,1,1v2C19.7,10.5,19.3,11,18.7,11z"/></g></g><g><path d="M18,36.1c-9.9,0-18-8.1-18-18s8.1-18,18-18s18,8.1,18,18S27.9,36.1,18,36.1z M18,2.1c-8.8,0-16,7.2-16,16s7.2,16,16,16 s16-7.2,16-16S26.8,2.1,18,2.1z"/></g></svg>
@@ -0,0 +1,13 @@
1
+ (() => {
2
+ $("button.collapse-all").on("click", () => {
3
+ $(".collapsible").addClass("hide");
4
+ $(".question--collapse .icon-expand").removeClass("hide");
5
+ $(".question--collapse .icon-collapse").addClass("hide");
6
+ });
7
+
8
+ $("button.expand-all").on("click", () => {
9
+ $(".collapsible").removeClass("hide");
10
+ $(".question--collapse .icon-expand").addClass("hide");
11
+ $(".question--collapse .icon-collapse").removeClass("hide");
12
+ });
13
+ })(window);
@@ -1,8 +1,9 @@
1
1
  // = require ./auto_buttons_by_min_items.component
2
2
  // = require ./auto_select_options_by_total_items.component
3
+ // = require ./live_text_update.component
3
4
 
4
5
  ((exports) => {
5
- const { AutoLabelByPositionComponent, AutoButtonsByPositionComponent, AutoButtonsByMinItemsComponent, AutoSelectOptionsByTotalItemsComponent, createFieldDependentInputs, createDynamicFields, createSortList } = exports.DecidimAdmin;
6
+ const { AutoLabelByPositionComponent, AutoButtonsByPositionComponent, AutoButtonsByMinItemsComponent, AutoSelectOptionsByTotalItemsComponent, createLiveTextUpdateComponent, createFieldDependentInputs, createDynamicFields, createSortList } = exports.DecidimAdmin;
6
7
  const { createQuillEditor } = exports.Decidim;
7
8
 
8
9
  const wrapperSelector = ".questionnaire-questions";
@@ -11,8 +12,15 @@
11
12
  const answerOptionFieldSelector = ".questionnaire-question-answer-option";
12
13
  const answerOptionsWrapperSelector = ".questionnaire-question-answer-options";
13
14
  const answerOptionRemoveFieldButtonSelector = ".remove-answer-option";
15
+ const matrixRowFieldSelector = ".questionnaire-question-matrix-row";
16
+ const matrixRowsWrapperSelector = ".questionnaire-question-matrix-rows";
17
+ const matrixRowRemoveFieldButtonSelector = ".remove-matrix-row";
18
+ const addMatrixRowButtonSelector = ".add-matrix-row";
14
19
  const maxChoicesWrapperSelector = ".questionnaire-question-max-choices";
15
20
 
21
+ const MULTIPLE_CHOICE_VALUES = ["single_option", "multiple_option", "sorting", "matrix_single", "matrix_multiple"];
22
+ const MATRIX_VALUES = ["matrix_single", "matrix_multiple"];
23
+
16
24
  const autoLabelByPosition = new AutoLabelByPositionComponent({
17
25
  listSelector: ".questionnaire-question:not(.hidden)",
18
26
  labelSelector: ".card-title span:first",
@@ -49,10 +57,29 @@
49
57
  handle: ".question-divider",
50
58
  placeholder: '<div style="border-style: dashed; border-color: #000"></div>',
51
59
  forcePlaceholderSize: true,
52
- onSortUpdate: () => { autoLabelByPosition.run() }
60
+ onSortUpdate: () => {
61
+ autoLabelByPosition.run();
62
+ autoButtonsByPosition.run();
63
+ }
53
64
  });
54
65
  };
55
66
 
67
+ const createDynamicQuestionTitle = (fieldId) => {
68
+ const targetSelector = `#${fieldId} .question-title-statement`;
69
+ const locale = $(targetSelector).data("locale");
70
+ const maxLength = $(targetSelector).data("max-length");
71
+ const omission = $(targetSelector).data("omission");
72
+ const placeholder = $(targetSelector).data("placeholder");
73
+
74
+ return createLiveTextUpdateComponent({
75
+ inputSelector: `#${fieldId} input[name$=\\[body_${locale}\\]]`,
76
+ targetSelector: targetSelector,
77
+ maxLength: maxLength,
78
+ omission: omission,
79
+ placeholder: placeholder
80
+ });
81
+ }
82
+
56
83
  const createDynamicFieldsForAnswerOptions = (fieldId) => {
57
84
  const autoButtons = createAutoButtonsByMinItemsForAnswerOptions(fieldId);
58
85
  const autoSelectOptions = createAutoMaxChoicesByNumberOfAnswerOptions(fieldId);
@@ -63,6 +90,7 @@
63
90
  containerSelector: ".questionnaire-question-answer-options-list",
64
91
  fieldSelector: answerOptionFieldSelector,
65
92
  addFieldButtonSelector: ".add-answer-option",
93
+ fieldTemplateSelector: ".decidim-answer-option-template",
66
94
  removeFieldButtonSelector: answerOptionRemoveFieldButtonSelector,
67
95
  onAddField: () => {
68
96
  autoButtons.run();
@@ -77,16 +105,42 @@
77
105
 
78
106
  const dynamicFieldsForAnswerOptions = {};
79
107
 
108
+ const createDynamicFieldsForMatrixRows = (fieldId) => {
109
+ return createDynamicFields({
110
+ placeholderId: "questionnaire-question-matrix-row-id",
111
+ wrapperSelector: `#${fieldId} ${matrixRowsWrapperSelector}`,
112
+ containerSelector: ".questionnaire-question-matrix-rows-list",
113
+ fieldSelector: matrixRowFieldSelector,
114
+ addFieldButtonSelector: addMatrixRowButtonSelector,
115
+ fieldTemplateSelector: ".decidim-matrix-row-template",
116
+ removeFieldButtonSelector: matrixRowRemoveFieldButtonSelector,
117
+ onAddField: () => {
118
+ },
119
+ onRemoveField: () => {
120
+ }
121
+ });
122
+ };
123
+
124
+ const dynamicFieldsForMatrixRows = {};
125
+
80
126
  const isMultipleChoiceOption = ($selectField) => {
81
127
  const value = $selectField.val();
82
128
 
83
- return value === "single_option" || value === "multiple_option" || value === "sorting"
129
+ return MULTIPLE_CHOICE_VALUES.indexOf(value) >= 0;
130
+ }
131
+
132
+ const isMatrix = ($selectField) => {
133
+ const value = $selectField.val();
134
+
135
+ return MATRIX_VALUES.indexOf(value) >= 0;
84
136
  }
85
137
 
86
138
  const setupInitialQuestionAttributes = ($target) => {
87
139
  const fieldId = $target.attr("id");
88
140
  const $fieldQuestionTypeSelect = $target.find(questionTypeSelector);
89
141
 
142
+ createDynamicQuestionTitle(fieldId);
143
+
90
144
  createFieldDependentInputs({
91
145
  controllerField: $fieldQuestionTypeSelect,
92
146
  wrapperSelector: fieldSelector,
@@ -103,21 +157,42 @@
103
157
  dependentFieldsSelector: maxChoicesWrapperSelector,
104
158
  dependentInputSelector: "select",
105
159
  enablingCondition: ($field) => {
106
- return $field.val() === "multiple_option"
160
+ return $field.val() === "multiple_option" || $field.val() === "matrix_multiple";
161
+ }
162
+ });
163
+
164
+ createFieldDependentInputs({
165
+ controllerField: $fieldQuestionTypeSelect,
166
+ wrapperSelector: fieldSelector,
167
+ dependentFieldsSelector: matrixRowsWrapperSelector,
168
+ dependentInputSelector: `${matrixRowFieldSelector} input`,
169
+ enablingCondition: ($field) => {
170
+ return isMatrix($field);
107
171
  }
108
172
  });
109
173
 
110
174
  dynamicFieldsForAnswerOptions[fieldId] = createDynamicFieldsForAnswerOptions(fieldId);
175
+ dynamicFieldsForMatrixRows[fieldId] = createDynamicFieldsForMatrixRows(fieldId);
111
176
 
112
- const dynamicFields = dynamicFieldsForAnswerOptions[fieldId];
177
+ const dynamicFieldsAnswerOptions = dynamicFieldsForAnswerOptions[fieldId];
178
+ const dynamicFieldsMatrixRows = dynamicFieldsForMatrixRows[fieldId];
113
179
 
114
180
  const onQuestionTypeChange = () => {
115
181
  if (isMultipleChoiceOption($fieldQuestionTypeSelect)) {
116
182
  const nOptions = $fieldQuestionTypeSelect.parents(fieldSelector).find(answerOptionFieldSelector).length;
117
183
 
118
184
  if (nOptions === 0) {
119
- dynamicFields._addField();
120
- dynamicFields._addField();
185
+ dynamicFieldsAnswerOptions._addField();
186
+ dynamicFieldsAnswerOptions._addField();
187
+ }
188
+ }
189
+
190
+ if (isMatrix($fieldQuestionTypeSelect)) {
191
+ const nRows = $fieldQuestionTypeSelect.parents(fieldSelector).find(matrixRowFieldSelector).length;
192
+
193
+ if (nRows === 0) {
194
+ dynamicFieldsMatrixRows._addField();
195
+ dynamicFieldsMatrixRows._addField();
121
196
  }
122
197
  }
123
198
  };
@@ -142,10 +217,20 @@
142
217
  containerSelector: ".questionnaire-questions-list",
143
218
  fieldSelector: fieldSelector,
144
219
  addFieldButtonSelector: ".add-question",
220
+ addSeparatorButtonSelector: ".add-separator",
221
+ fieldTemplateSelector: ".decidim-question-template",
222
+ separatorTemplateSelector: ".decidim-separator-template",
145
223
  removeFieldButtonSelector: ".remove-question",
146
224
  moveUpFieldButtonSelector: ".move-up-question",
147
225
  moveDownFieldButtonSelector: ".move-down-question",
148
226
  onAddField: ($field) => {
227
+ const $collapsible = $field.find(".collapsible");
228
+ if ($collapsible.length > 0) {
229
+ const collapsibleId = $collapsible.attr("id").replace("-question-card", "");
230
+ const toggleAttr = `${collapsibleId}-question-card button--collapse-question-${collapsibleId} button--expand-question-${collapsibleId}`;
231
+ $field.find(".question--collapse").data("toggle", toggleAttr);
232
+ }
233
+
149
234
  setupInitialQuestionAttributes($field);
150
235
  createSortableList();
151
236
 
@@ -163,6 +248,9 @@
163
248
  $field.find(answerOptionRemoveFieldButtonSelector).each((idx, el) => {
164
249
  dynamicFieldsForAnswerOptions[$field.attr("id")]._removeField(el);
165
250
  });
251
+ $field.find(matrixRowRemoveFieldButtonSelector).each((idx, el) => {
252
+ dynamicFieldsForMatrixRows[$field.attr("id")]._removeField(el);
253
+ });
166
254
  },
167
255
  onMoveUpField: () => {
168
256
  autoLabelByPosition.run();
@@ -0,0 +1,52 @@
1
+ /**
2
+ * This component allows for an element's text value to be updated with the value
3
+ * of an input whenever this input's value is changed.
4
+ *
5
+ * @param {object} options
6
+ *
7
+ * Available options:
8
+ * {string} `inputSelector`: The query selector to locate the input element
9
+ * {string} `targetSelector`: The query selector to locate the target element
10
+ * {number} `maxLength`: The maximum characters from the input value to be displayed in the target
11
+ * {string} `omission`: The string used to shorten the value to the given maxLength (e.g. "...")
12
+ * {string} `placeholder`: The string to be displayed in the target element when the input has no value
13
+ */
14
+ ((exports) => {
15
+ class LiveTextUpdateComponent {
16
+ constructor(options = {}) {
17
+ this.inputSelector = options.inputSelector;
18
+ this.targetSelector = options.targetSelector;
19
+ this.maxLength = options.maxLength;
20
+ this.omission = options.omission;
21
+ this.placeholder = options.placeholder;
22
+ this._bindEvent();
23
+ this._run();
24
+ }
25
+
26
+ _run() {
27
+ const $input = $(this.inputSelector);
28
+ const $target = $(this.targetSelector);
29
+
30
+ let text = $input.val() || this.placeholder;
31
+
32
+ // truncate string
33
+ if (text.length > this.maxLength) {
34
+ text = text.substring(0, this.maxLength - this.omission.length) + this.omission;
35
+ }
36
+
37
+ $target.text(text);
38
+ }
39
+
40
+ _bindEvent() {
41
+ const $input = $(this.inputSelector);
42
+ $input.on("change", this._run.bind(this));
43
+ }
44
+
45
+ }
46
+
47
+ exports.DecidimAdmin = exports.DecidimAdmin || {};
48
+ exports.DecidimAdmin.LiveTextUpdateComponent = LiveTextUpdateComponent;
49
+ exports.DecidimAdmin.createLiveTextUpdateComponent = (options) => {
50
+ return new LiveTextUpdateComponent(options);
51
+ }
52
+ })(window);
@@ -1,8 +1,9 @@
1
1
  // = require ./option_attached_inputs.component
2
2
  // = require ./autosortable_checkboxes.component
3
+ // = require ./max_choices_alert.component
3
4
 
4
5
  ((exports) => {
5
- const { createOptionAttachedInputs, createAutosortableCheckboxes } = exports.Decidim;
6
+ const { createOptionAttachedInputs, createAutosortableCheckboxes, createMaxChoicesAlertComponent } = exports.Decidim;
6
7
 
7
8
  $(".radio-button-collection, .check-box-collection").each((idx, el) => {
8
9
  createOptionAttachedInputs({
@@ -12,9 +13,49 @@
12
13
  });
13
14
  });
14
15
 
16
+ $.unique($(".check-box-collection").parents(".answer")).each((idx, el) => {
17
+ const maxChoices = $(el).data("max-choices");
18
+ if (maxChoices) {
19
+ createMaxChoicesAlertComponent({
20
+ wrapperField: $(el),
21
+ controllerFieldSelector: "input[type=checkbox]",
22
+ controllerCollectionSelector: ".check-box-collection",
23
+ alertElement: $(el).find(".max-choices-alert"),
24
+ maxChoices: maxChoices
25
+ });
26
+ }
27
+ });
28
+
15
29
  $(".sortable-check-box-collection").each((idx, el) => {
16
30
  createAutosortableCheckboxes({
17
31
  wrapperField: $(el)
18
32
  })
19
33
  });
34
+
35
+ const $form = $("form.answer-questionnaire");
36
+ if ($form.length > 0) {
37
+ $form.find("input, textarea, select").on("change", () => {
38
+ $form.data("changed", true);
39
+ });
40
+
41
+ const safePath = $form.data("safe-path").split("?")[0];
42
+ $(document).on("click", "a", (event) => {
43
+ window.exitUrl = event.currentTarget.href;
44
+ });
45
+ $(document).on("submit", "form", (event) => {
46
+ window.exitUrl = event.currentTarget.action;
47
+ });
48
+
49
+ window.onbeforeunload = () => {
50
+ const exitUrl = window.exitUrl;
51
+ const hasChanged = $form.data("changed");
52
+ window.exitUrl = null;
53
+
54
+ if (!hasChanged || (exitUrl && exitUrl.includes(safePath))) {
55
+ return null;
56
+ }
57
+
58
+ return "";
59
+ }
60
+ }
20
61
  })(window);
@@ -0,0 +1,44 @@
1
+ ((exports) => {
2
+ class MaxChoicesAlertComponent {
3
+ constructor(options = {}) {
4
+ this.wrapperField = options.wrapperField;
5
+ this.alertElement = options.alertElement;
6
+ this.controllerFieldSelector = options.controllerFieldSelector;
7
+ this.controllerCollectionSelector = options.controllerCollectionSelector;
8
+ this.maxChoices = options.maxChoices;
9
+ this.controllerSelector = this.wrapperField.find(this.controllerFieldSelector);
10
+ this._bindEvent();
11
+ this._run();
12
+ }
13
+
14
+ _run() {
15
+ const rows = this.wrapperField.find(this.controllerCollectionSelector);
16
+
17
+ let alert = false;
18
+
19
+ rows.each((rowIdx, row) => {
20
+ const checked = $(row).find(this.controllerFieldSelector).filter((checkboxIdx, checkbox) => $(checkbox).is(":checked"));
21
+
22
+ alert = alert || checked.length > this.maxChoices;
23
+ });
24
+
25
+ if (alert) {
26
+ this.alertElement.show();
27
+ }
28
+ else {
29
+ this.alertElement.hide();
30
+ }
31
+ }
32
+
33
+ _bindEvent() {
34
+ this.controllerSelector.on("change", () => {
35
+ this._run();
36
+ });
37
+ }
38
+ }
39
+
40
+ exports.Decidim = exports.Decidim || {};
41
+ exports.Decidim.createMaxChoicesAlertComponent = (options) => {
42
+ return new MaxChoicesAlertComponent(options);
43
+ };
44
+ })(window);
@@ -35,3 +35,42 @@
35
35
  .questionnaire-question_readonly-answer{
36
36
  font-weight: normal;
37
37
  }
38
+
39
+ .questionnaire-question-matrix{
40
+ .collection-input{
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: center;
44
+ flex-grow: 1;
45
+ flex-basis: 0;
46
+
47
+ input[type="text"]{
48
+ margin-top: auto;
49
+ margin-bottom: auto;
50
+ }
51
+
52
+ input[type="checkbox"],
53
+ input[type="radio"]{
54
+ margin-bottom: 0;
55
+
56
+ & ~ input[type="text"]{
57
+ margin-left: .5rem;
58
+ }
59
+ }
60
+ }
61
+
62
+ tr,
63
+ tr:nth-child(2n){
64
+ background: initial;
65
+ border-bottom: 1px solid #f0f0f0;
66
+ }
67
+
68
+ thead td{
69
+ text-align: center;
70
+ }
71
+
72
+ td{
73
+ min-width: 100px;
74
+ border-right: 1px solid #f0f0f0;
75
+ }
76
+ }
@@ -0,0 +1,5 @@
1
+ <li class="questionnaire-question_readonly-answer">
2
+ <%= translated_attribute(model.body) %>
3
+
4
+ (<%= answer_options %>)
5
+ </li>
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Forms
5
+ # This cell renders a possible matrix answer of a question (readonly)
6
+ class MatrixReadonlyCell < Decidim::ViewModel
7
+ def answer_options
8
+ model.question.answer_options.map { |option| translated_attribute(option.body) }.join(" / ")
9
+ end
10
+ end
11
+ end
12
+ end