chobble-forms 0.5.3 → 0.5.5

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/chobble_forms/form_grids.css +103 -90
  3. data/app/javascript/comment_toggles.js +76 -0
  4. data/app/javascript/na_number_toggles.js +64 -0
  5. data/app/javascript/na_toggles.js +76 -0
  6. data/lib/chobble_forms/engine.rb +1 -0
  7. data/lib/chobble_forms/helpers.rb +1 -0
  8. data/lib/chobble_forms/version.rb +1 -1
  9. data/views/chobble_forms/_auto_submit_select.html.erb +9 -9
  10. data/views/chobble_forms/_comment.html.erb +3 -6
  11. data/views/chobble_forms/_comment_checkbox.html.erb +1 -2
  12. data/views/chobble_forms/_errors.html.erb +6 -6
  13. data/views/chobble_forms/_field_with_link.html.erb +4 -4
  14. data/views/chobble_forms/_fields.html.erb +1 -1
  15. data/views/chobble_forms/_fieldset.html.erb +1 -1
  16. data/views/chobble_forms/_file_field_turbo_response.html.erb +2 -2
  17. data/views/chobble_forms/_form_context.html.erb +10 -12
  18. data/views/chobble_forms/_header.html.erb +1 -1
  19. data/views/chobble_forms/_integer_comment.html.erb +25 -6
  20. data/views/chobble_forms/_number.html.erb +1 -2
  21. data/views/chobble_forms/_number_pass_fail_comment.html.erb +3 -6
  22. data/views/chobble_forms/_number_pass_fail_na_comment.html.erb +5 -10
  23. data/views/chobble_forms/_pass_fail.html.erb +1 -2
  24. data/views/chobble_forms/_pass_fail_comment.html.erb +2 -4
  25. data/views/chobble_forms/_pass_fail_na_comment.html.erb +3 -5
  26. data/views/chobble_forms/_radio_comment.html.erb +2 -4
  27. data/views/chobble_forms/_radio_pass_fail.html.erb +5 -8
  28. data/views/chobble_forms/_search_field.html.erb +1 -1
  29. data/views/chobble_forms/_submit_button.html.erb +5 -1
  30. data/views/chobble_forms/_yes_no_radio.html.erb +1 -2
  31. data/views/chobble_forms/_yes_no_radio_comment.html.erb +3 -5
  32. metadata +5 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 291fbe89074db623fd71edea3063c4aa13584aed19e40e0f6b0d7a27cc28750e
4
- data.tar.gz: 1f9dfdc73890607e8484362d121fddb89be6e128462d99aa847813f175678ef4
3
+ metadata.gz: b92fb8d1150671025f0475b802e6484f337b554b072c1cd8464eb721e83df397
4
+ data.tar.gz: 1fa3020e94941bb44a3abb8fed8117939e236dfa3da8eb118bf86fcf4f37b718
5
5
  SHA512:
6
- metadata.gz: 29cf3ec6da77e2bd6552cde85f95cfb46769f8e05dad35c851755109b603798aabb82a341c0495625afd34bfc021d9123c11cf9b7cb0ffe85b6a5584151416f9
7
- data.tar.gz: c61849b91a1beba8ee445311e809071828cecb3c876acca9bbf5b9686e4c7f197106fc23e6187e38138237c7eba2fc7deae251f5c6697933d459763c68433958
6
+ metadata.gz: d198ecfde80af5079d2b137d3977e3d53691e79c97ba118d17e6474291092e7e38788c376a10c750b60a72b6494720513430568cce0803d0087ae24221cc1519
7
+ data.tar.gz: ac1658d4113f8970f5859837231567affeb0c790de34cc7afb98cf1cbbc7c9eea0de83cb0634d65b05e22c31f3cf716a0b3eca27242703081cc294d2f0e41cf3
@@ -3,206 +3,219 @@
3
3
  */
4
4
 
5
5
  .form-grid {
6
- display: grid;
7
- gap: 1rem;
8
- align-items: end;
9
- padding-top: 1rem;
6
+ display: grid;
7
+ gap: 1rem;
8
+ align-items: end;
9
+ padding-top: 1rem;
10
10
  }
11
11
 
12
12
  legend + .form-grid {
13
- border-top: none;
13
+ border-top: none;
14
14
  }
15
15
 
16
16
  /* Radio + Comment grid layout */
17
17
  .radio-comment {
18
- grid-template-areas:
19
- "label label"
20
- "pass-fail comment-label"
21
- "comment comment";
22
- grid-template-columns: auto 1fr;
23
- align-items: center;
18
+ grid-template-areas:
19
+ "label label"
20
+ "pass-fail comment-label"
21
+ "comment comment";
22
+ grid-template-columns: auto 1fr;
23
+ align-items: center;
24
24
  }
25
25
 
26
26
  @media (min-width: 768px) {
27
- .radio-comment {
28
- grid-template-areas:
29
- "label pass-fail comment-space comment-label"
30
- "comment comment comment comment";
31
- grid-template-columns: max-content auto 1fr auto;
32
- }
27
+ .radio-comment {
28
+ grid-template-areas:
29
+ "label pass-fail comment-space comment-label"
30
+ "comment comment comment comment";
31
+ grid-template-columns: max-content auto 1fr auto;
32
+ }
33
33
  }
34
34
 
35
35
  .radio-comment > .label {
36
- grid-area: label;
36
+ grid-area: label;
37
37
  }
38
38
 
39
39
  .radio-comment > .label label {
40
- flex-direction: column;
40
+ flex-direction: column;
41
41
  }
42
42
 
43
43
  .radio-comment > .pass-fail {
44
- grid-area: pass-fail;
44
+ grid-area: pass-fail;
45
45
  }
46
46
 
47
47
  .radio-comment > .comment-checkbox {
48
- grid-area: comment-label;
48
+ grid-area: comment-label;
49
49
  }
50
50
 
51
51
  .radio-comment > textarea {
52
- grid-area: comment;
52
+ grid-area: comment;
53
53
  }
54
54
 
55
55
  /* Number + Pass/Fail + Comment grid layout */
56
56
  .number-radio-comment {
57
- grid-template-areas:
58
- "label label"
59
- "number pass-fail"
60
- "comment-label comment-space"
61
- "comment comment";
62
- grid-template-columns: auto 1fr;
57
+ grid-template-areas:
58
+ "label label"
59
+ "number pass-fail"
60
+ "comment-label comment-space"
61
+ "comment comment";
62
+ grid-template-columns: auto 1fr;
63
63
  }
64
64
 
65
65
  @media (min-width: 768px) {
66
- .number-radio-comment {
67
- grid-template-areas:
68
- "label label label label"
69
- "number pass-fail comment-space comment-label"
70
- "comment comment comment comment";
71
- grid-template-columns: auto auto 1fr auto;
72
- }
66
+ .number-radio-comment {
67
+ grid-template-areas:
68
+ "label label label label"
69
+ "number pass-fail comment-space comment-label"
70
+ "comment comment comment comment";
71
+ grid-template-columns: auto auto 1fr auto;
72
+ }
73
73
  }
74
74
 
75
75
  .number-radio-comment > .label {
76
- grid-area: label;
76
+ grid-area: label;
77
77
  }
78
78
 
79
79
  .number-radio-comment > .label label {
80
- flex-direction: column;
80
+ flex-direction: column;
81
81
  }
82
82
 
83
83
  .number-radio-comment > .number {
84
- grid-area: number;
84
+ grid-area: number;
85
85
  }
86
86
 
87
87
  .number-radio-comment > .pass-fail {
88
- grid-area: pass-fail;
88
+ grid-area: pass-fail;
89
89
  }
90
90
 
91
91
  .number-radio-comment > .comment-checkbox {
92
- grid-area: comment-label;
92
+ grid-area: comment-label;
93
93
  }
94
94
 
95
95
  .number-radio-comment > textarea {
96
- grid-area: comment;
96
+ grid-area: comment;
97
97
  }
98
98
 
99
99
  /* Number + Comment grid layout */
100
100
  .number-comment {
101
- grid-template-areas:
102
- "label label"
103
- "number comment-label"
104
- "comment comment";
105
- grid-template-columns: min-content auto;
106
- align-items: center;
101
+ grid-template-areas:
102
+ "label label"
103
+ "number comment-label"
104
+ "comment comment";
105
+ grid-template-columns: min-content auto;
106
+ align-items: center;
107
107
  }
108
108
 
109
109
  @media (min-width: 768px) {
110
- .number-comment {
111
- grid-template-areas:
112
- "label number comment-space comment-label"
113
- "comment comment comment comment";
114
- grid-template-columns: max-content 6rem 1fr auto;
115
- }
110
+ .number-comment {
111
+ grid-template-areas:
112
+ "label number comment-space comment-label"
113
+ "comment comment comment comment";
114
+ grid-template-columns: max-content 6rem 1fr auto;
115
+ }
116
116
  }
117
117
 
118
118
  .number-comment > .label {
119
- grid-area: label;
120
- width: 14rem;
119
+ grid-area: label;
120
+ width: 14rem;
121
121
  }
122
122
 
123
123
  .number-comment > .number {
124
- grid-area: number;
124
+ grid-area: number;
125
125
  }
126
126
 
127
127
  .number-comment > .comment-checkbox {
128
- grid-area: comment-label;
128
+ grid-area: comment-label;
129
129
  }
130
130
 
131
131
  .number-comment > textarea {
132
- grid-area: comment;
132
+ grid-area: comment;
133
133
  }
134
134
 
135
135
  /* Checkbox + Comment grid layout */
136
136
  .checkbox-comment {
137
- display: grid;
138
- gap: 0.5rem;
139
- align-items: center;
140
- margin-bottom: 1rem;
141
- grid-template-areas:
142
- "label label label label"
143
- "check1 label2 comment-space comment-label";
144
- grid-template-columns: auto auto 1fr auto;
137
+ display: grid;
138
+ gap: 0.5rem;
139
+ align-items: center;
140
+ margin-bottom: 1rem;
141
+ grid-template-areas:
142
+ "label label label label"
143
+ "check1 label2 comment-space comment-label";
144
+ grid-template-columns: auto auto 1fr auto;
145
145
  }
146
146
 
147
147
  @media (min-width: 768px) {
148
- .checkbox-comment {
149
- grid-template-areas: "label check1 label2 comment-label";
150
- grid-template-columns: max-content auto auto auto;
151
- }
148
+ .checkbox-comment {
149
+ grid-template-areas: "label check1 label2 comment-label";
150
+ grid-template-columns: max-content auto auto auto;
151
+ }
152
152
  }
153
153
 
154
154
  .checkbox-comment > .label {
155
- grid-area: label;
155
+ grid-area: label;
156
156
  }
157
157
 
158
158
  .checkbox-comment > .checkbox {
159
- grid-area: check1;
159
+ grid-area: check1;
160
160
  }
161
161
 
162
162
  .checkbox-comment > .checkbox-label {
163
- grid-area: label2;
163
+ grid-area: label2;
164
164
  }
165
165
 
166
166
  .checkbox-comment > .comment-checkbox {
167
- grid-area: comment-label;
167
+ grid-area: comment-label;
168
168
  }
169
169
 
170
170
  .checkbox-comment > textarea {
171
- grid-column: 1 / -1;
172
- margin-top: 0.5rem;
171
+ grid-column: 1 / -1;
172
+ margin-top: 0.5rem;
173
173
  }
174
174
 
175
175
  /* Simple number layout */
176
176
  .number {
177
- grid-template-area: "label number";
178
- grid-template-columns: auto min-content;
179
- align-items: center;
177
+ grid-template-area: "label number";
178
+ grid-template-columns: auto min-content;
179
+ align-items: center;
180
180
  }
181
181
 
182
182
  .number-comment > .label {
183
- grid-area: label;
183
+ grid-area: label;
184
184
  }
185
185
 
186
186
  .number-comment > .number {
187
- grid-area: number;
188
- width: 14rem;
187
+ grid-area: number;
188
+ width: 14rem;
189
189
  }
190
190
 
191
191
  /* User capacity flexbox layout */
192
192
  fieldset#user_capacity {
193
- display: flex;
194
- flex-wrap: wrap;
195
- gap: 1rem;
193
+ display: flex;
194
+ flex-wrap: wrap;
195
+ gap: 1rem;
196
196
  }
197
197
 
198
198
  /* Mobile: 2 columns (2 rows of 2) */
199
199
  fieldset#user_capacity > * {
200
- flex: 1 1 calc(50% - 0.5rem);
200
+ flex: 1 1 calc(50% - 0.5rem);
201
201
  }
202
202
 
203
203
  /* Desktop: 4 columns (1 row of 4) */
204
204
  @media (min-width: 768px) {
205
- fieldset#user_capacity > * {
206
- flex: 1 1 calc(25% - 0.75rem);
207
- }
208
- }
205
+ fieldset#user_capacity > * {
206
+ flex: 1 1 calc(25% - 0.75rem);
207
+ }
208
+ }
209
+
210
+ .simple div {
211
+ display: flex;
212
+ flex-direction: row;
213
+ flex-wrap: wrap;
214
+ gap: 1rem;
215
+ justify-content: start;
216
+ align-items: center;
217
+ }
218
+
219
+ .simple input.number {
220
+ width: 6rem;
221
+ }
@@ -0,0 +1,76 @@
1
+ // CommentToggles - handles comment field visibility toggling
2
+ class CommentToggles {
3
+ constructor() {
4
+ this.processedToggles = new WeakSet();
5
+ }
6
+
7
+ init() {
8
+ this.attachListeners();
9
+ }
10
+
11
+ attachListeners() {
12
+ // Find all comment toggle checkboxes
13
+ const toggles = document.querySelectorAll("[data-comment-toggle]");
14
+ toggles.forEach((toggle) => this.setupToggle(toggle));
15
+ }
16
+
17
+ setupToggle(toggle) {
18
+ // Skip if already processed
19
+ if (this.processedToggles.has(toggle)) return;
20
+ this.processedToggles.add(toggle);
21
+
22
+ // Set initial state
23
+ this.updateVisibility(toggle);
24
+
25
+ // Handle changes
26
+ toggle.addEventListener("change", () => this.handleToggle(toggle));
27
+ }
28
+
29
+ handleToggle(toggle) {
30
+ this.updateVisibility(toggle);
31
+
32
+ const textareaId = toggle.getAttribute("data-comment-toggle");
33
+ const textarea = document.getElementById(textareaId);
34
+
35
+ if (!textarea) return;
36
+
37
+ if (toggle.checked) {
38
+ // Focus the textarea when showing
39
+ textarea.focus();
40
+ } else {
41
+ // Clear the textarea when hiding
42
+ textarea.value = "";
43
+ }
44
+ }
45
+
46
+ updateVisibility(toggle) {
47
+ const containerId = toggle.getAttribute("data-comment-container");
48
+ const container = document.getElementById(containerId);
49
+
50
+ if (!container) return;
51
+
52
+ container.style.display = toggle.checked ? "block" : "none";
53
+ }
54
+
55
+ cleanup() {
56
+ // Re-process any new toggles that appeared
57
+ this.attachListeners();
58
+ }
59
+ }
60
+
61
+ // Create singleton instance
62
+ const commentToggles = new CommentToggles();
63
+
64
+ // Initialize on first load
65
+ document.addEventListener("DOMContentLoaded", () => commentToggles.init());
66
+
67
+ // Reinitialize after Turbo navigation
68
+ document.addEventListener("turbo:load", () => {
69
+ commentToggles.cleanup();
70
+ commentToggles.init();
71
+ });
72
+
73
+ // Handle dynamically loaded content
74
+ document.addEventListener("turbo:frame-load", () => {
75
+ commentToggles.attachListeners();
76
+ });
@@ -0,0 +1,64 @@
1
+ class NaNumberToggles {
2
+ constructor() {
3
+ this.processedCheckboxes = new WeakSet();
4
+ }
5
+
6
+ init() {
7
+ this.attachListeners();
8
+ }
9
+
10
+ attachListeners() {
11
+ const naCheckboxes = document.querySelectorAll('.na-label input[type="checkbox"]');
12
+ naCheckboxes.forEach((checkbox) => this.setupCheckbox(checkbox));
13
+ }
14
+
15
+ setupCheckbox(checkbox) {
16
+ if (this.processedCheckboxes.has(checkbox)) return;
17
+ this.processedCheckboxes.add(checkbox);
18
+
19
+ this.updateNumberFieldState(checkbox);
20
+
21
+ checkbox.addEventListener("change", () => this.updateNumberFieldState(checkbox));
22
+ }
23
+
24
+ updateNumberFieldState(checkbox) {
25
+ const label = checkbox.parentElement;
26
+ const numberInput = label.previousElementSibling;
27
+
28
+ if (!numberInput || !numberInput.classList.contains('number')) {
29
+ console.warn('Could not find number input for N/A checkbox', checkbox);
30
+ return;
31
+ }
32
+
33
+ if (checkbox.checked) {
34
+ if (numberInput.value !== '0' && numberInput.value !== '') {
35
+ checkbox.dataset.previousValue = numberInput.value;
36
+ }
37
+ numberInput.disabled = true;
38
+ numberInput.value = '0';
39
+ } else {
40
+ numberInput.disabled = false;
41
+ if (checkbox.dataset.previousValue) {
42
+ numberInput.value = checkbox.dataset.previousValue;
43
+ delete checkbox.dataset.previousValue;
44
+ }
45
+ }
46
+ }
47
+
48
+ cleanup() {
49
+ this.attachListeners();
50
+ }
51
+ }
52
+
53
+ const naNumberToggles = new NaNumberToggles();
54
+
55
+ document.addEventListener("DOMContentLoaded", () => naNumberToggles.init());
56
+
57
+ document.addEventListener("turbo:load", () => {
58
+ naNumberToggles.cleanup();
59
+ naNumberToggles.init();
60
+ });
61
+
62
+ document.addEventListener("turbo:frame-load", () => {
63
+ naNumberToggles.attachListeners();
64
+ });
@@ -0,0 +1,76 @@
1
+ // NaToggles - handles N/A radio button functionality for pass/fail fields
2
+ class NaToggles {
3
+ constructor() {
4
+ this.processedRadios = new WeakSet();
5
+ }
6
+
7
+ init() {
8
+ this.attachListeners();
9
+ }
10
+
11
+ attachListeners() {
12
+ // Find all enum radio buttons (pass/fail/na)
13
+ const enumRadios = document.querySelectorAll(
14
+ 'input[type="radio"][value="pass"], input[type="radio"][value="fail"], input[type="radio"][value="na"]',
15
+ );
16
+ enumRadios.forEach((radio) => this.setupRadio(radio));
17
+ }
18
+
19
+ setupRadio(radio) {
20
+ // Skip if already processed
21
+ if (this.processedRadios.has(radio)) return;
22
+ this.processedRadios.add(radio);
23
+
24
+ // Set initial state
25
+ this.updatePassFailState(radio);
26
+
27
+ // Handle changes
28
+ radio.addEventListener("change", () => this.updatePassFailState(radio));
29
+ }
30
+
31
+ updatePassFailState(changedRadio) {
32
+ // Find all radio buttons with the same name (same field)
33
+ const fieldName = changedRadio.name;
34
+ const allRadios = document.querySelectorAll(
35
+ `input[type="radio"][name="${fieldName}"]`,
36
+ );
37
+
38
+ // Check if N/A is selected
39
+ const naRadio = Array.from(allRadios).find((radio) => radio.value === "na");
40
+ const isNaSelected = naRadio && naRadio.checked;
41
+
42
+ allRadios.forEach((radio) => {
43
+ const label = radio.parentElement;
44
+
45
+ if (isNaSelected && radio.value !== "na") {
46
+ // N/A is selected - mute pass/fail options
47
+ label.classList.add("muted");
48
+ } else {
49
+ // N/A is not selected - remove muted class
50
+ label.classList.remove("muted");
51
+ }
52
+ });
53
+ }
54
+
55
+ cleanup() {
56
+ // Re-process any new radios that appeared
57
+ this.attachListeners();
58
+ }
59
+ }
60
+
61
+ // Create singleton instance
62
+ const naToggles = new NaToggles();
63
+
64
+ // Initialize on first load
65
+ document.addEventListener("DOMContentLoaded", () => naToggles.init());
66
+
67
+ // Reinitialize after Turbo navigation
68
+ document.addEventListener("turbo:load", () => {
69
+ naToggles.cleanup();
70
+ naToggles.init();
71
+ });
72
+
73
+ // Handle dynamically loaded content
74
+ document.addEventListener("turbo:frame-load", () => {
75
+ naToggles.attachListeners();
76
+ });
@@ -28,6 +28,7 @@ module ChobbleForms
28
28
  initializer "chobble_forms.assets" do |app|
29
29
  if app.config.respond_to?(:assets) && app.config.assets
30
30
  app.config.assets.paths << root.join("app/assets/stylesheets")
31
+ app.config.assets.paths << root.join("app/javascript")
31
32
  app.config.assets.precompile += %w[chobble_forms.css]
32
33
  end
33
34
  end
@@ -70,6 +70,7 @@ module ChobbleForms
70
70
 
