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.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/chobble_forms/form_grids.css +103 -90
- data/app/javascript/comment_toggles.js +76 -0
- data/app/javascript/na_number_toggles.js +64 -0
- data/app/javascript/na_toggles.js +76 -0
- data/lib/chobble_forms/engine.rb +1 -0
- data/lib/chobble_forms/helpers.rb +1 -0
- data/lib/chobble_forms/version.rb +1 -1
- data/views/chobble_forms/_auto_submit_select.html.erb +9 -9
- data/views/chobble_forms/_comment.html.erb +3 -6
- data/views/chobble_forms/_comment_checkbox.html.erb +1 -2
- data/views/chobble_forms/_errors.html.erb +6 -6
- data/views/chobble_forms/_field_with_link.html.erb +4 -4
- data/views/chobble_forms/_fields.html.erb +1 -1
- data/views/chobble_forms/_fieldset.html.erb +1 -1
- data/views/chobble_forms/_file_field_turbo_response.html.erb +2 -2
- data/views/chobble_forms/_form_context.html.erb +10 -12
- data/views/chobble_forms/_header.html.erb +1 -1
- data/views/chobble_forms/_integer_comment.html.erb +25 -6
- data/views/chobble_forms/_number.html.erb +1 -2
- data/views/chobble_forms/_number_pass_fail_comment.html.erb +3 -6
- data/views/chobble_forms/_number_pass_fail_na_comment.html.erb +5 -10
- data/views/chobble_forms/_pass_fail.html.erb +1 -2
- data/views/chobble_forms/_pass_fail_comment.html.erb +2 -4
- data/views/chobble_forms/_pass_fail_na_comment.html.erb +3 -5
- data/views/chobble_forms/_radio_comment.html.erb +2 -4
- data/views/chobble_forms/_radio_pass_fail.html.erb +5 -8
- data/views/chobble_forms/_search_field.html.erb +1 -1
- data/views/chobble_forms/_submit_button.html.erb +5 -1
- data/views/chobble_forms/_yes_no_radio.html.erb +1 -2
- data/views/chobble_forms/_yes_no_radio_comment.html.erb +3 -5
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b92fb8d1150671025f0475b802e6484f337b554b072c1cd8464eb721e83df397
|
4
|
+
data.tar.gz: 1fa3020e94941bb44a3abb8fed8117939e236dfa3da8eb118bf86fcf4f37b718
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d198ecfde80af5079d2b137d3977e3d53691e79c97ba118d17e6474291092e7e38788c376a10c750b60a72b6494720513430568cce0803d0087ae24221cc1519
|
7
|
+
data.tar.gz: ac1658d4113f8970f5859837231567affeb0c790de34cc7afb98cf1cbbc7c9eea0de83cb0634d65b05e22c31f3cf716a0b3eca27242703081cc294d2f0e41cf3
|
@@ -3,206 +3,219 @@
|
|
3
3
|
*/
|
4
4
|
|
5
5
|
.form-grid {
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
display: grid;
|
7
|
+
gap: 1rem;
|
8
|
+
align-items: end;
|
9
|
+
padding-top: 1rem;
|
10
10
|
}
|
11
11
|
|
12
12
|
legend + .form-grid {
|
13
|
-
|
13
|
+
border-top: none;
|
14
14
|
}
|
15
15
|
|
16
16
|
/* Radio + Comment grid layout */
|
17
17
|
.radio-comment {
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
36
|
+
grid-area: label;
|
37
37
|
}
|
38
38
|
|
39
39
|
.radio-comment > .label label {
|
40
|
-
|
40
|
+
flex-direction: column;
|
41
41
|
}
|
42
42
|
|
43
43
|
.radio-comment > .pass-fail {
|
44
|
-
|
44
|
+
grid-area: pass-fail;
|
45
45
|
}
|
46
46
|
|
47
47
|
.radio-comment > .comment-checkbox {
|
48
|
-
|
48
|
+
grid-area: comment-label;
|
49
49
|
}
|
50
50
|
|
51
51
|
.radio-comment > textarea {
|
52
|
-
|
52
|
+
grid-area: comment;
|
53
53
|
}
|
54
54
|
|
55
55
|
/* Number + Pass/Fail + Comment grid layout */
|
56
56
|
.number-radio-comment {
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
76
|
+
grid-area: label;
|
77
77
|
}
|
78
78
|
|
79
79
|
.number-radio-comment > .label label {
|
80
|
-
|
80
|
+
flex-direction: column;
|
81
81
|
}
|
82
82
|
|
83
83
|
.number-radio-comment > .number {
|
84
|
-
|
84
|
+
grid-area: number;
|
85
85
|
}
|
86
86
|
|
87
87
|
.number-radio-comment > .pass-fail {
|
88
|
-
|
88
|
+
grid-area: pass-fail;
|
89
89
|
}
|
90
90
|
|
91
91
|
.number-radio-comment > .comment-checkbox {
|
92
|
-
|
92
|
+
grid-area: comment-label;
|
93
93
|
}
|
94
94
|
|
95
95
|
.number-radio-comment > textarea {
|
96
|
-
|
96
|
+
grid-area: comment;
|
97
97
|
}
|
98
98
|
|
99
99
|
/* Number + Comment grid layout */
|
100
100
|
.number-comment {
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
120
|
-
|
119
|
+
grid-area: label;
|
120
|
+
width: 14rem;
|
121
121
|
}
|
122
122
|
|
123
123
|
.number-comment > .number {
|
124
|
-
|
124
|
+
grid-area: number;
|
125
125
|
}
|
126
126
|
|
127
127
|
.number-comment > .comment-checkbox {
|
128
|
-
|
128
|
+
grid-area: comment-label;
|
129
129
|
}
|
130
130
|
|
131
131
|
.number-comment > textarea {
|
132
|
-
|
132
|
+
grid-area: comment;
|
133
133
|
}
|
134
134
|
|
135
135
|
/* Checkbox + Comment grid layout */
|
136
136
|
.checkbox-comment {
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
-
|
149
|
-
|
150
|
-
|
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
|
-
|
155
|
+
grid-area: label;
|
156
156
|
}
|
157
157
|
|
158
158
|
.checkbox-comment > .checkbox {
|
159
|
-
|
159
|
+
grid-area: check1;
|
160
160
|
}
|
161
161
|
|
162
162
|
.checkbox-comment > .checkbox-label {
|
163
|
-
|
163
|
+
grid-area: label2;
|
164
164
|
}
|
165
165
|
|
166
166
|
.checkbox-comment > .comment-checkbox {
|
167
|
-
|
167
|
+
grid-area: comment-label;
|
168
168
|
}
|
169
169
|
|
170
170
|
.checkbox-comment > textarea {
|
171
|
-
|
172
|
-
|
171
|
+
grid-column: 1 / -1;
|
172
|
+
margin-top: 0.5rem;
|
173
173
|
}
|
174
174
|
|
175
175
|
/* Simple number layout */
|
176
176
|
.number {
|
177
|
-
|
178
|
-
|
179
|
-
|
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
|
-
|
183
|
+
grid-area: label;
|
184
184
|
}
|
185
185
|
|
186
186
|
.number-comment > .number {
|
187
|
-
|
188
|
-
|
187
|
+
grid-area: number;
|
188
|
+
width: 14rem;
|
189
189
|
}
|
190
190
|
|
191
191
|
/* User capacity flexbox layout */
|
192
192
|
fieldset#user_capacity {
|
193
|
-
|
194
|
-
|
195
|
-
|
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
|
-
|
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
|
-
|
206
|
-
|
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
|
+
});
|
data/lib/chobble_forms/engine.rb
CHANGED
@@ -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
|
@@ -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>
|
@@ -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>
|
@@ -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
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
67
|
-
|
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 %>
|
@@ -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
|
57
|
+
<div
|
58
|
+
class="form-grid simple"
|
59
|
+
id="<%= integer_field %>">
|
47
60
|
<%= form.label integer_field, field_data[:field_label], class: "label" %>
|
48
|
-
|
49
|
-
|
50
|
-
|
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>
|
@@ -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>
|
@@ -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 %>
|
@@ -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.
|
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-
|
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
|