chobble-forms 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +3 -0
- data/lib/chobble-forms.rb +6 -0
- data/lib/engine.rb +21 -0
- data/lib/helpers.rb +206 -0
- data/lib/version.rb +3 -0
- data/views/chobble_forms/_auto_submit_select.html.erb +55 -0
- data/views/chobble_forms/_autocomplete_field.html.erb +17 -0
- data/views/chobble_forms/_checkbox.html.erb +12 -0
- data/views/chobble_forms/_comment.html.erb +40 -0
- data/views/chobble_forms/_comment_checkbox.html.erb +18 -0
- data/views/chobble_forms/_date_field.html.erb +15 -0
- data/views/chobble_forms/_decimal_comment.html.erb +58 -0
- data/views/chobble_forms/_display_field.html.erb +11 -0
- data/views/chobble_forms/_errors.html.erb +26 -0
- data/views/chobble_forms/_field_with_link.html.erb +33 -0
- data/views/chobble_forms/_fields.html.erb +9 -0
- data/views/chobble_forms/_fieldset.html.erb +29 -0
- data/views/chobble_forms/_file_field.html.erb +29 -0
- data/views/chobble_forms/_form_context.html.erb +74 -0
- data/views/chobble_forms/_header.html.erb +3 -0
- data/views/chobble_forms/_integer_comment.html.erb +58 -0
- data/views/chobble_forms/_number.html.erb +29 -0
- data/views/chobble_forms/_number_pass_fail_comment.html.erb +73 -0
- data/views/chobble_forms/_number_pass_fail_na_comment.html.erb +83 -0
- data/views/chobble_forms/_pass_fail.html.erb +14 -0
- data/views/chobble_forms/_pass_fail_comment.html.erb +47 -0
- data/views/chobble_forms/_pass_fail_na_comment.html.erb +49 -0
- data/views/chobble_forms/_radio_comment.html.erb +47 -0
- data/views/chobble_forms/_radio_pass_fail.html.erb +43 -0
- data/views/chobble_forms/_search_field.html.erb +15 -0
- data/views/chobble_forms/_select.html.erb +41 -0
- data/views/chobble_forms/_submit_button.html.erb +1 -0
- data/views/chobble_forms/_text_area.html.erb +22 -0
- data/views/chobble_forms/_text_field.html.erb +20 -0
- data/views/chobble_forms/_yes_no_radio.html.erb +15 -0
- data/views/chobble_forms/_yes_no_radio_comment.html.erb +48 -0
- metadata +186 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a667bfdadedd91552428ae7b8de49675340618beabd0a6784941861805a4655e
|
4
|
+
data.tar.gz: 6461e968376cf809af015f83dc89fd8cde783a79819c68faca28d3a938a945bb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ac860f4f3d10aa3ab790b19a49871d8802a099ea4868f626888d2bd7dd29de2901ec604bd4f4fe31e7e47b327ac870d7565007c6ea18c0f6ac9c097e5d6429a3
|
7
|
+
data.tar.gz: 7c32149a54105636daffb3b88dc74327293cd3c0bd60abc4396ce256cac2562fa732fc1a17e66426c52947deb7251c8205bb60f1b2b6bad47f95021ec305149b
|
data/README.md
ADDED
data/lib/engine.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module ChobbleForms
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace ChobbleForms
|
4
|
+
|
5
|
+
initializer "chobble_forms.add_view_paths" do |app|
|
6
|
+
ActiveSupport.on_load(:action_controller) do
|
7
|
+
prepend_view_path ChobbleForms::Engine.root.join("views")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
config.to_prepare do
|
12
|
+
ApplicationController.helper(ChobbleForms::Helpers)
|
13
|
+
end
|
14
|
+
|
15
|
+
initializer "chobble_forms.view_helpers" do
|
16
|
+
ActiveSupport.on_load(:action_view) do
|
17
|
+
include ChobbleForms::Helpers
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/helpers.rb
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
require "action_view"
|
2
|
+
|
3
|
+
module ChobbleForms
|
4
|
+
module Helpers
|
5
|
+
include ActionView::Helpers::NumberHelper
|
6
|
+
def form_field_setup(field, local_assigns)
|
7
|
+
validate_local_assigns(local_assigns)
|
8
|
+
validate_form_context
|
9
|
+
|
10
|
+
field_translations = build_field_translations(field)
|
11
|
+
value, prefilled = get_field_value_and_prefilled_status(@_current_form,
|
12
|
+
field)
|
13
|
+
|
14
|
+
build_field_setup_result(field_translations, value, prefilled)
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_field_value_and_prefilled_status(form_object, field)
|
18
|
+
return [nil, false] unless form_object&.object
|
19
|
+
model = form_object.object
|
20
|
+
resolved = resolve_field_value(model, field)
|
21
|
+
[resolved[:value], resolved[:prefilled]]
|
22
|
+
end
|
23
|
+
|
24
|
+
def comment_field_options(form, comment_field, base_field_label)
|
25
|
+
raise ArgumentError, "form_object required" unless form
|
26
|
+
model = form.object
|
27
|
+
|
28
|
+
comment_value, comment_prefilled =
|
29
|
+
get_field_value_and_prefilled_status(
|
30
|
+
form,
|
31
|
+
comment_field
|
32
|
+
)
|
33
|
+
|
34
|
+
has_comment = comment_value.present?
|
35
|
+
|
36
|
+
base_field = comment_field.to_s.chomp("_comment")
|
37
|
+
|
38
|
+
placeholder_text = t("shared.field_comment_placeholder", field: base_field_label)
|
39
|
+
textarea_id = "#{base_field}_comment_textarea_#{model.object_id}"
|
40
|
+
checkbox_id = "#{base_field}_has_comment_#{model.object_id}"
|
41
|
+
display_style = has_comment ? "block" : "none"
|
42
|
+
|
43
|
+
{
|
44
|
+
options: {
|
45
|
+
rows: 2,
|
46
|
+
placeholder: placeholder_text,
|
47
|
+
id: textarea_id,
|
48
|
+
style: "display: #{display_style};",
|
49
|
+
value: comment_value
|
50
|
+
},
|
51
|
+
prefilled: comment_prefilled,
|
52
|
+
has_comment: has_comment,
|
53
|
+
checkbox_id: checkbox_id
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def radio_button_options(prefilled, checked_value, expected_value)
|
58
|
+
(prefilled && checked_value == expected_value) ? {checked: true} : {}
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
ALLOWED_LOCAL_ASSIGNS = %i[
|
64
|
+
accept
|
65
|
+
field
|
66
|
+
max
|
67
|
+
min
|
68
|
+
number_options
|
69
|
+
options
|
70
|
+
preview_size
|
71
|
+
required
|
72
|
+
rows
|
73
|
+
step
|
74
|
+
type
|
75
|
+
]
|
76
|
+
|
77
|
+
def validate_local_assigns(local_assigns)
|
78
|
+
if local_assigns[:field].present? &&
|
79
|
+
local_assigns[:field].to_s.match?(/^[A-Z]/)
|
80
|
+
raise ArgumentError, "Field names must be snake_case symbols, not class names. Use :field, not Field."
|
81
|
+
end
|
82
|
+
|
83
|
+
locally_assigned_keys = (local_assigns || {}).keys
|
84
|
+
disallowed_keys = locally_assigned_keys - ALLOWED_LOCAL_ASSIGNS
|
85
|
+
|
86
|
+
if disallowed_keys.any?
|
87
|
+
raise ArgumentError, "local_assigns contains #{disallowed_keys.inspect}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def validate_form_context
|
92
|
+
raise ArgumentError, "missing i18n_base" unless @_current_i18n_base
|
93
|
+
raise ArgumentError, "missing form_object" unless @_current_form
|
94
|
+
end
|
95
|
+
|
96
|
+
def build_field_translations(field)
|
97
|
+
fields_key = "#{@_current_i18n_base}.fields.#{field}"
|
98
|
+
field_label = t(fields_key, raise: true)
|
99
|
+
|
100
|
+
base_parts = @_current_i18n_base.split(".")
|
101
|
+
root = base_parts[0..-2]
|
102
|
+
hint_key = (root + ["hints", field]).join(".")
|
103
|
+
placeholder_key = (root + ["placeholders", field]).join(".")
|
104
|
+
|
105
|
+
{
|
106
|
+
field_label:,
|
107
|
+
field_hint: t(hint_key, default: nil),
|
108
|
+
field_placeholder: t(placeholder_key, default: nil)
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_field_setup_result(field_translations, value, prefilled)
|
113
|
+
{
|
114
|
+
form_object: @_current_form,
|
115
|
+
i18n_base: @_current_i18n_base,
|
116
|
+
value:,
|
117
|
+
prefilled:
|
118
|
+
}.merge(field_translations)
|
119
|
+
end
|
120
|
+
|
121
|
+
def resolve_field_value(model, field)
|
122
|
+
field_str = field.to_s
|
123
|
+
|
124
|
+
# Never return values for password fields
|
125
|
+
if field_str.include?("password")
|
126
|
+
return {value: nil, prefilled: false}
|
127
|
+
end
|
128
|
+
|
129
|
+
# Check current model value
|
130
|
+
current_value = model.send(field) if model.respond_to?(field)
|
131
|
+
|
132
|
+
# Check if this field should be excluded from prefilling
|
133
|
+
if defined?(InspectionsController::NOT_COPIED_FIELDS) &&
|
134
|
+
InspectionsController::NOT_COPIED_FIELDS.include?(field_str)
|
135
|
+
return {value: current_value, prefilled: false}
|
136
|
+
end
|
137
|
+
|
138
|
+
# Extract previous value if available
|
139
|
+
previous_value = extract_previous_value(@previous_inspection, model, field)
|
140
|
+
|
141
|
+
# Return previous value if current is nil and previous exists
|
142
|
+
if current_value.nil? && !previous_value.nil?
|
143
|
+
return {
|
144
|
+
value: format_numeric_value(previous_value),
|
145
|
+
prefilled: true
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
if field_str.end_with?("_id") && field_str != "id"
|
150
|
+
resolve_association_value(model, field_str)
|
151
|
+
else
|
152
|
+
# Always return current value, even if nil
|
153
|
+
{value: current_value, prefilled: false}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def extract_previous_value(previous_inspection, current_model, field)
|
158
|
+
if !previous_inspection
|
159
|
+
nil
|
160
|
+
elsif current_model.class.name.include?("Assessment")
|
161
|
+
assessment_type = current_model.class.name.demodulize.underscore
|
162
|
+
previous_model = previous_inspection.send(assessment_type)
|
163
|
+
previous_model&.send(field)
|
164
|
+
else
|
165
|
+
previous_inspection.send(field)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def format_numeric_value(value)
|
170
|
+
if value.is_a?(String) &&
|
171
|
+
value.match?(/\A-?\d*\.?\d+\z/) &&
|
172
|
+
(float_value = Float(value, exception: false))
|
173
|
+
value = float_value
|
174
|
+
end
|
175
|
+
|
176
|
+
return value unless value.is_a?(Numeric)
|
177
|
+
|
178
|
+
number_with_precision(
|
179
|
+
value,
|
180
|
+
precision: 4,
|
181
|
+
strip_insignificant_zeros: true
|
182
|
+
)
|
183
|
+
end
|
184
|
+
|
185
|
+
def resolve_association_value(model, field_str)
|
186
|
+
base_name = field_str.chomp("_id")
|
187
|
+
association_name = base_name.to_sym
|
188
|
+
|
189
|
+
if model.respond_to?(association_name)
|
190
|
+
{value: model.send(association_name), prefilled: true}
|
191
|
+
elsif model.respond_to?(field_str)
|
192
|
+
value = model.send(field_str)
|
193
|
+
if value && model.class.reflect_on_association(association_name)
|
194
|
+
associated = model.class
|
195
|
+
.reflect_on_association(association_name)
|
196
|
+
.klass.find_by(id: value)
|
197
|
+
{value: associated, prefilled: true}
|
198
|
+
else
|
199
|
+
{value: value, prefilled: true}
|
200
|
+
end
|
201
|
+
else
|
202
|
+
{value: nil, prefilled: false}
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
data/lib/version.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
<%
|
2
|
+
# Auto-submit select field options
|
3
|
+
field = local_assigns[:field] or raise ArgumentError, "field is required for auto submit select"
|
4
|
+
options = local_assigns[:options] or raise ArgumentError, "options is required for auto submit select"
|
5
|
+
|
6
|
+
# Check if we're in a form context with form object or need standalone
|
7
|
+
if local_assigns[:form]
|
8
|
+
form_object = local_assigns[:form]
|
9
|
+
selected_value = params[field] || form_object.object.send(field) rescue nil
|
10
|
+
else
|
11
|
+
# Standalone usage - must provide url
|
12
|
+
url = local_assigns[:url] or raise ArgumentError, "url is required for standalone auto submit select"
|
13
|
+
selected_value = params[field]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Optional parameters
|
17
|
+
label = local_assigns[:label]
|
18
|
+
include_blank = local_assigns[:include_blank]
|
19
|
+
blank_text = local_assigns[:blank_text] || "All"
|
20
|
+
turbo_disabled = local_assigns.has_key?(:turbo_disabled) ? local_assigns[:turbo_disabled] : true
|
21
|
+
preserve_params = local_assigns[:preserve_params] || []
|
22
|
+
%>
|
23
|
+
|
24
|
+
<% if local_assigns[:form] %>
|
25
|
+
<% if label %>
|
26
|
+
<%= form_object.label field, label %>
|
27
|
+
<% end %>
|
28
|
+
|
29
|
+
<%= form_object.select field,
|
30
|
+
options_for_select(options, selected_value),
|
31
|
+
include_blank ? { include_blank: blank_text } : {},
|
32
|
+
{ onchange: "this.form.submit();" } %>
|
33
|
+
|
34
|
+
<% else %>
|
35
|
+
<!-- Standalone auto-submit select form -->
|
36
|
+
<%= form_with url: url, method: :get, data: (turbo_disabled ? { turbo: false } : {}) do |form| %>
|
37
|
+
|
38
|
+
<!-- Preserve other parameters -->
|
39
|
+
<% preserve_params.each do |param| %>
|
40
|
+
<% if params[param].present? %>
|
41
|
+
<%= form.hidden_field param, value: params[param] %>
|
42
|
+
<% end %>
|
43
|
+
<% end %>
|
44
|
+
|
45
|
+
<% if label %>
|
46
|
+
<%= form.label field, label %>
|
47
|
+
<% end %>
|
48
|
+
|
49
|
+
<%= form.select field,
|
50
|
+
options_for_select(options, selected_value),
|
51
|
+
include_blank ? { include_blank: blank_text } : {},
|
52
|
+
{ onchange: "this.form.submit();" } %>
|
53
|
+
|
54
|
+
<% end %>
|
55
|
+
<% end %>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<%
|
2
|
+
setup = form_field_setup(field, local_assigns)
|
3
|
+
|
4
|
+
field_options = {
|
5
|
+
required: local_assigns[:required] || false,
|
6
|
+
list: "#{field}_autocomplete_#{setup[:form_object].object.object_id}",
|
7
|
+
value: setup[:value]
|
8
|
+
}
|
9
|
+
%>
|
10
|
+
|
11
|
+
<%= setup[:form_object].label field, setup[:field_label] %>
|
12
|
+
<%= setup[:form_object].text_field field, field_options %>
|
13
|
+
<datalist id="<%= field_options[:list] %>">
|
14
|
+
<% options&.each do |option| %>
|
15
|
+
<option value="<%= option %>"></option>
|
16
|
+
<% end %>
|
17
|
+
</datalist>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<%
|
2
|
+
# Get i18n_base from section (already validated there)
|
3
|
+
setup = form_field_setup(field, local_assigns)
|
4
|
+
%>
|
5
|
+
|
6
|
+
<%= setup[:form_object].label field do %>
|
7
|
+
<%= setup[:form_object].check_box field %>
|
8
|
+
<%= setup[:field_label] %>
|
9
|
+
<% if setup[:field_hint].present? %>
|
10
|
+
<small><%= setup[:field_hint] %></small>
|
11
|
+
<% end %>
|
12
|
+
<% end %>
|
@@ -0,0 +1,40 @@
|
|
1
|
+
<%
|
2
|
+
# Form partial for comment fields with toggle visibility
|
3
|
+
# Required parameters:
|
4
|
+
# field: Symbol representing the field name
|
5
|
+
# Optional parameters:
|
6
|
+
# maxlength: Maximum character length (default: 1000)
|
7
|
+
# placeholder: Placeholder text
|
8
|
+
|
9
|
+
maxlength ||= 1000
|
10
|
+
placeholder ||= nil
|
11
|
+
|
12
|
+
form = @_current_form
|
13
|
+
checkbox_id = "toggle_#{field}_#{form.object.object_id}"
|
14
|
+
textarea_id = "#{form.object_name}_#{field}"
|
15
|
+
|
16
|
+
# Check if there's existing content
|
17
|
+
has_content = form.object.send(field).present?
|
18
|
+
%>
|
19
|
+
|
20
|
+
<div class="comment-field-container">
|
21
|
+
<label for="<%= checkbox_id %>">
|
22
|
+
<input type="checkbox" id="<%= checkbox_id %>"
|
23
|
+
<%= 'checked' if has_content %>
|
24
|
+
data-comment-toggle="<%= textarea_id %>"
|
25
|
+
data-comment-container="<%= textarea_id %>_container"
|
26
|
+
>
|
27
|
+
<%= t("shared.comment") %>
|
28
|
+
</label>
|
29
|
+
|
30
|
+
<div id="<%= textarea_id %>_container"
|
31
|
+
style="display: <%= has_content ? 'block' : 'none' %>"
|
32
|
+
>
|
33
|
+
<%= form.text_field field,
|
34
|
+
maxlength: maxlength,
|
35
|
+
placeholder: placeholder,
|
36
|
+
id: textarea_id,
|
37
|
+
title: "Max #{maxlength} characters"
|
38
|
+
%>
|
39
|
+
</div>
|
40
|
+
</div>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<%
|
2
|
+
# Partial for comment checkbox
|
3
|
+
# Required parameters:
|
4
|
+
# comment_field: The comment field name (e.g., :width_comment)
|
5
|
+
# checkbox_id: Unique ID for the checkbox
|
6
|
+
# textarea_id: ID of the textarea to show/hide
|
7
|
+
# has_comment: Whether comment exists
|
8
|
+
%>
|
9
|
+
|
10
|
+
<label for="<%= checkbox_id %>" class="comment-checkbox">
|
11
|
+
<input type="checkbox"
|
12
|
+
id="<%= checkbox_id %>"
|
13
|
+
<%= 'checked' if has_comment %>
|
14
|
+
data-comment-toggle="<%= textarea_id %>"
|
15
|
+
data-comment-container="<%= textarea_id %>"
|
16
|
+
>
|
17
|
+
<%= t('shared.comment') %>
|
18
|
+
</label>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<%
|
2
|
+
setup = form_field_setup(field, local_assigns)
|
3
|
+
required ||= false
|
4
|
+
field_options = {
|
5
|
+
placeholder: setup[:field_placeholder],
|
6
|
+
required: required
|
7
|
+
}.compact
|
8
|
+
field_options[:value] = local_assigns[:value] if local_assigns[:value]
|
9
|
+
%>
|
10
|
+
|
11
|
+
<%= setup[:form_object].label field, setup[:field_label] %>
|
12
|
+
<%= setup[:form_object].send(:date_field, field, field_options) %>
|
13
|
+
<% if setup[:field_hint].present? %>
|
14
|
+
<small><%= setup[:field_hint] %></small>
|
15
|
+
<% end %>
|
@@ -0,0 +1,58 @@
|
|
1
|
+
<%
|
2
|
+
# Composite partial for decimal + comment fields with grid layout
|
3
|
+
# Required parameters:
|
4
|
+
# field: Base field name (e.g., 'platform_height')
|
5
|
+
# Optional parameters:
|
6
|
+
# min: Minimum value
|
7
|
+
# max: Maximum value
|
8
|
+
# required: Whether the decimal field is required
|
9
|
+
# placeholder: Placeholder text
|
10
|
+
#
|
11
|
+
# This will render:
|
12
|
+
# - Decimal field: field (e.g., 'platform_height')
|
13
|
+
# - Comment field: field + '_comment' (e.g., 'platform_height_comment')
|
14
|
+
|
15
|
+
decimal_field = field
|
16
|
+
comment_field = "#{field}_comment".to_sym
|
17
|
+
|
18
|
+
form = @_current_form
|
19
|
+
model = form.object
|
20
|
+
|
21
|
+
field_data = form_field_setup(field, local_assigns)
|
22
|
+
|
23
|
+
decimal_options = {
|
24
|
+
inputmode: "decimal",
|
25
|
+
pattern: "[0-9]*[.]?[0-9]*",
|
26
|
+
placeholder: field_data[:field_placeholder] || local_assigns[:placeholder],
|
27
|
+
required: local_assigns[:required] || false,
|
28
|
+
class: "number"
|
29
|
+
}
|
30
|
+
|
31
|
+
if field_data[:prefilled]
|
32
|
+
decimal_options[:value] = field_data[:value]
|
33
|
+
end
|
34
|
+
|
35
|
+
decimal_options[:data] = {}
|
36
|
+
decimal_options[:data][:min] = local_assigns[:min] if local_assigns[:min]
|
37
|
+
decimal_options[:data][:max] = local_assigns[:max] if local_assigns[:max]
|
38
|
+
|
39
|
+
comment_info = comment_field_options(
|
40
|
+
form,
|
41
|
+
comment_field,
|
42
|
+
field_data[:field_label]
|
43
|
+
)
|
44
|
+
%>
|
45
|
+
|
46
|
+
<div class="form-grid number-comment" id="<%= decimal_field %>">
|
47
|
+
<%= form.label decimal_field, field_data[:field_label], class: "label" %>
|
48
|
+
<%= form.text_field decimal_field, decimal_options %>
|
49
|
+
|
50
|
+
<%= render 'chobble_forms/comment_checkbox',
|
51
|
+
comment_field: comment_field,
|
52
|
+
checkbox_id: comment_info[:checkbox_id],
|
53
|
+
textarea_id: comment_info[:options][:id],
|
54
|
+
has_comment: comment_info[:has_comment],
|
55
|
+
prefilled: comment_info[:prefilled] %>
|
56
|
+
|
57
|
+
<%= form.text_area comment_field, comment_info[:options] %>
|
58
|
+
</div>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<%
|
2
|
+
# Get i18n_base from section (already validated there)
|
3
|
+
setup = form_field_setup(field, local_assigns)
|
4
|
+
|
5
|
+
# Get value from model object automatically
|
6
|
+
model_object = setup[:form_object].object
|
7
|
+
value = model_object.send(field) if model_object.respond_to?(field)
|
8
|
+
%>
|
9
|
+
|
10
|
+
<%= setup[:form_object].label field, setup[:field_label] %>
|
11
|
+
<p><%= value %></p>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<%
|
2
|
+
# Form errors component - displays validation errors for any model object
|
3
|
+
model_object = local_assigns[:model] || local_assigns[:object]
|
4
|
+
raise ArgumentError, "model object is required for form errors" if model_object.nil?
|
5
|
+
|
6
|
+
# Use inherited i18n_base from form context
|
7
|
+
i18n_base = @_current_i18n_base
|
8
|
+
raise ArgumentError, "i18n_base is required for form errors" if i18n_base.nil?
|
9
|
+
|
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,
|
14
|
+
raise: true)
|
15
|
+
%>
|
16
|
+
|
17
|
+
<% if model_object.errors.any? %>
|
18
|
+
<aside class="form-errors" role="alert">
|
19
|
+
<h3><%= header_text %></h3>
|
20
|
+
<ul>
|
21
|
+
<% model_object.errors.each do |error| %>
|
22
|
+
<li><%= error.full_message %></li>
|
23
|
+
<% end %>
|
24
|
+
</ul>
|
25
|
+
</aside>
|
26
|
+
<% end %>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<%
|
2
|
+
# Field with optional link to the right
|
3
|
+
# Required locals:
|
4
|
+
# - field_type: :text_field or :autocomplete_field
|
5
|
+
# - field: field name
|
6
|
+
# - required: boolean
|
7
|
+
# Optional locals:
|
8
|
+
# - link_url: URL for the link
|
9
|
+
# - link_text: Text for the link
|
10
|
+
# - options: options for autocomplete_field
|
11
|
+
|
12
|
+
field_type = local_assigns[:field_type] || :text_field
|
13
|
+
field = local_assigns[:field]
|
14
|
+
required = local_assigns[:required] || false
|
15
|
+
link_url = local_assigns[:link_url]
|
16
|
+
link_text = local_assigns[:link_text]
|
17
|
+
options = local_assigns[:options] || []
|
18
|
+
|
19
|
+
has_link = link_url.present? && link_text.present?
|
20
|
+
css_classes = has_link ? "field field-with-link" : "field"
|
21
|
+
%>
|
22
|
+
|
23
|
+
<div class="<%= css_classes %>">
|
24
|
+
<% if field_type == :autocomplete_field %>
|
25
|
+
<%= render 'chobble_forms/autocomplete_field', field: field, required: required, options: options %>
|
26
|
+
<% else %>
|
27
|
+
<%= render 'chobble_forms/text_field', field: field, required: required %>
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
<% if has_link %>
|
31
|
+
<%= link_to link_text, link_url %>
|
32
|
+
<% end %>
|
33
|
+
</div>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<% model.class.form_fields(user: current_user).each do |fieldset| %>
|
2
|
+
<%= render 'chobble_forms/fieldset', legend_key: fieldset[:legend_i18n_key] do %>
|
3
|
+
<% fieldset[:fields].each do |field_config| %>
|
4
|
+
<% render_options = { field: field_config[:field] } %>
|
5
|
+
<% render_options.merge!(field_config[:attributes]) if field_config[:attributes] %>
|
6
|
+
<%= render "chobble_forms/#{field_config[:partial]}", render_options %>
|
7
|
+
<% end %>
|
8
|
+
<% end %>
|
9
|
+
<% end %>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<%
|
2
|
+
# Set form and i18n context for child form controls
|
3
|
+
@_current_form = local_assigns[:form] || @_current_form
|
4
|
+
i18n_base = @_current_i18n_base || local_assigns[:i18n_base]
|
5
|
+
raise ArgumentError, "i18n_base is required for form fieldsets" if i18n_base.nil?
|
6
|
+
@_current_i18n_base = i18n_base
|
7
|
+
|
8
|
+
# Determine legend text
|
9
|
+
if local_assigns[:legend]
|
10
|
+
legend_text = local_assigns[:legend]
|
11
|
+
elsif local_assigns[:legend_key] && i18n_base
|
12
|
+
# Remove .fields suffix if present to get to sections level
|
13
|
+
sections_base = i18n_base.sub(/\.fields$/, '')
|
14
|
+
legend_text = t("#{sections_base}.sections.#{local_assigns[:legend_key]}")
|
15
|
+
# Fail loudly if translation is missing
|
16
|
+
if legend_text.start_with?("translation missing:")
|
17
|
+
raise "Missing i18n key: #{sections_base}.sections.#{local_assigns[:legend_key]}"
|
18
|
+
end
|
19
|
+
else
|
20
|
+
legend_text = local_assigns[:legend_key]&.to_s&.humanize || "Section"
|
21
|
+
end
|
22
|
+
%>
|
23
|
+
|
24
|
+
<fieldset>
|
25
|
+
<% if legend_text.present? %>
|
26
|
+
<legend><%= legend_text %></legend>
|
27
|
+
<% end %>
|
28
|
+
<%= yield %>
|
29
|
+
</fieldset>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<%
|
2
|
+
# Get i18n_base from section (already validated there)
|
3
|
+
setup = form_field_setup(field, local_assigns)
|
4
|
+
|
5
|
+
# Form field options
|
6
|
+
accept = local_assigns[:accept] || "image/*"
|
7
|
+
preview_size = local_assigns[:preview_size] || 200
|
8
|
+
model = setup[:form_object].object
|
9
|
+
current_file = model.send(field) if model.respond_to?(field)
|
10
|
+
%>
|
11
|
+
|
12
|
+
<%= setup[:form_object].label field, setup[:field_label] %>
|
13
|
+
<%= setup[:form_object].file_field field, accept: accept %>
|
14
|
+
|
15
|
+
<% if current_file&.attached? %>
|
16
|
+
<div class="file-preview" style="margin-top: 10px;">
|
17
|
+
<% if current_file.image? %>
|
18
|
+
<%= image_tag current_file.variant(resize_to_limit: [preview_size, preview_size]),
|
19
|
+
alt: "Current #{setup[:field_label].downcase}" %>
|
20
|
+
<p><small>Current <%= setup[:field_label].downcase %>: <strong><%= current_file.filename %></strong></small></p>
|
21
|
+
<% else %>
|
22
|
+
<p>Current <%= setup[:field_label].downcase %>: <strong><%= current_file.filename %></strong></p>
|
23
|
+
<% end %>
|
24
|
+
</div>
|
25
|
+
<% end %>
|
26
|
+
|
27
|
+
<% if setup[:field_hint].present? %>
|
28
|
+
<small class="form-text"><%= setup[:field_hint] %></small>
|
29
|
+
<% end %>
|