decidim-forms 0.19.1 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/assets/config/admin/decidim_forms_manifest.css +3 -0
  4. data/app/assets/config/admin/decidim_forms_manifest.js +1 -0
  5. data/app/assets/config/decidim_forms_manifest.css +1 -0
  6. data/app/assets/images/decidim/surveys/icon.svg +1 -19
  7. data/app/assets/javascripts/decidim/forms/admin/auto_select_options_from_url.component.js.es6 +40 -0
  8. data/app/assets/javascripts/decidim/forms/admin/collapsible_questions.js.es6 +13 -0
  9. data/app/assets/javascripts/decidim/forms/admin/forms.js.es6 +260 -16
  10. data/app/assets/javascripts/decidim/forms/admin/live_text_update.component.js.es6 +52 -0
  11. data/app/assets/javascripts/decidim/forms/autosortable_checkboxes.component.js.es6 +54 -34
  12. data/app/assets/javascripts/decidim/forms/display_conditions.component.js.es6 +204 -0
  13. data/app/assets/javascripts/decidim/forms/forms.js.es6 +49 -1
  14. data/app/assets/javascripts/decidim/forms/max_choices_alert.component.js.es6 +44 -0
  15. data/app/assets/stylesheets/decidim/forms/forms.scss +39 -0
  16. data/app/assets/stylesheets/decidim/forms/questionnaire-answers-pdf.scss +69 -0
  17. data/app/cells/decidim/forms/matrix_readonly/show.erb +5 -0
  18. data/app/cells/decidim/forms/matrix_readonly_cell.rb +12 -0
  19. data/app/cells/decidim/forms/question_readonly/show.erb +5 -1
  20. data/app/cells/decidim/forms/question_readonly_cell.rb +5 -0
  21. data/app/cells/decidim/forms/step_navigation/show.erb +35 -0
  22. data/app/cells/decidim/forms/step_navigation_cell.rb +46 -0
  23. data/app/commands/decidim/forms/admin/update_questionnaire.rb +33 -1
  24. data/app/commands/decidim/forms/answer_questionnaire.rb +7 -2
  25. data/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb +52 -2
  26. data/app/controllers/decidim/forms/admin/concerns/has_questionnaire_answers.rb +97 -0
  27. data/app/controllers/decidim/forms/concerns/has_questionnaire.rb +58 -2
  28. data/app/forms/decidim/forms/admin/display_condition_form.rb +100 -0
  29. data/app/forms/decidim/forms/admin/question_form.rb +21 -1
  30. data/app/forms/decidim/forms/admin/question_matrix_row_form.rb +26 -0
  31. data/app/forms/decidim/forms/answer_choice_form.rb +1 -0
  32. data/app/forms/decidim/forms/answer_form.rb +31 -2
  33. data/app/forms/decidim/forms/questionnaire_form.rb +33 -3
  34. data/app/helpers/decidim/forms/admin/application_helper.rb +37 -0
  35. data/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_pagination_helper.rb +49 -0
  36. data/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_url_helper.rb +40 -0
  37. data/app/helpers/decidim/forms/admin/questionnaire_answers_helper.rb +27 -0
  38. data/app/jobs/decidim/forms/export_questionnaire_answers_job.rb +19 -0
  39. data/app/models/decidim/forms/answer.rb +2 -5
  40. data/app/models/decidim/forms/answer_choice.rb +7 -0
  41. data/app/models/decidim/forms/answer_option.rb +14 -0
  42. data/app/models/decidim/forms/display_condition.rb +51 -0
  43. data/app/models/decidim/forms/question.rb +52 -2
  44. data/app/models/decidim/forms/question_matrix_row.rb +15 -0
  45. data/app/models/decidim/forms/questionnaire.rb +13 -2
  46. data/app/presenters/decidim/forms/admin/questionnaire_answer_presenter.rb +43 -0
  47. data/app/presenters/decidim/forms/admin/questionnaire_participant_presenter.rb +60 -0
  48. data/app/presenters/decidim/forms/answer_option_presenter.rb +20 -0
  49. data/app/presenters/decidim/forms/question_presenter.rb +16 -0
  50. data/app/queries/decidim/forms/questionnaire_participant.rb +35 -0
  51. data/app/queries/decidim/forms/questionnaire_participants.rb +43 -0
  52. data/app/queries/decidim/forms/questionnaire_user_answers.rb +1 -1
  53. data/app/types/decidim/forms/answer_option_type.rb +14 -0
  54. data/app/types/decidim/forms/question_type.rb +23 -0
  55. data/app/types/decidim/forms/questionnaire_type.rb +22 -0
  56. data/app/views/decidim/forms/admin/questionnaires/_answer_option_template.html.erb +1 -1
  57. data/app/views/decidim/forms/admin/questionnaires/_display_condition.html.erb +88 -0
  58. data/app/views/decidim/forms/admin/questionnaires/_display_condition_template.html.erb +7 -0
  59. data/app/views/decidim/forms/admin/questionnaires/_form.html.erb +67 -8
  60. data/app/views/decidim/forms/admin/questionnaires/_matrix_row.html.erb +34 -0
  61. data/app/views/decidim/forms/admin/questionnaires/_matrix_row_template.html.erb +7 -0
  62. data/app/views/decidim/forms/admin/questionnaires/_question.html.erb +45 -6
  63. data/app/views/decidim/forms/admin/questionnaires/_separator.html.erb +41 -0
  64. data/app/views/decidim/forms/admin/questionnaires/answers/export/_answer.html.erb +31 -0
  65. data/app/views/decidim/forms/admin/questionnaires/answers/export/pdf.html.erb +13 -0
  66. data/app/views/decidim/forms/admin/questionnaires/answers/index.html.erb +53 -0
  67. data/app/views/decidim/forms/admin/questionnaires/answers/show.html.erb +48 -0
  68. data/app/views/decidim/forms/admin/questionnaires/edit.html.erb +9 -5
  69. data/app/views/decidim/forms/questionnaires/_answer.html.erb +28 -99
  70. data/app/views/decidim/forms/questionnaires/answers/_long_answer.html.erb +1 -0
  71. data/app/views/decidim/forms/questionnaires/answers/_matrix_multiple.html.erb +43 -0
  72. data/app/views/decidim/forms/questionnaires/answers/_matrix_single.html.erb +43 -0
  73. data/app/views/decidim/forms/questionnaires/answers/_multiple_option.html.erb +23 -0
  74. data/app/views/decidim/forms/questionnaires/answers/_separator.html.erb +1 -0
  75. data/app/views/decidim/forms/questionnaires/answers/_short_answer.html.erb +1 -0
  76. data/app/views/decidim/forms/questionnaires/answers/_single_option.html.erb +30 -0
  77. data/app/views/decidim/forms/questionnaires/answers/_sorting.html.erb +23 -0
  78. data/app/views/decidim/forms/questionnaires/show.html.erb +81 -28
  79. data/app/views/layouts/decidim/forms/admin/questionnaires/questionnaire_answers.html.erb +12 -0
  80. data/config/initializers/wicked_pdf.rb +25 -0
  81. data/config/locales/am-ET.yml +1 -0
  82. data/config/locales/ar.yml +12 -4
  83. data/config/locales/bg-BG.yml +14 -0
  84. data/config/locales/bg.yml +14 -0
  85. data/config/locales/ca.yml +98 -8
  86. data/config/locales/cs.yml +94 -4
  87. data/config/locales/da-DK.yml +1 -0
  88. data/config/locales/da.yml +1 -0
  89. data/config/locales/de.yml +92 -4
  90. data/config/locales/el-GR.yml +1 -0
  91. data/config/locales/el.yml +118 -0
  92. data/config/locales/en.yml +94 -4
  93. data/config/locales/eo.yml +1 -0
  94. data/config/locales/es-MX.yml +94 -4
  95. data/config/locales/es-PY.yml +94 -4
  96. data/config/locales/es.yml +94 -4
  97. data/config/locales/et-EE.yml +1 -0
  98. data/config/locales/et.yml +1 -0
  99. data/config/locales/eu.yml +7 -4
  100. data/config/locales/fi-plain.yml +94 -4
  101. data/config/locales/fi.yml +94 -4
  102. data/config/locales/fr-CA.yml +172 -0
  103. data/config/locales/fr.yml +94 -4
  104. data/config/locales/ga-IE.yml +1 -0
  105. data/config/locales/gl.yml +7 -4
  106. data/config/locales/hr-HR.yml +1 -0
  107. data/config/locales/hr.yml +1 -0
  108. data/config/locales/hu.yml +16 -4
  109. data/config/locales/id-ID.yml +7 -4
  110. data/config/locales/is-IS.yml +1 -0
  111. data/config/locales/is.yml +1 -0
  112. data/config/locales/it.yml +95 -5
  113. data/config/locales/ja-JP.yml +170 -0
  114. data/config/locales/ja.yml +170 -0
  115. data/config/locales/ko-KR.yml +1 -0
  116. data/config/locales/ko.yml +1 -0
  117. data/config/locales/lt-LT.yml +1 -0
  118. data/config/locales/lt.yml +1 -0
  119. data/config/locales/lv.yml +118 -0
  120. data/config/locales/mt-MT.yml +1 -0
  121. data/config/locales/mt.yml +1 -0
  122. data/config/locales/nl.yml +97 -7
  123. data/config/locales/no.yml +98 -1
  124. data/config/locales/om-ET.yml +1 -0
  125. data/config/locales/pl.yml +113 -26
  126. data/config/locales/pt-BR.yml +8 -5
  127. data/config/locales/pt.yml +111 -24
  128. data/config/locales/ro-RO.yml +167 -0
  129. data/config/locales/ru.yml +4 -2
  130. data/config/locales/sk-SK.yml +88 -0
  131. data/config/locales/sk.yml +90 -0
  132. data/config/locales/sl.yml +12 -0
  133. data/config/locales/so-SO.yml +1 -0
  134. data/config/locales/sr-CS.yml +1 -0
  135. data/config/locales/sv.yml +91 -4
  136. data/config/locales/ti-ER.yml +1 -0
  137. data/config/locales/tr-TR.yml +7 -4
  138. data/config/locales/vi-VN.yml +1 -0
  139. data/config/locales/vi.yml +1 -0
  140. data/config/locales/zh-CN.yml +172 -0
  141. data/config/locales/zh-TW.yml +1 -0
  142. data/db/migrate/20190315203056_add_session_token_to_decidim_forms_answers.rb +17 -0
  143. data/db/migrate/20190930094710_add_ip_hash_to_decidim_form_answers.rb +12 -0
  144. data/db/migrate/20200130194123_create_decidim_forms_display_conditions.rb +20 -0
  145. data/db/migrate/20200225123810_create_decidim_forms_question_matrix_rows.rb +11 -0
  146. data/db/migrate/20200304152939_add_matrix_row_id_to_decidim_forms_answer_choices.rb +11 -0
  147. data/lib/decidim/api/questionnaire_entity_interface.rb +18 -0
  148. data/lib/decidim/exporters/form_pdf.rb +33 -0
  149. data/lib/decidim/exporters/form_pdf_controller_helper.rb +11 -0
  150. data/lib/decidim/forms.rb +6 -0
  151. data/lib/decidim/forms/admin_engine.rb +1 -1
  152. data/lib/decidim/forms/api.rb +7 -0
  153. data/lib/decidim/forms/test.rb +6 -0
  154. data/lib/decidim/forms/test/factories.rb +80 -6
  155. data/lib/decidim/forms/test/shared_examples/has_questionnaire.rb +924 -66
  156. data/lib/decidim/forms/test/shared_examples/manage_questionnaire_answers.rb +108 -0
  157. data/lib/decidim/forms/test/shared_examples/manage_questionnaires.rb +33 -575
  158. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_display_conditions.rb +179 -0
  159. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_questions.rb +463 -0
  160. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_display_conditions.rb +93 -0
  161. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_questions.rb +461 -0
  162. data/lib/decidim/forms/user_answers_serializer.rb +23 -4
  163. data/lib/decidim/forms/version.rb +1 -1
  164. metadata +136 -9
@@ -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,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,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);