Closed SteffenDE closed 3 months ago
To reproduce the problem:
Application.put_env(:sample, Example.Endpoint,
http: [ip: {127, 0, 0, 1}, port: 5001],
server: true,
live_view: [signing_salt: "aaaaaaaa"],
secret_key_base: String.duplicate("a", 64)
)
Mix.install([
{:plug_cowboy, "~> 2.5"},
{:jason, "~> 1.0"},
{:phoenix, "~> 1.7"},
# please test your issue using the latest version of LV from GitHub!
{:phoenix_live_view, github: "phoenixframework/phoenix_live_view", branch: "main", override: true},
# uncomment to use the fix
# {:phoenix_live_view, github: "phoenixframework/phoenix_live_view", branch: "sd-issue-3194", override: true},
])
# build the LiveView JavaScript assets (this needs mix and npm available in your path!)
path = Phoenix.LiveView.__info__(:compile)[:source] |> Path.dirname() |> Path.join("../")
System.cmd("mix", ["deps.get"], cd: path, into: IO.binstream())
System.cmd("npm", ["install"], cd: Path.join(path, "./assets"), into: IO.binstream())
System.cmd("mix", ["assets.build"], cd: path, into: IO.binstream())
defmodule Example.ErrorView do
def render(template, _), do: Phoenix.Controller.status_message_from_template(template)
end
defmodule Example.HomeLive do
use Phoenix.LiveView, layout: {__MODULE__, :live}
def mount(_params, _session, socket) do
{:ok, assign(socket, :form, to_form(%{}, as: :foo))}
end
def render("live.html", assigns) do
~H"""
<script src="/assets/phoenix/phoenix.js"></script>
<script src="/assets/phoenix_live_view/phoenix_live_view.js"></script>
<%!-- <script src="http://127.0.0.1:8000/priv/static/phoenix_live_view.js"></script> --%>
<%!-- uncomment to use enable tailwind --%>
<%!-- <script src="https://cdn.tailwindcss.com"></script> --%>
<script>
let liveSocket = new window.LiveView.LiveSocket("/live", window.Phoenix.Socket)
liveSocket.connect()
</script>
<style>
* { font-size: 1.1em; }
</style>
<%= @inner_content %>
"""
end
def render(assigns) do
~H"""
<.form
for={@form}
phx-change="validate"
phx-submit="submit"
>
<input
id={@form[:store_number].id}
name={@form[:store_number].name}
value={@form[:store_number].value}
type="text"
phx-debounce="blur"
/>
</.form>
"""
end
def handle_event("submit", params, socket) do
{:noreply, push_navigate(socket, to: "/other")}
end
def handle_event("validate", params, socket) do
{:noreply, socket}
end
end
defmodule Example.OtherLive do
use Phoenix.LiveView, layout: {Example.HomeLive, :live}
def render(assigns) do
~H"""
<h2>The other one</h2>
"""
end
end
defmodule Example.Router do
use Phoenix.Router
import Phoenix.LiveView.Router
pipeline :browser do
plug(:accepts, ["html"])
end
scope "/", Example do
pipe_through(:browser)
live("/", HomeLive, :index)
live("/other", OtherLive, :index)
end
end
defmodule Example.Endpoint do
use Phoenix.Endpoint, otp_app: :sample
socket("/live", Phoenix.LiveView.Socket)
plug Plug.Static, from: {:phoenix, "priv/static"}, at: "/assets/phoenix"
plug Plug.Static, from: {:phoenix_live_view, "priv/static"}, at: "/assets/phoenix_live_view"
plug(Example.Router)
end
{:ok, _} = Supervisor.start_link([Example.Endpoint], strategy: :one_for_one)
Process.sleep(:infinity)
The e2e tests showed that this issue only happens in Chromium based browsers. Safari and Firefox seemingly don't trigger blur when a focused input is removed from the DOM.
❤️❤️❤️🐥🔥
When submitting a form with phx-debounce="blur" without ever leaving the field, the blur event would be fired twice on the input, as the browser would trigger a second blur event when the view is patched by morphdom and the currently focused input is removed, sending the event to the new LiveView.
My fix here is to check for the asyncFilter introduced in https://github.com/phoenixframework/phoenix_live_view/commit/5a62253004abc4617c51e3f7c2cbb68245d2f86d, which already checks if the view is destroyed or the element not in the DOM.
Fixes #3194.