view_partial_form_builder 0.1.4 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +64 -64
- data/lib/view_partial_form_builder/engine.rb +10 -2
- data/lib/view_partial_form_builder/form_builder.rb +6 -304
- data/lib/view_partial_form_builder/lookup_override.rb +8 -12
- data/lib/view_partial_form_builder/template_proxy.rb +120 -0
- data/lib/view_partial_form_builder/version.rb +1 -1
- data/lib/view_partial_form_builder.rb +5 -1
- metadata +14 -42
- data/lib/view_partial_form_builder/html_attributes.rb +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c09b26fa824aff0b6fc36212577a96ca2daecefdf5dda0614ab000ea5882d4a3
|
4
|
+
data.tar.gz: dfaccd8fd95ec8049e4518079b67af9962fa1b2ffb28125233f400f109bf054f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f3381c4245c6bd84e61c79d3d28b9ff195a4fa632f657be10153b7c58af1ce29ff2253556858450a9464b6b2b39a82aab83e53cc1327a046d77b2f21348be48
|
7
|
+
data.tar.gz: e0e2f622b47ed7bd4db4873f5864746c0663d64fed8583475a75ab4744b60567edcad91156cf721921aa4ee36d5a370a4116efd5b7c0b06839c597ae99825d40
|
data/README.md
CHANGED
@@ -37,7 +37,7 @@ Next, declare view partials that correspond to the [`FormBuilder`][FormBuilder]
|
|
37
37
|
helper method you'd like to have more control over:
|
38
38
|
|
39
39
|
```html+erb
|
40
|
-
<%# app/views/form_builder/_text_field.html.erb %>
|
40
|
+
<%# app/views/application/form_builder/_text_field.html.erb %>
|
41
41
|
|
42
42
|
<input
|
43
43
|
type="text"
|
@@ -48,7 +48,7 @@ helper method you'd like to have more control over:
|
|
48
48
|
<% end %>
|
49
49
|
>
|
50
50
|
|
51
|
-
<%# app/views/form_builder/_email_field.html.erb %>
|
51
|
+
<%# app/views/application/form_builder/_email_field.html.erb %>
|
52
52
|
|
53
53
|
<input
|
54
54
|
type="email"
|
@@ -59,7 +59,7 @@ helper method you'd like to have more control over:
|
|
59
59
|
<% end %>
|
60
60
|
>
|
61
61
|
|
62
|
-
<%# app/views/form_builder/_button.html.erb %>
|
62
|
+
<%# app/views/application/form_builder/_button.html.erb %>
|
63
63
|
|
64
64
|
<button
|
65
65
|
class="button button--primary"
|
@@ -76,7 +76,7 @@ You'll have local access to the `FormBuilder` instance as the template-local
|
|
76
76
|
generating HTML through Rails' helpers:
|
77
77
|
|
78
78
|
```html+erb
|
79
|
-
<%# app/views/form_builder/_email_field.html.erb %>
|
79
|
+
<%# app/views/application/form_builder/_email_field.html.erb %>
|
80
80
|
|
81
81
|
<div class="email-field-wrapper">
|
82
82
|
<%= form.email_field(method, required: true, **options)) %>
|
@@ -84,10 +84,10 @@ generating HTML through Rails' helpers:
|
|
84
84
|
```
|
85
85
|
|
86
86
|
```html+erb
|
87
|
-
<%# app/views/form_builder/_button.html.erb %>
|
87
|
+
<%# app/views/application/form_builder/_button.html.erb %>
|
88
88
|
|
89
89
|
<div class="button-wrapper">
|
90
|
-
<%= form.button(
|
90
|
+
<%= form.button(value, options, &block) %>
|
91
91
|
</div>
|
92
92
|
```
|
93
93
|
|
@@ -116,30 +116,8 @@ In addition, each view partial receives:
|
|
116
116
|
* `form` - a reference to the instance of `ViewPartialFormBuilder`, which is a
|
117
117
|
descendant of [`ActionView::Helpers::FormBuilder`][FormBuilder]
|
118
118
|
|
119
|
-
* `
|
120
|
-
|
121
|
-
helper by [splatting them][splat] out.
|
122
|
-
|
123
|
-
* `&block` - a callable, `yield`-able block if the helper method was passed one
|
124
|
-
|
125
|
-
In cases when a [`ActionView::Helpers::FormBuilder` helper
|
126
|
-
method][FormBuilder]'s last arguments are options (either `Hash` instances or
|
127
|
-
[keyword arguments][]), they're omitted from the `arguments` array.
|
128
|
-
|
129
|
-
If you want to pass-through all arguments, options, and block parameters, you
|
130
|
-
can splat them out:
|
131
|
-
|
132
|
-
```html+erb
|
133
|
-
<%# app/views/form_builder/_label.html.erb %>
|
134
|
-
|
135
|
-
<%= form.label(*arguments, **options, &block) %>
|
136
|
-
```
|
137
|
-
|
138
|
-
```html+erb
|
139
|
-
<%# app/views/form_builder/_select.html.erb %>
|
140
|
-
|
141
|
-
<%= form.select(*arguments, **html_options, &block) %>
|
142
|
-
```
|
119
|
+
* `block` - the block if the helper method was passed one. Forward it along to
|
120
|
+
field helpers as `&block`.
|
143
121
|
|
144
122
|
#### Handling DOMTokenList attributes
|
145
123
|
|
@@ -158,16 +136,12 @@ When rendering a field's DOMTokenList-backed attributes (like `class` or
|
|
158
136
|
controllers][stimulus-controller]), transforming and combining singular `String`
|
159
137
|
instances into lists of token can be very useful.
|
160
138
|
|
161
|
-
To simplify those scenarios, a partial's template-local optional attributes are
|
162
|
-
made available with the `#merge_token_lists` method.
|
163
|
-
|
164
139
|
These optional attributes are available through the `options` or `html_options`
|
165
140
|
partial-local variables. Their name will depend on the partial's corresponding
|
166
141
|
[`ActionView::Helpers::FormBuilder`][FormBuilder] interface.
|
167
142
|
|
168
|
-
|
169
|
-
|
170
|
-
Given call to `form.text_field` and a corresponding partial declaration:
|
143
|
+
To "merge" attributes together, you can combine Ruby's `String` interpolation
|
144
|
+
and `Hash#delete`:
|
171
145
|
|
172
146
|
```html+erb
|
173
147
|
<%# app/views/users/new.html.erb %>
|
@@ -175,9 +149,13 @@ Given call to `form.text_field` and a corresponding partial declaration:
|
|
175
149
|
<%= form.text_field(:name, class: "text-field--modifier") %>
|
176
150
|
<% end %>
|
177
151
|
|
178
|
-
<# app/views/form_builder/_text_field.html.erb %>
|
152
|
+
<# app/views/application/form_builder/_text_field.html.erb %>
|
179
153
|
|
180
|
-
<%= form.text_field(
|
154
|
+
<%= form.text_field(
|
155
|
+
method,
|
156
|
+
class: "text-field #{options.delete(:class)}",
|
157
|
+
**options
|
158
|
+
) %>
|
181
159
|
```
|
182
160
|
|
183
161
|
The resulting HTML `<input>` element will merge have its [`class`
|
@@ -192,7 +170,6 @@ values:
|
|
192
170
|
[button]: https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-button
|
193
171
|
[local_assigns]: https://api.rubyonrails.org/classes/ActionView/Template.html#method-i-local_assigns
|
194
172
|
[splat]: https://ruby-doc.org/core-2.2.0/doc/syntax/calling_methods_rdoc.html#label-Array+to+Arguments+Conversion
|
195
|
-
[keyword arguments]: https://thoughtbot.com/blog/ruby-2-keyword-arguments
|
196
173
|
[mdn-class]: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class
|
197
174
|
[DOMTokenList]: https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList
|
198
175
|
[classList]: https://developer.mozilla.org/en-US/docs/Web/API/Element/classList
|
@@ -210,7 +187,7 @@ block-local `form` variable:
|
|
210
187
|
```erb
|
211
188
|
<%# app/views/users/form_builder/_email_field.html.erb %>
|
212
189
|
|
213
|
-
<%= form.default.email_field(
|
190
|
+
<%= form.default.email_field(method, options) %>
|
214
191
|
```
|
215
192
|
|
216
193
|
When passing a `model:` or `scope:` to calls to [`form_with`][form_with],
|
@@ -219,44 +196,64 @@ look up path.
|
|
219
196
|
|
220
197
|
For example, when calling `form_with(model: User.new)`, a partial declared in
|
221
198
|
`app/views/users/form_builder/` would take precedent over a partial declared in
|
222
|
-
`app/views/form_builder/`.
|
199
|
+
`app/views/application/form_builder/`.
|
223
200
|
|
224
201
|
```erb
|
225
202
|
<%# app/views/users/form_builder/_password_field.html.erb %>
|
226
203
|
|
227
204
|
<div class="password-field-wrapper">
|
228
|
-
<%= form.password_field(
|
205
|
+
<%= form.password_field(method, options) %>
|
229
206
|
</div>
|
230
207
|
```
|
231
208
|
|
232
|
-
If you'd like to render a specific partial for a field,
|
233
|
-
|
209
|
+
If you'd like to render a specific partial for a field, make sure that you pass
|
210
|
+
along the `form:` (along with any other partial-local variables) as part of the
|
211
|
+
`render` call's `locals:` option:
|
212
|
+
|
234
213
|
|
235
214
|
```erb
|
236
215
|
<%# app/views/users/new.html.erb %>
|
237
216
|
|
238
217
|
<%= form_with(model: User.new) do |form| %>
|
239
|
-
<%=
|
218
|
+
<%= render("emails/my_special_email_field", {
|
219
|
+
form: form,
|
220
|
+
method: :email,
|
221
|
+
options: { class: "user-email" },
|
222
|
+
) %>
|
240
223
|
<% end %>
|
224
|
+
|
225
|
+
<%# app/views/emails/_my_special_email_field.html.erb %>
|
226
|
+
|
227
|
+
<%= form.email_field(
|
228
|
+
method,
|
229
|
+
class: "my-special-email #{options.delete(:class)},
|
230
|
+
**options
|
231
|
+
) %>
|
241
232
|
```
|
242
233
|
|
243
234
|
#### Composing partials
|
244
235
|
|
245
|
-
|
246
|
-
|
247
|
-
with a consumer facing site, but has
|
236
|
+
Layering partials on top of one another can be useful to share foundational
|
237
|
+
styles and configuration across your fields. For instance, consider an
|
238
|
+
administrative interface that shares styles with a consumer facing site, but has
|
239
|
+
additional bells and whistles.
|
248
240
|
|
249
241
|
Declare the consumer facing inputs (in this example, `<input type="search">`):
|
250
242
|
|
251
243
|
```html+erb
|
252
|
-
<%# app/views/form_builder/_search_field.html.erb %>
|
244
|
+
<%# app/views/application/form_builder/_search_field.html.erb %>
|
253
245
|
|
254
246
|
<%= form.search_field(
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
247
|
+
method,
|
248
|
+
class: "
|
249
|
+
search-field
|
250
|
+
#{options.delete(:class)}
|
251
|
+
",
|
252
|
+
"data-controller": "
|
253
|
+
input->search#executeQuery
|
254
|
+
#{options.delete(:"data-controller")}
|
255
|
+
",
|
256
|
+
**options
|
260
257
|
) %>
|
261
258
|
```
|
262
259
|
|
@@ -264,20 +261,23 @@ Then, declare the administrative interface's inputs, in terms of overriding the
|
|
264
261
|
foundation built by the more general definitions:
|
265
262
|
|
266
263
|
```html+erb
|
267
|
-
<%# app/views/admin/form_builder/_search_field.html.erb %>
|
264
|
+
<%# app/views/admin/application/form_builder/_search_field.html.erb %>
|
268
265
|
|
269
266
|
<%= form.search_field(
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
class
|
274
|
-
|
275
|
-
|
267
|
+
method,
|
268
|
+
class: "
|
269
|
+
search-field--admin
|
270
|
+
#{options.delete(:class}
|
271
|
+
",
|
272
|
+
"data-controller": "
|
273
|
+
focus->admin-search#clearResults
|
274
|
+
#{options.delete(:"data-controller")}
|
275
|
+
",
|
276
276
|
) %>
|
277
277
|
```
|
278
278
|
|
279
|
-
The rendered `admin/form_builder/search_field` partial combines
|
280
|
-
arguments from both partials:
|
279
|
+
The rendered `admin/application/form_builder/search_field` partial combines
|
280
|
+
options and arguments from both partials:
|
281
281
|
|
282
282
|
```html
|
283
283
|
<input
|
@@ -327,7 +327,7 @@ Models declared within modules will be delimited with `/`. For example,
|
|
327
327
|
### Configuration
|
328
328
|
|
329
329
|
View partials lookup and resolution will be scoped to the
|
330
|
-
`app/views/form_builder` directory.
|
330
|
+
`app/views/application/form_builder` directory.
|
331
331
|
|
332
332
|
To override this destination to another directory (for example,
|
333
333
|
`app/views/fields`, or `app/views/users/fields`), set
|
@@ -1,9 +1,17 @@
|
|
1
|
-
require "view_partial_form_builder/form_builder"
|
2
|
-
|
3
1
|
module ViewPartialFormBuilder
|
4
2
|
class Engine < ::Rails::Engine
|
5
3
|
ActiveSupport.on_load(:action_controller_base) do
|
6
4
|
default_form_builder ViewPartialFormBuilder::FormBuilder
|
7
5
|
end
|
6
|
+
|
7
|
+
ActiveSupport.on_load(:view_partial_form_builder) do
|
8
|
+
if Rails::VERSION::MAJOR > 7
|
9
|
+
self.aliased_field_helpers = {
|
10
|
+
checkbox: [:check_box],
|
11
|
+
collection_checkboxes: [:collection_check_boxes],
|
12
|
+
rich_textarea: [:rich_text_area]
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
8
16
|
end
|
9
17
|
end
|
@@ -1,313 +1,15 @@
|
|
1
|
-
require "view_partial_form_builder/lookup_override"
|
2
|
-
require "view_partial_form_builder/html_attributes"
|
3
|
-
|
4
1
|
module ViewPartialFormBuilder
|
5
2
|
class FormBuilder < ActionView::Helpers::FormBuilder
|
3
|
+
class_attribute :aliased_field_helpers, default: {}
|
4
|
+
|
6
5
|
attr_reader :default
|
7
6
|
|
8
7
|
def initialize(*)
|
9
8
|
super
|
10
|
-
|
11
|
-
@
|
12
|
-
object_name,
|
13
|
-
object,
|
14
|
-
@template,
|
15
|
-
options,
|
16
|
-
)
|
17
|
-
@lookup_override = LookupOverride.new(
|
18
|
-
prefixes: @template.lookup_context.prefixes,
|
19
|
-
object_name: object&.model_name || object_name,
|
20
|
-
view_partial_directory: ViewPartialFormBuilder.view_partial_directory,
|
21
|
-
)
|
22
|
-
end
|
23
|
-
|
24
|
-
def label(method, text = nil, **options, &block)
|
25
|
-
locals = {
|
26
|
-
method: method,
|
27
|
-
text: text,
|
28
|
-
options: HtmlAttributes.new(options),
|
29
|
-
block: block,
|
30
|
-
arguments: [method, text],
|
31
|
-
}
|
32
|
-
|
33
|
-
render_partial("label", locals, fallback: -> { super }, &block)
|
34
|
-
end
|
35
|
-
|
36
|
-
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
37
|
-
locals = {
|
38
|
-
method: method,
|
39
|
-
options: HtmlAttributes.new(options),
|
40
|
-
checked_value: checked_value,
|
41
|
-
unchecked_value: unchecked_value,
|
42
|
-
arguments: [method, options, checked_value, unchecked_value],
|
43
|
-
}
|
44
|
-
|
45
|
-
render_partial("check_box", locals, fallback: -> { super })
|
46
|
-
end
|
47
|
-
|
48
|
-
def radio_button(method, tag_value, **options)
|
49
|
-
locals = {
|
50
|
-
method: method,
|
51
|
-
tag_value: tag_value,
|
52
|
-
options: HtmlAttributes.new(options),
|
53
|
-
arguments: [method, tag_value],
|
54
|
-
}
|
55
|
-
|
56
|
-
render_partial("radio_button", locals, fallback: -> { super })
|
57
|
-
end
|
58
|
-
|
59
|
-
def select(method, choices = nil, options = {}, **html_options, &block)
|
60
|
-
html_options = @default_html_options.merge(html_options)
|
61
|
-
|
62
|
-
locals = {
|
63
|
-
method: method,
|
64
|
-
choices: choices,
|
65
|
-
options: options,
|
66
|
-
html_options: HtmlAttributes.new(html_options),
|
67
|
-
block: block,
|
68
|
-
arguments: [method, choices, options],
|
69
|
-
}
|
70
|
-
|
71
|
-
render_partial("select", locals, fallback: -> { super }, &block)
|
72
|
-
end
|
73
|
-
|
74
|
-
def collection_select(method, collection, value_method, text_method, options = {}, **html_options)
|
75
|
-
html_options = @default_html_options.merge(html_options)
|
76
|
-
|
77
|
-
locals = {
|
78
|
-
method: method,
|
79
|
-
collection: collection,
|
80
|
-
value_method: value_method,
|
81
|
-
text_method: text_method,
|
82
|
-
options: options,
|
83
|
-
html_options: html_options,
|
84
|
-
arguments: [method, collection, value_method, text_method, options],
|
85
|
-
}
|
86
|
-
|
87
|
-
render_partial("collection_select", locals, fallback: -> { super })
|
88
|
-
end
|
89
|
-
|
90
|
-
def collection_check_boxes(method, collection, value_method, text_method, options = {}, **html_options, &block)
|
91
|
-
html_options = @default_html_options.merge(html_options)
|
92
|
-
|
93
|
-
locals = {
|
94
|
-
method: method,
|
95
|
-
collection: collection,
|
96
|
-
value_method: value_method,
|
97
|
-
text_method: text_method,
|
98
|
-
options: options,
|
99
|
-
html_options: HtmlAttributes.new(html_options),
|
100
|
-
block: block,
|
101
|
-
arguments: [
|
102
|
-
method,
|
103
|
-
collection,
|
104
|
-
value_method,
|
105
|
-
text_method,
|
106
|
-
options,
|
107
|
-
],
|
108
|
-
}
|
109
|
-
|
110
|
-
render_partial("collection_check_boxes", locals, fallback: -> { super }, &block)
|
111
|
-
end
|
112
|
-
|
113
|
-
def collection_radio_buttons(method, collection, value_method, text_method, options = {}, **html_options, &block)
|
114
|
-
html_options = @default_html_options.merge(html_options)
|
115
|
-
|
116
|
-
locals = {
|
117
|
-
method: method,
|
118
|
-
collection: collection,
|
119
|
-
value_method: value_method,
|
120
|
-
text_method: text_method,
|
121
|
-
options: options,
|
122
|
-
html_options: HtmlAttributes.new(html_options),
|
123
|
-
block: block,
|
124
|
-
arguments: [
|
125
|
-
method,
|
126
|
-
collection,
|
127
|
-
value_method,
|
128
|
-
text_method,
|
129
|
-
options,
|
130
|
-
],
|
131
|
-
}
|
132
|
-
|
133
|
-
render_partial("collection_radio_buttons", locals, fallback: -> { super }, &block)
|
134
|
-
end
|
135
|
-
|
136
|
-
def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, **html_options)
|
137
|
-
html_options = @default_html_options.merge(html_options)
|
138
|
-
|
139
|
-
locals = {
|
140
|
-
method: method,
|
141
|
-
collection: collection,
|
142
|
-
group_method: group_method,
|
143
|
-
group_label_method: group_label_method,
|
144
|
-
option_key_method: option_key_method,
|
145
|
-
option_value_method: option_value_method,
|
146
|
-
html_options: HtmlAttributes.new(html_options),
|
147
|
-
options: options,
|
148
|
-
arguments: [
|
149
|
-
method,
|
150
|
-
collection,
|
151
|
-
group_method,
|
152
|
-
group_label_method,
|
153
|
-
option_key_method,
|
154
|
-
option_value_method,
|
155
|
-
options,
|
156
|
-
],
|
157
|
-
}
|
158
|
-
|
159
|
-
render_partial("grouped_collection_select", locals, fallback: -> { super })
|
160
|
-
end
|
161
|
-
|
162
|
-
def time_zone_select(method, priority_zones = nil, options = {}, **html_options)
|
163
|
-
html_options = @default_html_options.merge(html_options)
|
164
|
-
|
165
|
-
locals = {
|
166
|
-
method: method,
|
167
|
-
priority_zones: priority_zones,
|
168
|
-
html_options: HtmlAttributes.new(html_options),
|
169
|
-
options: options,
|
170
|
-
arguments: [method, priority_zones, options],
|
171
|
-
}
|
172
|
-
|
173
|
-
render_partial("time_zone_select", locals, fallback: -> { super })
|
174
|
-
end
|
175
|
-
|
176
|
-
def date_select(method, options = {}, **html_options)
|
177
|
-
locals = {
|
178
|
-
method: method,
|
179
|
-
options: options,
|
180
|
-
html_options: HtmlAttributes.new(html_options),
|
181
|
-
arguments: [method, options, html_options],
|
182
|
-
}
|
183
|
-
|
184
|
-
render_partial("date_select", locals, fallback: -> { super })
|
185
|
-
end
|
186
|
-
|
187
|
-
def hidden_field(method, **options)
|
188
|
-
@emitted_hidden_id = true if method == :id
|
189
|
-
|
190
|
-
locals = {
|
191
|
-
method: method,
|
192
|
-
options: HtmlAttributes.new(options),
|
193
|
-
arguments: [method],
|
194
|
-
}
|
195
|
-
|
196
|
-
render_partial("hidden_field", locals, fallback: -> { super })
|
197
|
-
end
|
198
|
-
|
199
|
-
def file_field(method, **options)
|
200
|
-
self.multipart = true
|
201
|
-
|
202
|
-
locals = {
|
203
|
-
method: method,
|
204
|
-
options: HtmlAttributes.new(options),
|
205
|
-
arguments: [method],
|
206
|
-
}
|
207
|
-
|
208
|
-
render_partial("file_field", locals, fallback: -> { super })
|
209
|
-
end
|
210
|
-
|
211
|
-
def submit(value = nil, **options)
|
212
|
-
value, options = nil, value if value.is_a?(Hash)
|
213
|
-
value ||= submit_default_value
|
214
|
-
|
215
|
-
locals = {
|
216
|
-
value: value,
|
217
|
-
options: HtmlAttributes.new(options),
|
218
|
-
arguments: [value],
|
219
|
-
}
|
220
|
-
|
221
|
-
render_partial("submit", locals, fallback: -> { super })
|
222
|
-
end
|
223
|
-
|
224
|
-
def button(value = nil, **options)
|
225
|
-
value, options = nil, value if value.is_a?(Hash)
|
226
|
-
value ||= submit_default_value
|
227
|
-
|
228
|
-
locals = {
|
229
|
-
value: value,
|
230
|
-
options: HtmlAttributes.new(options),
|
231
|
-
arguments: [value],
|
232
|
-
}
|
233
|
-
|
234
|
-
render_partial("button", locals, fallback: -> { super })
|
235
|
-
end
|
236
|
-
|
237
|
-
DYNAMICALLY_DECLARED = (
|
238
|
-
field_helpers +
|
239
|
-
[:rich_text_area] -
|
240
|
-
[:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]
|
241
|
-
)
|
242
|
-
|
243
|
-
DYNAMICALLY_DECLARED.each do |selector|
|
244
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
245
|
-
def #{selector}(method, **options)
|
246
|
-
render_partial(
|
247
|
-
"#{selector}",
|
248
|
-
{
|
249
|
-
method: method,
|
250
|
-
options: HtmlAttributes.new(options),
|
251
|
-
arguments: [method],
|
252
|
-
},
|
253
|
-
fallback: -> { super },
|
254
|
-
)
|
255
|
-
end
|
256
|
-
RUBY
|
257
|
-
end
|
258
|
-
|
259
|
-
private
|
260
|
-
|
261
|
-
def render_partial(field, locals, fallback:, &block)
|
262
|
-
options = locals.fetch(:options, {})
|
263
|
-
partial_override = options.delete(:partial)
|
264
|
-
locals = objectify_options(options).merge(locals, form: self)
|
265
|
-
|
266
|
-
partial = if partial_override.present?
|
267
|
-
find_partial(partial_override, locals, prefixes: [])
|
268
|
-
else
|
269
|
-
find_partial(field, locals, prefixes: prefixes_after(field))
|
270
|
-
end
|
271
|
-
|
272
|
-
if partial.nil? || about_to_recurse_infinitely?(partial)
|
273
|
-
fallback.call
|
274
|
-
else
|
275
|
-
partial.render(@template, locals, &block)
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
def find_partial(template_name, locals, prefixes:)
|
280
|
-
template_is_partial = true
|
281
|
-
|
282
|
-
@template.lookup_context.find_all(
|
283
|
-
template_name,
|
284
|
-
prefixes,
|
285
|
-
template_is_partial,
|
286
|
-
locals.keys,
|
287
|
-
).first
|
288
|
-
end
|
289
|
-
|
290
|
-
def prefixes_after(template_name)
|
291
|
-
prefixes = @lookup_override.prefixes
|
292
|
-
current_prefix = current_virtual_path.delete_suffix("/_#{template_name}")
|
293
|
-
|
294
|
-
if prefixes.include?(current_prefix)
|
295
|
-
prefixes.from(prefixes.index(current_prefix).to_i + 1)
|
296
|
-
else
|
297
|
-
prefixes
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
def about_to_recurse_infinitely?(partial)
|
302
|
-
partial.virtual_path == current_virtual_path
|
303
|
-
end
|
304
|
-
|
305
|
-
def current_virtual_path
|
306
|
-
if (current_template = @template.instance_values["current_template"])
|
307
|
-
current_template.virtual_path.to_s
|
308
|
-
else
|
309
|
-
@template.instance_values["virtual_path"].to_s
|
310
|
-
end
|
9
|
+
@default = dup
|
10
|
+
@template = TemplateProxy.new(builder: self, template: @template)
|
311
11
|
end
|
312
12
|
end
|
13
|
+
|
14
|
+
ActiveSupport.run_load_hooks(:view_partial_form_builder, FormBuilder)
|
313
15
|
end
|
@@ -11,28 +11,24 @@ module ViewPartialFormBuilder
|
|
11
11
|
|
12
12
|
prefixes = [
|
13
13
|
"#{object_name}/#{view_partial_directory}",
|
14
|
-
object_name,
|
15
|
-
view_partial_directory,
|
16
14
|
"#{root_prefix}/#{view_partial_directory}",
|
17
|
-
root_prefix,
|
18
15
|
]
|
19
16
|
|
20
17
|
overridden_prefixes.reverse_each do |prefix|
|
21
|
-
namespace, *files = prefix.split("/")
|
22
|
-
|
23
|
-
prefixes.unshift(prefix)
|
24
|
-
|
25
|
-
if namespace.present?
|
26
|
-
prefixes.unshift("#{namespace}/#{view_partial_directory}")
|
27
|
-
end
|
28
|
-
|
29
18
|
prefixes.unshift("#{prefix}/#{view_partial_directory}")
|
30
19
|
end
|
31
20
|
|
32
|
-
|
33
21
|
prefixes.uniq
|
34
22
|
end
|
35
23
|
|
24
|
+
def prefixes_after(current_prefix)
|
25
|
+
if prefixes.include?(current_prefix)
|
26
|
+
prefixes.from(prefixes.index(current_prefix).to_i + 1)
|
27
|
+
else
|
28
|
+
prefixes
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
36
32
|
private
|
37
33
|
|
38
34
|
attr_reader :object_name, :view_partial_directory
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module ViewPartialFormBuilder
|
2
|
+
class TemplateProxy
|
3
|
+
delegate :field_id, :field_name, to: :@template
|
4
|
+
|
5
|
+
def initialize(builder:, template:)
|
6
|
+
@template = template
|
7
|
+
@builder = builder
|
8
|
+
end
|
9
|
+
|
10
|
+
def button_tag(value, options, &block)
|
11
|
+
render(:button, arguments: [value, options], block: block) do
|
12
|
+
@template.button_tag(value, options, &block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def submit_tag(value, options)
|
17
|
+
render(:submit, arguments: [value, options]) do
|
18
|
+
@template.submit_tag(value, options)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def label(object_name, method, content_or_options = nil, options = nil, &block)
|
23
|
+
if content_or_options.is_a?(Hash)
|
24
|
+
options.merge! content_or_options
|
25
|
+
content = nil
|
26
|
+
else
|
27
|
+
content = content_or_options
|
28
|
+
end
|
29
|
+
|
30
|
+
render(:label, arguments: [method, content, options], block: block) do
|
31
|
+
@template.label(object_name, method, content, options, &block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def method_missing(name, *arguments, &block)
|
38
|
+
if @builder.respond_to?(name)
|
39
|
+
arguments_after_object_name = arguments.from(1)
|
40
|
+
|
41
|
+
render(name, arguments: arguments_after_object_name, block: block) do
|
42
|
+
@template.public_send(name, *arguments, &block)
|
43
|
+
end
|
44
|
+
elsif @template.respond_to?(name)
|
45
|
+
@template.public_send(name, *arguments, &block)
|
46
|
+
else
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def respond_to_missing?(name, include_private = false)
|
52
|
+
@builder.respond_to_missing?(name) || @template.respond_to_missing?(name)
|
53
|
+
end
|
54
|
+
|
55
|
+
def render(partial_name, arguments:, block: nil, &fallback)
|
56
|
+
locals = extract_partial_locals(partial_name, *arguments).merge(
|
57
|
+
form: @builder,
|
58
|
+
block: block
|
59
|
+
)
|
60
|
+
|
61
|
+
partial = find_partial(partial_name, locals)
|
62
|
+
|
63
|
+
if partial.nil? || about_to_recurse_infinitely?(partial)
|
64
|
+
fallback.call
|
65
|
+
else
|
66
|
+
partial.render(@template, locals, &block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def extract_partial_locals(form_method, *arguments)
|
71
|
+
parameters = @builder.method(form_method).parameters
|
72
|
+
|
73
|
+
parameters.each_with_index.each_with_object({}) { |(tuple, index), locals|
|
74
|
+
_type, parameter = tuple
|
75
|
+
|
76
|
+
locals[parameter] = arguments[index]
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
def find_partial(template_name, locals)
|
81
|
+
current_prefix = current_virtual_path.delete_suffix("/_#{template_name}")
|
82
|
+
template_is_partial = true
|
83
|
+
|
84
|
+
partials = template_names_for(template_name).lazy.map do |name|
|
85
|
+
@template.lookup_context.find_all(
|
86
|
+
name,
|
87
|
+
lookup_override.prefixes_after(current_prefix),
|
88
|
+
template_is_partial,
|
89
|
+
locals.keys
|
90
|
+
).first
|
91
|
+
end
|
92
|
+
|
93
|
+
partials.find(&:present?)
|
94
|
+
end
|
95
|
+
|
96
|
+
def template_names_for(template_name)
|
97
|
+
[template_name, *@builder.aliased_field_helpers[template_name]].compact
|
98
|
+
end
|
99
|
+
|
100
|
+
def about_to_recurse_infinitely?(partial)
|
101
|
+
partial.virtual_path == current_virtual_path
|
102
|
+
end
|
103
|
+
|
104
|
+
def current_virtual_path
|
105
|
+
if (current_template = @template.instance_values["current_template"])
|
106
|
+
current_template.virtual_path.to_s
|
107
|
+
else
|
108
|
+
@template.instance_values["virtual_path"].to_s
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def lookup_override
|
113
|
+
LookupOverride.new(
|
114
|
+
prefixes: @template.lookup_context.prefixes,
|
115
|
+
object_name: @builder.object.try(:model_name) || @builder.object_name,
|
116
|
+
view_partial_directory: ViewPartialFormBuilder.view_partial_directory
|
117
|
+
)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: view_partial_form_builder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Doyle
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-09-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionview
|
@@ -16,36 +16,22 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: railties
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 4.0.0
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 4.0.0
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: minitest-around
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
44
30
|
requirements:
|
45
31
|
- - ">="
|
46
32
|
- !ruby/object:Gem::Version
|
47
33
|
version: '0'
|
48
|
-
type: :
|
34
|
+
type: :runtime
|
49
35
|
prerelease: false
|
50
36
|
version_requirements: !ruby/object:Gem::Requirement
|
51
37
|
requirements:
|
@@ -53,33 +39,19 @@ dependencies:
|
|
53
39
|
- !ruby/object:Gem::Version
|
54
40
|
version: '0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
42
|
+
name: zeitwerk
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
58
44
|
requirements:
|
59
45
|
- - ">="
|
60
46
|
- !ruby/object:Gem::Version
|
61
|
-
version: 4.0
|
62
|
-
type: :
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: 4.0.0
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: activerecord-nulldb-adapter
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :development
|
47
|
+
version: 2.4.0
|
48
|
+
type: :runtime
|
77
49
|
prerelease: false
|
78
50
|
version_requirements: !ruby/object:Gem::Requirement
|
79
51
|
requirements:
|
80
52
|
- - ">="
|
81
53
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
54
|
+
version: 2.4.0
|
83
55
|
description: A Rails form builder where all designer-facing configuration is via templates.
|
84
56
|
email:
|
85
57
|
- sean.p.doyle24@gmail.com
|
@@ -93,14 +65,14 @@ files:
|
|
93
65
|
- lib/view_partial_form_builder.rb
|
94
66
|
- lib/view_partial_form_builder/engine.rb
|
95
67
|
- lib/view_partial_form_builder/form_builder.rb
|
96
|
-
- lib/view_partial_form_builder/html_attributes.rb
|
97
68
|
- lib/view_partial_form_builder/lookup_override.rb
|
69
|
+
- lib/view_partial_form_builder/template_proxy.rb
|
98
70
|
- lib/view_partial_form_builder/version.rb
|
99
71
|
homepage: https://github.com/seanpdoyle/view_partial_form_builder
|
100
72
|
licenses:
|
101
73
|
- MIT
|
102
74
|
metadata: {}
|
103
|
-
post_install_message:
|
75
|
+
post_install_message:
|
104
76
|
rdoc_options: []
|
105
77
|
require_paths:
|
106
78
|
- lib
|
@@ -115,8 +87,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
87
|
- !ruby/object:Gem::Version
|
116
88
|
version: '0'
|
117
89
|
requirements: []
|
118
|
-
rubygems_version: 3.
|
119
|
-
signing_key:
|
90
|
+
rubygems_version: 3.5.16
|
91
|
+
signing_key:
|
120
92
|
specification_version: 4
|
121
93
|
summary: Construct <form> element fields by combining ActionView::Helpers::FormBuilder
|
122
94
|
with Rails View Partials
|
@@ -1,47 +0,0 @@
|
|
1
|
-
module ViewPartialFormBuilder
|
2
|
-
class HtmlAttributes
|
3
|
-
def initialize(**attributes)
|
4
|
-
@attributes = attributes
|
5
|
-
end
|
6
|
-
|
7
|
-
def merge_token_lists(**token_list_attributes)
|
8
|
-
merge(
|
9
|
-
token_list_attributes.reduce({}) do |merged, (key, value)|
|
10
|
-
token_list = Array(attributes.delete(key)).unshift(value)
|
11
|
-
|
12
|
-
merged.merge(key => token_list.flatten.uniq)
|
13
|
-
end
|
14
|
-
)
|
15
|
-
end
|
16
|
-
|
17
|
-
def to_h
|
18
|
-
attributes.to_h
|
19
|
-
end
|
20
|
-
|
21
|
-
def to_hash
|
22
|
-
attributes.to_hash
|
23
|
-
end
|
24
|
-
|
25
|
-
def method_missing(method_name, *arguments, &block)
|
26
|
-
if attributes.respond_to?(method_name)
|
27
|
-
return_value = attributes.public_send(method_name, *arguments, &block)
|
28
|
-
|
29
|
-
if return_value.kind_of?(Hash)
|
30
|
-
HtmlAttributes.new(return_value)
|
31
|
-
else
|
32
|
-
return_value
|
33
|
-
end
|
34
|
-
else
|
35
|
-
super
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def respond_to_missing?(method_name, include_private = false)
|
40
|
-
attributes.respond_to?(method_name) || super
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
attr_reader :attributes
|
46
|
-
end
|
47
|
-
end
|