Yield `Tags::Label::LabelBuilder#translations`

When translating a `<label>` element's contents, it is difficult (or
"possible", yet undocumented) to make the translation text available to
a block scope.

For instance, when rendering a `rich_text_area`, passing the
`aria-label` attribute might be important.

Prior to this commit, doing so would require a double lookup of the
translation key:

```erb
<%# one time here, implicitly %>
<%= form.label(:content) do %>
  <%= form.rich_text_area(
    :content,
    # one time here, explicitly
    "aria-label" => translate("helpers.label.post.content"),
  ) %>
<% end %>
```

The current implementation of the `#label` helper method already yields
an instance of `ActionView::Helpers::Tags::Label::LabelBuilder`, but
that class is undocumented. Instance of that class respond to
`#translation` calls, which will return the translated text content.

By aliasing `#translation` to `#to_s`, we're able to expose that value
without the burden of exposing an additional class to the public API.
Instead, view-level interpolation (either `<%= %>`, `#{ }`, or direct
calls to [`capture`][capture] will coerce the value to a String, and
implicitly invoke `#translation`.

The new view code might look something like this:

```erb
<%= form.label(:content) do |label| %>
  <%= form.rich_text_area(:content, "aria-label" => label) %>
<% end %>
```

Callers of the helper are still free to omit the block parameter.

[capture]: https://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html#method-i-capture
This commit is contained in:
Sean Doyle 2020-02-22 01:45:00 -05:00
parent 76b33aa3d1
commit d38c214b75
4 changed files with 87 additions and 0 deletions

View File

@ -1,3 +1,8 @@
* Alias `ActionView::Helpers::Tags::Label::LabelBuilder#translation` to
`#to_s` so that `form.label` calls can yield that value to their blocks.
*Sean Doyle*
* Rename the new `TagHelper#class_names` method to `TagHelper#token_list`,
and make the original available as an alias.

View File

@ -1111,6 +1111,16 @@ module ActionView
# label(:post, :privacy, "Public Post", value: "public")
# # => <label for="post_privacy_public">Public Post</label>
#
# label(:post, :cost) do |translation|
# content_tag(:span, translation, class: "cost_label")
# end
# # => <label for="post_cost"><span class="cost_label">Total cost</span></label>
#
# label(:post, :cost) do |builder|
# content_tag(:span, builder.translation, class: "cost_label")
# end
# # => <label for="post_cost"><span class="cost_label">Total cost</span></label>
#
# label(:post, :terms) do
# raw('Accept <a href="/terms">Terms</a>.')
# end
@ -2245,6 +2255,24 @@ module ActionView
# label(:privacy, "Public Post", value: "public")
# # => <label for="post_privacy_public">Public Post</label>
#
# label(:cost) do |translation|
# content_tag(:span, translation, class: "cost_label")
# end
# # => <label for="post_cost"><span class="cost_label">Total cost</span></label>
#
# label(:cost) do |builder|
# content_tag(:span, builder.translation, class: "cost_label")
# end
# # => <label for="post_cost"><span class="cost_label">Total cost</span></label>
#
# label(:cost) do |builder|
# content_tag(:span, builder.translation, class: [
# "cost_label",
# ("error_label" if builder.object.errors.include?(:cost))
# ])
# end
# # => <label for="post_cost"><span class="cost_label error_label">Total cost</span></label>
#
# label(:terms) do
# raw('Accept <a href="/terms">Terms</a>.')
# end

View File

@ -25,6 +25,10 @@ module ActionView
content
end
def to_s
translation
end
end
def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)

View File

@ -945,6 +945,56 @@ class FormWithActsLikeFormForTest < FormWithTest
assert_dom_equal expected, output_buffer
end
def test_form_with_label_passes_translation_to_block_version
form_with(model: Post.new) do |f|
concat(
f.label(:title) do |label|
concat content_tag(:span, label)
end
)
end
expected = whole_form("/posts") do
%(<label for="post_title"><span>Title</span></label>)
end
assert_dom_equal expected, output_buffer
end
def test_form_with_label_passes_label_tag_builder_to_block_version
form_with(model: Post.new) do |f|
concat(
f.label(:title) do |builder|
concat content_tag(:span, builder.translation)
end
)
end
expected = whole_form("/posts") do
%(<label for="post_title"><span>Title</span></label>)
end
assert_dom_equal expected, output_buffer
end
def test_form_with_label_accesses_object_through_label_tag_builder
form_with(model: Post.new) do |f|
concat(
f.label(:title) do |builder|
concat tag.span(builder, {
class: ("new_record" unless builder.object.persisted?)
})
end
)
end
expected = whole_form("/posts") do
%(<label for="post_title"><span class="new_record">Title</span></label>)
end
assert_dom_equal expected, output_buffer
end
def test_form_with_label_error_wrapping
form_with(model: @post) do |f|
concat f.label(:author_name, class: "label")