You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
GET Submissions: don't merge into searchParams (#461)
* Dispatch `turbo:before-fetch-request` with URL
Dispatch `turbo:before-fetch-request` with a direct reference to the
`FetchRequest` instance's `url: URL` property as part of the
`event.detail`.
* `GET` Submissions: don't merge into `searchParams`
The background
---
According to the HTML Specification's [§ 4.10.21.3 Form submission
algorithm][] section, submissions transmitted as `GET` requests [mutate
the `[action]` URL][mutate], overriding any search parameters already
encoded into the `[action]` value:
> [Mutate action URL][algorithm]
> ---
>
> Let <var>pairs</var> be the result of converting to a list of
> name-value pairs with <var>entry list</var>.
>
> Let <var>query</var> be the result of running the
> `application/x-www-form-urlencoded` serializer with <var>pairs</var>
> and <var>encoding</var>.
>
> Set <Var>parsed action</var>'s query component to <var>query</var>.
>
> Plan to navigate to <var>parsed action</var>.
[§ 4.10.21.3 Form submission algorithm]: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm
[algorithm]: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#submit-mutate-action
[mutate]: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm:submit-mutate-action
Form submissions made with `POST` requests, on the other hand, encode
_both_ the `[action]` value's query parameters and any additionally
encoded body data:
> [Submit as entity body][post-submit]
> ---
>
> …
>
> Plan to navigate to a new request whose URL is <var>parsed
> action</var>, method is <var>method</var>, header list is
> « (`Content-Type`, mimeType) », and body is <var>body</var>.
[post-submit]: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#submit-body
The problem
---
Ever since [18585fc][] (and subsequently [#108][]),
Turbo's `FetchRequest` has merged submission values from `<form>`
elements with `[method="get"]` and without `[action]` attributes _into_
the current URL's query parameters.
For example, consider the following forms rendered on the `/articles`
page:
```html
<form>
<label for="q">Term</label>
<input id="q" name="q">
<button>Search</button>
</form>
<!-- elsewhere in the page -->
<form>
<button name="order" value="asc">Sort ascending</button>
<button name="order" value="desc">Sort ascending</button>
</form>
```
Without Turbo, entering "Hotwire" as the search term into
`input[name="q"]` and clicking the "Search" button would navigate the
browser to `/articles?q=Hotwire`. Then, clicking the "Sort ascending"
button would navigate the page to `/articles?order=asc`.
Without Turbo, entering "Hotwire" as the search term into
`input[name="q"]` and clicking the "Search" button navigates the
browser to `/articles?q=Hotwire`. Then, clicking the "Sort ascending"
button **navigates the page to `/articles?q=Hotwire&order=asc`**,
effectively _merging_ values from the page's URL and the `<form>`
element's fields.
[18585fc]: 18585fc#diff-68b647dc2963716dc27c070f261d0b942ee9c00be7c4149ecb3a5acd94842d40R135-R142
[#108]: #108
The solution
---
This commit modifies the way that `FetchRequest` constructs its `url:
URL` and `body: FormData | URLSearchParams` properties.
First, it _always_ assigns a `body` property, but _conditionally_
encodes that value into the `fetchOptions: RequestInit` property based
on whether or not the request is an idempotent `GET`.
Next, if constructed with a `body: URLSearchParams` that has entries,
**replace** the `url: URL` instance's search params _entirely_ with
those values, like the HTML Specification algorithm entails.
If constructed with a `body: URLSearchParams` that is empty, pass the
`url: URL` through and assign the property **without modifying it**.
Additionally, this commit adds test cases to ensure that `POST` requests
transmit data in both the body and the URL.
While the previous multi-form merging behavior can be desirable, it is
not the behavior outlined by the HTML Specification, so Turbo should not
provide it out-of-the-box.
Having said that, there are two ways for applications to restore that
behavior:
1. declare a [turbo:before-fetch-request][] event listener to merge
values _into_ the event's `detail.url` instance:
```js
addEventListener("turbo:before-fetch-request", ({ target, detail: { url, fetchOptions: { method } } }) => {
if (target instanceof HTMLFormElement && method == "GET") {
for (const [ name, value ] of new URLSearchParams(window.location.search)) {
// conditionally call `set` or `append`,
// depending on your application's needs
if (url.searchParams.has(name)) continue
else url.searchParams.set(name, value)
}
}
})
```
2. declare a [formdata][] event listener to merge values _into_ the
submitted form's [FormData][] instance prior to entering the Turbo
request pipeline:
```js
addEventListener("submit", (event) => {
if (event.defaultPrevented) return
const { target, submitter } = event
const action = submitter?.getAttribute("formaction") || target.getAttribute("action")
if (target.method == "get" && !action) {
target.addEventListener("formdata", ({ formData }) => {
for (const [ name, value ] of new URLSearchParams(window.location.search)) {
// conditionally call `set` or `append`,
// depending on your application's needs
if (formData.has(name)) continue
else formData.set(name, value)
}
}, { once: true })
}
})
```
The conditionals in both cases could be omitted if applications
controlled the behavior more directly, like by declaring a Stimulus
controller and action (e.g. `<form data-controller="form"
data-action="formdata->form#mergeSearchParams">`).
[turbo:before-fetch-request]: https://turbo.hotwired.dev/reference/events
[formdata]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/formdata_event
[FormData]: https://developer.mozilla.org/en-US/docs/Web/API/FormData
0 commit comments