Replies: 9 comments 11 replies
-
@fsateler maybe you could answer this one, as you've done some investigation into working with forms? |
Beta Was this translation helpful? Give feedback.
-
So, the rails helpers are basically wrappers around Unfortunately, I think the future of forms+view_component means somebody coming up with a set of form components, and a few helpers to actually render those components. The basic rails helpers are just printing out html (almost) directly. |
Beta Was this translation helpful? Give feedback.
-
I've been working on this for the past few weeks and I think that Rails already has some awesome conventions for forms that view-component should not have to reinvent. You could create your own builder: class KhigaFormBuilder < ActionView::Helpers::FormBuilder
def text_field(method, tag_value, options = {})
# in here you could build any html you want.
# You should be able to call your view component for input fields
# for example: KhigaDesingSystem::TextField(params)
end
end <%= form_for(@dog, builder: KhigaFormBuilder) do |form|>
<%= form.text_field :first_name %>
<% end %> The important bit is to allow your view component to follow the same conventions as Rails' default form builder methods which adds attributes like Talking about the controller, you can specify default form builders per controller: |
Beta Was this translation helpful? Give feedback.
-
Throwing in my two cents: I don't use One mental model that I think may be helpful is that the Rails form helpers are already abstracted into components -- and have years and years of battle-hardening and deep first-party support in the framework. <%= f.submit "Save", class: "btn btn-green" %> vs <%= render ButtonComponent.new(text: "Save", color: :green) %> I'm not sure you're really getting much benefit from ViewComponent in this case. If you want to make ViewComponents for visual parts of a form (a Card or an InputGroup or a Banner) that will work fine, but in my opinion avoid replacing text inputs, selects, buttons, |
Beta Was this translation helpful? Give feedback.
-
I'm late to this discussion, but I'm about to embark down this path of figuring out what forms look like in an app using ViewComponent. I wonder if it would make sense to pass in pseudocode, not tested: app/components/button_component.rb: class ButtonComponent < ViewComponent
attr_reader :f, :args
def initialize(f: f, *args)
@f = f
@args = args
end
end app/components/button_component.html.erb: <%= f.submit(args.merge(class: "btn btn-green")) %> Usage: <%= render ButtonComponent.new(f: f) %> This leverages Rails's helper abstractions, while allowing the developer to make their own as well. However, I have not tested this at all, and if there's anything I learned from forms in React, it's that forms are a PAIN to get right and to componentize and such. I'm pretty sure I'm oversimplifying this. |
Beta Was this translation helpful? Give feedback.
-
Dropping some thoughts in here. I haven't solved this problem for myself and my team, but I want to know what folks think of where I'm at. Rails form helpers ( As I'm building a form field component, I'm using the This characteristic is a double-edged sword - I think the goal is that you'd never use The trouble with making a component per field type is that there are some 20 different helpers, each with an API that the developer needs to define to their own tastes. In our case, we'd want to cover all the available attributes, perhaps set defaults, and add validations. It's a pretty big task, and we've already devoted six weeks to turning as much of our partials and styled elements into components. TL;DR Rendering components from within a form builder as @pinzonjulian suggested is definitely the goal, but reflecting the sheer number of field types in a component library you'll need to maintain is the part that's heavy lifting. |
Beta Was this translation helpful? Give feedback.
-
I've been working on this problem lately and I have some new information to share with everyone. First, there is now a PR #946 documenting the usage of forms with View Component that encourages users to keep using the Second, I've understood some things even deeper now about Rails and forms that will help us shape the path for forms in View Component. Why do we need ViewComponent in forms?@swanson mentioned the following:
To some extent, you are right. Rails already has a way to abstract inputs and we shouldn't be reinventing that. The DSL to interact with Rails forms shouldn't change because it's great already. However, the way to build more complex input types does need a refresh. The example we'll use throughout this postLet's imagine we want to build a text field that looks like this: This input field as a component would be composed of:
Current state: writing html in a
|
Beta Was this translation helpful? Give feedback.
-
I've had the same issue and right now I'm testing out a new solution it came to my mind after reading all the guys suggestions here. As stated by @pinzonjulian if you need something easy the capture is fine but if you need something more complex a component would perfectly fit the needs. My RequirementsMy requirements was to have a field looking like this one My GoalMy goal was to let the people of my team manage forms with the rails syntax they are used to like The issueThe main issue with other solutions was that it had a wrong field name, I ended up in understanding that I'm just fine with the rails generated input field, I just want it to have my custom classes and be wrapped in some others div for styling purpose. SolutionI've disabled the # app/form/dashero_form_builder.rb
class DasheroFormBuilder < ActionView::Helpers::FormBuilder
def text_field(attribute, options = {})
icons = { before: nil, after: nil }
icons = icons.merge(options[:icons]) if options.key?(:icons)
@template.render ::Input::Text.new(object: @object, attribute: attribute, **icons) do |component|
component.field do
super(
attribute,
objectify_options(
options.merge({ class: component.classes })
)
)
end
yield(component) if block_given?
end
end # app/components/input/text.rb
class Input::Text < ApplicationComponent
renders_one :field
renders_one :before
renders_one :after
attr_accessor :object, :attribute, :before_icon, :after_icon
def initialize(object:, attribute:, before: nil, after: nil)
self.object = object
self.attribute = attribute
self.before_icon = before
self.after_icon = after
super
end
def classes
%w[my-tailwind-classes]
end
def label_classes
%w[my-tailwind-class, ('my-additional-error-classes' if errors)]
end
def errors
object.errors[attribute].presence
end
end / app/components/input/text.html.slim
label class=label_classes
.mr-2.text-gray-700
= before
- unless before_icon.nil?
= render ::Unicons::Icon.new(icon: before_icon)
= field
.ml-2.text-gray-700
= after
- unless after_icon.nil?
= render ::Unicons::Icon.new(icon: after_icon)
- if errors
p.text-red.mt-2
= errors.join(', ') So this can be used like this:
= f.label :name
= f.text_field :name, placeholder: true
= f.label :name
= f.text_field :name, placeholder: true, icons: { before: 'icon-name', after: 'icon-name' }
= f.label :name
= f.text_field :name, placeholder: true do |component|
- component.after do
button ...
- component.before do
= link_to ... NOTE: ThoughtsThis is looking good to me cause I'm just extending form builder to use View Component with the usual input field and you still can use the default rails form builder if needed, please let me know what you think about! |
Beta Was this translation helpful? Give feedback.
-
Thanks a lot for sharing your thoughts and approaches here! The idea is to create a - <%= form_for @user do |f| %>
+ <%= form_for @user, builder: ViewComponent::Form::Builder do |f| %> You can also define a default FormBuilder at the controller level using default_form_builder. Then call your helpers as usual: <%# app/views/users/_form.html.erb %>
<%= form_for @user, builder: ViewComponent::Form::Builder do |f| %>
<%= f.label :first_name %> <%# renders a ViewComponent::Form::LabelComponent %>
<%= f.text_field :first_name %> <%# renders a ViewComponent::Form::TextFieldComponent %>
<%= f.label :last_name %> <%# renders a ViewComponent::Form::LabelComponent %>
<%= f.text_field :last_name %> <%# renders a ViewComponent::Form::TextFieldComponent %>
<%= f.label :email %> <%# renders a ViewComponent::Form::LabelComponent %>
<%= f.email_field :email %> <%# renders a ViewComponent::Form::EmailFieldComponent %>
<%= f.label :password %> <%# renders a ViewComponent::Form::LabelComponent %>
<%= f.password_field :password %> <%# renders a ViewComponent::Form::PasswordFieldComponent %>
<% end %> This would work out of the box. Then you can subclass the provided Later, the gem could include another "advanced"
You could end up with something along those lines: <%# app/views/users/_form.html.erb %>
<%= form_for @user, builder: CustomFormBuilder do |f| %>
<%= f.errors %>
<%# renders a ViewComponent::Form::GroupComponent
that takes a block, prefixes it with a label
and renders errors below %>
<%= f.group :first_name, hint: "How should we call you?" do %>
<%= f.text_field :first_name %> <%# renders a ViewComponent::Form::TextFieldComponent %>
<% end %>
<%# ... %>
<% end %> Overall, this approach is very similar to what @xkraty describes above. It aims at being a good starting point for anyone who wants to replicate this approach without writing a ton of boilerplate :) We're actually extracting code from an app that is already running in production. We started working on it here: view_component-form but it's still very early. I'll keep you posted in case some of you are interested in trying it out! |
Beta Was this translation helpful? Give feedback.
-
Hello! Our team is in the process of rolling out
view_components
to our rails app.Currently we have a
ButtonComponent
that renders a html button element of a specified type.We also have a
ButtonLinkComponent
that renders a html link element styled as a button.However, we had not considered rails helpers like
submit_tag
, orf.submit
which are bound to a form that we also want to style as buttons. HTML wise, these render something like,Functionally it doesn't seem like replacing these rails submit helpers with
<button type ="submit"/>
has consequences, but I'm not entirely sure either-- anyone have any idea? Either way, it seems like we should probably preserve form helpers.I was curious how others have handled rails helpers with their components, or have any ideas for how to approach this.
Beta Was this translation helpful? Give feedback.
All reactions