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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e4ac8d05fd7b8b98a9b0dbaf91dac4adbe81d74c9810fea9e33109d37ef0cdc
4
- data.tar.gz: fb32068394c78d4fe2a0cd937c3a5e7b3c0db314d38f06125408ed4a3e86394d
3
+ metadata.gz: d516d9564b3e2e33581e101ed5f5fed1fb294407f6de918e2532a3fcef1acea5
4
+ data.tar.gz: f3f23ffc0489acddce45c29f4324d38397e088fc4b7fc872fb06ed6cf4d07c64
5
5
  SHA512:
6
- metadata.gz: 250cb7801b3400c293670f50e075391e4b67a7c65a3f34d50d5fe0e0c43b218ac19d81bedf74914f2165082f44c0189ef35e5727056181d81a9372d391ceb627
7
- data.tar.gz: 1fd7d7480949532bdbb683e7f1f2ca9539d0ea399d421471a812e629e4af7c2ace5f111d5783a3df72d0541bad5fa543a771ffb73ca8b50a79eda71746a6db3a
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 always return empty options since we use suggest mode
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
- return nil if value.blank?
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 value.respond_to?(:id)
36
+ if foreign_key_value.respond_to?(:id)
36
37
  # Already loaded record
37
- value
38
+ foreign_key_value
38
39
  else
39
40
  # ID value, need to load the record
40
- association_class.find_by(id: value)
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
- return field[:association] if field[:association]
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
- input(
21
- type: "text",
22
- class: single_select_input_classes,
23
- placeholder: "Select #{@field[:label].downcase}...",
24
- readonly: true,
25
- value: current_display_value,
26
- data: {
27
- select_field_target: "display",
28
- action: "click->select-field#toggleDropdown"
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
- render_option("", "All")
43
-
44
- if @field[:options]
45
- @field[:options].each do |option_value, option_label|
46
- render_option(option_value, option_label)
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[#{@field[:name]}_eq]",
55
- value: @search_params["#{@field[:name]}_eq"] || "",
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["#{@field[:name]}_eq"].to_s == value.to_s
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["#{@field[:name]}_eq"].blank?
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["#{@field[:name]}_eq"].to_s
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["#{@field[:name]}_eq"].to_s
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 = @record.public_send(@field_config[:name])
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
  }
@@ -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
@@ -1,3 +1,3 @@
1
1
  module EasyAdmin
2
- VERSION = "0.1.7"
2
+ VERSION = "0.1.9"
3
3
  end
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.7
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-01 00:00:00.000000000 Z
11
+ date: 2025-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails