partial_form 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +7 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +154 -0
  5. data/app/views/layouts/form/_button.html.erb +2 -0
  6. data/app/views/layouts/form/_check_box.html.erb +4 -0
  7. data/app/views/layouts/form/_collection_check_boxes.html.erb +5 -0
  8. data/app/views/layouts/form/_collection_radio_buttons.html.erb +4 -0
  9. data/app/views/layouts/form/_collection_select.html.erb +4 -0
  10. data/app/views/layouts/form/_color_field.html.erb +4 -0
  11. data/app/views/layouts/form/_date_field.html.erb +4 -0
  12. data/app/views/layouts/form/_datetime_field.html.erb +4 -0
  13. data/app/views/layouts/form/_datetime_local_field.html.erb +4 -0
  14. data/app/views/layouts/form/_email_field.html.erb +4 -0
  15. data/app/views/layouts/form/_file_field.html.erb +4 -0
  16. data/app/views/layouts/form/_grouped_collection_select.html.erb +4 -0
  17. data/app/views/layouts/form/_hidden_field.html.erb +4 -0
  18. data/app/views/layouts/form/_label.html.erb +4 -0
  19. data/app/views/layouts/form/_month_field.html.erb +4 -0
  20. data/app/views/layouts/form/_number_field.html.erb +4 -0
  21. data/app/views/layouts/form/_password_field.html.erb +4 -0
  22. data/app/views/layouts/form/_phone_field.html.erb +4 -0
  23. data/app/views/layouts/form/_radio_button.html.erb +4 -0
  24. data/app/views/layouts/form/_range_field.html.erb +4 -0
  25. data/app/views/layouts/form/_search_field.html.erb +4 -0
  26. data/app/views/layouts/form/_select.html.erb +4 -0
  27. data/app/views/layouts/form/_submit.html.erb +2 -0
  28. data/app/views/layouts/form/_telephone_field.html.erb +4 -0
  29. data/app/views/layouts/form/_text_area.html.erb +4 -0
  30. data/app/views/layouts/form/_text_field.html.erb +4 -0
  31. data/app/views/layouts/form/_time_field.html.erb +4 -0
  32. data/app/views/layouts/form/_time_zone_select.html.erb +4 -0
  33. data/app/views/layouts/form/_url_field.html.erb +4 -0
  34. data/app/views/layouts/form/_week_field.html.erb +4 -0
  35. data/app/views/layouts/form/_weekday_select.html.erb +4 -0
  36. data/app/views/layouts/form/_wrapper.html.erb +15 -0
  37. data/lib/generators/partial_form/partials_generator.rb +42 -0
  38. data/lib/partial_form/builder.rb +309 -0
  39. data/lib/partial_form/engine.rb +5 -0
  40. data/lib/partial_form/version.rb +3 -0
  41. data/lib/partial_form.rb +6 -0
  42. metadata +102 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 35c22d1a3abf0d4b2827af73c6a28a1128d5a3ea37719aadfe6b033966bd3f90
