view_partial_form_builder 0.1.0 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e30eab928ee84a6217afbabc1c746aa7a7906efa5417fc323f6b78e476aa3fbb
4
- data.tar.gz: 21af6978419ac00cebba9989b86943eb2d4d102397d880cee50acb8a7ef11c7c
3
+ metadata.gz: 1d1550e43fb981483d25dedf89ab64478b5dd2c5803b4aa7345ae6d7efba4fe2
4
+ data.tar.gz: d8bb992d8d959d2288c761d764275af9e470081145884b6bfcff7b7dda3fdc45
5
5
  SHA512:
6
- metadata.gz: 848d4ddf155cb750a63400e9a5aaa3ea062a4d01b3041294c6ddbf64c3570882e28052d6b55066c731320daae6b43e7fa53c47f1a89924603bab50c3ba3885be
7
- data.tar.gz: 05e69629609a81b0563bc5a234c3305dc0355e6f0b458d6aea41e75d49009d26a1e274a9ce7e4ccf6b50aa4ff90afa79c30220f44d97a4c754d9988fd9476046
6
+ metadata.gz: 121978b8280e71e3cf114291cf4874949c9323f7b909ab7cd93602f1a3fb00b143c2028ed1188623c8b926efe8b3a27e67005947599d66b8e1cb33c98d8c589e
7
+ data.tar.gz: 9e56d40123f55fac7ee53b3bd3e087ccc35bdf3ca709485cad0d636fb41f7f684744dbc068d7af13bcdc6aa246e9707373c557f792d86a1d3fee1cd76468e4f0
data/README.md CHANGED
@@ -87,7 +87,7 @@ generating HTML through Rails' helpers:
87
87
  <%# app/views/form_builder/_button.html.erb %>
88
88
 
89
89
  <div class="button-wrapper">
90
- <%= form.button(*arguments, **options, &block) %>
90
+ <%= form.button(value, options, &block) %>
91
91
  </div>
92
92
  ```
93
93
 
@@ -116,31 +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
- * `arguments` - an Array containing the arguments the helper received, in the
120
- order they were received. This can be useful to pass to the view partial's
121
- helper by [splatting them][splat] out.
122
-
123
119
  * `&block` - a callable, `yield`-able block if the helper method was passed one
124
120
 
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
- ```
143
-
144
121
  #### Handling DOMTokenList attributes
145
122
 
146
123
  An [HTML element's `class` attribute][mdn-class] is treated by browsers as a
@@ -158,16 +135,12 @@ When rendering a field's DOMTokenList-backed attributes (like `class` or
158
135
  controllers][stimulus-controller]), transforming and combining singular `String`
159
136
  instances into lists of token can be very useful.
160
137
 
161
- To simplify those scenarios, a partial's template-local optional attributes are
162
- made available with the `#merge_token_lists` method.
163
-
164
138
  These optional attributes are available through the `options` or `html_options`
165
139
  partial-local variables. Their name will depend on the partial's corresponding
166
140
  [`ActionView::Helpers::FormBuilder`][FormBuilder] interface.
167
141
 
168
- Calls to `#merge_token_lists` will merge the key-value pairs and return a new
169
- Hash-like structure. The attribute's value will be transformed into an `Array`.
170
- Given call to `form.text_field` and a corresponding partial declaration:
142
+ To "merge" attributes together, you can combine Ruby's `String` interpolation
143
+ and `Hash#delete`:
171
144
 
172
145
  ```html+erb
173
146
  <%# app/views/users/new.html.erb %>
@@ -177,7 +150,11 @@ Given call to `form.text_field` and a corresponding partial declaration:
177
150
 
178
151
  <# app/views/form_builder/_text_field.html.erb %>
179
152
 
180
- <%= form.text_field(*arguments, **options.merge_token_lists(class: "text-field")) %>
153
+ <%= form.text_field(
154
+ method,
155
+ class: "text-field #{options.delete(:class)}",
156
+ **options
157
+ ) %>
181
158
  ```
182
159
 
