Prior to this commit, the
[ActionView::Helpers::UrlHelper#button_to][button_to] helper rendered
`<input type="submit">` elements when passed its contents as a String
argument, and rendered `<button type="submit">` elements when passed its
contents as a block.
This difference is subtle, and might lead to surprises.
Additionally, a `<form>` element's submitter can encode a `name`/`value`
pairing, which will be submitted as part of the request. When
`button_to` renders an `<input type="submit">` element, the "button"
content is rendered as a `[value]` attribute, which prevents any
meaningful data from being encoded.
Since it's a single `<button>` or `<input type="submit">` within a
`<form>`, missing out on that opportunity to encode information might
not be a show stopper, but ensuring that a `<button>` element is
rendered _without_ a default `[value]` attribute enables applications to
encode additional information that can be accessed JavaScript as
`element.value`, instead of a workaround like
`element.getAttribute("data-value")`.
Support rendering `input` elements with button_to
---
To support the original behavior of `button_to` rendering `<input
type="submit">` elements when invoked _without_ a block, expose the
`app.config.button_to_generates_button_tag` configuration flag.
By default, it's set to `true` and ensures that all `button_to` calls
render `<button>` elements. To revert to the original behavior, set it
to `false`.
[button_to]: https://api.rubyonrails.org/v6.0/classes/ActionView/Helpers/UrlHelper.html#method-i-button_to
Co-authored-by: Dusan Orlovic <duleorlovic@gmail.com>
PR #39939 added support for the `Link` header being generated
automatically when using `stylesheet_link_tag` and
`javascript_include_tag`. However not everything should be
preloaded, e.g. a link to a legacy IE stylesheet has no need to be
preloaded because IE doesn't support the header and in some browsers it
will trigger the preload even though it's not used since it's inside an
IE conditional comment. This leads to increased bandwith costs and
slower application performance.
To allow more flexibility for sites that may have complex needs for the
`Link` header this commit adds a configuration option that disables it
completely and leaves it up to the application to decide how to handle
generating a `Link` header.
* Fix `SELECT COUNT` queries when rendering ActiveRecord collections
Fixes#40837
When rendering collections, calling `size` when the collection is an
ActiveRecord relation causes unwanted `SELECT COUNT(*)` queries. This
change ensures the collection is an array before getting the size, and
also loads the relation for any further array inspections.
* Test queries when rendering relation collections
* Add `length` support to partial collection iterator
Allows getting the size of a relation without duplicating records, but
still loads the relation. The length method existence needs to be
checked because you can pass in an `Enumerator`, which does not respond
to `length`.
* Ensure unsubscribed from notifications after tests
[Rafael Mendonça França + aar0nr]
When a stylesheet or javascript link tag (or preload link tag) is output
in the view, it also gets sent as a Link header for preloading. This
Link header is missing the integrity attribute even when one is set on
the tag, which prevents the browser from using the preloaded resource.
Co-authored-by: Adrianna Chang <adrianna.chang@shopify.com>
`I18n.translate` returns `nil` when given a `nil` key, *unless* a
`default` is also specified. If a `default` is specified, the `nil` key
is treated as a missing key.
In Rails 6.0, the `translate` helper always returned `nil` when given a
`nil` key. After #40773, the `translate` helper always raised an
`I18n::ArgumentError` when given a `nil` key. This commit fixes the
`translate` helper to mirror the `I18n.translate` behavior when given a
`nil` key, with and without a `default`.
Follow-up to #39989.
`I18n.translate` converts the initial key (but not `default` keys) to a
string before performing a lookup. For parity, we should do the same.
Browser native support for lazy loading images is now a part of the
official HTML standard. To indicate to the browser that an image should
be lazily loaded, add the `loading="lazy"` attribute to the `img` tag.
Or, in Rails parlance, add the `loading: "lazy"` option to the
`image_tag` call.
This commit adds `Rails.application.config.action_view.image_loading` to
configure the default value of the `image_tag` `:loading` option. Thus
by setting `config.action_view.image_loading = "lazy"`, an application
can opt in to lazy loading images sitewide, without changing view code.
`ActionView::Helpers::FormBuilder#id`
---
Generate an HTML `id` attribute value.
Return the [`<form>` element's][mdn-form] `id` attribute.
```html+erb
<%= form_for @post do |f| %>
<%# ... %>
<% content_for :sticky_footer do %>
<%= form.button(form: f.id) %>
<% end %>
<% end %>
```
In the example above, the `:sticky_footer` content area will exist
outside of the `<form>` element. [By declaring the `form` HTML
attribute][mdn-button-attr-form], we hint to the browser that the
generated `<button>` element should be treated as the `<form>` element's
submit button, regardless of where it exists in the DOM.
[A similar pattern could be used for `<input>`
elements][mdn-input-attr-form] (or other form controls) that do not
descend from the `<form>` element.
`ActionView::Helpers::FormBuilder#field_id`
---
Generate an HTML <tt>id</tt> attribute value for the given field
Return the value generated by the <tt>FormBuilder</tt> for the given
attribute name.
```html+erb
<%= form_for @post do |f| %>
<%= f.label :title %>
<%= f.text_field :title, aria: { describedby: form.field_id(:title, :error) } %>
<span id="<%= f.field_id(:title, :error) %>">is blank</span>
<% end %>
```
In the example above, the <tt><input type="text"></tt> element built by
the call to <tt>FormBuilder#text_field</tt> declares an
<tt>aria-describedby</tt> attribute referencing the <tt><span></tt>
element, sharing a common <tt>id</tt> root (<tt>post_title</tt>, in this
case).
This method is powered by the `field_id` helper declared in
`action_view/helpers/form_tag_helper`, which is made available for
general template calls, separate from a `FormBuilder` instance.
[mdn-form]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form
[mdn-button-attr-form]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-form
[mdn-input-attr-form]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-form
[mdn-aria-describedby]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-describedby_attribute
[w3c-wai]: https://www.w3.org/WAI/tutorials/forms/notifications/#listing-errors
Transforms a Hash into HTML Attributes, ready to be interpolated into
ERB.
```html+erb
<input <%= tag.attributes(type: :text, aria: { label: "Search" }) %> >
<%# => <input type="text" aria-label="Search"> %>
```
Utilizes the `ActionView::Helpers::TagHelper#tag_options` implementation
to enable combining ERB with attribute transformation, without requiring
templates to replace HTML strings with `tag` or `content_tag`
invocations.
Co-authored-by: David Heinemeier Hansson <david@loudthinking.com>
When serializing a Regexp instance, encode the [Regexp#source][]. When
encoding a value into a [pattern][] attribute from ERB/Ruby, declaring
the String can be tedious. For example, one might attempt to encode
`\w+` as `"\\\w+"`, but once serialized to the browser, that is not
equivalent to the `"\w+"` HTML attribute.
Instead, enable declaring Regexp and Regexp literals as attributes, and
encoding them as their source String.
[Regexp#source]: https://ruby-doc.org/core-2.7.2/Regexp.html#method-i-source
[pattern]: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern
When placeholder set to true on `text_field_tag`, it will try to look up its value from the translation files.
The documentation referenced an incorrect translation key scope, it is defined in [Placeholderable][1] as `helpers.placeholder` (singular).
Followup for #37054
Also improves wording.
[1]: 4640cff2b2/actionview/lib/action_view/helpers/tags/placeholderable.rb
* Fix regression for select tag helper with array
v6.1.0.rc1 does not generate DOM with the selected attribute
when the object's method returns an array.
This is because it has been changed by #34809 to always convert
to a string.
This commit fixes the issue.
## Steps to reproduce
```ruby
# frozen_string_literal: true
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "rails", ENV["RAILS_VERSION"]
end
require "action_view"
require "minitest/autorun"
class BugTest < ActionView::TestCase
Post = Struct.new("Post", :tags)
def test_stuff
@post = Post.new
@post.tags = ["foo", "bar"]
expected = <<~DOM.strip
<select name="post[tags]" id="post_tags"><option selected="selected" value="foo">foo</option>
<option selected="selected" value="bar">bar</option>
<option value="buz">buz</option></select>
DOM
assert_dom_equal(expected, select("post", "tags", %W(foo bar buz), { multiple: true }))
end
end
```
The test succeeds on v6.0.3.4, but the test fails on v6.1.0.rc1.
* Update actionview/lib/action_view/helpers/tags/select.rb
[Takumi Shotoku + Rafael Mendonça França]
As a follow-up to [rails/rails#40479][], ensure that empty Hash and
Array arguments are treated as `nil`.
For example, when conditionally rendering an [aria-describedby][]
attribute to associate an input with a related validation error, treat
an empty Array as `nil`, an omit the attribute entirely:
```html+erb
<% post = Post.new %>
<%= form_with model: post do |form| %>
<%= form.text_field :title, aria: { describedby: { post_title_error: post.errors[:title].any? } } %>
<%= tag.span(post.errors[:title].to_sentence, id: :post_title_error) if post.errors[:title].any? %>
<% end %>
```
In this example, when there are no errors, the desired outcome is for
the `<input type="text" name="post[title]">` element to _omit_ the
`[aria-describedby="post_title_error"]` attribute, and to only include
it when there are errors on the `title` attribute.
Without this change, the desired outcome can be achieved with a
combination of a `#token_list` and `#presence` call:
```diff
<% post = Post.new %>
<%= form_with model: post do |form| %>
- <%= form.text_field :title, aria: { describedby: {post_title_error: post.errors[:title].any?} }
+ <%= form.text_field :title, aria: { describedby: token_list(post_title_error: post.errors[:title].any?).presence } %>
<%= tag.span(post.errors[:title].to_sentence, id: :post_title_error) if post.errors[:title].any? %>
<% end %>
```
[rails/rails#40479]: https://github.com/rails/rails/pull/40479
[aria-describedby]: https://www.w3.org/WAI/WCAG21/Techniques/aria/ARIA21#example-2-identifying-errors-in-data-format
When translating a `<button>` element's contents, it is tedious to make
the translation text available to a block scope.
For instance, when rendering a `<button type="submit">` with an SVG
element as its child, passing translated label text to that SVG
element's [`<title>`][svg-title] element requires an extra call to
`I18n.translate`.
Prior to this commit, doing so would require a double lookup of the
translation key:
```erb
<%# one time here, implicitly %>
<%= form.button do %>
<svg>
<title>
<!-- one time here, explicitly -->
<%= translate("helpers.submit.post.create") %>
</title>
<!-- ... -->
</svg>
<% end %>
```
This commit modifies the `ActionView::Helpers::FormBuilder#button` to
check for invocations that are passed a block, and conditionally yield
the contents of `submit_default_value` as the argument.
The new view code might look something like this:
```erb
<%= form.button do |text| %>
<svg>
<title><%= text %></title>
<!-- ... -->
</svg>
<% end %>
```
Callers of the helper are still free to omit the block parameter.
[svg-title]: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title
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 allows Action Text to be used without having an
ApplicationController defined. In doing so, it also fixes Action Text
attachments to render the correct URL host in mailers.
It also avoids allocating an ActionController::Renderer per request.
Fixes#37183.
Fixes#35578.
Fixes#36963.
Closes#38714.
Co-authored-by: Jeremy Daer <jeremydaer@gmail.com>