Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<%= field_wrapper(**field_wrapper_args) do %>
<%= @form.select @field.id, options_for_select(@field.options_for_select, selected: @field.value), {
<%= @form.select @field.id, options, {
include_blank: @field.include_blank
},
multiple: @field.multiple,
Expand Down
7 changes: 7 additions & 0 deletions app/components/avo/fields/select_field/edit_component.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
# frozen_string_literal: true

class Avo::Fields::SelectField::EditComponent < Avo::Fields::EditComponent
def options
if @field.grouped_options.present?
grouped_options_for_select(@field.grouped_options, selected: @field.value)
else
options_for_select(@field.options_for_select, selected: @field.value)
end
end
end
3 changes: 3 additions & 0 deletions db/factories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@
factory :volunteer do
name { Faker::Name.name }
role { Faker::Job.title }
event { create :event }
department { ["hr", "Finance", "legal"].sample }
skills { Volunteer::SKILLS_OPTIONS.values.flatten.map(&:last).sample(2) }
end

factory :store do
Expand Down
70 changes: 70 additions & 0 deletions lib/avo/fields/select_field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,22 @@ def initialize(id, **args, &block)
args[:options]
end

@grouped_options = args[:grouped_options]
@enum = args[:enum]
@multiple = args[:multiple]
@display_value = args[:display_value] || false
end

def grouped_options
Avo::ExecutionContext.new(
target: @grouped_options,
record: record,
resource: resource,
view: view,
field: self
).handle
end

def options_for_select
# If options are array don't need any pre-process
return options if options.is_a?(Array)
Expand All @@ -43,6 +54,11 @@ def options_for_select
def label
return "—" if value.nil? || (@multiple && value.empty?)

# Handle grouped options first
if @grouped_options.present?
return label_from_grouped_options
end

# If options are array don't need any pre-process
if options.is_a?(Array)
return @multiple ? value.join(", ") : value
Expand Down Expand Up @@ -89,6 +105,60 @@ def options
field: self
).handle
end

def label_from_grouped_options
grouped_opts = grouped_options
return value.to_s unless grouped_opts.is_a?(Hash)

if @multiple
# Handle multiple values
labels = Array.wrap(value).map do |val|
find_label_in_grouped_options(grouped_opts, val) || val.to_s
end

labels.join(", ")
else
# Handle single value
find_label_in_grouped_options(grouped_opts, value) || value.to_s
end
end

def find_label_in_grouped_options(grouped_opts, search_value)
grouped_opts.each do |group_name, group_options|
# Handle different group_options formats
case group_options
when Hash
# Hash format: { "Label" => "value" }
group_options.each do |label, option_value|
if option_value.to_s == search_value.to_s
return display_value ? option_value.to_s : label.to_s
end
end
when Array
# Array format: [["Label", "value"], ...] or ["value1", "value2", ...]
group_options.each do |option|
if option.is_a?(Array) && option.length >= 2
# Nested array format: ["Label", "value"]
label, option_value = option
if option_value.to_s == search_value.to_s
return display_value ? option_value.to_s : label.to_s
end
elsif option.to_s == search_value.to_s
# Simple array format: ["value1", "value2"]
return option.to_s
end
end
else
# Single value format
if group_options.to_s == search_value.to_s
return group_options.to_s
end
end
end

# If not found in any group, return nil so the caller can handle it
nil
end
end
end
end
4 changes: 3 additions & 1 deletion spec/dummy/app/avo/resources/volunteer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ def fields
field :name, as: :text
field :role, as: :text
field :event, as: :belongs_to, placeholder: "—"
end

field :department, as: :select, grouped_options: Volunteer::DEPARTMENT_OPTIONS
field :skills, as: :select, multiple: true, grouped_options: Volunteer::SKILLS_OPTIONS end
end
38 changes: 38 additions & 0 deletions spec/dummy/app/models/volunteer.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,41 @@
class Volunteer < ApplicationRecord
belongs_to :event, primary_key: "uuid"

# Define the department options with groups
DEPARTMENT_OPTIONS = {
"Administration" => [
["HR", "hr"],
"Finance",
["Legal", "legal"]
],
"Operations" => {
"Events" => "events",
"Logistics" => "logistics",
"Facilities" => "facilities"
},
"Technology" => [
["Development", "development"],
["Support", "support"],
["Infrastructure", "infrastructure"]
]
}.freeze

