mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
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
4 changed files with 112 additions and 3 deletions
|
@ -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
|
* Change `ActionView::Helpers::UrlHelper#button_to` to *always* render a
|
||||||
`<button>` element, regardless of whether or not the content is passed as
|
`<button>` element, regardless of whether or not the content is passed as
|
||||||
the first argument or as a block.
|
the first argument or as a block.
|
||||||
|
|
||||||
<%= button_to "Delete", post_path(@post), method: :delete %>
|
<%= 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 %>
|
<%= button_to post_path(@post), method: :delete do %>
|
||||||
Delete
|
Delete
|
||||||
<% end %>
|
<% 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*
|
*Sean Doyle*, *Dusan Orlovic*
|
||||||
|
|
||||||
|
|
|
@ -2546,6 +2546,11 @@ module ActionView
|
||||||
value = @template.capture { yield(value) }
|
value = @template.capture { yield(value) }
|
||||||
end
|
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)
|
@template.button_tag(value, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2386,6 +2386,66 @@ class FormHelperTest < ActionView::TestCase
|
||||||
end
|
end
|
||||||
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
|
def test_nested_fields_for
|
||||||
@comment.body = "Hello World"
|
@comment.body = "Hello World"
|
||||||
form_for(@post) do |f|
|
form_for(@post) do |f|
|
||||||
|
|
|
@ -323,14 +323,42 @@ Output:
|
||||||
<form accept-charset="UTF-8" action="/search" method="post">
|
<form accept-charset="UTF-8" action="/search" method="post">
|
||||||
<input name="_method" type="hidden" value="patch" />
|
<input name="_method" type="hidden" value="patch" />
|
||||||
<input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
|
<input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
|
||||||
...
|
<!-- ... -->
|
||||||
</form>
|
</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 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.
|
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
|
Making Select Boxes with Ease
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue