Easy "confirm action" in Phoenix for click, submit, etc

I have a button like this:

<.button phx-click="delete-everything" phx-disable-with="Please wait...">
  Delete Everything
</.button>

and I want to gracefully handle “accidental” clicks.

So I have this very simple way to “confirm user action” that I will share.

First, add this JS to app.js:

import ultraConfirm from "./ultraConfirm";
window.addEventListener("ultra-confirm", ultraConfirm.confirmAndExec);

Then create a file ultraConfirm.js beside app.js with this code:

const execAttr = (el, attrName) => {
  const attr = el.getAttribute(attrName);
  attr && liveSocket.execJS(el, attr);
};

const confirmAndExec = ({ detail, srcElement }) => {
  const { message } = detail;

  if (confirm(message || "Are you sure?")) {
    execAttr(srcElement, "phx-ultra-confirm-ok");
  } else {
    execAttr(srcElement, "phx-ultra-confirm-cancel");
  }
};

export default { confirmAndExec };

You can see this is using Javascript’s confirm, which is like alert except the user can OK or Cancel.

Now all I need to do is rewrite my button like this:

<.button
  phx-click={
    JS.dispatch("ultra-confirm",
      detail: %{message: "Are you sure you want to delete everything?"}
    )
  }
  phx-ultra-confirm-ok={JS.push("delete-everything")}
  phx-disable-with="Please wait..."
>
  Delete Everything
</.button>

You can send a payload (what is usually called params in the handle_event/3 callback) with the JS.push call. Just check the docs for that function (Phoenix.LiveView.JS — Phoenix LiveView v0.20.17).

Note that I named the attribute phx-ultra-confirm-ok. I did this because if I named it, e.g. ultra-confirm-ok then core components like button will complain about “unexpected attribute”.

Note finally that the phx-disable-with works as we would expect it too.

2 Likes

I have done things like this in the past and I personally always try to avoid JS where possible.

What I usually do is create a component that is the confirm modal and handle the actual confirm with pure liveview.

1 Like

To each their own! Since this is my simple default approach, I wanted a way that does no extraneous round-trips between client and server. My approach does all the confirmation stuff in the browser.

When you say “component” do you mean a LiveComponent?

2 Likes

Yes, but it can be a stateless component.

I’m not positive such optimizations bring many advantages to the table, my usual problem with writing hooks and JS is the “global” nature of how JS operates, it becomes messy really fast. For example you have to show this modal not for 1 button, but for 200 and with different warning text, this really starts to become a mess.

1 Like

For a prototype I have used confirm dialogs, but I have found in pretty much every app I’ve worked on with this sort of requirement that I end up needing to add a custom styled modal fairly early on. That doesn’t require any extra server round trips though…

1 Like

I am not sure when I would need to show 200 “are you sure?” confirm dialogs.

Yes, businesses will usually want a custom styled modal.

1 Like

Indeed this is case to case. IMO from my experience with liveview, it shines the most when you keep the custom JS interaction to minimum, or maybe that is because I want to avoid working with JS :smiley: .

This is rarely possible, but I observed that projects that inevitably start to rely on more and more custom JS lose the advantages that liveview give in the first place, namely a unified codebase that can be iterated and understood fast.

1 Like

I should maybe have stressed that this was meant to be an “easy” solution for “user confirmation”. Custom modals are definitely nicer, but they are more work. Personally, I love using default, built-in tools because I know they will work everywhere. The people who maintain browsers make sure that confirm will work on every device imaginable, and will continue to do so for a long time.

1 Like

Please share your code here if you have a better way. Thanks. :green_heart:

Phoenix (HTML) comes with confirm functionality: data-confirm="msg"

https://hexdocs.pm/phoenix_html/Phoenix.HTML.html#module-javascript-library

2 Likes

Yes this way is nice too. I wanted to be able to optionally handle the “Cancel” path. Not sure how to do that with data-confirm.