easy-admin-rails 0.1.7 → 0.1.9
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/components/easy_admin/combined_filters_component.rb +1 -1
- data/app/components/easy_admin/fields/form/belongs_to_component.rb +10 -1
- data/app/components/easy_admin/fields/form/has_many_component.rb +9 -0
- data/app/components/easy_admin/fields/form/select_component.rb +7 -1
- data/app/components/easy_admin/fields/index/belongs_to_component.rb +34 -5
- data/app/components/easy_admin/fields/index/filters/base_component.rb +2 -1
- data/app/components/easy_admin/fields/index/filters/select_component.rb +89 -23
- data/app/components/easy_admin/filters_component.rb +1 -1
- data/app/components/easy_admin/resources/table_cell_component.rb +10 -1
- data/app/javascript/easy_admin/controllers/select_field_controller.js +37 -1
- data/lib/easy_admin/resource.rb +6 -2
- data/lib/easy_admin/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d516d9564b3e2e33581e101ed5f5fed1fb294407f6de918e2532a3fcef1acea5
|
4
|
+
data.tar.gz: f3f23ffc0489acddce45c29f4324d38397e088fc4b7fc872fb06ed6cf4d07c64
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3632ce6f54bb99a75db6a45471f53f01eb34c132480367f84017c90ab45f4605527f2a87f89a4446e41ff3182decf590c9df0e3d6178034cc713421b41d30ff6
|
7
|
+
data.tar.gz: 20b25f16081adfd5eea8cbbf62d96c35aee0b38da356befed86132d576d20819c9985987ae1d04e557f49b61964492f2c714b1fe9dca981c4867b9dce8051d60
|
@@ -171,7 +171,7 @@ module EasyAdmin
|
|
171
171
|
|
172
172
|
def render_filter_field(field)
|
173
173
|
component_class = filter_component_for_field(field)
|
174
|
-
render component_class.new(field: field, search_params: @search_params)
|
174
|
+
render component_class.new(field: field, search_params: @search_params, resource_class: @resource_class)
|
175
175
|
end
|
176
176
|
|
177
177
|
def filter_component_for_field(field)
|
@@ -54,8 +54,17 @@ module EasyAdmin
|
|
54
54
|
url
|
55
55
|
end
|
56
56
|
|
57
|
-
# Override to
|
57
|
+
# Override to return empty options for suggest mode unless custom options are provided
|
58
58
|
def options
|
59
|
+
# Check if field has custom options (static or proc)
|
60
|
+
if field[:options]
|
61
|
+
opts = field[:options]
|
62
|
+
# If options is a Proc, evaluate it with the form object
|
63
|
+
opts = opts.call(form.object) if opts.is_a?(Proc)
|
64
|
+
return opts
|
65
|
+
end
|
66
|
+
|
67
|
+
# For suggest mode, return empty array
|
59
68
|
[]
|
60
69
|
end
|
61
70
|
|
@@ -284,6 +284,15 @@ module EasyAdmin
|
|
284
284
|
end
|
285
285
|
|
286
286
|
def build_options
|
287
|
+
# Check if field has custom options (static or proc)
|
288
|
+
if field[:options]
|
289
|
+
opts = field[:options]
|
290
|
+
# If options is a Proc, evaluate it with the form object
|
291
|
+
opts = opts.call(form.object) if opts.is_a?(Proc)
|
292
|
+
return opts
|
293
|
+
end
|
294
|
+
|
295
|
+
# Otherwise build from available records
|
287
296
|
available_options.map do |item|
|
288
297
|
[display_text_for(item), item.id]
|
289
298
|
end
|
@@ -46,7 +46,13 @@ module EasyAdmin
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def options
|
49
|
-
field[:options] || []
|
49
|
+
opts = field[:options] || []
|
50
|
+
# If options is a Proc, evaluate it with the form object
|
51
|
+
if opts.is_a?(Proc)
|
52
|
+
opts.call(form.object)
|
53
|
+
else
|
54
|
+
opts
|
55
|
+
end
|
50
56
|
end
|
51
57
|
|
52
58
|
def placeholder
|
@@ -28,21 +28,50 @@ module EasyAdmin
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def load_association_record
|
31
|
-
|
31
|
+
foreign_key_value = get_foreign_key_value
|
32
|
+
return nil if foreign_key_value.blank?
|
32
33
|
|
33
34
|
association_class = determine_association
|
34
35
|
|
35
|
-
if
|
36
|
+
if foreign_key_value.respond_to?(:id)
|
36
37
|
# Already loaded record
|
37
|
-
|
38
|
+
foreign_key_value
|
38
39
|
else
|
39
40
|
# ID value, need to load the record
|
40
|
-
association_class.find_by(id:
|
41
|
+
association_class.find_by(id: foreign_key_value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_foreign_key_value
|
46
|
+
# If foreign_key is explicitly specified, use that field
|
47
|
+
if field[:foreign_key]
|
48
|
+
@record.public_send(field[:foreign_key])
|
49
|
+
elsif field[:association]
|
50
|
+
# Use Rails association reflection to get the foreign key
|
51
|
+
reflection = @record.class.reflect_on_association(field[:association])
|
52
|
+
if reflection
|
53
|
+
@record.public_send(reflection.foreign_key)
|
54
|
+
else
|
55
|
+
# Fallback to the original value (field name)
|
56
|
+
value
|
57
|
+
end
|
58
|
+
else
|
59
|
+
# Default behavior - use the field value
|
60
|
+
value
|
41
61
|
end
|
42
62
|
end
|
43
63
|
|
44
64
|
def determine_association
|
45
|
-
|
65
|
+
if field[:association]
|
66
|
+
# If association is specified, get the class from the model's reflection
|
67
|
+
association_name = field[:association].to_s
|
68
|
+
reflection = @record.class.reflect_on_association(association_name)
|
69
|
+
if reflection
|
70
|
+
return reflection.klass
|
71
|
+
else
|
72
|
+
raise ArgumentError, "Association '#{association_name}' not found on #{@record.class}"
|
73
|
+
end
|
74
|
+
end
|
46
75
|
|
47
76
|
# Try to determine from attribute name
|
48
77
|
if field_name.to_s.end_with?('_id')
|
@@ -3,9 +3,10 @@ module EasyAdmin
|
|
3
3
|
module Index
|
4
4
|
module Filters
|
5
5
|
class BaseComponent < Phlex::HTML
|
6
|
-
def initialize(field:, search_params: {})
|
6
|
+
def initialize(field:, search_params: {}, resource_class: nil)
|
7
7
|
@field = field
|
8
8
|
@search_params = search_params
|
9
|
+
@resource_class = resource_class
|
9
10
|
end
|
10
11
|
|
11
12
|
def view_template
|
@@ -4,6 +4,14 @@ module EasyAdmin
|
|
4
4
|
module Filters
|
5
5
|
class SelectComponent < BaseComponent
|
6
6
|
private
|
7
|
+
|
8
|
+
def filter_field_name
|
9
|
+
@filter_field_name ||= if @field[:type] == :belongs_to
|
10
|
+
"#{@field[:name]}_id"
|
11
|
+
else
|
12
|
+
@field[:name]
|
13
|
+
end
|
14
|
+
end
|
7
15
|
|
8
16
|
def render_filter_input
|
9
17
|
div(
|
@@ -12,22 +20,39 @@ module EasyAdmin
|
|
12
20
|
controller: "select-field",
|
13
21
|
select_field_multiple_value: "false",
|
14
22
|
select_field_placeholder_value: "Select #{@field[:label].downcase}...",
|
23
|
+
select_field_suggest_value: @field[:suggest].to_s,
|
24
|
+
select_field_suggest_url_value: @field[:suggest] ? suggest_url : "",
|
15
25
|
field_name: @field[:name]
|
16
26
|
}
|
17
27
|
) do
|
18
|
-
# Display input
|
28
|
+
# Display/Search input
|
19
29
|
div(class: "relative") do
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
30
|
+
if @field[:suggest]
|
31
|
+
# For suggest mode, use a search input
|
32
|
+
input(
|
33
|
+
type: "text",
|
34
|
+
class: single_select_input_classes,
|
35
|
+
placeholder: "Type to search...",
|
36
|
+
value: current_display_value,
|
37
|
+
data: {
|
38
|
+
select_field_target: "search",
|
39
|
+
action: "input->select-field#filter keydown->select-field#handleKeydown focus->select-field#openDropdown"
|
40
|
+
}
|
41
|
+
)
|
42
|
+
else
|
43
|
+
# For static mode, use display input
|
44
|
+
input(
|
45
|
+
type: "text",
|
46
|
+
class: single_select_input_classes,
|
47
|
+
placeholder: "Select #{@field[:label].downcase}...",
|
48
|
+
readonly: true,
|
49
|
+
value: current_display_value,
|
50
|
+
data: {
|
51
|
+
select_field_target: "display",
|
52
|
+
action: "click->select-field#toggleDropdown"
|
53
|
+
}
|
54
|
+
)
|
55
|
+
end
|
31
56
|
# Dropdown arrow
|
32
57
|
div(class: "absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none") do
|
33
58
|
unsafe_raw('<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>')
|
@@ -39,27 +64,35 @@ module EasyAdmin
|
|
39
64
|
class: "select-dropdown absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-auto opacity-0 invisible transform scale-95 transition-all duration-200 ease-out",
|
40
65
|
data: { select_field_target: "dropdown" }
|
41
66
|
) do
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
67
|
+
if @field[:suggest]
|
68
|
+
# For suggest mode, options will be loaded dynamically
|
69
|
+
render_no_results_message
|
70
|
+
render_loading_message
|
71
|
+
else
|
72
|
+
# For static mode, render all options
|
73
|
+
render_option("", "All")
|
74
|
+
|
75
|
+
if @field[:options]
|
76
|
+
@field[:options].each do |option_value, option_label|
|
77
|
+
render_option(option_value, option_label)
|
78
|
+
end
|
47
79
|
end
|
80
|
+
render_no_results_message
|
48
81
|
end
|
49
82
|
end
|
50
83
|
|
51
84
|
# Hidden input for the actual value
|
52
85
|
input(
|
53
86
|
type: "hidden",
|
54
|
-
name: "q[#{
|
55
|
-
value: @search_params["#{
|
87
|
+
name: "q[#{filter_field_name}_eq]",
|
88
|
+
value: @search_params["#{filter_field_name}_eq"] || "",
|
56
89
|
data: { select_field_target: "hiddenInput" }
|
57
90
|
)
|
58
91
|
end
|
59
92
|
end
|
60
93
|
|
61
94
|
def render_option(value, text)
|
62
|
-
selected = @search_params["#{
|
95
|
+
selected = @search_params["#{filter_field_name}_eq"].to_s == value.to_s
|
63
96
|
|
64
97
|
div(
|
65
98
|
class: "select-option px-3 py-2 text-sm cursor-pointer hover:bg-blue-50 hover:text-blue-900 transition-colors duration-150 #{selected ? 'bg-blue-50 text-blue-900' : 'text-gray-900'}",
|
@@ -74,15 +107,48 @@ module EasyAdmin
|
|
74
107
|
end
|
75
108
|
|
76
109
|
def current_display_value
|
77
|
-
return "All" if @search_params["#{
|
110
|
+
return "All" if @search_params["#{filter_field_name}_eq"].blank?
|
78
111
|
|
79
112
|
if @field[:options]
|
80
113
|
@field[:options].each do |option_value, option_label|
|
81
|
-
return option_label.to_s if option_value.to_s == @search_params["#{
|
114
|
+
return option_label.to_s if option_value.to_s == @search_params["#{filter_field_name}_eq"].to_s
|
82
115
|
end
|
83
116
|
end
|
84
117
|
|
85
|
-
@search_params["#{
|
118
|
+
@search_params["#{filter_field_name}_eq"].to_s
|
119
|
+
end
|
120
|
+
|
121
|
+
def render_no_results_message
|
122
|
+
div(
|
123
|
+
class: "select-no-results px-3 py-2 text-sm text-gray-500 text-center",
|
124
|
+
data: { select_field_target: "noResults" },
|
125
|
+
style: "display: none;"
|
126
|
+
) do
|
127
|
+
plain "No options found"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def render_loading_message
|
132
|
+
div(
|
133
|
+
class: "select-loading px-3 py-2 text-sm text-gray-500 text-center",
|
134
|
+
data: { select_field_target: "loading" },
|
135
|
+
style: "display: none;"
|
136
|
+
) do
|
137
|
+
plain "Loading..."
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def suggest_url
|
142
|
+
return "" unless @field[:suggest]
|
143
|
+
|
144
|
+
# Use the current resource being filtered (e.g., posts when filtering posts by user)
|
145
|
+
resource_name = @resource_class.route_key
|
146
|
+
|
147
|
+
easy_admin_url_helpers.suggest_resource_path(resource_name, field: @field[:name])
|
148
|
+
end
|
149
|
+
|
150
|
+
def easy_admin_url_helpers
|
151
|
+
EasyAdmin::Engine.routes.url_helpers
|
86
152
|
end
|
87
153
|
|
88
154
|
def single_select_input_classes
|
@@ -76,7 +76,7 @@ module EasyAdmin
|
|
76
76
|
|
77
77
|
def render_filter_field(field)
|
78
78
|
component_class = filter_component_for_field(field)
|
79
|
-
render component_class.new(field: field, search_params: @search_params)
|
79
|
+
render component_class.new(field: field, search_params: @search_params, resource_class: @resource_class)
|
80
80
|
end
|
81
81
|
|
82
82
|
def filter_component_for_field(field)
|
@@ -28,7 +28,7 @@ module EasyAdmin
|
|
28
28
|
|
29
29
|
def render_field_value
|
30
30
|
# Use the existing field rendering logic from the Rails views
|
31
|
-
field_value =
|
31
|
+
field_value = get_field_value
|
32
32
|
|
33
33
|
# Use the same render_field helper that the ERB templates use
|
34
34
|
unsafe_raw render_field(
|
@@ -38,6 +38,15 @@ module EasyAdmin
|
|
38
38
|
record: @record
|
39
39
|
)
|
40
40
|
end
|
41
|
+
|
42
|
+
def get_field_value
|
43
|
+
# For belongs_to fields, use the association name if specified
|
44
|
+
if @field_config[:type] == :belongs_to && @field_config[:association]
|
45
|
+
@record.public_send(@field_config[:association])
|
46
|
+
else
|
47
|
+
@record.public_send(@field_config[:name])
|
48
|
+
end
|
49
|
+
end
|
41
50
|
|
42
51
|
def render_inline_edit_trigger
|
43
52
|
div do
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
2
2
|
|
3
3
|
export default class extends Controller {
|
4
|
-
static targets = ["search", "dropdown", "selectedItems", "option", "hiddenInput", "display", "noResults", "loading"]
|
4
|
+
static targets = ["search", "dropdown", "selectedItems", "option", "hiddenInput", "display", "noResults", "loading", "searchInput", "optionsContainer"]
|
5
5
|
static values = { multiple: Boolean, placeholder: String, suggest: Boolean, suggestUrl: String }
|
6
6
|
|
7
7
|
connect() {
|
@@ -615,4 +615,40 @@ export default class extends Controller {
|
|
615
615
|
this.loadingTarget.style.display = 'none'
|
616
616
|
}
|
617
617
|
}
|
618
|
+
|
619
|
+
// Filter options for index page filters
|
620
|
+
filterOptions(event) {
|
621
|
+
const searchTerm = event.target.value.toLowerCase()
|
622
|
+
|
623
|
+
if (this.hasOptionsContainerTarget) {
|
624
|
+
// For index page filters
|
625
|
+
const options = this.optionsContainerTarget.querySelectorAll('[data-select-field-target="option"]')
|
626
|
+
let visibleCount = 0
|
627
|
+
|
628
|
+
options.forEach(option => {
|
629
|
+
const text = option.textContent.toLowerCase()
|
630
|
+
if (text.includes(searchTerm)) {
|
631
|
+
option.style.display = 'block'
|
632
|
+
visibleCount++
|
633
|
+
} else {
|
634
|
+
option.style.display = 'none'
|
635
|
+
}
|
636
|
+
})
|
637
|
+
|
638
|
+
// Show/hide no results message
|
639
|
+
if (visibleCount === 0 && this.hasNoResultsTarget) {
|
640
|
+
this.noResultsTarget.style.display = 'block'
|
641
|
+
this.noResultsTarget.textContent = 'No results found'
|
642
|
+
} else if (this.hasNoResultsTarget) {
|
643
|
+
this.noResultsTarget.style.display = 'none'
|
644
|
+
}
|
645
|
+
} else {
|
646
|
+
// Fallback to existing filter logic
|
647
|
+
this.filter(event)
|
648
|
+
}
|
649
|
+
}
|
650
|
+
|
651
|
+
stopPropagation(event) {
|
652
|
+
event.stopPropagation()
|
653
|
+
}
|
618
654
|
}
|
data/lib/easy_admin/resource.rb
CHANGED
@@ -56,7 +56,9 @@ module EasyAdmin
|
|
56
56
|
suggest: options[:suggest], # for dynamic option loading
|
57
57
|
display_method: options[:display_method], # for association fields
|
58
58
|
help_text: options[:help_text],
|
59
|
-
editable: options[:editable]
|
59
|
+
editable: options[:editable],
|
60
|
+
association: options[:association],
|
61
|
+
foreign_key: options[:foreign_key] # for belongs_to fields
|
60
62
|
}]
|
61
63
|
end
|
62
64
|
|
@@ -321,7 +323,9 @@ module EasyAdmin
|
|
321
323
|
multiple: options[:multiple],
|
322
324
|
placeholder: options[:placeholder],
|
323
325
|
help_text: options[:help_text],
|
324
|
-
editable: options[:editable]
|
326
|
+
editable: options[:editable],
|
327
|
+
association: options[:association],
|
328
|
+
foreign_key: options[:foreign_key]
|
325
329
|
}
|
326
330
|
|
327
331
|
if @current_tab
|
data/lib/easy_admin/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: easy-admin-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Slaurmagan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-09-
|
11
|
+
date: 2025-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|