Search code examples
ruby-on-railsrubyruby-on-rails-5simple-form

Rails generate select with both options and option groups


Is it possible to generate select like:

<select>
  <option value>Some nill value label</option>
  <option value="1">Option one</option>
  <optgroup label="Option Group 1" class="css-class-for-option-group-1">
    <option value="1:1">Option one in group 1</option>
    <option value="1:2">Option two in group 1</option>
  </optgroup>
  <optgroup label="Option Group 2" class="css-class-for-option-group-2">
    <option value="2:1">Option one in group 2</option>
    <option value="2:2">Option two in group 2</option>
  </optgroup>
</select>

I tried to use the grouped_options_for_select, but I failed to find I way to pass both option and groups to it. Is there any way to achieve this? I am open to suggestions using SimpleForm as well.

Thanks!


Solution

  • This is hard to answer precisely without knowing what the data looks like, but I'm going to guess that you have something like this:

    @grouped_options = [
      ["Some nil value label"],
      ["Option one", "1"],
      ["Option Group 1",
        [
          ["Option one in group 1", "1:1"],
          ["Option two in group 1", "1:2"],
        ]
      ],
      ["Option Group 2",
        [
          ["Option one in group 2", "2:1"],
          ["Option two in group 2", "2:2"],
        ]
      ],
    ]
    

    With that, you have a couple options. In plain ERB, you can do it like this:

    <%= select "thing", "some_attr" do %>
      <% @grouped_options.each do |label, value_or_options| %>
        <% if Array === value_or_options %>
          <%= tag.optgroup options_for_select(value_or_options), label: label %>
        <% else %>
          <%= tag.option label, value: value_or_options %>
        <% end %>
      <% end %>
    <% end %>
    

    Personally, though, I would write a helper. The Enumerable#chunk method splits an array into runs of values that return the same thing for the given block, making it easy to separate the grouped from the non-grouped items so we can use grouped_options_for_select and options_for_select, respectively:

    def ungrouped_and_grouped_options_for_select(choices, selected_key = nil)
      capture do
        choices
          .chunk {|_, choice_or_group| Array === choice_or_group }
          .each do |is_group, choices_or_grouped_choices|
            if is_group
              concat grouped_options_for_select(choices_or_grouped_choices, selected_key)
            else
              concat options_for_select(choices_or_grouped_choices, selected_key)
            end
          end
      end
    end
    

    You could then use it this:

    <%= select "thing", "some_attr" do %>
      <%= ungrouped_and_grouped_options_for_select(@grouped_options) %>
    <% end %>
    

    You can see both of these approaches in action on repl.it: https://repl.it/@jrunning/UnacceptablePlaintiveServer (see views/tests/index.html.erb, controllers/tests_controller.rb, and helpers/application_helper.rb).