view_component-form 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +16 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +170 -0
  5. data/app/components/view_component/form/base_component.rb +53 -0
  6. data/app/components/view_component/form/button_component.rb +19 -0
  7. data/app/components/view_component/form/check_box_component.rb +27 -0
  8. data/app/components/view_component/form/collection_check_boxes_component.rb +50 -0
  9. data/app/components/view_component/form/collection_radio_buttons_component.rb +50 -0
  10. data/app/components/view_component/form/collection_select_component.rb +49 -0
  11. data/app/components/view_component/form/color_field_component.rb +9 -0
  12. data/app/components/view_component/form/date_field_component.rb +9 -0
  13. data/app/components/view_component/form/date_select_component.rb +34 -0
  14. data/app/components/view_component/form/datetime_field_component.rb +9 -0
  15. data/app/components/view_component/form/datetime_local_field_component.rb +9 -0
  16. data/app/components/view_component/form/datetime_select_component.rb +34 -0
  17. data/app/components/view_component/form/email_field_component.rb +9 -0
  18. data/app/components/view_component/form/field_component.rb +65 -0
  19. data/app/components/view_component/form/file_field_component.rb +9 -0
  20. data/app/components/view_component/form/grouped_collection_select_component.rb +57 -0
  21. data/app/components/view_component/form/label_component.rb +42 -0
  22. data/app/components/view_component/form/month_field_component.rb +9 -0
  23. data/app/components/view_component/form/number_field_component.rb +9 -0
  24. data/app/components/view_component/form/password_field_component.rb +9 -0
  25. data/app/components/view_component/form/radio_button_component.rb +27 -0
  26. data/app/components/view_component/form/range_field_component.rb +9 -0
  27. data/app/components/view_component/form/rich_text_area_component.rb +9 -0
  28. data/app/components/view_component/form/search_field_component.rb +9 -0
  29. data/app/components/view_component/form/select_component.rb +37 -0
  30. data/app/components/view_component/form/submit_component.rb +19 -0
  31. data/app/components/view_component/form/telephone_field_component.rb +9 -0
  32. data/app/components/view_component/form/text_area_component.rb +9 -0
  33. data/app/components/view_component/form/text_field_component.rb +9 -0
  34. data/app/components/view_component/form/time_field_component.rb +9 -0
  35. data/app/components/view_component/form/time_select_component.rb +34 -0
  36. data/app/components/view_component/form/time_zone_select_component.rb +36 -0
  37. data/app/components/view_component/form/url_field_component.rb +9 -0
  38. data/app/components/view_component/form/week_field_component.rb +9 -0
  39. data/lib/generators/vcf/builder/builder_generator.rb +32 -0
  40. data/lib/generators/vcf/builder/templates/builder.rb.erb +4 -0
  41. data/lib/view_component/form/builder.rb +212 -0
  42. data/lib/view_component/form/class_names_helper.rb +37 -0
  43. data/lib/view_component/form/engine.rb +15 -0
  44. data/lib/view_component/form/test_helpers.rb +29 -0
  45. data/lib/view_component/form/version.rb +7 -0
  46. data/lib/view_component/form.rb +5 -0
  47. metadata +152 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b8b8e714bdbf45731abe0cf2081fc829c0564cd50097040e45e46beb368f2190
