superglue icon indicating copy to clipboard operation
superglue copied to clipboard

Form helpers for Superglue

Open jho406 opened this issue 3 years ago • 1 comments

Rail's form helpers are an incredible way to quickly build forms from backend objects. I'd like to have some parity on the superglue side so quickly build uncontrolled components on the React side without using RailsTag

Something like this:

json.form_props form_with_props(
  url: onboarding_income_index_path,
  model: current_user.profile,
  method: :post,
  scope: :profile, local: true) do |f|

  f.number_field :email, max_length: 5
  f.number_field :password
  f.number_field :password_confirmation
end
   {
     .....
     email: {
       input: { type: "email",  maxLength: 5}
      }
   }

then on the Javascript side:

<form {...formProps}>
  <label {...formProps.email.label} />
  <input {...formProps.email.input}/>

  <label {...formProps.password.label}>
  <input {...formProps.password.input}>

  <RailsForm.Field {...formProps.passwordConfirmation} />

  <input type="submit" />
</RailsForm>

We leave how to structure the form up to the user so they can do this if they want in only html:

<label {...formProps.email.label}>
  <input {...formProps.email.input}/>
</label>

jho406 avatar Jun 16 '22 00:06 jho406

@jho406 here's where I left off:

require "active_support/core_ext/string"
require "rspec"

# TODO: form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
def form_props(url: nil, scope: nil, **options)
  builder = MyFormBuilder.new(url: url, scope: scope, **options)
  yield builder

  builder.to_h
end

class MyFormBuilder
  attr_reader :scope

  def initialize(url: nil, scope: nil, **options)
    @scope = scope
    @output = {
      accept_charset: "UTF-8",
      action: url || "/",
      method: options [:method] || "post",
      elements: {}
    }
  end

  def text_field(method, options = {})
    @output[:elements][scope ? "#{scope}_#{method}".to_sym : method.to_s.to_sym] = {
      type: "text",
      label: method.to_s.humanize,
      name: scope ? "#{scope}[#{method}]" : method.to_s
    }.merge(options)
  end

  def email_field(method, options = {})
    @output[:elements][scope ? "#{scope}_#{method}".to_sym : method.to_s.to_sym] = {
      type: "email",
      label: method.to_s.humanize,
      name: scope ? "#{scope}[#{method}]" : method.to_s
    }.merge(options)
  end

  def to_h
    @output
  end
end

RSpec.describe "form props" do
  it "builds default attributes for the form" do
    props = form_props {}

    expect(props).to eq(
      accept_charset: "UTF-8",
      action: "/",
      method: "post",
      elements: {}
    )
  end

  it "sets attributes for the form" do
    props = form_props(url: "/some_url", method: "get") {}

    expect(props).to eq(
      accept_charset: "UTF-8",
      action: "/some_url",
      method: "get",
      elements: {}
    )
  end

  it "builds attributes for the form elements" do
    props = form_props do |my_form_builder|
      my_form_builder.text_field :first_name, min: 50
      my_form_builder.email_field :email, required: true, label: "Email address"
    end

    expect(props[:elements]).to eq(
      first_name: {
        type: "text",
        min: 50,
        label: "First name",
        name: "first_name"
      },
      email: {
        type: "email",
        required: true,
        label: "Email address",
        name: "email"
      }
    )
  end

  it "scopes form elements" do
    props = form_props(scope: "user") do |my_form_builder|
      my_form_builder.text_field :first_name
      my_form_builder.email_field :email
    end

    expect(props[:elements]).to eq(
      user_first_name: {
        type: "text",
        label: "First name",
        name: "user[first_name]"
      },
      user_email: {
        type: "email",
        label: "Email",
        name: "user[email]"
      }
    )
  end
end

stevepolitodesign avatar Jul 15 '22 17:07 stevepolitodesign

Closing this as https://github.com/thoughtbot/form_props has been created! 🥳

jho406 avatar Jul 13 '23 00:07 jho406