71
71
  ALLOWED_LOCAL_ASSIGNS = T.let(%i[
72
72
  accept
73
+ add_not_applicable
73
74
  field
74
75
  max
75
76
  min
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module ChobbleForms
5
- VERSION = "0.5.3"
5
+ VERSION = "0.5.5"
6
6
  end
@@ -2,7 +2,7 @@
2
2
  # Auto-submit select field options
3
3
  field = local_assigns[:field] or raise ArgumentError, "field is required for auto submit select"
4
4
  options = local_assigns[:options] or raise ArgumentError, "options is required for auto submit select"
5
-
5
+
6
6
  # Check if we're in a form context with form object or need standalone
7
7
  if local_assigns[:form]
8
8
  form_object = local_assigns[:form]
@@ -12,7 +12,7 @@
12
12
  url = local_assigns[:url] or raise ArgumentError, "url is required for standalone auto submit select"
13
13
  selected_value = params[field]
14
14
  end
15
-
15
+
16
16
  # Optional parameters
17
17
  label = local_assigns[:label]
18
18
  include_blank = local_assigns[:include_blank]
@@ -25,31 +25,31 @@
25
25
  <% if label %>
26
26
  <%= form_object.label field, label %>
27
27
  <% end %>
28
-
28
+
29
29
  <%= form_object.select field,
30
30
  options_for_select(options, selected_value),
31
31
  include_blank ? { include_blank: blank_text } : {},
32
32
  { onchange: "this.form.submit();" } %>
33
-
33
+
34
34
  <% else %>
35
35
  <!-- Standalone auto-submit select form -->
36
36
  <%= form_with url: url, method: :get, data: (turbo_disabled ? { turbo: false } : {}) do |form| %>
37
-
37
+
38
38
  <!-- Preserve other parameters -->
39
39
  <% preserve_params.each do |param| %>
40
40
  <% if params[param].present? %>
41
41
  <%= form.hidden_field param, value: params[param] %>
42
42
  <% end %>
43
43
  <% end %>
44
-
44
+
45
45
  <% if label %>
46
46
  <%= form.label field, label %>
47
47
  <% end %>
48
-
48
+
49
49
  <%= form.select field,
50
50
  options_for_select(options, selected_value),
51
51
  include_blank ? { include_blank: blank_text } : {},
52
52
  { onchange: "this.form.submit();" } %>
53
-
53
+
54
54
  <% end %>
55
- <% end %>
55
+ <% end %>
@@ -22,19 +22,16 @@
22
22
  <input type="checkbox" id="<%= checkbox_id %>"
23
23
  <%= 'checked' if has_content %>
24
24
  data-comment-toggle="<%= textarea_id %>"
25
- data-comment-container="<%= textarea_id %>_container"
26
- >
25
+ data-comment-container="<%= textarea_id %>_container">
27
26
  <%= t("shared.comment") %>
28
27
  </label>
29
28
 
30
29
  <div id="<%= textarea_id %>_container"
31
- style="display: <%= has_content ? 'block' : 'none' %>"
32
- >
30
+ style="display: <%= has_content ? 'block' : 'none' %>">
33
31
  <%= form.text_field field,
34
32
  maxlength: maxlength,
35
33
  placeholder: placeholder,
36
34
  id: textarea_id,
37
- title: "Max #{maxlength} characters"
38
- %>
35
+ title: "Max #{maxlength} characters" %>
39
36
  </div>
40
37
  </div>
@@ -12,7 +12,6 @@
12
12
  id="<%= checkbox_id %>"
13
13
  <%= 'checked' if has_comment %>
14
14
  data-comment-toggle="<%= textarea_id %>"
15
- data-comment-container="<%= textarea_id %>"
16
- >
15
+ data-comment-container="<%= textarea_id %>">
17
16
  <%= t('shared.comment') %>
18
17
  </label>
@@ -2,15 +2,15 @@
2
2
  # Form errors component - displays validation errors for any model object
3
3
  model_object = local_assigns[:model] || local_assigns[:object]
4
4
  raise ArgumentError, "model object is required for form errors" if model_object.nil?
5
-
5
+
6
6
  # Use inherited i18n_base from form context
7
7
  i18n_base = @_current_i18n_base
8
8
  raise ArgumentError, "i18n_base is required for form errors" if i18n_base.nil?
9
-
9
+
10
10
  # Custom header text or use i18n lookup - no fallbacks
11
- header_text = local_assigns[:header] ||
12
- t("#{i18n_base}.errors.header",
13
- count: model_object.errors.count,
11
+ header_text = local_assigns[:header] ||
12
+ t("#{i18n_base}.errors.header",
13
+ count: model_object.errors.count,
14
14
  raise: true)
15
15
  %>
16
16
 
@@ -23,4 +23,4 @@
23
23
  <% end %>
24
24
  </ul>
25
25
  </aside>
26
- <% end %>
26
+ <% end %>
@@ -8,14 +8,14 @@
8
8
  # - link_url: URL for the link
9
9
  # - link_text: Text for the link
10
10
  # - options: options for autocomplete_field
11
-
11
+
12
12
  field_type = local_assigns[:field_type] || :text_field
13
13
  field = local_assigns[:field]
14
14
  required = local_assigns[:required] || false
15
15
  link_url = local_assigns[:link_url]
16
16
  link_text = local_assigns[:link_text]
17
17
  options = local_assigns[:options] || []
18
-
18
+
19
19
  has_link = link_url.present? && link_text.present?
20
20
  css_classes = has_link ? "field field-with-link" : "field"
21
21
  %>
@@ -26,8 +26,8 @@
26
26
  <% else %>
27
27
  <%= render 'chobble_forms/text_field', field: field, required: required %>
28
28
  <% end %>
29
-
29
+
30
30
  <% if has_link %>
31
31
  <%= link_to link_text, link_url %>
32
32
  <% end %>
33
- </div>
33
+ </div>
@@ -6,4 +6,4 @@
6
6
  <%= render "chobble_forms/#{field_config[:partial]}", render_options %>
7
7
  <% end %>
8
8
  <% end %>
9
- <% end %>
9
+ <% end %>
@@ -5,7 +5,7 @@
5
5
  raise ArgumentError, "i18n_base is required for form fieldsets" if i18n_base.nil?
6
6
  raise ArgumentError, "legend_key is required for form fieldsets" if local_assigns[:legend_key].nil?
7
7
  @_current_i18n_base = i18n_base
8
-
8
+
9
9
  sections_base = i18n_base.sub(/\.fields$/, '')
10
10
  legend_i18n_key = "#{sections_base}.sections.#{local_assigns[:legend_key]}"
11
11
  legend_text = t(legend_i18n_key)
@@ -7,7 +7,7 @@
7
7
  # i18n_base: The i18n base path
8
8
  # Optional parameters:
9
9
  # accept: File accept types (defaults to "image/*")
10
-
10
+
11
11
  model = local_assigns[:model]
12
12
  field = local_assigns[:field]
13
13
  turbo_frame_id = local_assigns[:turbo_frame_id]
@@ -21,4 +21,4 @@
21
21
  <% @_current_i18n_base = i18n_base %>
22
22
  <%= render 'chobble_forms/file_field', field: field, accept: accept %>
23
23
  <% end %>
24
- <% end %>
24
+ <% end %>
@@ -16,7 +16,7 @@
16
16
  # Non-model forms need explicit URL and default to POST
17
17
  method ||= :post
18
18
  end
19
-
19
+
20
20
  # Check if local form submission is requested
21
21
  use_local = local_assigns[:local] || false
22
22
  %>
@@ -25,8 +25,9 @@
25
25
  url: url,
26
26
  method: method,
27
27
  local: use_local,
28
- html: {
29
- multipart: true
28
+ html: {
29
+ multipart: true,
30
+ autocomplete: "off"
30
31
  }
31
32
  } %>
32
33
 
@@ -46,8 +47,7 @@ end %>
46
47
 
47
48
  <%= form_with(**form_options) do |form| %>
48
49
  <%= render "chobble_forms/header",
49
- title: I18n.t("#{i18n_base}.header")
50
- %>
50
+ title: I18n.t("#{i18n_base}.header") %>
51
51
 
52
52
  <% @_current_form = form %>
53
53
  <% @_current_i18n_base = i18n_base %>
@@ -59,14 +59,12 @@ end %>
59
59
  <% end %>
60
60
 
61
61
  <fieldset>
62
- <% if local_assigns[:secondary_link_url].present? && local_assigns[:secondary_link_text].present? %>
63
- <div class="form-actions">
64
- <%= render 'chobble_forms/submit_button' %>
62
+ <div class="form-actions">
63
+ <%= render 'chobble_forms/submit_button', submit_key: local_assigns[:submit_key] %>
64
+ <% if local_assigns[:secondary_link_url].present? && local_assigns[:secondary_link_text].present? %>
65
65
  <%= link_to secondary_link_text, secondary_link_url, role: "button", class: "secondary" %>
66
- </div>
67
- <% else %>
68
- <%= render 'chobble_forms/submit_button' %>
69
- <% end %>
66
+ <% end %>
67
+ </div>
70
68
  </fieldset>
71
69
 
72
70
  <%# Save message display for Turbo Streams %>
@@ -1,3 +1,3 @@
1
1
  <header>
2
2
  <h1><%= title %></h1>
3
- </header>
3
+ </header>
@@ -7,13 +7,16 @@
7
7
  # max: Maximum value
8
8
  # required: Whether the integer field is required (defaults to true)
9
9
  # placeholder: Placeholder text
10
+ # add_not_applicable: Whether to show a "Not Applicable" checkbox (defaults to false)
10
11
  #
11
12
  # This will render:
12
13
  # - Integer field: field (e.g., 'trough_depth')
14
+ # - Not Applicable checkbox: (if add_not_applicable is true)
13
15
  # - Comment field: field + '_comment' (e.g., 'trough_depth_comment')
14
16
 
15
17
  integer_field = field
16
18
  comment_field = "#{field}_comment".to_sym
19
+ show_na_checkbox = local_assigns.fetch(:add_not_applicable, false)
17
20
 
18
21
  form = @_current_form
19
22
  model = form.object
@@ -36,6 +39,14 @@
36
39
  integer_options[:data][:min] = local_assigns[:min] if local_assigns[:min]
37
40
  integer_options[:data][:max] = local_assigns[:max] if local_assigns[:max]
38
41
 
42
+ # Check if value is 0 to determine N/A state (not nil)
43
+ is_na = field_data[:value].present? && field_data[:value].to_i == 0
44
+
45
+ # Disable input if N/A
46
+ if is_na && show_na_checkbox
47
+ integer_options[:disabled] = true
48
+ end
49
+
39
50
  comment_info = comment_field_options(
40
51
  form,
41
52
  comment_field,
@@ -43,16 +54,24 @@
43
54
  )
44
55
  %>
45
56
 
46
- <div class="form-grid number-comment" id="<%= integer_field %>">
57
+ <div
58
+ class="form-grid simple"
59
+ id="<%= integer_field %>">
47
60
  <%= form.label integer_field, field_data[:field_label], class: "label" %>
48
- <%= form.text_field integer_field, integer_options %>
49
-
50
- <%= render 'chobble_forms/comment_checkbox',
61
+ <div>
62
+ <%= form.text_field integer_field, integer_options %>
63
+ <% if show_na_checkbox %>
64
+ <label class="na-label">
65
+ <input type="checkbox"<%= is_na ? " checked" : "" %>>
66
+ <%= t("shared.not_applicable") %>
67
+ </label>
68
+ <% end %>
69
+ <%= render 'chobble_forms/comment_checkbox',
51
70
  comment_field: comment_field,
52
71
  checkbox_id: comment_info[:checkbox_id],
53
72
  textarea_id: comment_info[:options][:id],
54
73
  has_comment: comment_info[:has_comment],
55
74
  prefilled: comment_info[:prefilled] %>
56
-
75
+ </div>
57
76
  <%= form.text_area comment_field, comment_info[:options] %>
58
- </div>
77
+ </div>
@@ -29,6 +29,5 @@
29
29
  required: number_options[:required],
30
30
  placeholder: field_data[:field_placeholder],
31
31
  value: number_options[:value],
32
- class: "number"
33
- %>
32
+ class: "number" %>
34
33
  </div>
@@ -51,14 +51,12 @@
51
51
  required: number_options[:required],
52
52
  placeholder: field_data[:field_placeholder],
53
53
  value: number_options[:value],
54
- class: "number"
55
- %>
54
+ class: "number" %>
56
55
 
57
56
  <%= render 'chobble_forms/radio_pass_fail',
58
57
  field: pass_fail_field,
59
58
  prefilled: pass_fail_prefilled,
60
- checked_value: pass_fail_checked
61
- %>
59
+ checked_value: pass_fail_checked %>
62
60
 
63
61
  <%= render 'chobble_forms/comment_checkbox',
64
62
  comment_field: comment_field,
@@ -68,6 +66,5 @@
68
66
  prefilled: comment_info[:prefilled] %>
69
67
 
70
68
  <%= form.text_area comment_field,
71
- comment_info[:options]
72
- %>
69
+ comment_info[:options] %>
73
70
  </div>
@@ -30,7 +30,7 @@
30
30
  form,
31
31
  pass_fail_field
32
32
  )
33
-
33
+
34
34
  # Debug: Check if the model value is being read correctly
35
35
  model_value = model.send(pass_fail_field) if model.respond_to?(pass_fail_field)
36
36
  actual_value = pass_fail_prefilled ? pass_fail_value : model_value
@@ -42,7 +42,6 @@
42
42
  comment_field,
43
43
  field_data[:field_label]
44
44
  )
45
-
46
45
  %>
47
46
 
48
47
  <div class="form-grid number-radio-comment" id="<%= number_field %>">
@@ -58,15 +57,12 @@
58
57
  required: number_options[:required],
59
58
  placeholder: field_data[:field_placeholder],
60
59
  value: number_options[:value],
61
- class: "number"
62
- %>
60
+ class: "number" %>
63
61
 
64
62
  <%= render 'chobble_forms/radio_pass_fail',
65
63
  field: pass_fail_field,
66
64
  prefilled: pass_fail_prefilled && !na_checked,
67
- checked_value: pass_fail_checked
68
- %>
69
-
65
+ checked_value: pass_fail_checked %>
70
66
 
71
67
  <%= render 'chobble_forms/comment_checkbox',
72
68
  comment_field: comment_field,
@@ -76,6 +72,5 @@
76
72
  prefilled: comment_info[:prefilled] %>
77
73
 
78
74
  <%= form.text_area comment_field,
79
- comment_info[:options]
80
- %>
81
- </div>
75
+ comment_info[:options] %>
76
+ </div>
@@ -8,6 +8,5 @@
8
8
  <%= render 'chobble_forms/radio_pass_fail',
9
9
  field: field,
10
10
  prefilled: field_data[:prefilled],
11
- checked_value: checked_value
12
- %>
11
+ checked_value: checked_value %>
13
12
  </div>
@@ -32,16 +32,14 @@
32
32
  <%= render 'chobble_forms/radio_pass_fail',
33
33
  field: field,
34
34
  prefilled: field_data[:prefilled],
35
- checked_value: checked_value
36
- %>
35
+ checked_value: checked_value %>
37
36
 
38
37
  <%= render 'chobble_forms/comment_checkbox',
39
38
  comment_field: comment_field,
40
39
  checkbox_id: comment_info[:checkbox_id],
41
40
  textarea_id: comment_info[:options][:id],
42
41
  has_comment: comment_info[:has_comment],
43
- prefilled: comment_info[:prefilled]
44
- %>
42
+ prefilled: comment_info[:prefilled] %>
45
43
 
46
44
  <%= form.text_area comment_field, comment_info[:options] %>
47
45
  </div>
@@ -33,16 +33,14 @@
33
33
  <%= render 'chobble_forms/radio_pass_fail',
34
34
  field: field,
35
35
  prefilled: field_data[:prefilled],
36
- checked_value: checked_value
37
- %>
36
+ checked_value: checked_value %>
38
37
 
39
38
  <%= render 'chobble_forms/comment_checkbox',
40
39
  comment_field: comment_field,
41
40
  checkbox_id: comment_info[:checkbox_id],
42
41
  textarea_id: comment_info[:options][:id],
43
42
  has_comment: comment_info[:has_comment],
44
- prefilled: comment_info[:prefilled]
45
- %>
43
+ prefilled: comment_info[:prefilled] %>
46
44
 
47
45
  <%= form.text_area comment_field, comment_info[:options] %>
48
- </div>
46
+ </div>
@@ -32,16 +32,14 @@
32
32
  <%= render 'chobble_forms/radio_pass_fail',
33
33
  field: radio_field,
34
34
  prefilled: field_data[:prefilled],
35
- checked_value: checked_value
36
- %>
35
+ checked_value: checked_value %>
37
36
 
38
37
  <%= render 'chobble_forms/comment_checkbox',
39
38
  comment_field: comment_field,
40
39
  checkbox_id: comment_info[:checkbox_id],
41
40
  textarea_id: comment_info[:options][:id],
42
41
  has_comment: comment_info[:has_comment],
43
- prefilled: comment_info[:prefilled]
44
- %>
42
+ prefilled: comment_info[:prefilled] %>
45
43
 
46
44
  <%= form.text_area comment_field, comment_info[:options] %>
47
45
  </div>
@@ -1,11 +1,11 @@
1
1
  <%
2
2
  form = @_current_form
3
3
  yes_no ||= false
4
-
4
+
5
5
  # Check if this field is an enum by looking at the model
6
6
  model = form.object
7
7
  is_enum = model.class.respond_to?(:defined_enums) && model.class.defined_enums.key?(field.to_s)
8
-
8
+
9
9
  if is_enum
10
10
  pass_value = "pass"
11
11
  fail_value = "fail"
@@ -21,22 +21,19 @@
21
21
  <%= t("shared.#{yes_no ? "yes" : "pass"}") %>
22
22
  <%= form.radio_button field,
23
23
  pass_value,
24
- radio_button_options(prefilled, checked_value, pass_value)
25
- %>
24
+ radio_button_options(prefilled, checked_value, pass_value) %>
26
25
  </label>
27
26
  <label>
28
27
  <%= form.radio_button field,
29
28
  fail_value,
30
- radio_button_options(prefilled, checked_value, fail_value)
31
- %>
29
+ radio_button_options(prefilled, checked_value, fail_value) %>
32
30
  <%= t("shared.#{yes_no ? "no" : "fail"}") %>
33
31
  </label>
34
32
  <% if is_enum %>
35
33
  <label>
36
34
  <%= form.radio_button field,
37
35
  na_value,
38
- radio_button_options(prefilled, checked_value, na_value)
39
- %>
36
+ radio_button_options(prefilled, checked_value, na_value) %>
40
37
  <%= t("shared.not_applicable") %>
41
38
  </label>
42
39
  <% end %>
@@ -12,4 +12,4 @@
12
12
  <%= form.text_field field_name, placeholder: placeholder, value: params[field_name] %>
13
13
  <%= form.submit submit_text %>
14
14
  <% end %>
15
- </div>
15
+ </div>
@@ -1 +1,5 @@
1
- <%= @_current_form.submit t("#{@_current_i18n_base}.submit", raise: true) %>
1
+ <%
2
+ submit_key = local_assigns[:submit_key] || "submit"
3
+ submit_text = t("#{@_current_i18n_base}.#{submit_key}", raise: true)
4
+ %>
5
+ <%= @_current_form.submit submit_text %>
@@ -10,6 +10,5 @@
10
10
  field: field,
11
11
  prefilled: field_data[:prefilled],
12
12
  checked_value: checked_value,
13
- yes_no: true
14
- %>
13
+ yes_no: true %>
15
14
  </div>
@@ -33,16 +33,14 @@
33
33
  field: radio_field,
34
34
  prefilled: field_data[:prefilled],
35
35
  checked_value: checked_value,
36
- yes_no: true
37
- %>
36
+ yes_no: true %>
38
37
 
39
38
  <%= render 'chobble_forms/comment_checkbox',
40
39
  comment_field: comment_field,
41
40
  checkbox_id: comment_info[:checkbox_id],
42
41
  textarea_id: comment_info[:options][:id],
43
42
  has_comment: comment_info[:has_comment],
44
- prefilled: comment_info[:prefilled]
45
- %>
43
+ prefilled: comment_info[:prefilled] %>
46
44
 
47
45
  <%= form.text_area comment_field, comment_info[:options] %>
48
- </div>
46
+ </div>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chobble-forms
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chobble.com
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-07 00:00:00.000000000 Z
11
+ date: 2025-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -165,6 +165,9 @@ files:
165
165
  - app/assets/stylesheets/chobble_forms/form_fields.css
166
166
  - app/assets/stylesheets/chobble_forms/form_grids.css
167
167
  - app/assets/stylesheets/chobble_forms/radio_buttons.css
168
+ - app/javascript/comment_toggles.js
169
+ - app/javascript/na_number_toggles.js
170
+ - app/javascript/na_toggles.js
168
171
  - lib/chobble-forms.rb
169
172
  - lib/chobble_forms/engine.rb
170
173
  - lib/chobble_forms/field_utils.rb