-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
Environment
- Elixir version (elixir -v): 1.18.4
- Phoenix version (mix deps): 1.8.1
- Phoenix LiveView version (mix deps): 1.1.14
- Operating system: macos
- Browsers you attempted to reproduce this bug on (the more the merrier): Firefox, specifically
- Does the problem persist after removing "assets/node_modules" and trying again? Yes/no:
Actual behavior
Given a view like:
def render(assigns) do
~H"""
<div>
<form id="demo-form" phx-change="change">
<input name="f1" />
<input form="demo-form" name="f2" />
</form>
<input form="demo-form" name="f3" />
<button phx-click="terminate">terminate</button>
</div>
"""
end
def handle_event("change", _, socket) do
{:noreply, socket}
end
def handle_event("terminate", _, _socket) do
exit(:normal)
endthe "change" handler receives %{"f1" => "", "f2" => "", "f3" => ""} after a normal change, but only %{"f1" => ""} in the autorecovery phase.
Expected behavior
The "change" handler should always receive all of %{"f1" => "", "f2" => "", "f3" => ""}.
Investigation notes
There are two ways to assign an input element to a form:
- placing it inside the form element
- using the
formattribute
When both are used conflictingly (i.e. the input element is inside the form element but its form attribute points to another form that is not its DOM ancestor), browsers seem to resolve this conflict differently:
- Chrome and Safari prioritize the DOM tree structure
- Firefox prioritizes the
formattribute
Therefore, after clonedForm.appendChild(clonedEl), Firefox does not include clonedEl in clonedForm.elements, because its form is still pointing to the original form element before the cloning. As a result, it is not included in the submit payload created by FormData or other means.
A minimal fix that I found is
--- assets/js/phoenix_live_view/view.js
+++ assets/js/phoenix_live_view/view.js
@@ -2160,6 +2160,7 @@ export default class View {
const clonedEl = el.cloneNode(true);
morphdom(clonedEl, el);
DOM.copyPrivates(clonedEl, el);
+ clonedEl.removeAttribute("form");
clonedForm.appendChild(clonedEl);
});
return clonedForm;
However, this only addresses the f3 field in my example.
The f2 field is covered by the if (form.contains(el)) branch and this simple change does not work for it. (However, this case has an available userspace workaround of not using the form attribute redundantly when the input is already inside the form.)