Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

check asyncFilter on blur (closes #3194) #3253

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Conversation

SteffenDE
Copy link
Collaborator

@SteffenDE SteffenDE commented May 15, 2024

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 5a62253, which already checks if the view is destroyed or the element not in the DOM.

Fixes #3194.

@SteffenDE
Copy link
Collaborator Author

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)

@SteffenDE
Copy link
Collaborator Author

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.

@SteffenDE
Copy link
Collaborator Author

https://groups.google.com/a/chromium.org/g/blink-dev/c/L1aI9JZTrBs?pli=1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant