Translate FormBuilder#button calls with formmethod:
When submitting a `<form>`, browsers will serialize the element that initiated the submission as part of the [FormData][], including its `name` and `value` attributes. Browser support for `<form>` submission HTTP verbs is limited to `GET` and `POST`. Rails currently works around this [limitation by constructing `<input type="hidden" name="_method" value="VERB">` which serializes `_method="VERB"` to the FormData][_method]. To support varied HTTP actions within the same form, this commit intervenes when a `form.button formmethod: "..."` call is made during form construction, and translates any `formmethod:` value to the corresponding work-around version. [FormData]: https://developer.mozilla.org/en-US/docs/Web/API/FormData [_method]: https://edgeguides.rubyonrails.org/form_helpers.html#how-do-forms-with-patch-put-or-delete-methods-work-questionmark [button-formmethod]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formmethod
This commit is contained in:
parent
46165a0fe9
commit
b8c9c9d06f
|
@ -1,14 +1,30 @@
|
|||
* Change `ActionView::Helpers::FormBuilder#button` to transform `formmethod`
|
||||
attributes into `_method="$VERB"` Form Data to enable varied same-form actions:
|
||||
|
||||
<%= form_with model: post, method: :put do %>
|
||||
<%= form.button "Update" %>
|
||||
<%= form.button "Delete", formmethod: :delete %>
|
||||
<% end %>
|
||||
<%# => <form action="posts/1">
|
||||
=> <input type="hidden" name="_method" value="put">
|
||||
=> <button type="submit">Update</button>
|
||||
=> <button type="submit" formmethod="post" name="_method" value="delete">Delete</button>
|
||||
=> </form>
|
||||
%>
|
||||
|
||||
*Sean Doyle*
|
||||
|
||||
* Change `ActionView::Helpers::UrlHelper#button_to` to *always* render a
|
||||
`<button>` element, regardless of whether or not the content is passed as
|
||||
the first argument or as a block.
|
||||
|
||||
<%= button_to "Delete", post_path(@post), method: :delete %>
|
||||
<%# => <form method="/posts/1"><input type="_method" value="delete"><button type="submit">Delete</button></form>
|
||||
<%# => <form action="/posts/1"><input type="hidden" name="_method" value="delete"><button type="submit">Delete</button></form>
|
||||
|
||||
<%= button_to post_path(@post), method: :delete do %>
|
||||
Delete
|
||||
<% end %>
|
||||
<%# => <form method="/posts/1"><input type="_method" value="delete"><button type="submit">Delete</button></form>
|
||||
<%# => <form action="/posts/1"><input type="hidden" name="_method" value="delete"><button type="submit">Delete</button></form>
|
||||
|
||||
*Sean Doyle*, *Dusan Orlovic*
|
||||
|
||||
|
|
|
@ -2546,6 +2546,11 @@ module ActionView
|
|||
value = @template.capture { yield(value) }
|
||||
end
|
||||
|
||||
formmethod = options[:formmethod]
|
||||
if /(post|get)/i.match(formmethod).nil? && formmethod.present? && !options.key?(:name) && !options.key?(:value)
|
||||
options.merge! formmethod: :post, name: "_method", value: formmethod
|
||||
end
|
||||
|
||||
@template.button_tag(value, options)
|
||||
end
|
||||
|
||||
|
|
|
@ -2386,6 +2386,66 @@ class FormHelperTest < ActionView::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_button_with_get_formmethod_attribute
|
||||
form_for(@post, as: :another_post) do |f|
|
||||
concat f.button "GET", formmethod: :get
|
||||
end
|
||||
|
||||
expected = whole_form("/posts/123", "edit_another_post", "edit_another_post", method: "patch") do
|
||||
"<button type='submit' formmethod='get' name='button'>GET</button>"
|
||||
end
|
||||
|
||||
assert_dom_equal expected, output_buffer
|
||||
end
|
||||
|
||||
def test_button_with_post_formmethod_attribute
|
||||
form_for(@post, as: :another_post) do |f|
|
||||
concat f.button "POST", formmethod: :post
|
||||
end
|
||||
|
||||
expected = whole_form("/posts/123", "edit_another_post", "edit_another_post", method: "patch") do
|
||||
"<button type='submit' formmethod='post' name='button'>POST</button>"
|
||||
end
|
||||
|
||||
assert_dom_equal expected, output_buffer
|
||||
end
|
||||
|
||||
def test_button_with_other_formmethod_attribute
|
||||
form_for(@post, as: :another_post) do |f|
|
||||
concat f.button "Delete", formmethod: :delete
|
||||
end
|
||||
|
||||
expected = whole_form("/posts/123", "edit_another_post", "edit_another_post", method: "patch") do
|
||||
"<button type='submit' formmethod='post' name='_method' value='delete'>Delete</button>"
|
||||
end
|
||||
|
||||
assert_dom_equal expected, output_buffer
|
||||
end
|
||||
|
||||
def test_button_with_other_formmethod_attribute_and_name
|
||||
form_for(@post, as: :another_post) do |f|
|
||||
concat f.button "Delete", formmethod: :delete, name: "existing"
|
||||
end
|
||||
|
||||
expected = whole_form("/posts/123", "edit_another_post", "edit_another_post", method: "patch") do
|
||||
"<button type='submit' formmethod='delete' name='existing'>Delete</button>"
|
||||
end
|
||||
|
||||
assert_dom_equal expected, output_buffer
|
||||
end
|
||||
|
||||
def test_button_with_other_formmethod_attribute_and_value
|
||||
form_for(@post, as: :another_post) do |f|
|
||||
concat f.button "Delete", formmethod: :delete, value: "existing"
|
||||
end
|
||||
|
||||
expected = whole_form("/posts/123", "edit_another_post", "edit_another_post", method: "patch") do
|
||||
"<button type='submit' formmethod='delete' name='button' value='existing'>Delete</button>"
|
||||
end
|
||||
|
||||
assert_dom_equal expected, output_buffer
|
||||
end
|
||||
|
||||
def test_nested_fields_for
|
||||
@comment.body = "Hello World"
|
||||
form_for(@post) do |f|
|
||||
|
|
|
@ -323,14 +323,42 @@ Output:
|
|||
<form accept-charset="UTF-8" action="/search" method="post">
|
||||
<input name="_method" type="hidden" value="patch" />
|
||||
<input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
|
||||
...
|
||||
<!-- ... -->
|
||||
</form>
|
||||
```
|
||||
|
||||
When parsing POSTed data, Rails will take into account the special `_method` parameter and act as if the HTTP method was the one specified inside it ("PATCH" in this example).
|
||||
|
||||
When rendering a form, submission buttons can override the declared `method` attribute through the `formmethod:` keyword:
|
||||
|
||||
```erb
|
||||
<%= form_with url: "/posts/1", method: :patch do |form| %>
|
||||
<%= form.button "Delete", formmethod: :delete, data: { confirm: "Are you sure?" } %>
|
||||
<%= form.button "Update" %>
|
||||
<% end %>
|
||||
```
|
||||
|
||||
Similar to `<form>` elements, most browsers _don't support_ overriding form methods declared through [formmethod][] other than "GET" and "POST".
|
||||
|
||||
Rails works around this issue by emulating other methods over POST through a combination of [formmethod][], [value][button-value], and [name][button-name] attributes:
|
||||
|
||||
```html
|
||||
<form accept-charset="UTF-8" action="/posts/1" method="post">
|
||||
<input name="_method" type="hidden" value="patch" />
|
||||
<input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
|
||||
<!-- ... -->
|
||||
|
||||
<button type="submit" formmethod="post" name="_method" value="delete" data-confirm="Are you sure?">Delete</button>
|
||||
<button type="submit" name="button">Update</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
IMPORTANT: In Rails 6.0 and 5.2, all forms using `form_with` implement `remote: true` by default. These forms will submit data using an XHR (Ajax) request. To disable this include `local: true`. To dive deeper see [Working with JavaScript in Rails](working_with_javascript_in_rails.html#remote-elements) guide.
|
||||
|
||||
[formmethod]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formmethod
|
||||
[button-name]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-name
|
||||
[button-value]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-value
|
||||
|
||||
Making Select Boxes with Ease
|
||||
-----------------------------
|
||||
|
||||
|
|
Loading…
Reference in New Issue