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

Overriding the default confirm behaviour - Make the example work #3225

Merged
merged 4 commits into from May 15, 2024

Conversation

Neophen
Copy link
Contributor

@Neophen Neophen commented Apr 25, 2024

Add a working example of overriding the data-confirm behaviour

Add a working example of customising the `data-confirm` behaviour
@chrismccord
Copy link
Member

Which part about the existing docs didn't work? Also please keep type="button". Thanks!

@Neophen
Copy link
Contributor Author

Neophen commented Apr 29, 2024

Click handler events are not asynchronous, so you need to cancel or allow the event straight away.

The example should have never worked as no other methods can pause javascript executions like window.confirm/alert/prompt, as the click event just fires through and the e.preventDefault() in the vox callback does nothing.

We need to have a workaround, like my re-trigger example here.

Here's the more in depth article
Here's people writing about it
I've also seen some issues on github, can't find them at the moment.

@Neophen Neophen requested a review from chrismccord May 2, 2024 18:11
@Neophen
Copy link
Contributor Author

Neophen commented May 7, 2024

@chrismccord not sure if need to ping at all, the ci seems to fail for no reason.

lib/phoenix_component.ex Outdated Show resolved Hide resolved
lib/phoenix_component.ex Outdated Show resolved Hide resolved
lib/phoenix_component.ex Outdated Show resolved Hide resolved
lib/phoenix_component.ex Outdated Show resolved Hide resolved
lib/phoenix_component.ex Outdated Show resolved Hide resolved
More comments and small indent changes
@SteffenDE
Copy link
Collaborator

Working single file script with these changes:

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},
])

# 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, :count, 0)}
  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="/assets/phoenix_html/phoenix_html.js"></script>
    <script src="https://unpkg.com/vex-js@4.1.0/dist/js/vex.combined.min.js"></script>
    <script>vex.defaultOptions.className = 'vex-theme-os'</script>
    <link rel="stylesheet" href="https://unpkg.com/vex-js@4.1.0/dist/css/vex.css">
    <link rel="stylesheet" href="https://unpkg.com/vex-js@4.1.0/dist/css/vex-theme-os.css">
    <%!-- uncomment to use enable tailwind --%>
    <%!-- <script src="https://cdn.tailwindcss.com"></script> --%>
    <script>
      // Compared to a javascript window.confirm, the custom dialog does not block
      // javascript execution. Therefore to make this work as expected we store
      // the successful confirmation as an attribute and re-trigger the click event.
      // On the second click, the `data-confirm-resolved` attribute is set and we proceed.
      const RESOLVED_ATTRIBUTE = "data-confirm-resolved";
      // listen on document.body, so it's executed before the default of
      // phoenix_html, which is listening on the window object
      document.body.addEventListener('phoenix.link.click', function (e) {
        // Prevent default implementation
        e.stopPropagation();
        // Introduce alternative implementation
        var message = e.target.getAttribute("data-confirm");
        if(!message){ return; }

        // Confirm is resolved execute the click event
        if (e.target?.hasAttribute(RESOLVED_ATTRIBUTE)) {
          e.target.removeAttribute(RESOLVED_ATTRIBUTE);
          return;
        }

        // Confirm is needed, preventDefault and show your modal
        e.preventDefault();
        e.target?.setAttribute(RESOLVED_ATTRIBUTE, "");

        vex.dialog.confirm({
          message: message,
          callback: function (value) {
            if (value == true) {
              // Customer confirmed, re-trigger the click event.
              e.target?.click();
            } else {
              // Customer canceled
              e.target?.removeAttribute(RESOLVED_ATTRIBUTE);
            }
          }
        })
      }, false);

      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"""
    <%= @count %>
    <button phx-click="inc" data-confirm="oh!">+</button>
    <button phx-click="dec">-</button>
    """
  end

  def handle_event("inc", _params, socket) do
    {:noreply, assign(socket, :count, socket.assigns.count + 1)}
  end

  def handle_event("dec", _params, socket) do
    {:noreply, assign(socket, :count, socket.assigns.count - 1)}
  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)
  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 Plug.Static, from: {:phoenix_html, "priv/static"}, at: "/assets/phoenix_html"

  plug(Example.Router)
end

{:ok, _} = Supervisor.start_link([Example.Endpoint], strategy: :one_for_one)
Process.sleep(:infinity)

@SteffenDE SteffenDE merged commit 0e1a235 into phoenixframework:main May 15, 2024
6 checks passed
@SteffenDE
Copy link
Collaborator

Thank you! 🙌🏻

I'll try to add an e2e test for this separately.

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

Successfully merging this pull request may close these issues.

None yet

3 participants