4
+ data.tar.gz: 3f63f0465f0d6547ae2740e312cc6f814644acd59c91923ef02166e21bf8b8ca
5
+ SHA512:
6
+ metadata.gz: 5b225a18088404fb2746c000c262780207b97d2db81992cb83a95e9591cc186e9ae63dd7dc276057413fc9e7b8ac0157d79ec729f6a0abd61854b0f26e6f7e1b
7
+ data.tar.gz: 77b4cf2729f0e77a2656203c5e630957931562adb04327a9d488bac9a24977f904a7a6d0664f22288f10206f05bb0b2ecd294f08198c2d37a14415d2f8e48963
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ ## [0.1.0] - 2024-11-01
2
+
3
+ - Initial release
4
+
5
+ ## [Unreleased]
6
+
7
+ - ...
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Tom Rothe
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,154 @@
1
+ # PartialForm
2
+
3
+ Customizable forms for Rails using partials!
4
+
5
+ The PartialForm gem simplifies the rendering of form fields by providing a `FormBuilder` that leverages partials. This approach streamlines the management and maintenance of the field helper markup.
6
+
7
+ Potential uses are:
8
+
9
+ - Show error messages right next to the input field
10
+ - Render labels along with the inputs
11
+ - Indicate if a field is required or optional
12
+ - Keep the field markup & CSS classes consistent throughout the app
13
+ - Add more complex input fields (such as a barcode input or a custom date range picker)
14
+
15
+ ## Installation & Usage
16
+
17
+ You can install the gem using `bundler add partial_form`.
18
+
19
+ The gem provides a form builder named `PartialForm::Builder`, here an example:
20
+
21
+ ```erb
22
+ <%# app/views/article/_form.html.erb %>
23
+ <%= form_with model: @article, builder: PartialForm::Builder do |f| %>
24
+ <%= f._text_field :title # mind the prefix, renders app/views/form/_text_field.html.erb %>
25
+ <%= f._date_field :published_on, hint: "The release date" # pass arbitrary options to the partial %>
26
+ <%= f.text_field :slug # the default form helper is available without prefix %>
27
+ <% end %>
28
+ ```
29
+
30
+ The `PartialForm::Builder` mimics the API of the [default FormBuilder](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html) but adds a `_` prefix.
31
+ The partial builder comes with default templates for each helper. These render something like this (for an invalid field):
32
+
33
+ ```html
34
+ <!-- example for the rendered output of an included template -->
35
+ <div class="form-group">
36
+ <label for="article_title">Published On</label>
37
+ <input type="date" name="article[published_on]" id="article_published_on" class="invalid">
38
+ <p class="form-errors">can't be blank</p>
39
+ <p class="form-hint">The release date</p>
40
+ </div>
41
+ ```
42
+
43
+ If you want to set the builder as default builder for all your forms, you can:
44
+
45
+ ```ruby
46
+ class ApplicationController < ActionController::Base
47
+ default_form_builder PartialForm::Builder
48
+ end
49
+ ```
50
+
51
+ ## Documentation
52
+
53
+ Find the [latest documentation](https://motine.github.io/partial_form/).
54
+
55
+ Here an extensive list of all helpers: `_label`, `_text_field`, `_color_field`, `_date_field`, `_datetime_field`, `_datetime_local_field`, `_email_field`, `_file_field`, `_hidden_field`, `_month_field`, `_number_field`, `_password_field`, `_phone_field`, `_range_field`, `_search_field`, `_telephone_field`, `_text_area`, `_time_field`, `_url_field`, `_week_field`, `_check_box`, `_radio_button`, `_select`, `_collection_select`, `_grouped_collection_select`, `_time_zone_select`, `_weekday_select`, `_collection_check_boxes`, `_collection_radio_buttons`, `_button`, `_submit`
56
+
57
+ ## Customize the builder
58
+
59
+ You can copy the standard partials to your application using:
60
+
61
+ ```bash
62
+ rails g partial_form:partials
63
+ # Which partials do you want to copy? [default, all] default
64
+ # create app/views/layouts/form/_wrapper.html.erb
65
+ # create app/views/layouts/form/_text_field.html.erb
66
+ # create app/views/layouts/form/_label.html.erb
67
+ # create app/views/layouts/form/_submit.html.erb
68
+ ```
69
+
70
+ Here an example how to **customize an existing helper** (`_text_field`) partial:
71
+
72
+ ```erb
73
+ <%# available locals: f, method, options, errors; all keyword arguments passed to the `_text_field` are accessible via `options` %>
74
+ <%= f.label method, class: "mb-3" # call the default form builder helper %>
75
+ <%= f.text_field method, options.merge!(class: class_names(options[:class], "mb-3 border", invalid: errors.any?)) # make sure to retain passed `class` %>
76
+ <% if errors.any? -%>
77
+ <p class="mt-4 text-red-200"><%= errors.to_sentence %></p>
78
+ <% end %>
79
+ ```
80
+
81
+ Please mind that some helpers use `options` and others `html_options` and check the [documentation](https://motine.github.io/partial_form/).
82
+
83
+ To **add a new helper method**, you can derive from the `PartialForm::Builder` and a new method as well as a partial:
84
+
85
+ ```ruby
86
+ class MyBuilder < PartialForm::Builder
87
+ def _fancy_field(method, options = {})
88
+ render_simple_field(:_fancy_field, method, options) # renders the partial `views/layouts/form/_fancy_field` and passes the default arguments such as `f`, `method`, `errors`, etc.
89
+ end
90
+
91
+ def _super_fancy_field(bells)
92
+ whistles = Whistle.all
93
+ render_partial("super", {f: self, bells:, whistles:}) # renders `views/layouts/form/_super` and passes `f`, `bells`, `whistles`
94
+ end
95
+ end
96
+ ```
97
+
98
+ ## Develop & Contribute
99
+
100
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/motine/partial_form).
101
+
102
+ The easiest way to get a development environment is to build and use a Docker image:
103
+
104
+ ```bash
105
+ # build and enter the dev container
106
+ docker build -t partial_form .
107
+ docker run -it --rm -v $(pwd):/app -p 3000:3000 partial_form
108
+
109
+ # use the demo app for testing
110
+ _demo_app/bin/rails s -b 0.0.0.0 # visit http://localhost:3000 (the gem is reloaded in a very sloppy manner)
111
+
112
+ # useful commands
113
+ rake test
114
+ rake standard:fix
115
+ rake # run both
116
+ rake rdoc
117
+ ```
118
+
119
+ ## Release
120
+
121
+ This section is intended for the maintainer.
122
+
123
+ ```shell
124
+ # make changes
125
+ rake # preflight
126
+ # review and edit CHANGELOG.md
127
+ # edit lib/partial_form/version.rb
128
+ export VERSION=0.1.0
129
+ git commit -am "version bump"
130
+ git tag $VERSION
131
+ gem build partial_form.gemspec
132
+ gem push partial_form-$VERSION.gem
133
+ git push && git push --tags
134
+ ```
135
+
136
+ # Alternatives
137
+
138
+ Currently, there are multiple ways to solve the problem of adapting the markup for form fields. There is a [dedicated section](https://guides.rubyonrails.org/form_helpers.html#customizing-form-builders) in the [Action View Form Helpers Guide](https://guides.rubyonrails.org/form_helpers.html) on how to customize form elements. Some cases can even be solved with dedicated configuration options such as [field_error_proc](https://guides.rubyonrails.org/configuring.html#config-action-view-field-error-proc).
139
+
140
+ In my opinion, these approaches are either verbose or too limited to solve the use cases above.
141
+
142
+ - **Copy the required logic & markup into templates** Repeating the markup in some views may keep the cross-dependencies low. However, copying the same (faulty) markup/logic to all forms, decreases maintainability.
143
+ - **Introduce helper methods** Helpers are a very convenient way to refactor repeating code. However, specifying markup in helpers can easily become unreadable if the logic becomes more involved. Also, helpers do not have access to the form object unless passed explicitly.
144
+ - **Add a form builder** Form builders are made for adapting and extending form field rendering. They are a great tool. However, similar to introducing helper methods, specifying the markup can become cumbersome ([example gist](https://gist.github.com/motine/dc47f6ceeaaea96e855a2dd7f4ef83ae)) and hard to read.
145
+ - **Refactor to partials** Partials are geared towards defining markup structures. They keep code readable even if logic becomes more complicated. However, partials do not have access to the form object per se. Also, using the `render` syntax does not communicate the type of input as well as a form builder method (`render "forms/text_field", method: :title, builder: f` vs. `f.text_field :title`).
146
+ - **Pull in a form builder gem** There are [many gems](https://awesome-ruby.com/#-form-builder), that allow customizing form rendering. Each of them, has its own way to adapt the behavior. This requires the developer to learn these ([sometimes complicated](https://github.com/heartcombo/simple_form?tab=readme-ov-file#the-wrappers-api)) concepts. Also, some gems provide additional functionality which may not be needed (e.g. automatic collection population).
147
+
148
+ If you are interested in more details, please check the discussion on [discuss.rubyonrails.org](https://discuss.rubyonrails.org/t/introduce-a-standard-formbuilder-that-leverages-partials/86790).
149
+
150
+ The [view_partial_form_builder gem](https://github.com/seanpdoyle/view_partial_form_builder) is similar to this gem, however there are subtle differences.
151
+
152
+ ## License
153
+
154
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,2 @@
1
+ <%# available locals: f, value, options, block %>
2
+ <%= f.button value, options, &block %>
@@ -0,0 +1,4 @@
1
+ <%# available locals: f, method, options, checked_value, unchecked_value, errors %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.check_box method, options, checked_value, unchecked_value %>
4
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <%# available locals: f, method, collection, value_method, text_method, options, html_options, block, errors %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.collection_check_boxes method, collection, value_method, text_method, options, html_options, &block %>
4
+ <% end %>
5
+
@@ -0,0 +1,4 @@
1
+ <%# available locals: f, method, collection, value_method, text_method, options, html_options, block, errors %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.collection_radio_buttons method, collection, value_method, text_method, options, html_options, &block %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# available locals: method, collection, value_method, text_method, options, html_options, errors %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.collection_select method, collection, value_method, text_method, options, html_options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.color_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.date_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.datetime_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.datetime_local_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.email_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.file_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# available locals: f, method, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options, errors %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.grouped_collection_select method, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.hidden_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# available locals: f, method, options, block, errors and text (can be `nil` to infer via I18n or `false` to skip this label) %>
2
+ <% unless text == false # we always render the label unless the `false` is given -%>
3
+ <%= f.label method, text, options, &block %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.month_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.number_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.password_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.phone_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# available locals: f, method, tag_value, options, errors %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.radio_button method, tag_value, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.range_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.search_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# available locals: f, method, choices, options, html_options, block, errors %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.select method, choices, options, html_options, &block %>
4
+ <% end %>
@@ -0,0 +1,2 @@
1
+ <%# available locals: f, value, options %>
2
+ <%= f.submit value, options %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.telephone_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.text_area method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# available locals: f, method, options, errors %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.text_field method, options -%>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.time_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# available locals: f, method, priority_zones, options, html_options, errors %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.time_zone_select method, priority_zones, options, html_options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.url_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.week_field method, options %>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%# available locals: f, method, options, html_options, errors %>
2
+ <%= render("layouts/form/wrapper", **local_assigns) do -%>
3
+ <%= f.weekday_select method, options, html_options %>
4
+ <% end %>
@@ -0,0 +1,15 @@
1
+ <%# please see _text_field partial for available locals %>
2
+ <%
3
+ options_target = defined?(html_options) ? html_options : options # in some helper methods the class must be added to `options`, in others to `html_options`
4
+ options_target.merge!(class: class_names(options_target[:class], invalid: errors.any?))
5
+ %>
6
+ <div class="form-group">
7
+ <%= f._label method, options.delete(:label) -%>
8
+ <%= yield -%>
9
+ <% if errors.any? -%>
10
+ <p class="form-errors"><%= errors.to_sentence %></p>
11
+ <% end %>
12
+ <% if options[:hint].present? %>
13
+ <p class="form-hint"><%= options[:hint] %></p>
14
+ <% end %>
15
+ </div>
@@ -0,0 +1,42 @@
1
+ module PartialForm
2
+ module Generators
3
+ class PartialsGenerator < Rails::Generators::Base
4
+ SELECTION_GLOBS = {
5
+ default: "_{wrapper,text_field,label,submit}*.erb",
6
+ all: "*.erb"
7
+ }
8
+ FORM_PARTIALS_PATH = Pathname(__dir__).join("../../../app/views/layouts/form")
9
+ source_root FORM_PARTIALS_PATH
10
+ argument :given_selection, type: :string, optional: true, banner: "SELECTION" # optional because we ask for it if the user does not specify it
11
+
12
+ def self.banner # :nodoc:
13
+ <<~BANNER
14
+ rails g partial_form:partials SELECTION [options]
15
+
16
+ Copies partial templates to your application.
17
+ You can choose if all partials or only a subset is copied.
18
+ Pick on of: #{SELECTION_GLOBS.keys.join(", ")}
19
+ BANNER
20
+ end
21
+
22
+ def validate_selection
23
+ @selection = given_selection
24
+ valid_selections = SELECTION_GLOBS.keys.map(&:to_s)
25
+ if @selection.blank?
26
+ @selection = ask("Which partials do you want to copy?", limited_to: valid_selections)
27
+ end
28
+ unless valid_selections.include?(@selection)
29
+ raise Thor::Error, "Please choose a valid selection (first parameter): #{valid_selections.join(", ")}"
30
+ end
31
+ end
32
+
33
+ def copy_partials
34
+ pattern = SELECTION_GLOBS[@selection.to_sym]
35
+ filenames = FORM_PARTIALS_PATH.glob(pattern).map { _1.basename }
36
+ filenames.each do |filename|
37
+ copy_file filename, "app/views/layouts/form/#{filename}"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,309 @@
1
+ require "action_view"
2
+
3
+ module PartialForm
4
+ ##
5
+ # The builder allows the rendering of form fields similar to rails default `FormBuilder`.
6
+ # This Builder uses partials to manage and maintain the form field markup code.
7
+ # The Builder mimics the API of the default [Rails FormBuilder](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html) with the prefix `_`.
8
+ #
9
+ # The general idea is that each helper renders a dedicated partial and passes the arguments: `f`, `method`, `options`, `errors`.
10
+ #
11
+ # Please see README.md for more details.
12
+ class Builder < ActionView::Helpers::FormBuilder
13
+ def initialize(*) # :nodoc:
14
+ super
15
+ # CAUTION
16
+ # we want to make sure that fields are not wrapped by a "field_with_errors" proc
17
+ # this change affects all builders working on the current template
18
+ @template.field_error_proc = proc { |html_tag, instance| html_tag }
19
+ end
20
+
21
+ SIMPLE_FIELD_HELPER = [ # :nodoc:
22
+ :color_field, :date_field, :datetime_field, :datetime_local_field, :email_field, :file_field,
23
+ :hidden_field, :month_field, :number_field, :password_field, :phone_field, :range_field,
24
+ :search_field, :telephone_field, :text_field, :text_area, :time_field, :url_field, :week_field
25
+ ]
26
+
27
+ ##
28
+ # :method: _text_field
29
+ # :call-seq: _text_field(method, options = {})
30
+ #
31
+ # Renders the partial `views/layouts/form/_text_field`.
32
+ # All options are forwarded to the partial. Typically, options such as `label` and `hint` are taken into account with the template.
33
+ # Please checkout the examples in the README.md
34
+ #
35
+ # The partial template usually wraps the original field helper which can be found here: [ActionView::Helpers::FormBuilder#text_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-text_field).
36
+
37
+ ##
38
+ # :method: _color_field
39
+ # :call-seq: _color_field(method, options = {})
40
+ #
41
+ # Renders the partial `views/layouts/form/_color_field`.
42
+ #
43
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#color_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-color_field).
44
+
45
+ ##
46
+ # :method: _date_field
47
+ # :call-seq: _date_field(method, options = {})
48
+ #
49
+ # Renders the partial `views/layouts/form/_date_field`.
50
+ #
51
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#date_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-date_field).
52
+
53
+ ##
54
+ # :method: _datetime_field
55
+ # :call-seq: _datetime_field(method, options = {})
56
+ #
57
+ # Renders the partial `views/layouts/form/_datetime_field`.
58
+ #
59
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#datetime_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-datetime_field).
60
+
61
+ ##
62
+ # :method: _datetime_local_field
63
+ # :call-seq: _datetime_local_field(method, options = {})
64
+ #
65
+ # Renders the partial `views/layouts/form/_datetime_local_field`.
66
+ #
67
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#text_fielddatetime_local_fieldhttps://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-datetime_local_field).
68
+
69
+ ##
70
+ # :method: _email_field
71
+ # :call-seq: _email_field(method, options = {})
72
+ #
73
+ # Renders the partial `views/layouts/form/_email_field`.
74
+ #
75
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#email_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-email_field).
76
+
77
+ ##
78
+ # :method: _file_field
79
+ # :call-seq: _file_field(method, options = {})
80
+ #
81
+ # Renders the partial `views/layouts/form/_file_field`.
82
+ #
83
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#file_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-file_field).
84
+
85
+ ##
86
+ # :method: _hidden_field
87
+ # :call-seq: _hidden_field(method, options = {})
88
+ #
89
+ # Renders the partial `views/layouts/form/_hidden_field`.
90
+ #
91
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#hidden_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-hidden_field).
92
+
93
+ ##
94
+ # :method: _month_field
95
+ # :call-seq: _month_field(method, options = {})
96
+ #
97
+ # Renders the partial `views/layouts/form/_month_field`.
98
+ #
99
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#month_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-month_field).
100
+
101
+ ##
102
+ # :method: _number_field
103
+ # :call-seq: _number_field(method, options = {})
104
+ #
105
+ # Renders the partial `views/layouts/form/_number_field`.
106
+ #
107
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#number_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-number_field).
108
+
109
+ ##
110
+ # :method: _password_field
111
+ # :call-seq: _password_field(method, options = {})
112
+ #
113
+ # Renders the partial `views/layouts/form/_password_field`.
114
+ #
115
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#password_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-password_field).
116
+
117
+ ##
118
+ # :method: _phone_field
119
+ # :call-seq: _phone_field(method, options = {})
120
+ #
121
+ # Renders the partial `views/layouts/form/_phone_field`.
122
+ #
123
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#phone_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-phone_field).
124
+
125
+ ##
126
+ # :method: _range_field
127
+ # :call-seq: _range_field(method, options = {})
128
+ #
129
+ # Renders the partial `views/layouts/form/_range_field`.
130
+ #
131
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#range_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-range_field).
132
+
133
+ ##
134
+ # :method: _search_field
135
+ # :call-seq: _search_field(method, options = {})
136
+ #
137
+ # Renders the partial `views/layouts/form/_search_field`.
138
+ #
139
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#search_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-search_field).
140
+
141
+ ##
142
+ # :method: _telephone_field
143
+ # :call-seq: _telephone_field(method, options = {})
144
+ #
145
+ # Renders the partial `views/layouts/form/_telephone_field`.
146
+ #
147
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#telephone_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-telephone_field).
148
+
149
+ ##
150
+ # :method: _text_area
151
+ # :call-seq: _text_area(method, options = {})
152
+ #
153
+ # Renders the partial `views/layouts/form/_text_area`.
154
+ #
155
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#text_area](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-text_area).
156
+
157
+ ##
158
+ # :method: _time_field
159
+ # :call-seq: _time_field(method, options = {})
160
+ #
161
+ # Renders the partial `views/layouts/form/_time_field`.
162
+ #
163
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#time_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-time_field).
164
+
165
+ ##
166
+ # :method: _url_field
167
+ # :call-seq: _url_field(method, options = {})
168
+ #
169
+ # Renders the partial `views/layouts/form/_url_field`.
170
+ #
171
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#url_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-url_field).
172
+
173
+ ##
174
+ # :method: _week_field
175
+ # :call-seq: _week_field(method, options = {})
176
+ #
177
+ # Renders the partial `views/layouts/form/_week_field`.
178
+ #
179
+ # Please see #_text_field for more details. The partial template usually wraps [ActionView::Helpers::FormBuilder#week_field](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-week_field).
180
+
181
+ SIMPLE_FIELD_HELPER.each do |method_name|
182
+ define_method :"_#{method_name}" do |method, options = {}|
183
+ render_simple_field(__method__, method, options)
184
+ end
185
+ end
186
+
187
+ ##
188
+ # Renders the partial `views/layouts/form/_label`.
189
+ #
190
+ # The partial template usually wraps [ActionView::Helpers::FormBuilder#label](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-label).
191
+ def _label(method, text = nil, options = {}, &block)
192
+ errors = object.errors.messages_for(method)
193
+ render_partial("label", {f: self, method:, text:, errors:, options:, block:})
194
+ end
195
+
196
+ ##
197
+ # Renders the partial `views/layouts/form/_button`.
198
+ #
199
+ # The partial template usually wraps [ActionView::Helpers::FormBuilder#button](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-button).
200
+ def _button(value = nil, options = {}, &block)
201
+ render_partial("button", {f: self, value:, options:, block:})
202
+ end
203
+
204
+ ##
205
+ # Renders the partial `views/layouts/form/_submit`.
206
+ #
207
+ # The partial template usually wraps [ActionView::Helpers::FormBuilder#submit](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-submit).
208
+ def _submit(value = nil, options = {})
209
+ render_partial("submit", {f: self, value:, options:})
210
+ end
211
+
212
+ ##
213
+ # Renders the partial `views/layouts/form/_check_box`.
214
+ #
215
+ # The partial template usually wraps [ActionView::Helpers::FormBuilder#check_box](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-check_box).
216
+ def _check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
217
+ errors = object.errors.messages_for(method)
218
+ render_partial("check_box", {f: self, method:, options:, checked_value:, unchecked_value:, errors:})
219
+ end
220
+
221
+ ##
222
+ # Renders the partial `views/layouts/form/_radio_button`.
223
+ #
224
+ # The partial template usually wraps [ActionView::Helpers::FormBuilder#radio_button](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-radio_button).
225
+ def _radio_button(method, tag_value, options = {})
226
+ errors = object.errors.messages_for(method)
227
+ render_partial("radio_button", {f: self, method:, tag_value:, options:, errors:})
228
+ end
229
+
230
+ ##
231
+ # Renders the partial `views/layouts/form/_select`.
232
+ #
233
+ # The partial template usually wraps [ActionView::Helpers::FormBuilder#select](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-select).
234
+ def _select(method, choices = nil, options = {}, html_options = {}, &block)
235
+ errors = object.errors.messages_for(method)
236
+ render_partial("select", {f: self, method:, choices:, options:, html_options:, block:, errors:})
237
+ end
238
+
239
+ ##
240
+ # Renders the partial `views/layouts/form/_collection_select`.
241
+ #
242
+ # The partial template usually wraps [ActionView::Helpers::FormBuilder#collection_select](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-collection_select).
243
+ def _collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
244
+ errors = object.errors.messages_for(method)
245
+ render_partial("collection_select", {f: self, method:, collection:, value_method:, text_method:, options:, html_options:, errors:})
246
+ end
247
+
248
+ ##
249
+ # Renders the partial `views/layouts/form/_grouped_collection_select`.
250
+ #
251
+ # The partial template usually wraps [ActionView::Helpers::FormBuilder#grouped_collection_select](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-grouped_collection_select).
252
+ def _grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
253
+ errors = object.errors.messages_for(method)
254
+ render_partial("grouped_collection_select", {f: self, method:, collection:, group_method:, group_label_method:, option_key_method:, option_value_method:, options:, html_options:, errors:})
255
+ end
256
+
257
+ ##
258
+ # Renders the partial `views/layouts/form/_time_zone_select`.
259
+ #
260
+ # The partial template usually wraps [ActionView::Helpers::FormBuilder#time_zone_select](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-time_zone_select).
261
+ def _time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
262
+ errors = object.errors.messages_for(method)
263
+ render_partial("time_zone_select", {f: self, method:, priority_zones:, options:, html_options:, errors:})
264
+ end
265
+
266
+ ##
267
+ # Renders the partial `views/layouts/form/_weekday_select`.
268
+ #
269
+ # The partial template usually wraps [ActionView::Helpers::FormBuilder#weekday_select](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-weekday_select).
270
+ def _weekday_select(method, options = {}, html_options = {})
271
+ errors = object.errors.messages_for(method)
272
+ render_partial("weekday_select", {f: self, method:, options:, html_options:, errors:})
273
+ end
274
+
275
+ ##
276
+ # Renders the partial `views/layouts/form/_collection_check_boxes`.
277
+ #
278
+ # The partial template usually wraps [ActionView::Helpers::FormBuilder#collection_check_boxes](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-collection_check_boxes).
279
+ def _collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
280
+ errors = object.errors.messages_for(method)
281
+ render_partial("collection_check_boxes", {f: self, method:, collection:, value_method:, text_method:, options:, html_options:, block:, errors:})
282
+ end
283
+
284
+ ##
285
+ # Renders the partial `views/layouts/form/_collection_radio_buttons`.
286
+ #
287
+ # The partial template usually wraps [ActionView::Helpers::FormBuilder#collection_radio_buttons](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-collection_radio_buttons).
288
+ def _collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
289
+ errors = object.errors.messages_for(method)
290
+ render_partial("collection_radio_buttons", {f: self, method:, collection:, value_method:, text_method:, options:, html_options:, block:, errors:})
291
+ end
292
+
293
+ protected
294
+
295
+ def render_simple_field(type_with_prefix, method, options) # :nodoc:
296
+ type = type_with_prefix.to_s.delete_prefix("_")
297
+ errors = object.errors.messages_for(method)
298
+ render_partial(type, {f: self, method:, errors:, options:})
299
+ end
300
+
301
+ # Renders the partial with the given name, forwarding the locals to it.
302
+ def render_partial(partial_name, locals = {})
303
+ # layout name: controller.send :_layout, self.lookup_context, []
304
+ partial_path = "layouts/form/#{partial_name}"
305
+ raise "Please provide #{partial_path} partial" unless @template.lookup_context.exists?(partial_path, [], true) # true stands for partial
306
+ @template.render(partial: partial_path, locals:)
307
+ end
308
+ end
309
+ end
@@ -0,0 +1,5 @@
1
+ require "rails"
2
+ module PartialForm # :nodoc:
3
+ class Engine < ::Rails::Engine # :nodoc:
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module PartialForm
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,6 @@
1
+ require_relative "partial_form/version"
2
+ require_relative "partial_form/engine"
3
+ require_relative "partial_form/builder"
4
+
5
+ module PartialForm
6
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: partial_form
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tom Rothe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-11-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: actionview
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '7.1'
27
+ description: The gem simplifies the rendering of form fields by providing a `FormBuilder`
28
+ that leverages partials. This approach streamlines the management and maintenance
29
+ of the field helper markup.
30
+ email:
31
+ - info@tomrothe.de
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - CHANGELOG.md
37
+ - LICENSE.txt
38
+ - README.md
39
+ - app/views/layouts/form/_button.html.erb
40
+ - app/views/layouts/form/_check_box.html.erb
41
+ - app/views/layouts/form/_collection_check_boxes.html.erb
42
+ - app/views/layouts/form/_collection_radio_buttons.html.erb
43
+ - app/views/layouts/form/_collection_select.html.erb
44
+ - app/views/layouts/form/_color_field.html.erb
45
+ - app/views/layouts/form/_date_field.html.erb
46
+ - app/views/layouts/form/_datetime_field.html.erb
47
+ - app/views/layouts/form/_datetime_local_field.html.erb
48
+ - app/views/layouts/form/_email_field.html.erb
49
+ - app/views/layouts/form/_file_field.html.erb
50
+ - app/views/layouts/form/_grouped_collection_select.html.erb
51
+ - app/views/layouts/form/_hidden_field.html.erb
52
+ - app/views/layouts/form/_label.html.erb
53
+ - app/views/layouts/form/_month_field.html.erb
54
+ - app/views/layouts/form/_number_field.html.erb
55
+ - app/views/layouts/form/_password_field.html.erb
56
+ - app/views/layouts/form/_phone_field.html.erb
57
+ - app/views/layouts/form/_radio_button.html.erb
58
+ - app/views/layouts/form/_range_field.html.erb
59
+ - app/views/layouts/form/_search_field.html.erb
60
+ - app/views/layouts/form/_select.html.erb
61
+ - app/views/layouts/form/_submit.html.erb
62
+ - app/views/layouts/form/_telephone_field.html.erb
63
+ - app/views/layouts/form/_text_area.html.erb
64
+ - app/views/layouts/form/_text_field.html.erb
65
+ - app/views/layouts/form/_time_field.html.erb
66
+ - app/views/layouts/form/_time_zone_select.html.erb
67
+ - app/views/layouts/form/_url_field.html.erb
68
+ - app/views/layouts/form/_week_field.html.erb
69
+ - app/views/layouts/form/_weekday_select.html.erb
70
+ - app/views/layouts/form/_wrapper.html.erb
71
+ - lib/generators/partial_form/partials_generator.rb
72
+ - lib/partial_form.rb
73
+ - lib/partial_form/builder.rb
74
+ - lib/partial_form/engine.rb
75
+ - lib/partial_form/version.rb
76
+ homepage: https://github.com/motine/partial_form
77
+ licenses:
78
+ - MIT
79
+ metadata:
80
+ homepage_uri: https://github.com/motine/partial_form
81
+ source_code_uri: https://github.com/motine/partial_form/
82
+ changelog_uri: https://github.com/motine/partial_form/blob/master/CHANGELOG.md
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 3.2.0
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubygems_version: 3.5.16
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: Customizable forms for Rails using partials!
102
+ test_files: []