rubymonolith / superform

Build highly customizable forms in Rails
MIT License
263 stars 14 forks source link

collection_select support ? #1

Closed tstatter closed 8 months ago

tstatter commented 10 months ago

To add a drop down for collection_select in old form

  <div>
    <%= form.label :collection_point_id, 'CP' %>
    <%= form.collection_select :collection_point_id, CollectionPoint.all, :id, :name, prompt: true %>
  </div>

Is there already a component for this or do we need to create a custom one ?

I tried a few variations such as (type: :select), no errors raised, but seems to always render a normal text input box e.g


 render field(:collection_point_id).input(type: :select, collection: collection_points.map { |cp| [cp.name, cp.id] },
                                                  selected: model.collection_point_id,
                                                  include_blank: true)

Thanks

bradgessler commented 10 months ago

I didn't get this far yet in the gem, but it's definitely something I want to include in the core library for a 1.0 release. If you'd be interested in building this out and including it in this gem I'd be happy to work through a PR with you and give you an overview of how this could be componentized.

I think the API would look something like (being lazy and making this a one-liner):

render field(:collection_point).select { CollectionPoint.all { |cp| option(value: cp.id) { cp.name } }

This would stay "as close to the HTML and Phlex" as possible (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select) if the select block is a Phlex tag that renders a bunch of option tags within it.

bradgessler commented 10 months ago

I'd also add that this could be shortened for ActiveRecord associations to just this:

render field(:collection_point).select.options(items: CollectionPoint.all) { |cp| cp.name }

Not sure about that exact method name and API, but the idea here is that you can pass select_associated an ActiveRecord association so it can reflect on the ID of that record. I'd add that later since the most important thing for this library is exposing form helpers as closely to HTML and Phlex as possible, then adding syntactic sugar like this on top.

tstatter commented 10 months ago

Ok cool, I think I have a working component so will raise PR asap

tstatter commented 10 months ago

I have created a branch, but cannot push to it. Do you prefer I fork it, then raise PR in the fork ?

bradgessler commented 10 months ago

Fork and PR is fine! 👍

bradgessler commented 9 months ago

I started an implementation of this at https://github.com//rubymonolith/demo/blob/c3c221e4fdaac7b3c8082defdb6d4f25ebdee081/app/views/application_form.rb#L10-L24

So far it looks like this:

  class Form < ApplicationForm
    def template
      labeled(field(:blog_id).select do |s|
        s.options(Blog.pluck(:id, :title))
        s.blank_option
      end)
      labeled field(:title).input.focus
      labeled field(:publish_at).input
      labeled field(:content).textarea(rows: 6)

      submit
    end
  end

Syntax is still a bit awkward, but it does work. One thing I don't like is how the s.options call takes an array and makes assumptions from it. A better API design that I'll explore is one that doesn't need an array for this to work.

bradgessler commented 8 months ago

I haven't released this gem yet, but the following example derived from the original post would work in main:

render field(:collection_point).select CollectionPoint.select(:id, :name), nil

I started populating more advanced examples in the README at https://github.com/rubymonolith/superform#form-field-guide. Here's what it looks like if you get crazy with your select form and want to do something like grouped inputs:

    div do
      render field(:source).label { "How did you hear about us?" }
      render field(:source).select do |s|
        # Pretend WebSources is an ActiveRecord scope with a "Social" category that has "Facebook, X, etc"
        # and a "Search" category with "AltaVista, Yahoo, etc."
        WebSources.select(:id, :name).group_by(:category) do |category, sources|
          s.optgroup(label: category) do
            s.options(sources)
          end
        end
      end
    end

There's intentionally a lot of boilerplate in that example — its expected that people would reduce that with component extraction or by adding helper methods to the form class.