183
160
  The resulting HTML `<input>` element will merge have its [`class`
@@ -192,7 +169,6 @@ values:
192
169
  [button]: https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-button
193
170
  [local_assigns]: https://api.rubyonrails.org/classes/ActionView/Template.html#method-i-local_assigns
194
171
  [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
172
  [mdn-class]: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class
197
173
  [DOMTokenList]: https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList
198
174
  [classList]: https://developer.mozilla.org/en-US/docs/Web/API/Element/classList
@@ -210,7 +186,7 @@ block-local `form` variable:
210
186
  ```erb
211
187
  <%# app/views/users/form_builder/_email_field.html.erb %>
212
188
 
213
- <%= form.default.email_field(*arguments, **options) %>
189
+ <%= form.default.email_field(method, options) %>
214
190
  ```
215
191
 
216
192
  When passing a `model:` or `scope:` to calls to [`form_with`][form_with],
@@ -225,21 +201,128 @@ For example, when calling `form_with(model: User.new)`, a partial declared in
225
201
  <%# app/views/users/form_builder/_password_field.html.erb %>
226
202
 
227
203
  <div class="password-field-wrapper">
228
- <%= form.password_field(*arguments, **options) %>
204
+ <%= form.password_field(method, options) %>
229
205
  </div>
230
206
  ```
231
207
 
232
- If you'd like to render a specific partial for a field, you can declare the name
233
- as the `partial:` option:
208
+ If you'd like to render a specific partial for a field, make sure that you pass
209
+ along the `form:` (along with any other partial-local variables) as part of the
210
+ `render` call's `locals:` option:
211
+
234
212
 
235
213
  ```erb
236
214
  <%# app/views/users/new.html.erb %>
237
215
 
238
216
  <%= form_with(model: User.new) do |form| %>
239
- <%= form.email_field(:email, partial: "emails/my_special_text_field") %>
217
+ <%= render("emails/my_special_email_field", {
218
+ form: form,
219
+ method: :email,
220
+ options: { class: "user-email" },
221
+ ) %>
240
222
  <% end %>
223
+
224
+ <%# app/views/emails/_my_special_email_field.html.erb %>
225
+
226
+ <%= form.email_field(
227
+ method,
228
+ class: "my-special-email #{options.delete(:class)},
229
+ **options
230
+ ) %>
241
231
  ```
242
232
 
233
+ #### Composing partials
234
+
235
+ Layering partials on top of one another can be useful to share foundational
236
+ styles and configuration across your fields. For instance, consider an
237
+ administrative interface that shares styles with a consumer facing site, but has
238
+ additional bells and whistles.
239
+
240
+ Declare the consumer facing inputs (in this example, `<input type="search">`):
241
+
242
+ ```html+erb
243
+ <%# app/views/form_builder/_search_field.html.erb %>
244
+
245
+ <%= form.search_field(
246
+ method,
247
+ class: "
248
+ search-field
249
+ #{options.delete(:class}
250
+ ",
251
+ "data-controller": "
252
+ input->search#executeQuery
253
+ #{options.delete(:"data-controller")}
254
+ ",
255
+ **options
256
+ ) %>
257
+ ```
258
+
259
+ Then, declare the administrative interface's inputs, in terms of overriding the
260
+ foundation built by the more general definitions:
261
+
262
+ ```html+erb
263
+ <%# app/views/admin/form_builder/_search_field.html.erb %>
264
+
265
+ <%= form.search_field(
266
+ method,
267
+ class: "
268
+ search-field--admin
269
+ #{options.delete(:class}
270
+ ",
271
+ "data-controller": "
272
+ focus->admin-search#clearResults
273
+ #{options.delete(:"data-controller")}
274
+ ",
275
+ ) %>
276
+ ```
277
+
278
+ The rendered `admin/form_builder/search_field` partial combines options and
279
+ arguments from both partials:
280
+
281
+ ```html
282
+ <input
283
+ type="search"
284
+ class="
285
+ search-field
286
+ search-field--admin
287
+ "
288
+ data-controller="
289
+ input->search#executeQuery
290
+ focus->admin-search#clearResults
291
+ "
292
+ >
293
+ ```
294
+
295
+ When constructing fields within a `form_with(model: ...)` block, partials will
296
+ use the `model:` instance's [`tableize`-d model name][tableize] to resolve
297
+ partials.
298
+
299
+ For example, `posts/form_builder/_text_field.html.erb` will be resolved ahead of
300
+ `form_builder/_text_field.html.erb`:
301
+
302
+ ```html+erb
303
+ <%# app/views/posts/form_builder/_text_field.html.erb %>
304
+
305
+ <%= form.text_field(method, class: "post-text #{options.delete(:class)}", **options) %>
306
+
307
+ <%# app/views/application/form_builder/_text_field.html.erb %>
308
+
309
+ <%= form.text_field(method, class: "text #{options.delete(:class)}", **options) %>
310
+ ```
311
+
312
+ The rendered `posts/form_builder/text_field` partial could combine options and
313
+ arguments from both partials:
314
+
315
+ ```html
316
+ <input type="text" class="post-text text">
317
+ ```
318
+
319
+ Models declared within modules will be delimited with `/`. For example,
320
+ `Special::Post` instances would first resolve partials within the
321
+ `app/views/special/posts/form_builder` directory, before falling back to
322
+ `app/views/application/form_builder`.
323
+
324
+ [tableize]: https://api.rubyonrails.org/classes/String.html#method-i-tableize
325
+
243
326
  ### Configuration
244
327
 
245
328
  View partials lookup and resolution will be scoped to the
@@ -1,4 +1,4 @@
1
- require "view_partial_form_builder/lookup_context"
1
+ require "view_partial_form_builder/lookup_override"
2
2
  require "view_partial_form_builder/html_attributes"
3
3
 
4
4
  module ViewPartialFormBuilder
@@ -14,9 +14,9 @@ module ViewPartialFormBuilder
14
14
  @template,
15
15
  options,
16
16
  )
17
- @lookup_context = LookupContext.new(
18
- overridden_context: @template.lookup_context,
19
- object_name: object_name,
17
+ @lookup_override = LookupOverride.new(
18
+ prefixes: @template.lookup_context.prefixes,
19
+ object_name: object&.model_name || object_name,
20
20
  view_partial_directory: ViewPartialFormBuilder.view_partial_directory,
21
21
  )
22
22
  end
@@ -173,6 +173,17 @@ module ViewPartialFormBuilder
173
173
  render_partial("time_zone_select", locals, fallback: -> { super })
174
174
  end
175
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
+
176
187
  def hidden_field(method, **options)
177
188
  @emitted_hidden_id = true if method == :id
178
189
 
@@ -247,50 +258,90 @@ module ViewPartialFormBuilder
247
258
 
248
259
  private
249
260
 
250
- attr_reader :lookup_context
251
-
252
261
  def render_partial(field, locals, fallback:, &block)
253
- return fallback.call if about_to_recurse_infinitely?(field)
254
-
255
262
  options = locals.fetch(:options, {})
256
263
  partial_override = options.delete(:partial)
257
- locals = objectify_options(options).merge(locals, form: self)
258
-
259
- lookup_context.override do
260
- if partial_override.present?
261
- render(partial_override, locals, &block)
262
- elsif partial_exists?(field)
263
- render(field, locals, &block)
264
- else
265
- fallback.call
266
- end
264
+ options_as_locals = objectify_options(options)
265
+ locals = DeprecatedHash.new(
266
+ options_as_locals.merge(locals, form: self),
267
+ deprecated_keys: options_as_locals.keys + [:arguments],
268
+ )
269
+
270
+ partial = if partial_override.present?
271
+ ActiveSupport::Deprecation.new("0.2.0", "ViewPartialFormBuilder").warn(<<~WARNING)
272
+ Providing a `partial:` option for a form field is deprecated.
273
+ WARNING
274
+
275
+ find_partial(partial_override, locals, prefixes: [])
276
+ else
277
+ find_partial(field, locals, prefixes: prefixes_after(field))
278
+ end
279
+
280
+ if partial.nil? || about_to_recurse_infinitely?(partial)
281
+ fallback.call
282
+ else
283
+ partial.render(@template, locals, &block)
267
284
  end
268
285
  end
269
286
 
270
- def partial_exists?(template_name)
287
+ def find_partial(template_name, locals, prefixes:)
271
288
  template_is_partial = true
272
289
 
273
- lookup_context.template_exists?(
290
+ @template.lookup_context.find_all(
274
291
  template_name,
275
- lookup_context.prefixes,
292
+ prefixes,
276
293
  template_is_partial,
277
- )
294
+ locals.keys,
295
+ ).first.tap do |partial|
296
+ root_directory = ViewPartialFormBuilder.view_partial_directory
297
+
298
+ if partial&.virtual_path == "#{root_directory}/_#{template_name}"
299
+ ActiveSupport::Deprecation.new("0.2.0", "ViewPartialFormBuilder").warn(<<~WARNING.strip)
300
+ Declare root-level partials in app/views/application/#{root_directory}/, not app/views/#{root_directory}/.
301
+ WARNING
302
+ end
303
+ end
304
+ end
305
+
306
+ def prefixes_after(template_name)
307
+ prefixes = @lookup_override.prefixes
308
+ current_prefix = current_virtual_path.delete_suffix("/_#{template_name}")
309
+
310
+ if prefixes.include?(current_prefix)
311
+ prefixes.from(prefixes.index(current_prefix).to_i + 1)
312
+ else
313
+ prefixes
314
+ end
315
+ end
316
+
317
+ def about_to_recurse_infinitely?(partial)
318
+ partial.virtual_path == current_virtual_path
278
319
  end
279
320
 
280
- def render(partial_name, locals, &block)
281
- if block.present?
282
- @template.render(layout: partial_name, locals: locals, &block)
321
+ def current_virtual_path
322
+ if (current_template = @template.instance_values["current_template"])
323
+ current_template.virtual_path.to_s
283
324
  else
284
- @template.render(partial: partial_name, locals: locals)
325
+ @template.instance_values["virtual_path"].to_s
285
326
  end
286
327
  end
328
+ end
287
329
 
288
- def about_to_recurse_infinitely?(field)
289
- @template.instance_eval do
290
- *, partial = @virtual_path.split("/")
330
+ class DeprecatedHash < Hash
331
+ def initialize(hash, deprecated_keys: [])
332
+ super()
333
+ merge!(hash)
334
+ @deprecated_keys = deprecated_keys
335
+ end
291
336
 
292
- partial == "_#{field}"
337
+ def [](key)
338
+ if @deprecated_keys.include?(key)
339
+ ActiveSupport::Deprecation.new("0.2.0", "ViewPartialFormBuilder").warn <<~WARNING
340
+ Accessing `#{key}` from partials is deprecated.
341
+ WARNING
293
342
  end
343
+
344
+ super
294
345
  end
295
346
  end
296
347
  end
@@ -1,5 +1,13 @@
1
1
  module ViewPartialFormBuilder
2
2
  class HtmlAttributes
3
+ deprecate merge_token_lists: <<~'WARNING', deprecator: ActiveSupport::Deprecation.new("0.2.0", "ViewPartialFormBuilder")
4
+
5
+ As an alternative, merge arguments through String interpolation:
6
+
7
+ <%= form.label(method, class: "label #{options.delete(:class)}", **options) %>
8
+
9
+ WARNING
10
+
3
11
  def initialize(**attributes)
4
12
  @attributes = attributes
5
13
  end
@@ -1,29 +1,12 @@
1
1
  module ViewPartialFormBuilder
2
- class LookupContext
3
- delegate_missing_to :overridden_context
4
-
5
- def initialize(overridden_context:, object_name:, view_partial_directory:)
6
- @object_name = object_name.to_s.pluralize
7
- @overridden_context = overridden_context
8
- @prefixes = overridden_context.prefixes.dup.freeze
2
+ class LookupOverride
3
+ def initialize(prefixes:, object_name:, view_partial_directory:)
4
+ @object_name = object_name.to_s.tableize
5
+ @prefixes = prefixes
9
6
  @view_partial_directory = view_partial_directory
10
7
  end
11
8
 
12
- def override(&block)
13
- previous_prefixes = overridden_context.prefixes
14
-
15
- overridden_context.prefixes = prefix_overrides
16
-
17
- yield
18
- ensure
19
- overridden_context.prefixes = previous_prefixes
20
- end
21
-
22
- private
23
-
24
- attr_reader :overridden_context, :object_name, :view_partial_directory
25
-
26
- def prefix_overrides
9
+ def prefixes
27
10
  *overridden_prefixes, root_prefix = @prefixes.dup
28
11
 
29
12
  prefixes = [
@@ -49,5 +32,9 @@ module ViewPartialFormBuilder
49
32
 
50
33
  prefixes.uniq
51
34
  end
35
+
36
+ private
37
+
38
+ attr_reader :object_name, :view_partial_directory
52
39
  end
53
40
  end
@@ -1,3 +1,3 @@
1
1
  module ViewPartialFormBuilder
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.5'
3
3
  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.1.0
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Doyle
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-12 00:00:00.000000000 Z
11
+ date: 2020-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionview
@@ -94,7 +94,7 @@ files:
94
94
  - lib/view_partial_form_builder/engine.rb
95
95
  - lib/view_partial_form_builder/form_builder.rb
96
96
  - lib/view_partial_form_builder/html_attributes.rb
97
- - lib/view_partial_form_builder/lookup_context.rb
97
+ - lib/view_partial_form_builder/lookup_override.rb
98
98
  - lib/view_partial_form_builder/version.rb
99
99
  homepage: https://github.com/seanpdoyle/view_partial_form_builder
100
100
  licenses: