All Projects → pantographe → view_component-form

pantographe / view_component-form

Licence: MIT license
Rails FormBuilder for ViewComponent

Programming Languages

ruby
36898 projects - #4 most used programming language

Projects that are alternatives of or similar to view component-form

Forms Angular
Probably the most opinionated framework in the world
Stars: ✭ 412 (+243.33%)
Mutual labels:  forms, form-builder
Rsformview
A Cocoapods library designed to easily create forms with multiple data entry fields
Stars: ✭ 84 (-30%)
Mutual labels:  forms, form-builder
Django Rest Formly
Generate angular-formly form configuration object for Django REST endpoints.
Stars: ✭ 7 (-94.17%)
Mutual labels:  forms, form-builder
Tellform
✏️ Free Opensource Alternative to TypeForm or Google Forms ⛺
Stars: ✭ 2,941 (+2350.83%)
Mutual labels:  forms, form-builder
Forms
📝 Simple form & survey app for Nextcloud
Stars: ✭ 127 (+5.83%)
Mutual labels:  forms, form-builder
Django Fobi
Form generator/builder application for Django done right: customisable, modular, user- and developer- friendly.
Stars: ✭ 360 (+200%)
Mutual labels:  forms, form-builder
Formium
The headless form builder for the modern web.
Stars: ✭ 78 (-35%)
Mutual labels:  forms, form-builder
yii2-forms
Forms CRUD - formbuilder, generator code
Stars: ✭ 32 (-73.33%)
Mutual labels:  forms, form-builder
Form Manager
PHP library to create and validate html forms
Stars: ✭ 124 (+3.33%)
Mutual labels:  forms, form-builder
Form For
ReactJS forms made easy
Stars: ✭ 118 (-1.67%)
Mutual labels:  forms, form-builder
Formvuelate
Dynamic schema-based form rendering for VueJS
Stars: ✭ 262 (+118.33%)
Mutual labels:  forms, form-builder
Core
The Form Tools Core.
Stars: ✭ 156 (+30%)
Mutual labels:  forms, form-builder
React Reactive Form
Angular like reactive forms in React.
Stars: ✭ 259 (+115.83%)
Mutual labels:  forms, form-builder
React Hook Form
📋 React Hooks for form state management and validation (Web + React Native)
Stars: ✭ 24,831 (+20592.5%)
Mutual labels:  forms, form-builder
grav-plugin-form
Grav Form Plugin
Stars: ✭ 48 (-60%)
Mutual labels:  forms, form-builder
Usetheform
React library for composing declarative forms, manage their state, handling their validation and much more.
Stars: ✭ 40 (-66.67%)
Mutual labels:  forms, form-builder
django-formidable
On the way to glory! again!
Stars: ✭ 19 (-84.17%)
Mutual labels:  forms, form-builder
react-emotion-multi-step-form
React multi-step form library with Emotion styling
Stars: ✭ 25 (-79.17%)
Mutual labels:  forms, form-builder
Uniforms
A React library for building forms from any schema.
Stars: ✭ 1,368 (+1040%)
Mutual labels:  forms, form-builder
Formmaster
Easily build big and bigger forms with minimal effort
Stars: ✭ 152 (+26.67%)
Mutual labels:  forms, form-builder

ViewComponent::Form

ViewComponent::Form provides a FormBuilder with the same interface as ActionView::Helpers::FormBuilder, but using ViewComponents for rendering the fields. It's a starting point for writing your own custom ViewComponents.

⚠️ This is an early release: the API is subject to change until we reach v1.0.0.

Compatibility

This gem is tested on:

  • Rails 6.0+ (with or without ActionText)
  • Ruby 2.7+

Installation

Add this line to your application's Gemfile:

gem 'view_component-form'

And then execute:

$ bundle install

Usage

Add a builder param to your form_for, form_with, fields_for or fields:

- <%= 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, aria: { describedby: f.field_id(:password, :description) } %>
                                    <%# renders a ViewComponent::Form::PasswordFieldComponent %>
  <div id="<%= f.field_id(:title, :description) %>">
    <%= f.hint :password, 'The password should be at least 8 characters long' %>
                                      <%# renders a ViewComponent::Form::HintComponent %>
    <%= f.error_message :password %>  <%# renders a ViewComponent::Form::ErrorMessageComponent %>
  </div>
<% end %>

It should work out of the box, but does nothing particularly interesting for now.

<form class="edit_user" id="edit_user_1" action="/users/1" accept-charset="UTF-8" method="post">
  <input type="hidden" name="_method" value="patch" />
  <input type="hidden" name="authenticity_token" value="[...]" />

  <label for="user_first_name">First name</label>
  <input type="text" value="John" name="user[first_name]" id="user_first_name" />

  <label for="user_last_name">Last name</label>
  <input type="text" value="Doe" name="user[last_name]" id="user_last_name" />

  <label for="user_email">E-mail</label>
  <input type="email" value="[email protected]" name="user[email]" id="user_email" />

  <label for="user_password">Password</label>
  <input type="password" name="user[password]" id="user_password" aria-describedby="user_password_description" />
  <div id="user_password_description">
    <div>The password should be at least 8 characters long</div>
  </div>
</form>

The ViewComponent::Form::* components are included in the gem.

Customizing the FormBuilder and the components

First, generate your own FormBuilder:

bin/rails generate vcf:builder CustomFormBuilder

      create  lib/custom_form_builder.rb

This allows you to pick the namespace your components will be loaded from.

# lib/custom_form_builder.rb
class CustomFormBuilder < ViewComponent::Form::Builder
  # Set the namespace you want to use for your own components
  namespace Custom::Form
end

Use the generator options to change the default namespace or the path where the file will be created:

bin/rails generate vcf:builder AnotherCustomFormBuilder --namespace AnotherCustom::Form --path app/forms

      create  app/forms/another_custom_form_builder.rb
# app/forms/another_custom_form_builder.rb
class AnotherCustomFormBuilder < ViewComponent::Form::Builder
  # Set the namespace you want to use for your own components
  namespace AnotherCustom::Form
end

Now let's generate your own components to customize their rendering. We can use the standard view_component generator:

bin/rails generate component Custom::Form::TextField --inline --parent ViewComponent::Form::TextFieldComponent

      invoke  test_unit
      create  test/components/custom/form/text_field_component_test.rb
      create  app/components/custom/form/text_field_component.rb

⚠️ The --parent option is available since ViewComponent v2.41.0. If you're using a previous version, you can always edit the generated Custom::Form::CustomTextFieldComponent class to make it inherit from ViewComponent::Form::TextFieldComponent.

Change your forms to use your new builder:

- <%= form_for @user, builder: ViewComponent::Form::Builder do |f| %>
+ <%= form_for @user, builder: CustomFormBuilder do |f| %>

You can then customize the behavior of your Custom::Form::CustomTextFieldComponent:

# app/components/custom/form/text_field_component.rb

class Admin::Form::TextFieldComponent < ViewComponent::Form::TextFieldComponent
  def html_class
    class_names("custom-text-field", "has-error": method_errors?)
  end
end

In this case we leverage the #class_names helper to:

  • always add the custom-text-field class;
  • add the has-error class if there is an error on the attribute (using ViewComponent::Form::FieldComponent#method_errors?).

The rendered form field will now look like this:

<input class="custom-text-field" type="text" value="John" name="user[first_name]" id="user_first_name">

You can use the same approach to inject options, wrap the input in a <div>, etc.

We'll add more use cases to the documentation soon.

Building your own components

When building your own ViewComponents for using in forms, it's recommended to inherit from ViewComponent::Form::FieldComponent, so you get access to the following helpers:

#label_text

Returns the translated text for the label of the field (looking up for helpers.label.OBJECT.METHOD_NAME), or humanized version of the method name if not available.

# app/components/custom/form/group_component.rb
class Custom::Form::GroupComponent < ViewComponent::Form::FieldComponent
end
<%# app/components/custom/form/group_component.html.erb %>
<div class="custom-form-group">
  <label>
    <%= label_text %><br />
    <%= content %>
  </label>
</div>
<%# app/views/users/_form.html.erb %>
<%= form_for @user do |f| %>
  <%= f.group :first_name do %>
    <%= f.text_field :first_name %>
  <% end %>
<% end %>
# config/locales/en.yml
en:
  helpers:
    label:
      user:
        first_name: Your first name

Renders:

<form class="edit_user" id="edit_user_1" action="/users/1" accept-charset="UTF-8" method="post">
  <!-- ... -->
  <label>
    Your first name<br />
    <input type="text" value="John" name="user[first_name]" id="user_first_name" />
  </label>
</form>

Validations

Let's consider the following model for the examples below.

# app/models/user.rb
class User < ActiveRecord::Base
  validates :first_name, presence: true, length: { minimum: 2, maximum: 255 }
end
Accessing validations with #validators

Returns all validators for the method name.

# app/components/custom/form/group_component.rb
class Custom::Form::GroupComponent < ViewComponent::Form::FieldComponent
  private

  def validation_hint
    if length_validator
      "between #{length_validator.options[:minimum]} and #{length_validator.options[:maximum]} chars"
    end
  end

  def length_validator
    validators.find { |v| v.is_a?(ActiveModel::Validations::LengthValidator) }
  end
end
<%# app/components/custom/form/group_component.html.erb %>
<div class="custom-form-group">
  <label>
    <%= label_text %> (<%= validation_hint %>)<br />
    <%= content %>
  </label>
</div>
Using #required? and #optional?
<%# app/components/custom/form/group_component.html.erb %>
<div class="custom-form-group">
  <label>
    <%= label_text %><%= " (required)" if required? %><br />
    <%= content %>
  </label>
</div>
Validation contexts

When using validation contexts, you can specify a context to the helpers above.

# app/models/user.rb
class User < ActiveRecord::Base
  validates :first_name, presence: true, length: { minimum: 2, maximum: 255 }
  validates :email, presence: true, on: :registration
end
<%# app/views/users/_form_.html.erb %>
<%= form_with model: @user,
              builder: ViewComponent::Form::Builder,
              validation_context: :registration do |f| %>
  <%= f.group :email do %>
    <%= f.email_field :email %>
  <% end %>
<% end %>

In this case, ViewComponent::Form::Builder accepts a validation_context option and passes it as a default value to the #validators, #required? and #optional? helpers.

Alternatively, you can pass the context to the helpers:

<%= "(required)" if required?(context: :registration) %>
def length_validator
  validators(context: :registration).find { |v| v.is_a?(ActiveModel::Validations::LengthValidator) }
end

Using your form components without a backing model

If you want to ensure that your fields display consistently across your app, you'll need to lean on Rails' own helpers. You may be used to using form tag helpers such as text_field_tag to generate tags, or even writing out plain HTML tags. These can't be integrated with a form builder, so they won't offer you the benefits of this gem.

You'll most likely want to use either:

  • form_with and supply a route as the endpoint, e.g. form_with url: users_path do |f| ..., or
  • fields, supplying a namespace if necessary. fields do |f| ... ought to work in the most basic case.

fields_for may also be of interest. To make consistent use of view_component-form, you'll want to be using these three helpers to build your forms wherever possible.

Supported helpers

The following helpers are currently supported by ViewComponent::Form.

ActionView::Helpers::FormBuilder

Supported: button check_box collection_check_boxes collection_radio_buttons collection_select color_field date_field date_select datetime_field datetime_local_field datetime_select email_field fields fields_for file_field field_id grouped_collection_select hidden_field month_field number_field password_field phone_field radio_button range_field search_field select submit telephone_field text_area text_field time_field time_select time_zone_select to_model to_partial_path url_field week_field weekday_select

Partially supported: label (blocks not supported) rich_text_area (untested)

Unsupported for now: field_name

Specific to ViewComponent::Form

Supported: error_message hint

Testing your components

RSpec

Configuration

This assumes your already have read and configured tests for view_component.

# spec/rails_helper.rb
require "view_component/test_helpers"
require "view_component/form/test_helpers"
require "capybara/rspec"

RSpec.configure do |config|
  config.include ViewComponent::TestHelpers, type: :component
  config.include ViewComponent::Form::TestHelpers, type: :component
  config.include Capybara::RSpecMatchers, type: :component
end

Example

# spec/components/form/text_field_component_spec.rb
RSpec.describe Form::TextFieldComponent, type: :component do
  let(:object)  { User.new } # replace with a model of your choice
  let(:form)    { form_with(object) }
  let(:options) { {} }

  let(:component) { render_inline(described_class.new(form, object_name, :first_name, options)) }

  context "with simple args" do
    it do
      expect(component.to_html)
        .to have_tag("input", with: { name: "user[first_name]", id: "user_first_name", type: "text" })
    end
  end
end

For more complex components, we recommend the rspec-html-matchers gem.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/pantographe/view_component-form. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the ViewComponent::Form project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].