decidim-forms 0.21.0 → 0.22.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.
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