4
+ data.tar.gz: 184b04a2a168ecf53dcb7ff3385ef4b1889b42afe32f409236efaf708a96387c
5
+ SHA512:
6
+ metadata.gz: 83ffb7cea594f39a75418673f041d7c4f3ce006db5d306615b2e40575c57bab98c5619c9cc1dd71c122280fe963f88281f0b3c589be8b20a703f0aae4859a0ae
7
+ data.tar.gz: 19bbe58b645bb23ed625bdc3c240cdfa17d51e8481386b33e96c1d8583ef88dc622a92ade574cd551d1a97502674dffd969d7ae8bf877c82cd6c9f71d12124b4
data/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+
11
+ - `FormBuilder`: add `.namespace` method to allow local lookup of components (#54)
12
+ - Add basic `ViewComponent::Form::Builder` that can be used in place of Rails' `ActionView::Helpers::FormBuilder` (#1)
13
+ - Add all standard FormBuilder helpers provided by Rails, implemented as ViewComponents (#4)
14
+ - Add a custom FormBuilder generator (#34)
15
+ - Add CHANGELOG (#50)
16
+ - Add CI (#2)
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Pantographe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,170 @@
1
+ # ViewComponent::Form
2
+
3
+ **ViewComponent::Form** provides a `FormBuilder` with the same interface as [`ActionView::Helpers::FormBuilder`](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html), but using [ViewComponent](https://github.com/github/view_component)s for rendering the fields. It's a starting point for writing your own custom ViewComponents.
4
+
5
+ :warning: **This is an early release: the API is subject to change until we reach `v1.0.0`.**
6
+
7
+ ## Compatibility
8
+
9
+ This gem is tested on:
10
+ - Rails 6.0+
11
+ - Ruby 2.7+
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'view_component-form'
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ $ bundle install
24
+
25
+ ## Usage
26
+
27
+ Add a `builder` param to your `form_for` of `form_with`:
28
+
29
+ ```diff
30
+ - <%= form_for @user do |f| %>
31
+ + <%= form_for @user, builder: ViewComponent::Form::Builder do |f| %>
32
+ ```
33
+
34
+ You can also define a default FormBuilder at the controller level using [default_form_builder](https://api.rubyonrails.org/classes/ActionController/FormBuilder.html#method-i-default_form_builder).
35
+
36
+ Then call your helpers as usual:
37
+
38
+ ```erb
39
+ <%# app/views/users/_form.html.erb %>
40
+ <%= form_for @user, builder: ViewComponent::Form::Builder do |f| %>
41
+ <%= f.label :first_name %> <%# renders a ViewComponent::Form::LabelComponent %>
42
+ <%= f.text_field :first_name %> <%# renders a ViewComponent::Form::TextFieldComponent %>
43
+
44
+ <%= f.label :last_name %> <%# renders a ViewComponent::Form::LabelComponent %>
45
+ <%= f.text_field :last_name %> <%# renders a ViewComponent::Form::TextFieldComponent %>
46
+
47
+ <%= f.label :email %> <%# renders a ViewComponent::Form::LabelComponent %>
48
+ <%= f.email_field :email %> <%# renders a ViewComponent::Form::EmailFieldComponent %>
49
+
50
+ <%= f.label :password %> <%# renders a ViewComponent::Form::LabelComponent %>
51
+ <%= f.password_field :password %> <%# renders a ViewComponent::Form::PasswordFieldComponent %>
52
+ <% end %>
53
+ ```
54
+
55
+ It should work out of the box, but does nothing particularly interesting for now.
56
+
57
+ ```html
58
+ <form class="edit_user" id="edit_user_1" action="/users/1" accept-charset="UTF-8" method="post">
59
+ <input type="hidden" name="_method" value="patch" />
60
+ <input type="hidden" name="authenticity_token" value="[...]" />
61
+
62
+ <label for="user_first_name">First name</label>
63
+ <input type="text" value="John" name="user[first_name]" id="user_first_name" />
64
+
65
+ <label for="user_last_name">Last name</label>
66
+ <input type="text" value="Doe" name="user[last_name]" id="user_last_name" />
67
+
68
+ <label for="user_email">E-mail</label>
69
+ <input type="email" value="john.doe@example.com" name="user[email]" id="user_email" />
70
+
71
+ <label for="user_password">Password</label>
72
+ <input type="password" name="user[password]" id="user_password" />
73
+ </form>
74
+ ```
75
+
76
+ The `ViewComponent::Form::*` components are included in the gem.
77
+
78
+ ### Customizing the `FormBuilder` and the components
79
+
80
+ First, generate your own `FormBuilder`:
81
+
82
+ ```console
83
+ bin/rails generate vcf:builder CustomFormBuilder
84
+
85
+ create lib/custom_form_builder.rb
86
+ ```
87
+
88
+ This allows you to pick the namespace your components will be loaded from.
89
+
90
+ ```rb
91
+ # lib/custom_form_builder.rb
92
+ class CustomFormBuilder < ViewComponent::Form::Builder
93
+ # Set the namespace you want to use for your own components
94
+ namespace Form
95
+ end
96
+ ```
97
+
98
+ You can change the default namespace and path:
99
+
100
+ ```console
101
+ bin/rails generate vcf:builder AnotherCustomFormBuilder --namespace Forms::Components --path app/forms
102
+
103
+ create app/forms/another_custom_form_builder.rb
104
+ ```
105
+
106
+ ```rb
107
+ # app/forms/another_custom_form_builder.rb
108
+ class AnotherCustomFormBuilder < ViewComponent::Form::Builder
109
+ # Set the namespace you want to use for your own components
110
+ namespace Forms::Components
111
+ end
112
+ ```
113
+
114
+ :warning: **Everything below this line describes the future usage and is subject to change. It does not work yet as the gem is still under heavy development.**
115
+
116
+ Now let's generate your own components to customize the rendering.
117
+
118
+ ```console
119
+ bin/rails generate vcf:component Form::TextField
120
+
121
+ invoke test_unit
122
+ create test/components/form/text_field_component_test.rb
123
+ create app/components/form/text_field_component.rb
124
+ create app/components/form/text_field_component.html.erb
125
+ ```
126
+
127
+ Change your forms to use your new builder:
128
+
129
+ ```diff
130
+ - <%= form_for @user, builder: ViewComponent::Form::Builder do |f| %>
131
+ + <%= form_for @user, builder: CustomFormBuilder do |f| %>
132
+ ```
133
+
134
+ You can then customize the behavior of your `Form::TextFieldComponent`:
135
+
136
+ ```rb
137
+ # app/components/form/text_field_component.rb
138
+
139
+ module Form
140
+ class TextFieldComponent < ViewComponent::Form::TextFieldComponent
141
+ def html_class
142
+ class_names("text-field", "border-error": method_errors?)
143
+ end
144
+ end
145
+ end
146
+ ```
147
+
148
+ The generated form field with now have your class names:
149
+
150
+ ```html
151
+ <input class="text-field" type="text" value="John" name="user[first_name]" id="user_first_name">
152
+ ```
153
+
154
+ ## Development
155
+
156
+ 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.
157
+
158
+ 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](https://rubygems.org).
159
+
160
+ ## Contributing
161
+
162
+ 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](https://github.com/pantographe/view_component-form/blob/master/CODE_OF_CONDUCT.md).
163
+
164
+ ## License
165
+
166
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
167
+
168
+ ## Code of Conduct
169
+
170
+ Everyone interacting in the ViewComponent::Form project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/pantographe/view_component-form/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Form
5
+ class BaseComponent < ViewComponent::Base
6
+ class << self
7
+ attr_accessor :default_options
8
+ end
9
+
10
+ if Gem::Version.new(Rails::VERSION::STRING) < Gem::Version.new("6.1")
11
+ require "view_component/form/class_names_helper"
12
+ include ClassNamesHelper
13
+ end
14
+
15
+ attr_reader :form, :object_name, :options
16
+
17
+ delegate :object, to: :form
18
+ delegate :errors, to: :object, prefix: true
19
+
20
+ def initialize(form, object_name, options = {})
21
+ @form = form
22
+
23
+ # See: https://github.com/rails/rails/blob/83217025a171593547d1268651b446d3533e2019/actionview/lib/action_view/helpers/tags/base.rb#L13
24
+ @object_name = object_name.to_s.dup
25
+ @options = options
26
+
27
+ super()
28
+ end
29
+
30
+ def object_errors?
31
+ object.errors.any?
32
+ end
33
+
34
+ def html_class
35
+ nil
36
+ end
37
+
38
+ protected
39
+
40
+ def before_render
41
+ super
42
+
43
+ combine_options!
44
+ end
45
+
46
+ def combine_options!
47
+ @options = (self.class.default_options.deep_dup || {}).deep_merge(options).tap do |opts|
48
+ opts[:class] = class_names(options[:class], html_class) if (html_class || options[:class]).present?
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Form
5
+ class ButtonComponent < BaseComponent
6
+ attr_reader :value
7
+
8
+ def initialize(form, value, options = {})
9
+ @value = value
10
+
11
+ super(form, nil, options)
12
+ end
13
+
14
+ def call
15
+ button_tag(content || value, options)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Form
5
+ class CheckBoxComponent < FieldComponent
6
+ attr_reader :checked_value, :unchecked_value
7
+
8
+ def initialize(form, object_name, method_name, checked_value, unchecked_value, options = {}) # rubocop:disable Metrics/ParameterLists
9
+ @checked_value = checked_value
10
+ @unchecked_value = unchecked_value
11
+
12
+ super(form, object_name, method_name, options)
13
+ end
14
+
15
+ def call
16
+ ActionView::Helpers::Tags::CheckBox.new(
17
+ object_name,
18
+ method_name,
19
+ @view_context,
20
+ checked_value,
21
+ unchecked_value,
22
+ options
23
+ ).render
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Form
5
+ class CollectionCheckBoxesComponent < FieldComponent
6
+ attr_reader :collection, :value_method, :text_method, :html_options
7
+
8
+ def initialize( # rubocop:disable Metrics/ParameterLists
9
+ form,
10
+ object_name,
11
+ method_name,
12
+ collection,
13
+ value_method,
14
+ text_method,
15
+ options = {},
16
+ html_options = {}
17
+ )
18
+ @collection = collection
19
+ @value_method = value_method
20
+ @text_method = text_method
21
+ @html_options = html_options
22
+
23
+ super(form, object_name, method_name, options)
24
+
25
+ set_html_options!
26
+ end
27
+
28
+ def call # rubocop:disable Metrics/MethodLength
29
+ ActionView::Helpers::Tags::CollectionCheckBoxes.new(
30
+ object_name,
31
+ method_name,
32
+ @view_context,
33
+ collection,
34
+ value_method,
35
+ text_method,
36
+ options,
37
+ html_options,
38
+ &content
39
+ ).render
40
+ end
41
+
42
+ protected
43
+
44
+ def set_html_options!
45
+ @html_options[:class] = class_names(html_options[:class], html_class)
46
+ @html_options.delete(:class) if @html_options[:class].blank?
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Form
5
+ class CollectionRadioButtonsComponent < FieldComponent
6
+ attr_reader :collection, :value_method, :text_method, :html_options
7
+
8
+ def initialize( # rubocop:disable Metrics/ParameterLists
9
+ form,
10
+ object_name,
11
+ method_name,
12
+ collection,
13
+ value_method,
14
+ text_method,
15
+ options = {},
16
+ html_options = {}
17
+ )
18
+ @collection = collection
19
+ @value_method = value_method
20
+ @text_method = text_method
21
+ @html_options = html_options
22
+
23
+ super(form, object_name, method_name, options)
24
+
25
+ set_html_options!
26
+ end
27
+
28
+ def call # rubocop:disable Metrics/MethodLength
29
+ ActionView::Helpers::Tags::CollectionRadioButtons.new(
30
+ object_name,
31
+ method_name,
32
+ @view_context,
33
+ collection,
34
+ value_method,
35
+ text_method,
36
+ options,
37
+ html_options,
38
+ &content
39
+ ).render
40
+ end
41
+
42
+ protected
43
+
44
+ def set_html_options!
45
+ @html_options[:class] = class_names(html_options[:class], html_class)
46
+ @html_options.delete(:class) if @html_options[:class].blank?
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Form
5
+ class CollectionSelectComponent < FieldComponent
6
+ attr_reader :collection, :value_method, :text_method, :html_options
7
+
8
+ def initialize( # rubocop:disable Metrics/ParameterLists
9
+ form,
10
+ object_name,
11
+ method_name,
12
+ collection,
13
+ value_method,
14
+ text_method,
15
+ options = {},
16
+ html_options = {}
17
+ )
18
+ @collection = collection
19
+ @value_method = value_method
20
+ @text_method = text_method
21
+ @html_options = html_options
22
+
23
+ super(form, object_name, method_name, options)
24
+
25
+ set_html_options!
26
+ end
27
+
28
+ def call
29
+ ActionView::Helpers::Tags::CollectionSelect.new(
30
+ object_name,
31
+ method_name,
32
+ @view_context,
33
+ collection,
34
+ value_method,
35
+ text_method,
36
+ options,
37
+ html_options
38
+ ).render
39
+ end
40
+
41
+ protected
42
+
43
+ def set_html_options!
44
+ @html_options[:class] = class_names(html_options[:class], html_class)
45
+ @html_options.delete(:class) if @html_options[:class].blank?
46
+ end
47
+ end
48
+ end
49
+ end