TLDR; here's the result
The default experience
I really loved that Phoenix provided a nice plugin for having quick confirm behaviours without needing to do a lot of juggling things. While the behaviour works for testing it's really lacking UX for production use.
And as it turns out it's not really clear how to customise the behaviour, as out of the box it uses window.confirm which awaits the action before continuing on.
So if you want this behaviour you need to implement some sort of preventDefault, but execute after method.
Well you're in luck as today i've finally got around to writing this post.
The setup
First I've generated a phoenix application
mix phx.new better_data_confirm --databse sqlite3
Next I've generated a Posts resource so that i could show this behaviour without too much hassle:
mix phx.gen.live Blog Post posts title:string content:string
That will get you to the default behaviour video above
Adding the danger_dialog
Next i've added a dialog element to the lib/better_data_confirm_web/components/layouts/root.html.heex
root layout
Before:
<body class="bg-white antialiased">
<%= @inner_content %>
</body>
After:
<body class="bg-white antialiased">
<%= @inner_content %>
<dialog
id="danger_dialog"
class="backdrop:bg-slate-800/75 shadow-xl rounded-md bg-white p-6 border"
>
<form method="dialog" class="grid gap-6 place-items-center">
<h1 class="text-2xl" data-ref="title">
Are you sure?
</h1>
<div class="flex gap-4 items-center justify-end">
<.button data-ref="cancel" type="submit" value="cancel">
Cancel
</.button>
<.button data-ref="confirm" type="submit" value="confirm" class="bg-red-500">
Confirm
</.button>
</div>
</form>
</dialog>
</body>
Now this is up to your design choices and so on, you can have translated string, aria attributes, animations all that jazz, i'll link to a few good posts about that in the end.
But for the basic example it's a simple dialog with some styling and that's all.
Overriding the default behaviour
next in the assets/js/app.js
we need to add the overriding behaviour, pop this code at the end of the file.
// Attribute which we use to re-trigger the click event
const CONFIRM_ATTRIBUTE = "data-confirm-fired"
// our dialog from the `root.html.heex`
const DANGER_DIALOG = document.getElementById("danger_dialog");
// Here we override the behaviour
document.body.addEventListener('phoenix.link.click', function (event) {
// we prevent the default handling of this by phoenix html
event.stopPropagation();
// grab the target
const { target: targetButton } = event;
const title = targetButton.getAttribute("data-confirm");
// if the target does not have `data-confirm` we simply ignore and continue
if (!title) { return true; }
// For re-triggering the click event
if (targetButton.hasAttribute(CONFIRM_ATTRIBUTE)) {
targetButton.removeAttribute(CONFIRM_ATTRIBUTE)
return true;
}
// We do this since `window.confirm` prevents all execution by default.
// To recreate this behaviour we `preventDefault`
// Then add an attribute which will allow us to re-trigger the click event while skipping the dialog
event.preventDefault();
targetButton.setAttribute(CONFIRM_ATTRIBUTE, "")
// Reset the `returnValue` as otherwise on keyboard `Esc` it will simply take the most recent `returnValue`, causing all sorts of issues :D
DANGER_DIALOG.returnValue = "cancel";
// We use the title, which is nice we can have translated titles
DANGER_DIALOG.querySelector("[data-ref='title']").innerText = title;
// <dialog> is a very cool element and provides a lot of cool things out of the box, like showing the modal in the #top-layer
DANGER_DIALOG.showModal();
// Re-triggering logic
DANGER_DIALOG.addEventListener('close', ({ target }) => {
if (target.returnValue === "confirm") {
// we re-trigger the click event
// since we have the attribute set. This will just execute the click event
targetButton.click();
} else {
// Remove the attribute on cancel as otherwise the next click would execute the click event without the dialog
targetButton.removeAttribute(CONFIRM_ATTRIBUTE);
}
// once: true, automatically remove the listener after first execution
}, { once: true })
}, false)
And Voila
You now have a customised modal for data-confirm!
You can add any other things you need!
- allow customising button texts
- add a message
- translate
- change icons
- animate the appearance/hiding of the modal
- have different modal types danger/success/prompt
The sky is the limit...
Read more to create even better ux:
https://www.scelto.no/blog/promise-based-dialog
https://developer.chrome.com/blog/entry-exit-animations
Don't forget to leave a like!
Good luck with your adventures! If you enjoyed the post, likes are very appreciated, even better if you share this :D