# Define the skills options with groups
SKILLS_OPTIONS = {
"Technical Skills" => [
["Programming", "programming"],
["Database Management", "database"],
["System Administration", "sysadmin"]
],
"Communication Skills" => [
["Public Speaking", "public_speaking"],
["Writing", "writing"],
["Translation", "translation"]
],
"Leadership Skills" => [
["Team Management", "team_mgmt"],
["Project Management", "project_mgmt"],
["Strategic Planning", "strategic"]
]
}.freeze
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AddGroupedSelectFieldsToVolunteers < ActiveRecord::Migration[8.0]
def change
add_column :volunteers, :department, :string
add_column :volunteers, :skills, :string, array: true, default: []
end
end
4 changes: 3 additions & 1 deletion spec/dummy/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[8.0].define(version: 2025_03_10_175357) do
ActiveRecord::Schema[8.0].define(version: 2025_06_19_140631) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"

Expand Down Expand Up @@ -305,6 +305,8 @@
t.uuid "event_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "department"
t.string "skills", default: [], array: true
t.index ["event_id"], name: "index_volunteers_on_event_id"
end

Expand Down
86 changes: 86 additions & 0 deletions spec/features/avo/select_field_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -278,4 +278,90 @@ def test_array
end
end
end

describe "when options are grouped" do
let!(:event) { create :event, name: "Event 1" }
let!(:volunteer) { create :volunteer, name: "John Doe", role: "Helper" }

context "single select with grouped options" do
it "shows grouped options and saves the selected value" do
visit avo.new_resources_volunteer_path

expect(page).to have_select "volunteer_department"

# Check grouped options structure
within "select[name='volunteer[department]']" do
expect(page).to have_selector "optgroup[label='Administration']"
expect(page).to have_selector "optgroup[label='Operations']"
expect(page).to have_selector "optgroup[label='Technology']"
end

select "HR", from: "volunteer_department"
fill_in "volunteer_name", with: "Jane Smith"
fill_in "volunteer_role", with: "Administrator"

select "Event 1", from: "volunteer_event_id"

save

expect(find_field_element(:department)).to have_text("HR")
expect(Volunteer.last.department).to eq "hr"
end

it "displays the correct selected value on edit" do
volunteer.update!(department: "Finance")
visit avo.edit_resources_volunteer_path(volunteer)

expect(page).to have_select "volunteer_department", selected: "Finance"
end
end

context "multiple select with grouped options" do
it "allows selection of multiple grouped values" do
visit avo.new_resources_volunteer_path

expect(page).to have_select "volunteer_skills", multiple: true

# Check grouped options structure
within "select[name='volunteer[skills][]']" do
expect(page).to have_selector "optgroup[label='Technical Skills']"
expect(page).to have_selector "optgroup[label='Communication Skills']"
expect(page).to have_selector "optgroup[label='Leadership Skills']"
end

select "Programming", from: "volunteer_skills"
select "Public Speaking", from: "volunteer_skills"
fill_in "volunteer_name", with: "Tech Leader"
fill_in "volunteer_role", with: "Volunteer"

select "Event 1", from: "volunteer_event_id"

save

expect(find_field_element(:skills)).to have_text("Programming, Public Speaking")
expect(Volunteer.last.skills).to match_array(["programming", "public_speaking"])
end

it "displays the correct selected values on edit" do
volunteer.update(skills: ["database", "team_mgmt"])
visit avo.edit_resources_volunteer_path(volunteer)

expect(page).to have_select "volunteer_skills", selected: ["Database Management", "Team Management"], multiple: true
end

it "allows deselecting previously selected values" do
volunteer.update(skills: ["programming", "writing"])
visit avo.edit_resources_volunteer_path(volunteer)

expect(page).to have_select "volunteer_skills", selected: ["Programming", "Writing"], multiple: true

page.unselect "Programming", from: "Skills"

save

expect(find_field_element(:skills)).to have_text("Writing")
expect(Volunteer.last.skills).to match_array(["writing"])
end
end
end
end
Loading