decidim-forms 0.20.1 → 0.23.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/admin/decidim_forms_manifest.css +3 -0
  3. data/app/assets/config/admin/decidim_forms_manifest.js +1 -0
  4. data/app/assets/config/decidim_forms_manifest.css +1 -0
  5. data/app/assets/images/decidim/surveys/icon.svg +1 -19
  6. data/app/assets/javascripts/decidim/forms/admin/auto_select_options_from_url.component.js.es6 +40 -0
  7. data/app/assets/javascripts/decidim/forms/admin/collapsible_questions.js.es6 +13 -0
  8. data/app/assets/javascripts/decidim/forms/admin/forms.js.es6 +260 -16
  9. data/app/assets/javascripts/decidim/forms/admin/live_text_update.component.js.es6 +52 -0
  10. data/app/assets/javascripts/decidim/forms/autosortable_checkboxes.component.js.es6 +54 -34
  11. data/app/assets/javascripts/decidim/forms/display_conditions.component.js.es6 +203 -0
  12. data/app/assets/javascripts/decidim/forms/forms.js.es6 +49 -1
  13. data/app/assets/javascripts/decidim/forms/max_choices_alert.component.js.es6 +44 -0
  14. data/app/assets/stylesheets/decidim/forms/forms.scss +39 -0
  15. data/app/assets/stylesheets/decidim/forms/questionnaire-answers-pdf.scss +69 -0
  16. data/app/cells/decidim/forms/matrix_readonly/show.erb +5 -0
  17. data/app/cells/decidim/forms/matrix_readonly_cell.rb +12 -0
  18. data/app/cells/decidim/forms/question_readonly/show.erb +5 -1
  19. data/app/cells/decidim/forms/question_readonly_cell.rb +5 -0
  20. data/app/cells/decidim/forms/step_navigation/show.erb +35 -0
  21. data/app/cells/decidim/forms/step_navigation_cell.rb +46 -0
  22. data/app/commands/decidim/forms/admin/update_questionnaire.rb +33 -1
  23. data/app/commands/decidim/forms/answer_questionnaire.rb +2 -1
  24. data/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb +52 -2
  25. data/app/controllers/decidim/forms/admin/concerns/has_questionnaire_answers.rb +97 -0
  26. data/app/controllers/decidim/forms/concerns/has_questionnaire.rb +11 -2
  27. data/app/forms/decidim/forms/admin/display_condition_form.rb +100 -0
  28. data/app/forms/decidim/forms/admin/question_form.rb +21 -1
  29. data/app/forms/decidim/forms/admin/question_matrix_row_form.rb +26 -0
  30. data/app/forms/decidim/forms/answer_choice_form.rb +1 -0
  31. data/app/forms/decidim/forms/answer_form.rb +31 -2
  32. data/app/forms/decidim/forms/questionnaire_form.rb +30 -3
  33. data/app/helpers/decidim/forms/admin/application_helper.rb +37 -0
  34. data/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_pagination_helper.rb +49 -0
  35. data/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_url_helper.rb +40 -0
  36. data/app/helpers/decidim/forms/admin/questionnaire_answers_helper.rb +27 -0
  37. data/app/jobs/decidim/forms/export_questionnaire_answers_job.rb +19 -0
  38. data/app/models/decidim/forms/answer.rb +0 -3
  39. data/app/models/decidim/forms/answer_choice.rb +7 -0
  40. data/app/models/decidim/forms/answer_option.rb +14 -0
  41. data/app/models/decidim/forms/display_condition.rb +65 -0
  42. data/app/models/decidim/forms/question.rb +52 -2
  43. data/app/models/decidim/forms/question_matrix_row.rb +15 -0
  44. data/app/models/decidim/forms/questionnaire.rb +11 -1
  45. data/app/presenters/decidim/forms/admin/questionnaire_answer_presenter.rb +43 -0
  46. data/app/presenters/decidim/forms/admin/questionnaire_participant_presenter.rb +60 -0
  47. data/app/presenters/decidim/forms/answer_option_presenter.rb +20 -0
  48. data/app/presenters/decidim/forms/question_presenter.rb +16 -0
  49. data/app/queries/decidim/forms/questionnaire_participant.rb +35 -0
  50. data/app/queries/decidim/forms/questionnaire_participants.rb +43 -0
  51. data/app/types/decidim/forms/answer_option_type.rb +14 -0
  52. data/app/types/decidim/forms/question_type.rb +23 -0
  53. data/app/types/decidim/forms/questionnaire_type.rb +22 -0
  54. data/app/views/decidim/forms/admin/questionnaires/_answer_option_template.html.erb +1 -1
  55. data/app/views/decidim/forms/admin/questionnaires/_display_condition.html.erb +88 -0
  56. data/app/views/decidim/forms/admin/questionnaires/_display_condition_template.html.erb +7 -0
  57. data/app/views/decidim/forms/admin/questionnaires/_form.html.erb +67 -8
  58. data/app/views/decidim/forms/admin/questionnaires/_matrix_row.html.erb +34 -0
  59. data/app/views/decidim/forms/admin/questionnaires/_matrix_row_template.html.erb +7 -0
  60. data/app/views/decidim/forms/admin/questionnaires/_question.html.erb +45 -6
  61. data/app/views/decidim/forms/admin/questionnaires/_separator.html.erb +41 -0
  62. data/app/views/decidim/forms/admin/questionnaires/answers/export/_answer.html.erb +31 -0
  63. data/app/views/decidim/forms/admin/questionnaires/answers/export/pdf.html.erb +13 -0
  64. data/app/views/decidim/forms/admin/questionnaires/answers/index.html.erb +53 -0
  65. data/app/views/decidim/forms/admin/questionnaires/answers/show.html.erb +48 -0
  66. data/app/views/decidim/forms/admin/questionnaires/edit.html.erb +9 -5
  67. data/app/views/decidim/forms/questionnaires/_answer.html.erb +28 -99
  68. data/app/views/decidim/forms/questionnaires/answers/_long_answer.html.erb +1 -0
  69. data/app/views/decidim/forms/questionnaires/answers/_matrix_multiple.html.erb +43 -0
  70. data/app/views/decidim/forms/questionnaires/answers/_matrix_single.html.erb +43 -0
  71. data/app/views/decidim/forms/questionnaires/answers/_multiple_option.html.erb +23 -0
  72. data/app/views/decidim/forms/questionnaires/answers/_separator.html.erb +1 -0
  73. data/app/views/decidim/forms/questionnaires/answers/_short_answer.html.erb +1 -0
  74. data/app/views/decidim/forms/questionnaires/answers/_single_option.html.erb +30 -0
  75. data/app/views/decidim/forms/questionnaires/answers/_sorting.html.erb +23 -0
  76. data/app/views/decidim/forms/questionnaires/show.html.erb +78 -26
  77. data/app/views/layouts/decidim/forms/admin/questionnaires/questionnaire_answers.html.erb +12 -0
  78. data/config/initializers/wicked_pdf.rb +25 -0
  79. data/config/locales/am-ET.yml +1 -0
  80. data/config/locales/ar.yml +7 -4
  81. data/config/locales/bg-BG.yml +14 -0
  82. data/config/locales/bg.yml +14 -0
  83. data/config/locales/ca.yml +89 -5
  84. data/config/locales/cs.yml +89 -5
  85. data/config/locales/da-DK.yml +1 -0
  86. data/config/locales/da.yml +1 -0
  87. data/config/locales/de.yml +92 -4
  88. data/config/locales/el.yml +118 -0
  89. data/config/locales/en.yml +89 -5
  90. data/config/locales/eo.yml +1 -0
  91. data/config/locales/es-MX.yml +88 -4
  92. data/config/locales/es-PY.yml +88 -4
  93. data/config/locales/es.yml +88 -4
  94. data/config/locales/et-EE.yml +1 -0
  95. data/config/locales/et.yml +1 -0
  96. data/config/locales/eu.yml +7 -4
  97. data/config/locales/fi-plain.yml +88 -4
  98. data/config/locales/fi.yml +88 -4
  99. data/config/locales/fr-CA.yml +172 -0
  100. data/config/locales/fr.yml +89 -5
  101. data/config/locales/ga-IE.yml +1 -0
  102. data/config/locales/gl.yml +7 -4
  103. data/config/locales/hr-HR.yml +1 -0
  104. data/config/locales/hr.yml +1 -0
  105. data/config/locales/hu.yml +11 -5
  106. data/config/locales/id-ID.yml +7 -4
  107. data/config/locales/is.yml +1 -0
  108. data/config/locales/it.yml +89 -5
  109. data/config/locales/ja-JP.yml +170 -0
  110. data/config/locales/ja.yml +170 -0
  111. data/config/locales/ko-KR.yml +1 -0
  112. data/config/locales/ko.yml +1 -0
  113. data/config/locales/lt-LT.yml +1 -0
  114. data/config/locales/lt.yml +1 -0
  115. data/config/locales/lv.yml +118 -0
  116. data/config/locales/mt-MT.yml +1 -0
  117. data/config/locales/mt.yml +1 -0
  118. data/config/locales/nl.yml +92 -8
  119. data/config/locales/no.yml +54 -5
  120. data/config/locales/om-ET.yml +1 -0
  121. data/config/locales/pl.yml +113 -26
  122. data/config/locales/pt-BR.yml +8 -5
  123. data/config/locales/pt.yml +111 -24
  124. data/config/locales/ro-RO.yml +167 -0
  125. data/config/locales/ru.yml +4 -2
  126. data/config/locales/sk-SK.yml +88 -0
  127. data/config/locales/sk.yml +90 -0
  128. data/config/locales/sl.yml +12 -0
  129. data/config/locales/so-SO.yml +1 -0
  130. data/config/locales/sr-CS.yml +1 -0
  131. data/config/locales/sv.yml +88 -7
  132. data/config/locales/ti-ER.yml +1 -0
  133. data/config/locales/tr-TR.yml +7 -4
  134. data/config/locales/vi-VN.yml +1 -0
  135. data/config/locales/vi.yml +1 -0
  136. data/config/locales/zh-CN.yml +172 -0
  137. data/config/locales/zh-TW.yml +1 -0
  138. data/db/migrate/20200130194123_create_decidim_forms_display_conditions.rb +20 -0
  139. data/db/migrate/20200225123810_create_decidim_forms_question_matrix_rows.rb +11 -0
  140. data/db/migrate/20200304152939_add_matrix_row_id_to_decidim_forms_answer_choices.rb +11 -0
  141. data/lib/decidim/api/questionnaire_entity_interface.rb +18 -0
  142. data/lib/decidim/exporters/form_pdf.rb +33 -0
  143. data/lib/decidim/exporters/form_pdf_controller_helper.rb +11 -0
  144. data/lib/decidim/forms.rb +6 -0
  145. data/lib/decidim/forms/admin_engine.rb +1 -1
  146. data/lib/decidim/forms/api.rb +7 -0
  147. data/lib/decidim/forms/test.rb +6 -0
  148. data/lib/decidim/forms/test/factories.rb +55 -0
  149. data/lib/decidim/forms/test/shared_examples/has_questionnaire.rb +918 -60
  150. data/lib/decidim/forms/test/shared_examples/manage_questionnaire_answers.rb +108 -0
  151. data/lib/decidim/forms/test/shared_examples/manage_questionnaires.rb +33 -575
  152. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_display_conditions.rb +179 -0
  153. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_questions.rb +463 -0
  154. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_display_conditions.rb +93 -0
  155. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_questions.rb +461 -0
  156. data/lib/decidim/forms/user_answers_serializer.rb +21 -4
  157. data/lib/decidim/forms/version.rb +1 -1
  158. metadata +133 -10
@@ -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,59 +1,79 @@
1
+ /* eslint-disable no-ternary */
2
+
1
3
  ((exports) => {
2
4
  class AutosortableCheckboxesComponent {
3
5
  constructor(options = {}) {
4
6
  this.wrapperField = options.wrapperField;
5
7
  this._bindEvent();
6
- this._run();
8
+ this._order();
9
+ this._normalize();
7
10
  }
8
11
 
9
- _run() {
10
- $(this.wrapperField).find("input[type=checkbox]").each((idx, el) => {
11
- const $parentLabel = $(el).parents("label");
12
-
13
- if ($(el).is(":checked")) {
14
- const $lastSorted = this.wrapperField.find("label.sorted").last();
15
-
16
- if ($lastSorted.length > 0) {
17
- $lastSorted.removeClass("last-sorted");
18
- $parentLabel.insertAfter($lastSorted);
19
- } else {
20
- $parentLabel.insertBefore(this.wrapperField.find("label:first-child"));
21
- }
12
+ // Order by position
13
+ _order() {
14
+ const max = $(this.wrapperField).find(".collection-input").length;
15
+ $(this.wrapperField).find(".collection-input").each((idx, el) => {
16
+ const $positionField = $(el).find("input[name$=\\[position\\]]");
17
+ const position = $positionField.val()
18
+ ? parseInt($positionField.val(), 10)
19
+ : max;
22
20
 
23
- $parentLabel.addClass("sorted");
24
- $parentLabel.addClass("last-sorted");
25
- } else {
26
- const $lastUnsorted = this.wrapperField.find("label:not(.sorted)").last();
21
+ let $next = $(el).next();
22
+ while ($next.length > 0) {
23
+ const $nextPositionField = $next.find("input[name$=\\[position\\]]");
24
+ const nextPosition = $nextPositionField.val()
25
+ ? parseInt($nextPositionField.val(), 10)
26
+ : max;
27
27
 
28
- if ($lastUnsorted.length > 0) {
29
- $parentLabel.insertBefore($lastUnsorted);
30
- } else {
31
- $parentLabel.insertAfter(this.wrapperField.find("label:last-child"));
28
+ if (position > nextPosition) {
29
+ $next.insertBefore($(el));
32
30
  }
33
-
34
- $parentLabel.removeClass("sorted");
31
+ $next = $next.next();
35
32
  }
36
33
  });
34
+ }
37
35
 
38
- $(this.wrapperField).find("label").each((idx, el) => {
39
- const $positionSelector = $(el).find(".position");
36
+ _findLastPosition() {
37
+ let lastPosition = 0;
38
+ $(this.wrapperField).find(".collection-input").each((idx, el) => {
40
39
  const $positionField = $(el).find("input[name$=\\[position\\]]");
40
+ const position = parseInt($positionField.val(), 10);
41
+ if (position > lastPosition) {
42
+ lastPosition = position;
43
+ }
44
+ });
45
+ return lastPosition;
46
+ }
41
47
 
42
- if ($(el).hasClass("sorted")) {
48
+ _normalize() {
49
+ $(this.wrapperField).find(".collection-input .position").each((idx, el) => {
50
+ const $positionField = $(el).parent().find("input[name$=\\[position\\]]");
51
+ if ($positionField.val()) {
43
52
  $positionField.val(idx);
44
53
  $positionField.prop("disabled", false);
45
- $positionSelector.html(`${idx + 1}. `);
46
- } else {
47
- $positionField.val("");
48
- $positionField.prop("disabled", true);
49
- $positionSelector.html("");
54
+ $(el).html(`${idx + 1}. `);
50
55
  }
51
56
  });
52
57
  }
53
58
 
54
59
  _bindEvent() {
55
- $(this.wrapperField).find("input[type=checkbox]").on("change", () => {
56
- this._run();
60
+ $(this.wrapperField).find("input[type=checkbox]").on("change", (el) => {
61
+ const $parentLabel = $(el.target).parents("label");
62
+ const $positionSelector = $parentLabel.find(".position");
63
+ const $positionField = $parentLabel.find("input[name$=\\[position\\]]");
64
+ const lastPosition = this._findLastPosition();
65
+
66
+ if (el.target.checked) {
67
+ $positionField.val(lastPosition + 1);
68
+ $positionField.prop("disabled", false);
69
+ $positionSelector.html(lastPosition + 1);
70
+ } else {
71
+ $positionField.val("");
72
+ $positionField.prop("disabled", true);
73
+ $positionSelector.html("");
74
+ }
75
+ this._order();
76
+ this._normalize();
57
77
  });
58
78
  }
59
79
  }
@@ -0,0 +1,203 @@
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 $input = $(el).find("input[name$=\\[body\\]]");
33
+ const checked = $input.is(":checked");
34
+
35
+ if (checked) {
36
+ const text = $(el).find("input[name$=\\[custom_body\\]]").val();
37
+ const value = $input.val();
38
+ const id = $(el).find("input[name$=\\[answer_option_id\\]]").val();
39
+
40
+ multipleInput.push({ id, value, text });
41
+ }
42
+ });
43
+
44
+ return multipleInput;
45
+ }
46
+
47
+ getInputsToListen() {
48
+ const $conditionWrapperField = $(`.question[data-question-id='${this.conditionQuestion}']`);
49
+ const $textInput = $conditionWrapperField.find("textarea, input[type='text']:not([name$=\\[custom_body\\]])");
50
+
51
+ if ($textInput.length) {
52
+ return $textInput;
53
+ }
54
+
55
+ return $conditionWrapperField.find(".collection-input").find("input:not([type='hidden'])");
56
+ }
57
+
58
+ checkAnsweredCondition(value) {
59
+ if (typeof (value) !== "object") {
60
+ return Boolean(value);
61
+ }
62
+
63
+ return Boolean(value.some((it) => it.value));
64
+ }
65
+
66
+ checkNotAnsweredCondition(value) {
67
+ return !this.checkAnsweredCondition(value);
68
+ }
69
+
70
+ checkEqualCondition(value) {
71
+ if (value.length) {
72
+ return value.some((it) => it.id === this.answerOption.toString());
73
+ }
74
+ return false;
75
+ }
76
+
77
+ checkNotEqualCondition(value) {
78
+ if (value.length) {
79
+ return value.every((it) => it.id !== this.answerOption.toString());
80
+ }
81
+ return false;
82
+ }
83
+
84
+ checkMatchCondition(value) {
85
+ let regexp = new RegExp(this.value, "i");
86
+
87
+ if (typeof (value) !== "object") {
88
+ return Boolean(value.match(regexp));
89
+ }
90
+
91
+ return value.some(function (it) {
92
+ return it.text
93
+ ? it.text.match(regexp)
94
+ : it.value.match(regexp)
95
+ });
96
+ }
97
+
98
+ checkCondition() {
99
+ const value = this.getInputValue();
100
+ let fulfilled = false;
101
+
102
+ switch (this.type) {
103
+ case "answered":
104
+ fulfilled = this.checkAnsweredCondition(value);
105
+ break;
106
+ case "not_answered":
107
+ fulfilled = this.checkNotAnsweredCondition(value);
108
+ break;
109
+ case "equal":
110
+ fulfilled = this.checkEqualCondition(value);
111
+ break;
112
+ case "not_equal":
113
+ fulfilled = this.checkNotEqualCondition(value);
114
+ break;
115
+ case "match":
116
+ fulfilled = this.checkMatchCondition(value);
117
+ break;
118
+ default:
119
+ fulfilled = false;
120
+ break;
121
+ }
122
+
123
+ this.onFulfilled(fulfilled);
124
+ }
125
+ }
126
+
127
+ class DisplayConditionsComponent {
128
+ constructor(options = {}) {
129
+ this.wrapperField = options.wrapperField;
130
+ this.conditions = {};
131
+ this.showCount = 0;
132
+ this.initializeConditions();
133
+ }
134
+
135
+ initializeConditions() {
136
+ const $conditionElements = this.wrapperField.find(".display-condition");
137
+
138
+ $conditionElements.each((idx, el) => {
139
+ const $condition = $(el);
140
+ const id = $condition.data("id");
141
+ this.conditions[id] = {};
142
+
143
+ this.conditions[id] = new DisplayCondition({
144
+ wrapperField: this.wrapperField,
145
+ type: $condition.data("type"),
146
+ conditionQuestion: $condition.data("condition"),
147
+ answerOption: $condition.data("option"),
148
+ mandatory: $condition.data("mandatory"),
149
+ value: $condition.data("value"),
150
+ onFulfilled: (fulfilled) => {
151
+ this.onFulfilled(id, fulfilled);
152
+ }
153
+ });
154
+ });
155
+ }
156
+
157
+ mustShow() {
158
+ const conditions = Object.values(this.conditions);
159
+ const mandatoryConditions = conditions.filter((condition) => condition.mandatory);
160
+ const nonMandatoryConditions = conditions.filter((condition) => !condition.mandatory);
161
+
162
+ if (mandatoryConditions.length) {
163
+ return mandatoryConditions.every((condition) => condition.fulfilled);
164
+ }
165
+
166
+ return nonMandatoryConditions.some((condition) => condition.fulfilled);
167
+
168
+ }
169
+
170
+ onFulfilled(id, fulfilled) {
171
+ this.conditions[id].fulfilled = fulfilled;
172
+
173
+ if (this.mustShow()) {
174
+ this.showQuestion();
175
+ }
176
+ else {
177
+ this.hideQuestion();
178
+ }
179
+ }
180
+
181
+ showQuestion() {
182
+ this.wrapperField.fadeIn();
183
+ this.wrapperField.find("input, textarea").prop("disabled", null);
184
+ this.showCount++;
185
+ }
186
+
187
+ hideQuestion() {
188
+ if (this.showCount) {
189
+ this.wrapperField.fadeOut();
190
+ }
191
+ else {
192
+ this.wrapperField.hide();
193
+ }
194
+
195
+ this.wrapperField.find("input, textarea").prop("disabled", "disabled");
196
+ }
197
+ }
198
+
199
+ exports.Decidim = exports.Decidim || {};
200
+ exports.Decidim.createDisplayConditions = (options) => {
201
+ return new DisplayConditionsComponent(options);
202
+ };
203
+ })(window);
@@ -1,8 +1,10 @@
1
1
  // = require ./option_attached_inputs.component
2
2
  // = require ./autosortable_checkboxes.component
3
+ // = require ./display_conditions.component
4
+ // = require ./max_choices_alert.component
3
5
 
4
6
  ((exports) => {
5
- const { createOptionAttachedInputs, createAutosortableCheckboxes } = exports.Decidim;
7
+ const { createOptionAttachedInputs, createAutosortableCheckboxes, createMaxChoicesAlertComponent, createDisplayConditions } = exports.Decidim;
6
8
 
7
9
  $(".radio-button-collection, .check-box-collection").each((idx, el) => {
8
10
  createOptionAttachedInputs({
@@ -12,9 +14,55 @@
12
14
  });
13
15
  });
14
16
 
17
+ $.unique($(".check-box-collection").parents(".answer")).each((idx, el) => {
18
+ const maxChoices = $(el).data("max-choices");
19
+ if (maxChoices) {
20
+ createMaxChoicesAlertComponent({
21
+ wrapperField: $(el),
22
+ controllerFieldSelector: "input[type=checkbox]",
23
+ controllerCollectionSelector: ".check-box-collection",
24
+ alertElement: $(el).find(".max-choices-alert"),
25
+ maxChoices: maxChoices
26
+ });
27
+ }
28
+ });
29
+
15
30
  $(".sortable-check-box-collection").each((idx, el) => {
16
31
  createAutosortableCheckboxes({
17
32
  wrapperField: $(el)
18
33
  })
19
34
  });
35
+
36
+ $(".answer-questionnaire .question[data-conditioned='true']").each((idx, el) => {
37
+ createDisplayConditions({
38
+ wrapperField: $(el)
39
+ });
40
+ });
41
+
42
+ const $form = $("form.answer-questionnaire");
43
+ if ($form.length > 0) {
44
+ $form.find("input, textarea, select").on("change", () => {
45
+ $form.data("changed", true);
46
+ });
47
+
48
+ const safePath = $form.data("safe-path").split("?")[0];
49
+ $(document).on("click", "a", (event) => {
50
+ window.exitUrl = event.currentTarget.href;
51
+ });
52
+ $(document).on("submit", "form", (event) => {
53
+ window.exitUrl = event.currentTarget.action;
54
+ });
55
+
56
+ window.onbeforeunload = () => {
57
+ const exitUrl = window.exitUrl;
58
+ const hasChanged = $form.data("changed");
59
+ window.exitUrl = null;
60
+
61
+ if (!hasChanged || (exitUrl && exitUrl.includes(safePath))) {
62
+ return null;
63
+ }
64
+
65
+ return "";
66
+ }
67
+ }
20
68
  })(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);