easy-admin-rails 0.2.6 → 0.2.7
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/builds/easy_admin.base.js +7 -0
- data/app/assets/builds/easy_admin.base.js.map +2 -2
- data/app/assets/builds/easy_admin.css +207 -35
- data/app/components/easy_admin/fields/form/belongs_to_component.rb +0 -1
- data/app/components/easy_admin/form_layout_component.rb +553 -0
- data/app/components/easy_admin/permissions/user_role_permissions_component.rb +1 -3
- data/app/components/easy_admin/show_layout_component.rb +694 -24
- data/app/controllers/easy_admin/application_controller.rb +0 -5
- data/app/controllers/easy_admin/batch_actions_controller.rb +0 -1
- data/app/controllers/easy_admin/concerns/inline_field_editing.rb +4 -11
- data/app/controllers/easy_admin/concerns/resource_loading.rb +10 -9
- data/app/controllers/easy_admin/concerns/resource_pagination.rb +3 -0
- data/app/controllers/easy_admin/dashboards_controller.rb +0 -1
- data/app/controllers/easy_admin/resources_controller.rb +1 -5
- data/app/controllers/easy_admin/row_actions_controller.rb +1 -4
- data/app/helpers/easy_admin/fields_helper.rb +8 -22
- data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +12 -0
- data/app/views/easy_admin/resources/edit.html.erb +2 -2
- data/app/views/easy_admin/resources/new.html.erb +2 -2
- data/app/views/easy_admin/resources/show.html.erb +3 -1
- data/lib/easy_admin/field.rb +3 -2
- data/lib/easy_admin/layouts/builders/base_layout_builder.rb +245 -0
- data/lib/easy_admin/layouts/builders/form_layout_builder.rb +208 -0
- data/lib/easy_admin/layouts/builders/index_layout_builder.rb +22 -0
- data/lib/easy_admin/layouts/builders/show_layout_builder.rb +199 -0
- data/lib/easy_admin/layouts/dsl.rb +200 -0
- data/lib/easy_admin/layouts/layout_context.rb +189 -0
- data/lib/easy_admin/layouts/nodes/base_node.rb +88 -0
- data/lib/easy_admin/layouts/nodes/divider.rb +27 -0
- data/lib/easy_admin/layouts/nodes/field_node.rb +57 -0
- data/lib/easy_admin/layouts/nodes/grid.rb +60 -0
- data/lib/easy_admin/layouts/nodes/render_node.rb +41 -0
- data/lib/easy_admin/layouts/nodes/root.rb +25 -0
- data/lib/easy_admin/layouts/nodes/section.rb +46 -0
- data/lib/easy_admin/layouts/nodes/spacer.rb +17 -0
- data/lib/easy_admin/layouts/nodes/stubs.rb +109 -0
- data/lib/easy_admin/layouts/nodes/tab.rb +40 -0
- data/lib/easy_admin/layouts/nodes/tabs.rb +40 -0
- data/lib/easy_admin/layouts.rb +28 -0
- data/lib/easy_admin/permissions/resource_permissions.rb +1 -5
- data/lib/easy_admin/resource/base.rb +2 -2
- data/lib/easy_admin/resource/dsl.rb +2 -11
- data/lib/easy_admin/resource/field_registry.rb +58 -2
- data/lib/easy_admin/resource.rb +0 -9
- data/lib/easy_admin/resource_modules.rb +21 -4
- data/lib/easy_admin/version.rb +1 -1
- data/lib/generators/easy_admin/permissions/install_generator.rb +0 -10
- data/lib/generators/easy_admin/permissions/templates/migrations/create_permission_tables.rb +33 -3
- metadata +21 -9
- data/lib/easy_admin/resource/form_builder.rb +0 -123
- data/lib/easy_admin/resource/layout_builder.rb +0 -249
- data/lib/easy_admin/resource/show_builder.rb +0 -359
- data/lib/generators/easy_admin/permissions/templates/migrations/update_users_for_permissions.rb +0 -6
- data/lib/generators/easy_admin/rbac/rbac_generator.rb +0 -244
- data/lib/generators/easy_admin/rbac/templates/add_rbac_to_admin_users.rb +0 -23
- data/lib/generators/easy_admin/rbac/templates/super_admin.rb +0 -34
@@ -103,11 +103,6 @@ module EasyAdmin
|
|
103
103
|
private
|
104
104
|
|
105
105
|
def handle_authorization_failure(exception)
|
106
|
-
Rails.logger.warn "🚫 Authorization failed: #{exception.message}"
|
107
|
-
Rails.logger.warn "🚫 User: #{current_admin_user&.email} (Role: #{current_admin_user&.role&.name || 'None'})"
|
108
|
-
Rails.logger.warn "🚫 Action: #{action_name} on #{params[:resource_name]}"
|
109
|
-
|
110
|
-
# Extract meaningful error details
|
111
106
|
action_name = extract_action_from_exception(exception)
|
112
107
|
resource_name = extract_resource_from_exception(exception)
|
113
108
|
|
@@ -56,9 +56,7 @@ module EasyAdmin
|
|
56
56
|
if !field_config
|
57
57
|
field_config = @resource_class.fields_config.find { |f| f[:association].to_s == params[:field] }
|
58
58
|
end
|
59
|
-
|
60
|
-
Rails.logger.debug "Suggest field_config for #{params[:field]}: #{field_config}"
|
61
|
-
|
59
|
+
|
62
60
|
unless field_config && field_config[:suggest]
|
63
61
|
render json: { error: "Field not found or suggest not configured" }, status: :not_found
|
64
62
|
return
|
@@ -237,10 +235,7 @@ module EasyAdmin
|
|
237
235
|
display_method = field_config[:display_method] || :name
|
238
236
|
suggest_config = field_config[:suggest] || {}
|
239
237
|
search_fields = suggest_config[:search_fields] || [display_method]
|
240
|
-
|
241
|
-
Rails.logger.debug "HasMany suggest: association=#{association_name}, display_method=#{display_method}, search_fields=#{search_fields}"
|
242
|
-
|
243
|
-
# Build search query
|
238
|
+
|
244
239
|
records = association_class.all
|
245
240
|
|
246
241
|
if search_term.present?
|
@@ -261,7 +256,6 @@ module EasyAdmin
|
|
261
256
|
results = records.limit(limit).map do |record|
|
262
257
|
label = if record.respond_to?(display_method)
|
263
258
|
raw_label = record.public_send(display_method)
|
264
|
-
Rails.logger.debug "HasMany record #{record.id}: raw_label=#{raw_label.class}:#{raw_label}"
|
265
259
|
raw_label.to_s
|
266
260
|
elsif record.respond_to?(:name)
|
267
261
|
record.name.to_s
|
@@ -272,8 +266,7 @@ module EasyAdmin
|
|
272
266
|
end
|
273
267
|
[label, record.id]
|
274
268
|
end
|
275
|
-
|
276
|
-
Rails.logger.debug "HasMany suggest final results: #{results}"
|
269
|
+
|
277
270
|
results
|
278
271
|
end
|
279
272
|
|
@@ -294,4 +287,4 @@ module EasyAdmin
|
|
294
287
|
end
|
295
288
|
end
|
296
289
|
end
|
297
|
-
end
|
290
|
+
end
|
@@ -57,14 +57,12 @@ module EasyAdmin
|
|
57
57
|
# Check both form fields and all fields (including hidden ones)
|
58
58
|
all_fields = resource_class.form_fields + resource_class.fields_config
|
59
59
|
unique_fields = all_fields.uniq { |f| f[:name] }
|
60
|
-
|
61
|
-
Rails.logger.debug "🔍 [ResourceLoading] All fields for #{resource_class}:"
|
60
|
+
|
62
61
|
unique_fields.each_with_index do |field_config, index|
|
63
|
-
Rails.logger.debug "🔍 [ResourceLoading] Field #{index}: #{field_config[:name]} (type: #{field_config[:type]}, readonly: #{field_config[:readonly]})"
|
64
62
|
next if field_config[:readonly]
|
65
63
|
|
66
64
|
field_name = field_config[:name]
|
67
|
-
|
65
|
+
|
68
66
|
# Handle different field types that might need special parameter handling
|
69
67
|
case field_config[:type]
|
70
68
|
when :has_many
|
@@ -87,8 +85,12 @@ module EasyAdmin
|
|
87
85
|
permitted_attrs << field_name
|
88
86
|
end
|
89
87
|
when :json
|
90
|
-
# For JSON fields, permit
|
91
|
-
|
88
|
+
# For JSON fields, permit as string (JSONEditor submits as JSON string)
|
89
|
+
if field_name == :permissions_cache
|
90
|
+
permitted_attrs << { field_name => {} }
|
91
|
+
else
|
92
|
+
permitted_attrs << field_name
|
93
|
+
end
|
92
94
|
else
|
93
95
|
# For regular fields, check if it's the permissions_cache field which needs hash permission
|
94
96
|
if field_name == :permissions_cache
|
@@ -98,8 +100,7 @@ module EasyAdmin
|
|
98
100
|
end
|
99
101
|
end
|
100
102
|
end
|
101
|
-
|
102
|
-
Rails.logger.debug "🔍 [ResourceLoading] Final permitted attributes: #{permitted_attrs.inspect}"
|
103
|
+
|
103
104
|
permitted_attrs
|
104
105
|
end
|
105
106
|
|
@@ -146,4 +147,4 @@ module EasyAdmin
|
|
146
147
|
end
|
147
148
|
end
|
148
149
|
end
|
149
|
-
end
|
150
|
+
end
|
@@ -114,6 +114,9 @@ module EasyAdmin
|
|
114
114
|
|
115
115
|
# Check if pagination should reset due to filtering
|
116
116
|
def should_reset_pagination?
|
117
|
+
# Don't reset pagination for turbo_stream requests (infinite scroll)
|
118
|
+
return false if request.format.turbo_stream?
|
119
|
+
|
117
120
|
has_active_filters? && !first_page?
|
118
121
|
end
|
119
122
|
|
@@ -78,11 +78,7 @@ module EasyAdmin
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def update
|
81
|
-
Rails.logger.debug "Update called with params: #{record_params}"
|
82
|
-
Rails.logger.debug "Record before update: #{@record.inspect}"
|
83
|
-
|
84
81
|
if @record.update(record_params)
|
85
|
-
Rails.logger.debug "Record after update: #{@record.reload.inspect}"
|
86
82
|
respond_to do |format|
|
87
83
|
format.html {
|
88
84
|
redirect_to easy_admin.resource_path(@resource_class.route_key, @record),
|
@@ -183,4 +179,4 @@ module EasyAdmin
|
|
183
179
|
end
|
184
180
|
end
|
185
181
|
end
|
186
|
-
end
|
182
|
+
end
|
@@ -72,9 +72,6 @@ module EasyAdmin
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
rescue => e
|
75
|
-
Rails.logger.error "Row action execution failed: #{e.message}"
|
76
|
-
Rails.logger.error e.backtrace.join("\n")
|
77
|
-
|
78
75
|
respond_to do |format|
|
79
76
|
format.turbo_stream do
|
80
77
|
render turbo_stream: turbo_stream.update(
|
@@ -238,4 +235,4 @@ module EasyAdmin
|
|
238
235
|
end
|
239
236
|
end
|
240
237
|
end
|
241
|
-
end
|
238
|
+
end
|
@@ -17,43 +17,26 @@ module EasyAdmin
|
|
17
17
|
return ""
|
18
18
|
when :custom_content
|
19
19
|
if field[:block]
|
20
|
-
Rails.logger.debug "🔍 [FieldsHelper] Processing custom_content block"
|
21
|
-
Rails.logger.debug "🔍 [FieldsHelper] Form present: #{form.present?}"
|
22
|
-
Rails.logger.debug "🔍 [FieldsHelper] Record present: #{record.present?}"
|
23
|
-
|
24
|
-
# Create a context object with access to form, record, and helpers
|
25
20
|
context = OpenStruct.new(form: form, record: record, helpers: self)
|
26
|
-
|
27
|
-
# Call the block directly to get the result (don't use capture which converts to string)
|
21
|
+
|
28
22
|
result = field[:block].call(context)
|
29
|
-
|
30
|
-
Rails.logger.debug "🔍 [FieldsHelper] Block result class: #{result.class}"
|
31
|
-
Rails.logger.debug "🔍 [FieldsHelper] Result responds to call: #{result.respond_to?(:call)}"
|
32
|
-
Rails.logger.debug "🔍 [FieldsHelper] Result responds to view_template: #{result.respond_to?(:view_template)}"
|
33
|
-
|
34
|
-
# Handle different types of results
|
35
23
|
if result.respond_to?(:call) && result.respond_to?(:view_template)
|
36
|
-
Rails.logger.debug "🔍 [FieldsHelper] Rendering Phlex component"
|
37
|
-
# It's a Phlex component - use Rails render helper
|
38
24
|
render(result)
|
39
25
|
elsif result.is_a?(String)
|
40
|
-
Rails.logger.debug "🔍 [FieldsHelper] Rendering string result"
|
41
26
|
result.html_safe
|
42
27
|
elsif result.respond_to?(:to_s)
|
43
|
-
Rails.logger.debug "🔍 [FieldsHelper] Converting to string and rendering"
|
44
28
|
result.to_s.html_safe
|
45
29
|
else
|
46
|
-
Rails.logger.debug "🔍 [FieldsHelper] No valid result, returning empty string"
|
47
30
|
""
|
48
31
|
end
|
49
32
|
else
|
50
|
-
Rails.logger.debug "🔍 [FieldsHelper] No block provided for custom_content"
|
51
33
|
""
|
52
34
|
end
|
53
35
|
else
|
54
36
|
# Regular field rendering
|
37
|
+
field_type = field[:type] || field[:field_type] || :text
|
55
38
|
component = EasyAdmin::Field.render(
|
56
|
-
|
39
|
+
field_type,
|
57
40
|
action: action,
|
58
41
|
field: field,
|
59
42
|
value: value,
|
@@ -61,13 +44,16 @@ module EasyAdmin
|
|
61
44
|
form: form
|
62
45
|
)
|
63
46
|
|
64
|
-
component.call
|
47
|
+
result = component.call
|
48
|
+
|
49
|
+
result.html_safe
|
65
50
|
end
|
66
51
|
end
|
67
52
|
|
68
53
|
def field_component(field, action:, value: nil, record: nil, form: nil)
|
54
|
+
field_type = field[:type] || field[:field_type] || :text
|
69
55
|
EasyAdmin::Field.render(
|
70
|
-
|
56
|
+
field_type,
|
71
57
|
action: action,
|
72
58
|
field: field,
|
73
59
|
value: value,
|
@@ -19,14 +19,26 @@ export default class extends Controller {
|
|
19
19
|
// Called when the element is replaced by turbo stream
|
20
20
|
urlValueChanged() {
|
21
21
|
if (this.observer && this.sentinel) {
|
22
|
+
// Reset loading state when URL changes (e.g., when filters are applied)
|
23
|
+
this.isLoading = false
|
24
|
+
this.hideLoading()
|
25
|
+
|
22
26
|
// Re-setup observer with new URL
|
23
27
|
this.setupIntersectionObserver()
|
28
|
+
|
29
|
+
// Re-initialize UI state
|
30
|
+
this.initializeUI()
|
24
31
|
}
|
25
32
|
}
|
26
33
|
|
27
34
|
hasMoreValueChanged() {
|
28
35
|
if (!this.hasMoreValue) {
|
29
36
|
this.showEndMessage()
|
37
|
+
} else {
|
38
|
+
// Hide end message if we have more pages (e.g., after filters change)
|
39
|
+
if (this.hasEndTarget) {
|
40
|
+
this.endTarget.style.display = 'none'
|
41
|
+
}
|
30
42
|
}
|
31
43
|
}
|
32
44
|
|
@@ -116,8 +116,8 @@
|
|
116
116
|
<% end %>
|
117
117
|
|
118
118
|
<!-- Form Fields -->
|
119
|
-
<% if @resource_class.
|
120
|
-
|
119
|
+
<% if @resource_class.has_custom_form_layout? %>
|
120
|
+
<%== EasyAdmin::FormLayoutComponent.new(resource_class: @resource_class, form: form, record: @record).call %>
|
121
121
|
<% else %>
|
122
122
|
<!-- Default Single Card Layout -->
|
123
123
|
<div class="bg-white shadow-sm rounded-lg border border-gray-200">
|
@@ -65,8 +65,8 @@
|
|
65
65
|
<% end %>
|
66
66
|
|
67
67
|
<!-- Form Fields -->
|
68
|
-
<% if @resource_class.
|
69
|
-
|
68
|
+
<% if @resource_class.has_custom_form_layout? %>
|
69
|
+
<%== EasyAdmin::FormLayoutComponent.new(resource_class: @resource_class, form: form).call %>
|
70
70
|
<% else %>
|
71
71
|
<!-- Default Single Card Layout -->
|
72
72
|
<div class="bg-white shadow-sm rounded-lg border border-gray-200">
|
@@ -27,5 +27,7 @@
|
|
27
27
|
|
28
28
|
<!-- Show Layout Content -->
|
29
29
|
<div class="show-content">
|
30
|
-
|
30
|
+
<div class="container-fluid">
|
31
|
+
<%== EasyAdmin::ShowLayoutComponent.new(resource_class: @resource_class, record: @record).call %>
|
32
|
+
</div>
|
31
33
|
</div>
|
data/lib/easy_admin/field.rb
CHANGED
@@ -92,9 +92,9 @@ module EasyAdmin
|
|
92
92
|
|
93
93
|
begin
|
94
94
|
action_component_name.constantize
|
95
|
-
rescue NameError
|
95
|
+
rescue NameError => e
|
96
96
|
# Fallback to base text component for the action
|
97
|
-
case action
|
97
|
+
fallback_class = case action
|
98
98
|
when :index
|
99
99
|
EasyAdmin::Fields::Index::TextComponent
|
100
100
|
when :show
|
@@ -104,6 +104,7 @@ module EasyAdmin
|
|
104
104
|
else
|
105
105
|
EasyAdmin::Fields::Index::TextComponent
|
106
106
|
end
|
107
|
+
fallback_class
|
107
108
|
end
|
108
109
|
end
|
109
110
|
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Layouts
|
3
|
+
module Builders
|
4
|
+
# Base builder for constructing layout AST
|
5
|
+
class BaseLayoutBuilder
|
6
|
+
attr_reader :root_node, :resource_class
|
7
|
+
attr_accessor :current_container_stack
|
8
|
+
|
9
|
+
def initialize(layout_type = :show, resource_class: nil)
|
10
|
+
@root_node = Nodes::Root.new(layout_type: layout_type)
|
11
|
+
@current_container_stack = [@root_node]
|
12
|
+
@resource_class = resource_class
|
13
|
+
end
|
14
|
+
|
15
|
+
# Get current container for adding nodes
|
16
|
+
def current_container
|
17
|
+
@current_container_stack.last
|
18
|
+
end
|
19
|
+
|
20
|
+
# Add node to current container
|
21
|
+
def add_node(node)
|
22
|
+
current_container.add_child(node)
|
23
|
+
node
|
24
|
+
end
|
25
|
+
|
26
|
+
# Execute block with new container
|
27
|
+
def with_container(container_node, &block)
|
28
|
+
add_node(container_node)
|
29
|
+
@current_container_stack.push(container_node)
|
30
|
+
instance_exec(&block) if block_given?
|
31
|
+
@current_container_stack.pop
|
32
|
+
container_node
|
33
|
+
end
|
34
|
+
|
35
|
+
# DSL Methods
|
36
|
+
|
37
|
+
# Add tabs container
|
38
|
+
def tabs(type: :horizontal, **attributes, &block)
|
39
|
+
tabs_node = Nodes::Tabs.new(attributes.merge(type: type))
|
40
|
+
add_node(tabs_node)
|
41
|
+
|
42
|
+
if block_given?
|
43
|
+
@current_container_stack.push(tabs_node)
|
44
|
+
tabs_builder = TabsBuilder.new(self, tabs_node)
|
45
|
+
tabs_builder.instance_exec(&block)
|
46
|
+
@current_container_stack.pop
|
47
|
+
end
|
48
|
+
|
49
|
+
tabs_node
|
50
|
+
end
|
51
|
+
|
52
|
+
# Add section
|
53
|
+
def section(title = nil, **attributes, &block)
|
54
|
+
section_node = Nodes::Section.new(title, attributes)
|
55
|
+
|
56
|
+
if block_given?
|
57
|
+
with_container(section_node, &block)
|
58
|
+
else
|
59
|
+
add_node(section_node)
|
60
|
+
end
|
61
|
+
|
62
|
+
section_node
|
63
|
+
end
|
64
|
+
|
65
|
+
# Add grid layout
|
66
|
+
def grid(columns: 2, **attributes, &block)
|
67
|
+
grid_node = Nodes::Grid.new(attributes.merge(columns: columns))
|
68
|
+
|
69
|
+
if block_given?
|
70
|
+
with_container(grid_node, &block)
|
71
|
+
else
|
72
|
+
add_node(grid_node)
|
73
|
+
end
|
74
|
+
|
75
|
+
grid_node
|
76
|
+
end
|
77
|
+
|
78
|
+
# Add field
|
79
|
+
def field(name, **options)
|
80
|
+
field_node = Nodes::FieldNode.new(name, options)
|
81
|
+
add_node(field_node)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Add multiple fields at once
|
85
|
+
def fields(*names, **common_options)
|
86
|
+
names.each do |name|
|
87
|
+
field(name, **common_options)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Render custom component
|
92
|
+
def render(component_class, **props)
|
93
|
+
render_node = Nodes::RenderNode.new(component_class, props)
|
94
|
+
add_node(render_node)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Add divider
|
98
|
+
def divider(**attributes)
|
99
|
+
add_node(Nodes::Divider.new(attributes))
|
100
|
+
end
|
101
|
+
|
102
|
+
# Add spacer
|
103
|
+
def spacer(size: 4, **attributes)
|
104
|
+
add_node(Nodes::Spacer.new(attributes.merge(size: size)))
|
105
|
+
end
|
106
|
+
|
107
|
+
# Build and return the AST
|
108
|
+
def build
|
109
|
+
@root_node
|
110
|
+
end
|
111
|
+
|
112
|
+
# Allow custom DSL extensions
|
113
|
+
def method_missing(method, *args, &block)
|
114
|
+
# Check if it's a registered component type
|
115
|
+
if EasyAdmin::Layouts.registered_components.key?(method)
|
116
|
+
component_class = EasyAdmin::Layouts.registered_components[method]
|
117
|
+
render(component_class, *args)
|
118
|
+
else
|
119
|
+
super
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def respond_to_missing?(method, include_private = false)
|
124
|
+
EasyAdmin::Layouts.registered_components.key?(method) ||
|
125
|
+
field_method?(method) ||
|
126
|
+
super
|
127
|
+
end
|
128
|
+
|
129
|
+
# Field DSL delegation methods - delegate to resource class
|
130
|
+
|
131
|
+
def id_field(**options)
|
132
|
+
delegate_to_resource(:id_field, **options)
|
133
|
+
field(:id, type: :number, **options)
|
134
|
+
end
|
135
|
+
|
136
|
+
def text_field(name, **options)
|
137
|
+
delegate_to_resource(:text_field, name, **options)
|
138
|
+
field(name, type: :string, **options)
|
139
|
+
end
|
140
|
+
|
141
|
+
def textarea_field(name, **options)
|
142
|
+
delegate_to_resource(:textarea_field, name, **options)
|
143
|
+
field(name, type: :text, **options)
|
144
|
+
end
|
145
|
+
|
146
|
+
def number_field(name, **options)
|
147
|
+
delegate_to_resource(:number_field, name, **options)
|
148
|
+
field(name, type: :number, **options)
|
149
|
+
end
|
150
|
+
|
151
|
+
def email_field(name, **options)
|
152
|
+
delegate_to_resource(:email_field, name, **options)
|
153
|
+
field(name, type: :email, **options)
|
154
|
+
end
|
155
|
+
|
156
|
+
def date_field(name, **options)
|
157
|
+
delegate_to_resource(:date_field, name, **options)
|
158
|
+
field(name, type: :date, **options)
|
159
|
+
end
|
160
|
+
|
161
|
+
def datetime_field(name, **options)
|
162
|
+
delegate_to_resource(:datetime_field, name, **options)
|
163
|
+
field(name, type: :datetime, **options)
|
164
|
+
end
|
165
|
+
|
166
|
+
def boolean_field(name, **options)
|
167
|
+
delegate_to_resource(:boolean_field, name, **options)
|
168
|
+
field(name, type: :boolean, **options)
|
169
|
+
end
|
170
|
+
|
171
|
+
def select_field(name, **options)
|
172
|
+
delegate_to_resource(:select_field, name, **options)
|
173
|
+
field(name, type: :select, **options)
|
174
|
+
end
|
175
|
+
|
176
|
+
def belongs_to_field(name, **options)
|
177
|
+
delegate_to_resource(:belongs_to_field, name, **options)
|
178
|
+
field(name, type: :belongs_to, **options)
|
179
|
+
end
|
180
|
+
|
181
|
+
def has_many_field(name, **options)
|
182
|
+
delegate_to_resource(:has_many_field, name, **options)
|
183
|
+
field(name, type: :has_many, **options)
|
184
|
+
end
|
185
|
+
|
186
|
+
def file_field(name, **options)
|
187
|
+
delegate_to_resource(:file_field, name, **options)
|
188
|
+
field(name, type: :file, **options)
|
189
|
+
end
|
190
|
+
|
191
|
+
def json_field(name, **options)
|
192
|
+
delegate_to_resource(:json_field, name, **options)
|
193
|
+
field(name, type: :json, **options)
|
194
|
+
end
|
195
|
+
|
196
|
+
def password_field(name = :password, **options)
|
197
|
+
delegate_to_resource(:password_field, name, **options)
|
198
|
+
field(name, type: :password, **options)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Delegate content method calls
|
202
|
+
def content(&block)
|
203
|
+
content_node = Nodes::Content.new(block: block)
|
204
|
+
add_node(content_node)
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
def field_method?(method)
|
210
|
+
method.to_s.end_with?('_field') && resource_class&.respond_to?(method)
|
211
|
+
end
|
212
|
+
|
213
|
+
def delegate_to_resource(method, *args, **options)
|
214
|
+
if resource_class
|
215
|
+
resource_class.send(method, *args, **options)
|
216
|
+
else
|
217
|
+
raise "Resource class not available for field delegation"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Specialized builder for tabs
|
223
|
+
class TabsBuilder
|
224
|
+
def initialize(parent_builder, tabs_node)
|
225
|
+
@parent_builder = parent_builder
|
226
|
+
@tabs_node = tabs_node
|
227
|
+
end
|
228
|
+
|
229
|
+
def tab(name, **attributes, &block)
|
230
|
+
tab_node = Nodes::Tab.new(name, attributes)
|
231
|
+
@tabs_node.add_child(tab_node)
|
232
|
+
|
233
|
+
if block_given?
|
234
|
+
# Push tab as current container and execute block in parent builder context
|
235
|
+
@parent_builder.current_container_stack.push(tab_node)
|
236
|
+
@parent_builder.instance_exec(&block)
|
237
|
+
@parent_builder.current_container_stack.pop
|
238
|
+
end
|
239
|
+
|
240
|
+
tab_node
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|